Git
Chapters ▾ 2nd Edition

7.14 Git Tools - Het opslaan van inloggegevens

Het opslaan van inloggegevens

Als je het SSH transport gebruikt om verbinding te maken met remotes, is het mogelijk om een sleutel te hebben zonder wachtwoord, wat je in staat stelt veilig gegevens uit te wisselen zonder je gebruikersnaam en wachtwoord in te typen. Dit is echter niet mogelijk met de HTTP protocollen - elke connectie heeft een gebruikersnaam en wachtwoord nodig. Het wordt zelfs lastiger voor systemen met twee-factor authenticatie, waar het token dat je gebruikt voor een wachtwoord willekeurig wordt gegenereerd en onuitspreekbaar is.

Gelukkig heeft Git een credentials systeem die ons daarbij kan helpen. Git heeft standaard een aantal opties in de aanbieding:

  • De standaard is om helemaal niets op te slaan. Elke verbinding wordt je gevraagd om je gebruikersnaam en wachtwoord.

  • De “cache” modus houdt deze gegevens voor een bepaalde tijd in het geheugen. Geen van de wachtwoorden worden ooit op schijf opgeslagen, en ze worden na 15 minuten uit de cache verwijderd.

  • De “store” modus bewaart deze gegevens in bestand in een leesbaar tekstformaat, en ze verlopen nooit. Dit houdt in dat totdat je je wachtwoord wijzigt op de Git host, je nooit meer je gegevens hoeft in te typen. Het nadeel van deze aanpak is dat je wachtwoorden onversleuteld bewaard worden in een gewoon bestand in je home directory.

  • Als je een Mac gebruikt, wordt Git geleverd met een “osxkeychain” modus, waarbij de gegevens opgeslagen worden in een beveiligde sleutelring die verbonden is aan je systeem account. Deze methode bewaart je gegevens op schijf, en ze verlopen nooit, maar ze zijn versleuteld met het zelfde systeem dat de HTTPS certificaten en Safari auto-fills bewaart.

  • Als je Windows gebruikt, kan je het hulpprogramma “winstore” installeren. Dit is vergelijkbaar met het “osxkeychain” programma zoals hierboven beschreven, maar het gebruikt de Windows Credential Store om de gevoelige gegevens te beheren. Dit kan gevonden worden op https://gitcredentialstore.codeplex.com.

Je kunt een van deze methoden kiezen door een Git configuratie waarde in te stellen:

$ git config --global credential.helper cache

Een aantal van deze hulpprogramma’s hebben opties. De “store” helper accepteert een --file <path> argument, waarmee je kunt sturen waar het leesbare bestand wordt opgeslagen (standaard is ~/.git-credentials). De “cache” helper accepteert de --timeout <seconds> optie, die de tijdsduur wijzigt gedurende welke de daemon blijft draaien (standaard is dit “900”, ofwel 15 minuten). Hier is een voorbeeld van hoe je de “store” helper configureert met een eigen bestandsnaam:

$ git config --global credential.helper store --file ~/.my-credentials

Git staat het je zelfs toe om meerdere helpers te configureren. Als Git op zoek gaat naar inloggegevens voor een specifieke host, zal Git ze in volgorde afvragen, en stoppen als het eerste antwoord wordt gegeven. Bij het opslaan van de gegevens, zal Git de gebruikersnaam en wachtwoord naar alle helpers in de lijst sturen, en zij kunnen besluiten wat met deze gegevens te doen. Hier is hoe een .gitconfig eruit zou kunnen zien als je een credentials bestand op een stickie zou hebben staan, maar de opslag in het geheugen zou willen gebruiken om wat typen te besparen als die stick niet ingeplugd is:

[credential]
    helper = store --file /mnt/thumbdrive/.git-credentials
    helper = cache --timeout 30000

Onder de motorkap

Hoe werkt dit nu allemaal? Het basiscommando van Git voor het credential-helper systeem is git credential, wat een commando als argument neemt, en daarna meer invoer vanuit stdin.

