Chapters ▾ 2nd Edition

7.14 Git-verktyg - Lagring av inloggningsuppgifter

Lagring av inloggningsuppgifter

Om du använder SSH‑transporten för att ansluta till fjärrkodförråd kan du ha en nyckel utan lösenfras, vilket gör att du kan överföra data säkert utan att skriva in användarnamn och lösenord. Det är dock inte möjligt med HTTP-protokollen — varje anslutning kräver ett användarnamn och lösenord. Det blir ännu svårare för system med tvåfaktorsautentisering, där tokenen du använder som lösenord är slumpmässigt genererad och omöjlig att uttala.

Lyckligtvis har Git ett system för inloggningsuppgifter som kan hjälpa. Git har några alternativ direkt ur lådan:

  • Standard är att inte mellanlagra alls. Varje anslutning frågar efter användarnamn och lösenord.

  • Läget “cache” håller inloggningsuppgifter i minnet under en viss tid. Inga lösenord lagras någonsin på disk, och de rensas ur mellanlagringen efter 15 minuter.

  • Läget “store” sparar inloggningsuppgifterna i en klartextfil på disk och de löper aldrig ut. Det betyder att tills du byter lösenord på Git‑värden behöver du aldrig skriva in dina inloggningsuppgifter igen. Nackdelen med detta är att dina lösenord lagras i klartext i en vanlig fil i din hemmakatalog.

  • Om du använder macOS levereras Git med läget “osxkeychain”, som mellanlagrar inloggningsuppgifter i den säkra nyckelringen som är kopplad till ditt systemkonto. Den här metoden lagrar inloggningsuppgifterna på disk, och de löper aldrig ut, men de är krypterade med samma system som lagrar HTTPS‑certifikat och som Safari använder för autofyll.

  • Om du använder Windows kan du aktivera funktionen Git Credential Manager när du installerar Git for Windows eller installera senaste GCM separat som en fristående tjänst. Detta liknar hjälparen “osxkeychain” ovan, men använder Windows Credential Store för att hantera känslig information. Den kan också tillhandahålla inloggningsuppgifter till WSL1 eller WSL2. Se installationsinstruktioner för GCM för mer information.

Du kan välja en av dessa metoder genom att sätta ett Git‑konfigurationsvärde:

$ git config --global credential.helper cache

Några av dessa hjälpare har inställningar. Hjälparen “store” kan ta argumentet --file <sökväg>, som anpassar var klartextfilen sparas (standard är ~/.git-credentials). Hjälparen “cache” accepterar alternativet --timeout <sekunder>, som ändrar hur länge dess demon hålls igång (standard är “900”, eller 15 minuter). Här är ett exempel på hur du konfigurerar hjälparen “store” med ett anpassat filnamn:

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

Git låter dig till och med konfigurera flera hjälpare. När Git letar efter inloggningsuppgifter för en viss värd frågar den dem i ordning och stannar efter det första svaret. När Git sparar inloggningsuppgifter skickar den användarnamn och lösenord till alla hjälpare i listan, och de kan välja vad de vill göra med dem. Här är hur en .gitconfig kan se ut om du har en inloggningsfil på ett USB‑minne men vill använda minnesmellanlagringen för att spara lite knappande om minnet inte sitter i:

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

Under huven

Hur fungerar allt detta? Gits rotkommando för systemet med inloggningshjälpare är git credential, som tar ett kommando som argument och sedan mer indata via stdin.

Det blir tydligare med ett exempel. Säg att en inloggningshjälpare har konfigurerats och att hjälparen har sparat inloggningsuppgifter för mygithost. Här är en session som använder kommandot “fill”, som anropas när Git försöker hitta inloggningsuppgifter för en värd:

$ 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. Detta är kommandoraden som startar interaktionen.

  2. Git‑credential väntar sedan på indata på stdin. Vi ger den det vi vet: protokollet och värdnamnet.

  3. En tom rad betyder att indata är komplett och att inloggningssystemet ska svara med det den känner till.

  4. Git‑credential tar sedan över och skriver till stdout med den information den hittade.

  5. Om inloggningsuppgifter inte hittas frågar Git användaren efter användarnamn och lösenord och ger dem tillbaka till den som anropade på stdout (här sitter de på samma konsol).

Inloggningssystemet anropar faktiskt ett program som är separat från Git; vilket och hur beror på konfigurationsvärdet credential.helper. Det finns flera former det kan ta:

