Git Credential Helper
Git ciągle mnie zaskakuje modularnością i rozszerzalnością swojej architektury. W pracy mam standardowe zablokowane porty SSH, które są używane przez większość popularnych serwisów hostujących serwery gita (Giltab, Github); również mój prywatny serwer stoi na niestandardowym porcie, który padł ofiarą działu IT. W związku z tym, nie mogę korzystać z logowania przy pomocy kluczy SSH i od kilku lat, kiedy tylko chcę uzyskać dostęp do prywatnego repozytorium lub spushować jakąś zmianę, muszę korzystać z komunikacji przez HTTPS i wpisywać swoją nazwę użytkownika i hasło.
Słabe rozwiązanie: cache
Jakiś czas temu nieco ułatwiłem sobie to żmudne zadanie ustawiając cache’owanie haseł:
$ git config --global credential.helper 'cache --timeout=3600'
Okazuje się jednak, że nie do końca pojąłem pełną złożoność tego ustawienia.
cache
jest bowiem jedynie programem rozprowadzanym wraz z gitem
(git-credential-cache), który jest wywoływany z tego powodu, że został
ustawiony jako wartość pola helper sekcji [credential] w pliku
konfiguracyjnym gita. Może to być jednak dowolne inne polecenie, które wypisze
na standardowe wyjście odpowiednio sformatowane dane logowania.
Dobre rozwiązanie: własny pomocnik
Na co dzień do zarządzania hasłami korzystam z programu pass, który szyfrowanie haseł deleguje do gpg, a sam jedynie zarządza zaszyfrowanymi plikami z hasłami. Jest to niezwykle wygodne narzędzie z tego względu, że w naturalny sposób integruje się z systemem operacyjnym – dla każdego programu, który jest w stanie wywołać dowolne polecenie shella możemy w prosty sposób napisać manager haseł. Dzięki temu, że pass korzysta bezpośrednio z gpg, możemy użyć gpg-agenta, który, w zależności od konfiguracji, będzie w odpowiedni sposób pytał i przechowywał hasła kluczy PGP.
Wobec powyższego, naturalnym dla mnie przykładem będzie napisanie prostego skryptu-pomocnika konwertującego wyjście programu pass na format akceptowany przez gita.
W skrócie: git akceptuje listę klucz=wartość
, gdzie każda para zakończona jest
znakiem nowej linii, zaś pole klucz przyjmuje jedną z następujących wartości:
protocol
, host
, path
, username
, password
. Lista par nie musi zawierać
wszystkich wartości, szczególnie że nie zawsze mają one sens. W naszym przypadku
zwrócenie pól username
i password
w zupełności wystarcza.
Przykład owej listy, którą powinien zwracać helper:
username=johnny
password=haxorpass
Format passa zaś jest wymuszony architekturą samego programu; pierwsza
linijka to hasło, pozostałe dane to metadane, które generalnie są ignorowane
przez passa jako takie, jednak można je wypisać na ekran przy pomocy komendy
pass show <nazwa>
:
haxorpass
username: johnny
Wobec bijącego po oczach podobieństwa, zamiana jednego formatu na drugi to prosta transformacja, którą można wykonać przy pomocy jednego obrotu seda. Zanim jednak podam kod programu, musimy się zaznajomić ze sposobem, w jaki git będzie go uruchamiał.
credential.helper
Jak już na początku wspomniałem, git wykona komendę zapisaną w polu helper w sekcji [credential] konfiguracji1. Nie jest to jednak pełna informacja, gdyż przed wykonaniem git wykonuje pewną transformację wpisu:
-
jeśli wpis rozpoczyna się wykrzyknikiem, to wszystko po nim jest traktowane jako kod shella i zostaje użyte tak jak zostało wpisane;
ini helper = !f() { echo "password=abc" }; f
-
jeśli wpis jest ścieżką bezwzględną, zostanie on użyty jako wywoływane polecenie tak, jak został wpisany;
ini helper = /usr/local/bin/foobar
-
w przeciwnym wypadku (gdy wpis jest ścieżką względną), zostanie on wywołany jako
git credential-$NAME
(czyli w $PATH musi być dostępny program o nazwie “git-credential-$NAME”).ini helper = foobar
Do wynikowego polecenia git następnie dopisuje, jako ostatni argument, typ operacji:
- get – prośba o zwrócenie danych logowania
- store – prośba o zapisanie danych logowania wewnątrz programu
- erase – prośba o usunięcie zapisanych danych logowania
Na przykład, dla helper = foo
git wywoła polecenie git credential-foo get
,
ale dla helper = !foo
wywoła foo get
.
Skrypt
Uzbrojeni w tę wiedzę możemy stworzyć skrypt i odpowiednio skonfigurować gita.
Zacznijmy od konfiguracji gita. Jeśli skrypt nazwiemy git-credential-pass
i
umieścimy go w $PATH, to dla interesujących nas serwisów możemy dodać
następujące pola konfiuracyjne:
[credential "https://gitlab.com"]
helper = pass my-credentials/gitlab.com
[credential "https://github.com"]
helper = pass my-credentials/github.com
Sam skrypt zaś to proste filtrowanie wyjścia passa, o którym mówiłem już wcześniej. Należy umieścić go gdzieś w $PATH lub $GIT_EXEC_PATH:
#!/bin/sh
test "$2" = "get" || exit 0
pass show "$1" | sed -e '1 s/^\(.*\)$/password=\1/' \
-e '2,$ s/username: \?/username=/'
Więcej szczegółów, jak zwykle, w dokumentacji2.