Dit is misschien beter te begrijpen met een voorbeeld. Laten we zeggen dat een credential helper geconfigureerd is, en de helper heeft gegevens bewaard voor mygithost. Hier is een sessie die het “fill” commando gebruikt, wat wordt aangeroepen als Git probeert inloggegevens te vinden voor een host.

$ git credential fill (1)
protocol=https (2)
host=mygithost
(3)
protocol=https (4)
host=mygithost
username=bob
password=s3cre7
$ git credential fill (5)
protocol=https
host=unknownhost

Username for 'https://unknownhost': bob
Password for 'https://bob@unknownhost':
protocol=https
host=unknownhost
username=bob
password=s3cre7
  1. Dit is de commando regel die de interactie initieert.

  2. Git-credential gaat dan wachten op invoer van stdin. We geven het de dingen die we weten: het protocol en de hostnaam.

  3. Een blanco regel geeft aan dat de invoer compleet is, en het credential systeem moet nu antwoorden met wat het weet.

  4. Git-credential neemt het daarna over, en schrijft de stukken informatie het gevonden heeft naar stdout.

  5. Als er geen inloggegevens gevonden zijn, vraag Git de gebruiker om de gebruikersnaam en wachtwoord en stelt die ter beschikking door stdout aan te roepen (hier zitten ze verbonden met dezelfde console).

Het credential systeem roept feitelijk een programma aan dat los staat van Git zelf; welke dat is en hoe hangt af van de waarde die is ingevuld bij credential.helper. Deze kan verschillende vormen aannemen:

Configuratie waarde Gedrag

foo

Roept git-credential-foo aan

foo -a --opt=bcd

Roept git-credential-foo -a --opt=bcd aan

/absolute/path/foo -xyz

Roept /absolute/path/foo -xyz aan

!f() { echo "password=s3cre7"; }; f

Code na ! wordt in shell geëvalueerd

Dus de helpers die hierboven zijn beschreven heten eigenlijk git-credential-cache, git-credential-store, en zo voorts, en we kunnen ze configureren om commando-regel argumenten te accepteren. De algemene vorm voor dit is “git-credential-foo [args] <actie>.” Het stdin/stdout protocol is dezelfde als git-credential, maar deze gebruiken een iets andere set acties:

  • get is een verzoek voor een gebruikersnaam/wachtwoord paar.

  • store is een verzoek om een groep van inloggegevens in het geheugen van de helper op te slaan.

  • erase verwijder de inloggegevens voor de opgegeven kenmerken uit het geheugen van deze helper.

Voor de store en erase acties, is geen antwoord nodig (Git negeert het in elk geval). Voor de get actie echter is Git zeer geïntereseerd in het antwoord van de helper. Als de helper geen zinnig antwoord kan geven, kan het simpelweg stoppen zonder uitvoer, maar als het wel een antwoord heeft, moet het de gegeven informatie aanvullen met de gegevens die het heeft opgeslagen. De uitvoer wordt behandeld als een reeks van toewijzigs-opdrachten; alles wat wordt aangereikt zal wat Git hierover al weet vervangen.

Hier is het zelfde voorbeeld als hierboven, maar git-credential wordt overgeslagen en er wordt direct naar git-credential-store gegaan:

$ git credential-store --file ~/git.store store (1)
protocol=https
host=mygithost
username=bob
password=s3cre7
$ git credential-store --file ~/git.store get (2)
protocol=https
host=mygithost

