Git
Chapters ▾ 2nd Edition

7.14 Git инструменти - Credential Storage система

Credential Storage система

Ако използвате SSH като транспортен протокол, възможно е да имате ключ без passphrase, позволяващ ви защитѐн трансфер на данни без въвеждане на име и парола. Обаче това не е така при HTTP протоколите — всяка връзка изисква да ги въвеждате. Това става дори по-сложно при системи с двустъпкова автентикация, където стрингът изпълняващ ролята на парола се генерира случайно и е невъзможен за произнасяне.

За щастие Git разполага с credentials система на ваше разположение. Имате няколко фабрични опции:

  • По подразбиране не се кешира нищо. Всяка конекция ще изисква име и парола.

  • Режимът “cache” запазва данните в паметта за определен период от време. Никоя от паролите не се записва на диска и те се изчистват от кеша на всеки 15 минути.

  • Режимът “store” съхранява данните за достъп в обикновен текстов файл на диска и те не се премахват от там. Това значи, че докато не смените паролата си на Git хоста, няма да е нужно да я въвеждате. Недостатъкът е, че паролите ви се съхраняват в чист текст в домашната директория.

  • Ако използвате Mac, Git предлага “osxkeychain” режим, при който данните за достъп се съхраняват в защитена keychain прикрепена към системния ви акаунт. Този метод пази данните на диска, те остават валидни, но са криптирани със същата система, която съхранява HTTPS сертификати и auto-fill данните на браузъра Safari.

  • Под Windows, можете да инсталирате helper наречен “Git Credential Manager for Windows.” Това е подобно на “osxkeychain”, но използва Windows Credential Store за контрол на поверителната информация. Може да се намери на https://github.com/Microsoft/Git-Credential-Manager-for-Windows.

Избирате един от тези методи чрез конфигурацията на Git:

$ git config --global credential.helper cache

Някои от helper-ите имат опции. Например “store” може да приема аргумент --file <path>, който определя къде да се пази текстовия файл (по подразбиране ~/.git-credentials). При “cache” е наличен аргумента --timeout <seconds>, с който можете да промените времето на работа на неговия daemon (по подразбиране в секунди е “900”, т.е. 15 минути). Ето как да променим името на файла при “store” helper-а:

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

Git позволява конфигурирането и на няколко helper-а. Търсейки данните за достъп до определен хост, Git ще ги изпитва един по един и ще спре при първия получен отговор. При запис на данни за достъп, Git ще изпраща името и паролата към всички helper-и в списъка и те поемат грижата за това как да ги ползват. Ето как би изглеждал .gitconfig, ако пазите данните за достъп на външно устройство, но искате да използвате in-memory кеша, за да си спестите малко писане, когато стикчето не е включено:

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

Зад кулисите

Как работи всичко това? Основната команда на Git за credential-helper системата е git credential, която приема допълнителна команда като аргумент и данни от стандартния вход.

По-лесно за разбиране е с пример. Нека кажем, че имаме настроен credential helper и че той пази данните за достъп до mygithost. Ето една сесия, която използва “fill” командата, която се изпълнява, когато Git се опитва да намери данните за достъп до даден хост:

$ 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. Това е командата, която стартира поредицата.

  2. Git-credential след това чака за вход от stdin. Ние подаваме нещата, които знаем: протокола и името на хоста.

  3. Празният ред показва, че входът е приключил и credential системата трябва да отговори с това, което тя знае.

  4. Git-credential поема инициативата и изпраща в stdout намерените данни.

  5. Ако данните за достъп не са намерени, Git пита за име и парола и ги връща обратно към stdout (тук те са в една и съща конзола).

Credential системата реално извиква програма, която не е част от Git — коя и как зависи от стойността на конфигурационния ключ credential.helper. Това може да изглежда по няколко начина:

Configuration Value Behavior

foo

Runs git-credential-foo

foo -a --opt=bcd

Runs git-credential-foo -a --opt=bcd

/absolute/path/foo -xyz