Konfigurationsvärde Beteende

foo

Kör git-credential-foo

foo -a --opt=bcd

Kör git-credential-foo -a --opt=bcd

/absolute/path/foo -xyz

Kör /absolute/path/foo -xyz

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

Kod efter ! utvärderas i skalet

Så hjälparna som beskrivs ovan heter egentligen git-credential-cache, git-credential-store och så vidare, och vi kan konfigurera dem att ta kommandoradsargument. Den allmänna formen för detta är “git-credential-foo [args] <action>.” Stdin/stdout‑protokollet är detsamma som git‑credential, men de använder en något annorlunda uppsättning åtgärder:

  • get är en begäran om ett användarnamn/lösenords‑par.

  • store är en begäran om att spara en uppsättning inloggningsuppgifter i denna hjälpares minne.

  • erase rensar inloggningsuppgifterna för de givna egenskaperna ur hjälparens minne.

För åtgärderna store och erase krävs inget svar (Git ignorerar det ändå). För åtgärden get är Git däremot mycket intresserad av vad hjälparen har att säga. Om hjälparen inte vet något användbart kan den helt enkelt avsluta utan utdata, men om den gör det ska den komplettera den information som gavs med den information den har lagrat. Utdata behandlas som en serie tilldelningssatser; allt som ges ersätter det Git redan vet.

Här är samma exempel som ovan, men vi hoppar över git-credential och går direkt till 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. Här säger vi åt git-credential-store att spara inloggningsuppgifter: användarnamnet “bob” och lösenordet “s3cre7” ska användas när https://mygithost nås.

  2. Nu hämtar vi inloggningsuppgifterna. Vi ger delarna av anslutningen vi redan känner till (https://mygithost), och en tom rad.

  3. git-credential-store svarar med användarnamn och lösenord som vi lagrade ovan.

Så här ser filen ~/git.store ut:

https://bob:s3cre7@mygithost

Det är bara en serie rader, där varje rad innehåller en URL dekorerad med inloggningsuppgifter. Hjälparna osxkeychain och wincred använder sina underliggande lagringsformat, medan cache använder sitt eget minnesformat (som inga andra processer kan läsa).

En anpassad inloggningsmellanlagring

Eftersom git-credential-store och dess vänner är separata program från Git är det ingen lång tanke att vilket program som helst kan vara en Git‑hjälpare. Hjälparna som följer med Git täcker många vanliga användningsfall, men inte alla. Till exempel, säg att ditt team har vissa inloggningsuppgifter som delas av hela teamet, kanske för driftsättning. Dessa lagras i en delad katalog, men du vill inte kopiera dem till din egen inloggningslagring, eftersom de ändras ofta. Inga av de befintliga hjälparna täcker detta fall; låt oss se vad som krävs för att skriva vår egen. Det finns flera nyckelfunktioner som det här programmet behöver ha:

  1. Den enda åtgärden vi behöver bry oss om är get; store och erase är skrivoperationer, så vi avslutar rent när de tas emot.

  2. Filformatet för den delade inloggningsfilen är detsamma som används av git-credential-store.

  3. Platsen för den filen är ganska standard, men vi bör låta användaren skicka en anpassad sökväg för säkerhets skull.

Återigen skriver vi den här utbyggnaden i Ruby, men vilket språk som helst fungerar så länge Git kan köra det färdiga resultatet. Här är hela källkoden till vår nya inloggningshjälpare:

#!/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.exist? 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. Här tolkar vi kommandoradsalternativen och låter användaren ange indatafilen. Standard är ~/.git-credentials.

  2. Det här programmet svarar bara om åtgärden är get och lagringsfilen finns.

  3. Här itererar vi över stdin tills den första tomma raden nås. Indata lagras i hash‑tabellen known för senare referens.

  4. Här itererar vi över innehållet i lagringsfilen och letar efter träffar. Om protokoll, värd och användarnamn från known matchar den här raden skriver programmet ut resultatet till stdout och avslutar.

Vi sparar vår hjälpare som git-credential-read-only, lägger den någonstans i vår PATH och gör den körbar. Så här ser en interaktiv session ut:

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

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

Eftersom namnet börjar med “git-” kan vi använda den enkla syntaxen för konfigurationsvärdet:

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

Som du ser är det ganska rakt på sak att bygga ut detta system och det kan lösa några vanliga problem för dig och ditt team.