username=bob (3)
password=s3cre7
  1. Hier vertellen we git-credential-store om wat inloggegevens te bewaren: de gebruikersnaam “bob” en het wachtwoord “s3cre7” moeten worden gebruikt as https://mygithost wordt benaderd.

  2. Nu gaan we deze inloggegevens ophalen. We geven de delen van de verbinding die we al weten (https://mygithost) en een lege regel.

  3. De git-credential-store antwoordt met de gebruikersnaam en wachtwoord die we hierboven hebben opgeslagen.

Hier is hoe het ~/git.store bestand eruit zal zien:

https://bob:s3cre7@mygithost

Het is niet meer dan een reeks regels, die elk een van inloggegevens voorziene URL bevat. De osxkeychain en winstore helpers gebruiken het eigen formaat van hun eigen achterliggende opslag, terwijl cache zijn eigen in-memory formaat gebruikt (wat geen enkel ander proces kan lezen).

Een eigen inloggegevens cache

Gegeven dat git-credential-store en zijn vriendjes programma’s zijn die los staan van Git, is het geen grote stap om te beseffen dat elk programma een Git credential helper kan zijn. De helpers die bij Git worden geleverd dekken veel gewone gebruiksvoorkomsten, maar niet alle. Bijvoorbeeld, stel nu dat je team een aantal inloggegevens hebben die met het hele team worden gedeeld, misschien om te deployen. Deze worden opgeslagen in een gedeelde directory, maar je wilt ze niet naar je eigen credential opslagplaats kopiëren, omdat ze vaak veranderen. Geen van de bestaande helpers kan hierin voorzien; laten we eens kijken hoeveel moeite het kost om er zelf een te schrijven. Er zijn een aantal sleutelkenmerken die dit programma moet hebben:

  1. De enige actie waar we aandacht aan moeten besteden is get; store en erase zijn schrijf-acties, dus we negeren deze en sluiten gewoon af als ze worden ontvangen.

  2. Het bestandsformaat van het gedeelde credential bestand is dezelfde als die wordt gebruikt door git-credential-store.

  3. De locatie van dat bestand is redelijk standaard, maar we moeten toestaan dat de gebruiker een aangepast pad doorgeeft, voor het geval dat.

Nogmaals, we schrijven deze extensie in Ruby, maar een andere willekeurige taal werkt ook, zolang Git het uiteindelijke product maar kan aanroepen. Hier is de volledige broncode van onze nieuwe credential helper:

#!/usr/bin/env ruby

require 'optparse'

path = File.expand_path '~/.git-credentials' (1)
OptionParser.new do |opts|
    opts.banner = 'USAGE: git-credential-read-only [options] <action>'
    opts.on('-f', '--file PATH', 'Specify path for backing store') do |argpath|
        path = File.expand_path argpath
    end
end.parse!

exit(0) unless ARGV[0].downcase == 'get' (2)
exit(0) unless File.exists? path

known = {} (3)
while line = STDIN.gets
    break if line.strip == ''
    k,v = line.strip.split '=', 2
    known[k] = v
end

File.readlines(path).each do |fileline| (4)
    prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first
    if prot == known['protocol'] and host == known['host'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. Hier parsen we de commando-regel opties, waarbij we de gebruiker het invoerbestand kunnen laten aangeven. De standaardwaarde is ~/.git-credentials.

  2. Dit programma geeft alleen antwoord als de actie get is, en het achterliggende bestand bestaat.

  3. In deze lus wordt stdin gelezen tot de eerste blanco regel wordt bereikt. De invoergegevens worden opgeslagen in de known hash voor later gebruik.

  4. In deze lus wordt de inhoud van het achterliggende bestand gelezen op zoek naar passende inhoud. Als het protocol en de host van known gelijk is aan deze regel, drukt het programma de resultaten af op stdout en stopt.

We zullen onze helper als git-credential-read-only opslaan, zetten het ergens in onze PATH en maken het uitvoerbaar. Hier is hoe een interactieve sessie eruit zou zien:

$ git credential-read-only --file=/mnt/shared/creds get
protocol=https
host=mygithost

protocol=https
host=mygithost
username=bob
password=s3cre7

Omdat de naam met “git-” begint, kunnen we de eenvoudige syntax voor de configuratie waarde gebruiken:

$ git config --global credential.helper read-only --file /mnt/shared/creds

Zoals je kunt zien, is het uitbreiden van dit systeem redelijk eenvoudig, en we kunnen een aantal gebruikelijke problemen voor jou en je team oplossen.