Runs /absolute/path/foo -xyz

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

Code after ! evaluated in shell

Така helper-ите описани по-горе в действителност са наречени git-credential-cache, git-credential-store и т.н. и можем да ги настроим да приемат аргументи от командния ред. Шаблонът за това е “git-credential-foo [args] <action>.” Протоколът stdin/stdout е същият като git-credential, но те използват леко различни набори от действия:

  • get е запитване за чифт от име и парола.

  • store е заявка за запис на набор от данни за достъп в паметта на този helper.

  • erase изтрива данните за достъп до дадени свойства от паметта на този helper.

За store и erase действията не се изисква отговор (и да има такъв Git го игнорира). Обаче за действието get, Git е силно заинтересован за това, което има да каже helper-а. Ако той не знае нищо важно, Git може просто да излезе без отговор, но ако знае трябва да сравни предоставената информация с тази, която е съхранена. Изходът се третира като серия от assignment оператори и всичко подадено ще замени това, което Git вече знае.

Ето примера отгоре, но с пропуснат git-credential и преминаване направо към 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. Тук казваме на git-credential-store да съхрани малко данни за достъп: потребителското име “bob''и паролата ``s3cre7” трябва да се ползват за достъп до адрес https://mygithost.

  2. Сега ще извлечем тези данни. Предоставяме частите от конекцията, които вече знаем (https://mygithost) и след това празен ред.

  3. git-credential-store отговаря с името и паролата, които записахме по-рано.

Ето как изглежда файла ~/git.store:

https://bob:s3cre7@mygithost

Той е просто серия от редове, всеки от които съдържа URL форматиран така, че да подава името и паролата. Helper-ите osxkeychain и wincred използват нативния формат на техните системи за съхранение, докато cache използва свой собствен in-memory формат (който е недостъпен за четене от други процеси).

Потребителски Credential Cache

Предвид това, че git-credential-store и подобните са отделни програми от Git, лесно е да се досетим, че всяка програма би могла да играе ролята на Git credential helper. Helper-ите осигурени от Git покриват много от най-честите сценарии за използване, но не всички. Да допуснем, че екипът ви пази данни за достъп споделени между всички разработчици в него, например за цели свързани с внедряване на продукти. Те се пазят в споделена директория, но не желаете да ги запазвате локално в собствения ви credential store, защото често се променят. В този случай, нито един от стандартните helper-и не отговаря на ситуацията и вариантът е да си напишем свой собствен такъв. Хипотетичната ни помощна програма трябва да има няколко ключови способности:

  1. Единственото действие, с което трябва да се съобразява е get; store и erase са записващи действия, така че просто ще излизаме чисто, когато ги получим.

  2. Файловият формат на споделения credential файл е същият като този използван от git-credential-store.

  3. Локацията на файла е сравнително стандартна, но бихме искали да позволим на потребителя да подаде специфичен такъв просто за всеки случай.

Отново, ще напишем програмата си на Ruby, но всеки език би работил стига Git да може да изпълни финалната програма. Ето пълния изходен код на нашия нов 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'] and user == known['username'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. Първо парсваме аргументите от командния ред, позволявайки на потребителя да укаже входен файл. По подразбиране използваме ~/.git-credentials.

  2. Програмата отговаря само на действието get и само ако въпросния файл съществува.

  3. Цикълът чете вход от stdin докато срещне празен ред. Подадените стойности се пазят в known хеша за по-късно ползване.

  4. Този цикъл чете съдържанието на storage файла, търсейки съвпадения. Ако протоколът и хостът от known съответстват на дадения ред, програмата печата резултатите в stdout и спира.

Ще запишем нашия helper като git-credential-read-only, ще го добавим към PATH променливата и ще го направим изпълним. Ето как изглежда интерактивната сесия:

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

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

Понеже името му започва с “git-”, можем да използваме опростения синтаксис за конфигурация:

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

Както виждате, разширяването на системата е доста праволинейно и може да бъде полезно за вас и вашия екип.