Git
Chapters ▾ 2nd Edition

7.14 Инструменты Git - Хранилище учетных данных

Хранилище учетных данных

Если для подключения к удаленным серверам вы используете SSH-транспорт, то вы можете использовать ключ без пароля, что позволит вам безопасно передавать данные без ввода логина и пароля. Однако, это невозможно при использовании HTTP-протоколов – каждое подключение требует пары логин, пароль. Все ещё сложнее для систем с двухфакторной аутентификацией, когда выражение, которое вы используете в качестве пароля, генерируется случайно и его сложно воспроизвести.

К счастью, в Git есть система управления учетными данными, которая может помочь в этом. В Git "из коробки" есть несколько опций:

  • По умолчанию Git не кеширует учетные данные совсем. Каждое подключение будет запрашивать у вас логин и пароль.

  • В режиме “cache” учетные данные сохраняются в памяти в течении определенного периода времени. Ни один из паролей никогда не сохраняется на диск и все они удаляются из кеша через 15 минут.

  • В режиме “store” учетные данные сохраняются на неограниченное время в открытом виде в файле на диске. Это значит что, до тех пор пока вы не измените пароль к Git-серверу, вам не потребуется больше вводить ваши учетные данные. Недостатком такого подхода является то, что ваш пароль хранится в открытом виде в файле в вашем домашнем каталоге.

  • На случай если вы используете Mac, в Git есть режим “osxkeychain”, при использовании которого учетные данные хранятся в защищенном хранилище, привязанному к вашему системному аккаунту. В этом режиме учетные данные сохраняются на диск на неограниченное время, но они шифруются с использованием той же системы, с помощью которой сохраняются HTTPS-сертификаты и автозаполнения для Safari.

  • В случае если вы используете Windows, вы можете установить помощник, называемый “winstore”. Он похож на “osxkeychain”, описанный выше, но для управления секретной информацией использует Windows Credential Store. Найти его можно по ссылке https://gitcredentialstore.codeplex.com.

Мы можете выбрать один из этих методов, изменив настройки Git:

$ git config --global credential.helper cache

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

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

Git позволяет настраивать сразу несколько помощников. При поиске учетных данных для конкретного сервера, Git будет по порядку запрашивать у них учетные данные и остановится при получении первого ответа. При сохранении учетных данных, Git отправит их всем помощникам в списке, которые уже в свою очередь могут решить, что с этими данными делать. Ниже приведено как будет выглядеть .gitconfig, если у вас есть файл с учетными данными на флэш-диске, но, на случай его отсутствия, вы хотите дополнительно использовать кеширование в оперативной памяти.

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

Под капотом

Как же это все работает? Корневой командой Git для системы помощников авторизации является git credential, которая принимает команду через аргумент, а все остальные входные данные через стандартный поток ввода.

Возможно, это проще понять на примере. Допустим, помощник авторизации был настроен и в нем сохранены учетные данные для 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 ожидает данные из стандартного потока ввода. Мы передаем ему то, что знаем: протокол и имя сервера.

  3. Пустая строка обозначает, что ввод закончен и система управления учетными данными должна ответить, что ей известно.

  4. После этого Git-credential выполняет какую-то работу и выводит обнаруженную информацию.

  5. Если учетные данные не найдены, Git спрашивает у пользователя логин/пароль, и выводит их обратно в задействованный поток вывода (в данном примере это одна и та же консоль).

В действительности, система управления учетными данными вызывает программы, отделенные от самого Git; какие и как зависит в том числе и от настроек credential.helper. Существует несколько вариантов вызова:

Настройки Поведение

foo

Выполняется git-credential-foo

foo -a --opt=bcd

Выполняется git-credential-foo -a --opt=bcd

/absolute/path/foo -xyz

Выполняется /absolute/path/foo -xyz

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

Код после символа ! выполняется в шелле

Итак, помощники, описанные выше на самом деле называются git-credential-cache, git-credential-store и т.д. и мы можем настроить их на прием аргументов командной строки. Общая форма для этого git-credential-foo [args] <action>. Протокол ввода/вывода такой же как и у git-credential, но они используют немного другой набор операций:

  • get запрос логина и пароля.

  • store запрос на сохранение учетных данных в памяти помощника.

  • erase удаляет учетные данные для заданных параметров из памяти используемого помощника.

Для операций store и erase не требуется ответа (в любом случае Git его игнорирует). Однако, для Git очень важно, что помощник ответит на операцию get. Если помощник не знает что-либо полезного, он может просто завершить работу не выводя ничего, но если знает – он должен добавить к введенной информации имеющуюся у него информацию. Вывод обрабатывается как набор операций присваивания; выведенные значения заменят те, что 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, включающий в себя учетные данные. Помощники osxkeychain и winstore используют форматы, лежащие в основе их хранилищ, а cache использует его собственный формат хранения во внутренней памяти (который другие процессы прочитать не могут).

Собственное хранилище учетных данных

Поскольку git-credential-store и подобные ей утилиты являются отдельными от Git программами, не сложно сделать так, чтобы любая программа могла быть помощником авторизации Git. Помощники предоставляемые Git покрывают наиболее распространенные варианты использования, но не все. Для примера допустим, что ваша команда имеет некоторые учетные данные, совместно используемые всей командой, например, для развертывания. Эти данные хранятся в общедоступной директории, но вы не хотите копировать их в ваше собственное хранилище учетных данных, так как они часто изменяются. Ни один из существующих помощников не покрывает этот случай; давайте посмотрим, что будет стоить написать свой собственный. Есть несколько ключевых особенностей, которым должна удовлетворять эта программа:

  1. Мы должны уделить внимание только одной операции get; store и erase являются операциями записи, поэтому мы не будем ничего делать при их получении.

  2. Формат файла с совместно используемыми учетными данными такой же как и у git-credential-store.

  3. Расположение это файла более-менее стандартное, но, на всякий случай, мы должны позволять пользователям передавать свой собственный путь.

Мы снова напишем расширение на Ruby, но подойдет любой язык, так как Git может использовать всё, что сможет запустить на выполнение. Ниже приведен полный исходный код нашего нового помощника авторизации:

#!/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. Здесь мы разбираем аргументы командной строки, позволяя указывать пользователям входной файл. По умолчанию это ~/.git-credentials.

  2. Эта программа отвечает только если операцией является get и файл хранилища существует.

  3. В цикле считываются данные из стандартного ввода, до тех пор пока не будет прочитана пустая строка. Введенные данные для дальнейшего использования сохраняются в отображении known.

  4. Этот цикл читает содержимое файла хранилища, выполняя поиск соответствия. Если протокол и сервер из known соответствуют текущей строке, программа выводит результат и завершает работу.

Мы сохраним нашего помощника как 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

Как вы видите, расширять эту систему довольно просто и это позволяет решить некоторые общие проблемы, которые могут возникнуть у вас и вашей команды.