Git
Chapters ▾ 2nd Edition

7.14 Git Tools - Anmeldeinformationen speichern

Anmeldeinformationen speichern

Wenn du das SSH-Protokoll für die Verbindung zu Remotes verwendest, kannst du einen Schlüssel ohne Passwort verwenden. Damit kannst du Daten sicher übertragen, ohne deinen Benutzernamen und ein Passwort einzugeben. Mit den HTTP-Protokollen ist das aber nicht möglich. Jede Verbindung benötigt einen Benutzernamen und ein Passwort. Noch schwieriger wird das bei Systemen mit Zwei-Faktor-Authentifizierung, bei denen das Token, das du für ein Kennwort verwendest, zufällig generiert wird und nicht aussprechbar ist.

Glücklicherweise hat Git ein Anmeldesystem, das hier weiterhelfen kann. Git hat dazu ein paar Optionen im Angebot:

  • Standardmäßig wird überhaupt nicht zwischengespeichert. Bei jeder Verbindung wird nach deinem Benutzernamen und Passwort gefragt.

  • Der „Cache“-Modus hält die Anmeldedaten für eine gewisse Zeitspanne im Zwischenspeicher. Keines der Passwörter wird jemals auf der Festplatte abgelegt und nach 15 Minuten werden sie aus dem Cache gelöscht.

  • Der „Speicher“-Modus speichert die Zugangsdaten in einer Klartextdatei auf der Festplatte und sie verfallen nie. Das bedeutet, dass du deine Anmeldedaten nie wieder eingeben musst, bis du dein Passwort für den Git-Host änderst. Der Nachteil dieses Ansatzes ist, dass deine Passwörter im Klartext in einer einfachen Datei in deinem Homeverzeichnis gespeichert werden.

  • Wenn du einen Mac verwendest, verfügt Git über einen „osxkeychain“-Modus, der die Anmeldeinformationen im sicheren Schlüsselbund, der an deinem Systemkonto angehängt ist, zwischenspeichert. Diese Methode speichert die Zugangsdaten, ohne Laufzeitbegrenzung auf der Festplatte. Sie werden mit dem gleichen Verfahren verschlüsselt, das auch HTTPS-Zertifikate und Safari-Auto-Vervollständigungen verwaltet.

  • Wenn du Windows verwendest, kannst du das Feature Git Credential Manager aktivieren. Dazu musst du Git für Windows installieren oder the latest GCM als eigenständiger Dienst installieren. Dies ähnelt dem oben beschriebenen "`osxkeychain"`-Hilfsprogramm. Es verwendet jedoch den Windows-Anmeldeinformationsspeicher, um vertrauliche Informationen zu verwalten. Es kann auch Anmeldeinformationen für WSL1 oder WSL2 bereitstellen. Weitere Informationen findest du unter GCM-Installationsanweisungen.

Du kannst eine dieser Methoden durch Setzen eines Git-Konfigurationswertes wählen:

$ git config --global credential.helper cache

Einige dieser Hilfsmittel haben Optionen. Der „store“-Assistent kann das Argument --file <path> benutzen, das den Speicherort der Klartextdatei anpasst (der Standard ist ~/.git-credentials). Der „cache“-Assistent akzeptiert die Option --timeout <seconds>, welche die Zeitspanne ändert, in der der Dämon läuft (die Vorgabe ist „900“, oder 15 Minuten). Hier folgt ein Beispiel, wie du den „store“-Assistent mit einem benutzerdefinierten Dateinamen konfigurieren könntest:

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

Mit Git kannst du auch mehrere Assistenten konfigurieren. Wenn du nach Anmeldeinformationen für einen bestimmten Host suchst, fragt Git diese der Reihe nach ab und stoppt nach der ersten, erhaltenen Antwort. Beim Speichern der Zugangsdaten sendet Git den Benutzernamen und das Passwort an alle Assistenten der Liste, diese entscheiden, was damit zu tun ist. So würde eine .gitconfig aussehen, wenn du eine Datei mit Zugangsdaten auf einem USB-Stick hättest. Damit könntest du den Zwischenspeicher nutzen, um dir die Eingabe zu sparen, wenn das Laufwerk nicht angeschlossen ist:

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

Unter der (Motor-)Haube

Wie funktioniert das alles? Der Hauptbefehl für das Anmelde-System ist git credential, der einen Befehl als Argument und dann weitere Eingaben über stdin (Standardeingabe=Tastatur) entgegennimmt.

Mit einem Beispiel ist das vielleicht leichter verständlich. Nehmen wir an, dass ein Assistent für die Anmeldung konfiguriert wurde und er Anmeldedaten für mygithost gespeichert hat. Die folgende Sitzung verwendet den Befehl „fill“, der aufgerufen wird, wenn Git versucht, Zugangsdaten für einen Host zu finden:

$ 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. Diese Befehlszeile leitet die eigentliche Interaktion ein.

  2. Das Anmeldesystem wartet auf die Eingabe von stdin. Wir geben das „Protokoll“ und den „Hostnamen“ ein.

  3. Eine leere Zeile signalisiert, dass die Eingabe vollständig ist und das Anmeldesystem sollte mit dem antworten, was ihm bekannt ist.

  4. Dann übernimmt das Programm Git-Credential und gibt auf stdout die gefundenen Informationen aus.

  5. Falls keine Anmeldeinformationen gefunden werden, fragt Git den Benutzer nach dem Benutzernamen und dem Kennwort und gibt sie an den aufrufenden stdout zurück (hier sind sie an dieselbe Konsole angeschlossen).

Das Anmeldesystem ruft in Wirklichkeit ein Programm auf, das von Git selbst unabhängig ist und bei dem der Konfigurationswert credential.helper bestimmt, was und auf welche Weise es aufgerufen wird. Es kann unterschiedliche Varianten anbieten:

Konfiguration-Wert Verhalten

foo

führt git-credential-foo aus

foo -a --opt=bcd

führt git-credential-foo -a --opt=bcd aus

/absolute/path/foo -xyz

führt /absolute/path/foo -xyz aus

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

Code nach ! wird in der Shell ausgewertet

Die oben beschriebenen Hilfsprogramme heißen also eigentlich git-credential-cache, git-credential-store usw.. Wir können sie so konfigurieren, dass sie Befehlszeilenargumente übernehmen. Die allgemeine Form dafür ist „git-credential-foo [Argument] <Aktion>“. Das stdin/stdout-Protokoll ist das Gleiche wie git-credential, aber sie verwenden einen etwas anderen Befehls-Satz:

  • get ist die Abfrage nach einem Benutzernamen/Passwort-Paar.

  • store ist die Aufforderung, einen Satz von Anmeldeinformationen im Speicher dieses Assistenten zu hinterlegen.

  • erase löscht die Anmeldeinformationen für die angegebenen Einstellungen aus dem Speicher dieses Assistenten.

Für die store und erase Aktionen ist keine Reaktion erforderlich (Git ignoriert sie ohnehin). Für die Aktion get ist Git allerdings stark daran interessiert, was der Assistent sagt. Wenn das Hilfsprogramm nichts Sinnvolles kennt, kann es einfach ohne Ausgabe abbrechen. Weiß es aber etwas, sollte es die bereitgestellten Informationen mit den gespeicherten Informationen ergänzen. Die Ausgabe wird wie eine Reihe von Assignment-Statements behandelt. Alles, was zur Verfügung gestellt wird, ersetzt das, was Git bereits kennt.

Hier ist das gleiche Beispiel von oben, aber ohne git-credential und direkt mit git-credential-store:

$ 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 weisen wir git-credential-store an, einige Anmeldedaten zu speichern: der Benutzername „bob“ und das Passwort „s3cre7“ sollen verwendet werden, wenn auf https://mygithost zugegriffen wird.

  2. Jetzt rufen wir diese Anmeldedaten ab. Wir geben die bereits bekannten Teile der Internetadresse (https://mygithost) und eine leere Zeile ein.

  3. git-credential-store antwortet mit dem Benutzernamen und dem Passwort, die wir beide oben gespeichert haben.

So sieht die Datei ~/git.store jetzt aus:

https://bob:s3cre7@mygithost

Sie besteht nur aus einer Reihe von Zeilen, von denen jede eine mit einem Anmeldeinformationen versehene URL enthält. Die Assistenten osxkeychain und wincred verwenden das native Format ihrer Zwischenspeicher, während cache ein eigenes Speicherformat verwendet (das kein anderer Prozess lesen kann).

Benutzerdefinierter Cache für Anmeldeinformationen.

Angenommen, dass git-credential-store und seine Freunde von Git getrennte Programme sind. Dann ist es nicht schwierig zu erkennen, dass jedes Programm ein Hilfsprogramm für die Git-Anmeldung sein kann. Die von Git bereitgestellten Assistenten decken viele gewöhnliche Anwendungsfälle ab, aber nicht alle. Nehmen wir zum Beispiel an, dein Team hat einige Anmeldedaten, die dem gesamten Team zur Verfügung gestellt werden sollen, z.B. für die Entwicklung. Diese werden in einem freigegebenen Verzeichnis gespeichert. Du möchtest sie jedoch nicht in dein eigenes Anmeldesystem kopieren, da sie sich häufig ändern. Keines der vorhandenen Hilfsprogramme ist auf diesen Fall anwendbar. Schauen wir mal, was nötig wäre, um unser Eigenes zu schreiben. Dieses Programm muss mehrere Schlüsselfunktionen haben:

  1. Die einzige Aktion, auf die wir achten müssen, ist get. Die Aktionen store und erase sind Schreiboperationen, also werden wir sie einfach sauber beenden, sobald sie auftauchen.

  2. Das Dateiformat der Datei für die gemeinsamen Anmeldedaten ist das Gleiche wie es von git-credential-store verwendet wird.

  3. Der Speicherort dieser Datei ist eigentlich standardisiert, aber wir sollten es dem Benutzer erlauben, einen benutzerdefinierten Pfad anzugeben, nur für den Fall, dass er es wünscht.

Wir werden diese Erweiterung wieder in Ruby schreiben, aber jede Programmiersprache wird funktionieren, solange Git das fertige Produkt ausführen kann. Hier ist der vollständige Quellcode unseres neuen Anmeldehelfers:

#!/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'] and user == known['username'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. Hier parsen wir die Befehlszeilenoptionen und erlauben dem Benutzer, die Eingabedatei zu spezifizieren. Die Vorgabe ist ~/.git-credentials.

  2. Dieses Programm antwortet nur, wenn die Aktion get lautet und die Backup-Speicherdatei existiert.

  3. Die Schleife hier, liest von stdin, bis die erste leere Zeile erkannt wird. Die Eingaben werden im Hash known zur späteren Referenz gespeichert.

  4. Diese Schleife liest den Inhalt der Speicherdatei und sucht nach Übereinstimmungen. Wenn die Protokolldaten, der Host und der Benutzername mit known in dieser Zeile übereinstimmen, gibt das Programm die Ergebnisse auf stdout aus und beendet sich.

Wir speichern unser Hilfsprogramm als git-credential-read-only, legen es irgendwo in unserem PATH ab und markieren es als ausführbar. So sieht dann eine interaktive Sitzung aus:

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

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

Da der Name mit „git-“ beginnt, können wir die einfache Syntax für den Konfigurationswert verwenden:

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

Wie du siehst, ist die Erweiterung dieses Systems ziemlich unkompliziert und kann einige typische Probleme für dich und dein Team lösen.

scroll-to-top