Git
Chapters ▾ 2nd Edition

7.14 Інструменти Git - Збереження посвідчення (credential)

Збереження посвідчення (credential)

Якщо ви використовуєте протокол SSH для зʼєднання з віддаленими сховищами, то можете використовувати ключ без пароля, що дозволяє безпечно передавати дані без набирання імʼя користувача та пароля. Втім, це неможливо з HTTP протоколами – кожне зʼєднання потребує імені та пароля. Все стає ще складнішим з двокроковою авторизацією: значення, яке треба використати як пароль, випадково згенероване та невимовне.

На щастя, Git має систему посвідчень, яка може тут зарадити. Щойно встановлений Git пропонує чимало опцій:

  • Типова поведінка — взагалі нічого не запамʼятовувати. Кожне зʼєднання потребує від вас імʼя користувача та пароль.

  • Режим “cache” зберігає посвідчення в памʼяті визначений термін. Жоден пароль не зберігається на диску, та вичищається з памʼяті за 15 хвилин.

  • Режим “store” зберігає посвідчення до простого текстового файлу на диску, та ніколи не застаріває. Це означає, що доки ви не зміните пароль для Git, вам ніколи не доведеться набирати ваші дані знов. Недоліком цього методу є те, що ваші паролі зберігаються текстом у простому файлі в домашній директорії.

  • Якщо ви використовуєте Mac, Git має режим “osxkeychain”, який зберігає посвідчення у безпечному ланцюгу ключів (keychain), що є привʼязаним до вашого системного облікового запису. Цей метод зберігає посвідчення на диску і ніколи не застаріває, проте його зашифровано так само, як система зберігає сертифікати HTTPS та автозаповнювачі 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

Деякі з цих помічників мають опції. Помагач “store” може приймати аргумент --file <шлях>, який задає, куди зберігається текстовий файл (типове значення ~/.git-credentials). Помагач “cache” приймає опцію --timeout <секунд>, яка змінює термін, протягом якого демон працює (типове значення “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, яка приймає команду в якості аргументу, а потім ще ввід через stdin.

Можливо це легше зрозуміти за допомогою прикладу. Припустімо, помічник посвідчень вже налаштовано, та він вже зберіг посвідчення для mygithost. Ось сесія, що використовує команду “fill”, яка викликається при спробі знайти посвідчення для хосту:

$ 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. Порожній рядок означає, що ввід завершено, та система посвідчень має відповісти, що вона знає.

  4. Далі Git-credential приймає керування та пише до stdout інформацію, яку знайшов.

  5. Якщо посвідчень не знайдено, Git запитує в користувача імʼя та пароль, та видає їх назад до stdout, з якого був викликаний (у даному випадку вони підʼєднані до однієї консолі).

Насправді, система посвідчень викликає програму, яка відокремлена від власно 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

Код після ! передається на виконання до оболонки (shell)

Отже, вищеописані помічники насправді мають назви git-credential-cache, git-credential-store тощо, та ми можемо їх налаштувати, щоб вони приймали аргументи командного рядка. Загальна форма для цього “git-credential-foo [аргументи] <дія>.” Протокол stdin/stdout такий самий, як для git-credential, проте вони використовують трохи інших набір дій:

  • get — це запит пари імені/пароля.

  • store — це запит на зберігання набору посвідчень у памʼяті помічника.

  • erase — очистити посвідчення для наданих властивостей з памʼяті помічника.

Для дій store та erase відповідь не потрібна (Git все одно її ігнорує). Втім, щодо дії get, Git дуже зацікавлений у тому, що скаже помічник. Якщо помічник не знає нічого корисного, він може просто вийти без виводу, проте, якщо він щось знає, він має доповнити прийняту інформацію тою, яку зберіг. Вивід сприймається як послідовність виразів присвоєння; будь-що надане замінить те, що 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 і wincred використовують локальний формат своїх cховищ, у той час як 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'] 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. Цей цикл читає вміст файлу з даними — шукає відповідності. Якщо протокол та хост з known відповідають поточному рядку, програма друкує результати до stdout та завершує свою роботу.

Ми збережемо наш помічник як 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'

Як ви можете бачити, розширення системи є доволі прямолінійним, та може вирішувати якісь повсякденні завдання для вас та вашої команди.