Gitは認証が必要なリポジトリにアクセスするとき、credential helperと呼ばれるコマンドを実行します。credential helperはただの標準入出力を扱うプログラムで、一般的には git-credential-xxx という命名になっています。おそらく以下のうちどれかをよく使うのではないかと思います。
- git-credential-cache
- git-credential-store
- git-credential-osxkeychain (macOS)
- git-credential-libsecret (Linux)
- git-credential-manager (Windows)
macOSの場合は、デフォルトでgit-credential-osxkeychainが使われるので、自分で設定することは少ないかもしれませんね。helperコマンドはgit config
で設定します。
git config --global credential.helper cache
Gitは認証が必要だとわかった時点で、git config
で設定したhelperコマンドを実行します。このときコマンドには、以下のような値が標準入力経由で渡されることになります。入力はname=value
形式で1行1つ記述されていて、最後に空行で終わります。これらの値は状況によって省略されるものもあります。
protocol=https host=github.com path=path/to username=lufia password=xxx
helperは入力を空行まで読み込んで、条件に対応する値を標準出力でGitに返す必要があります。基本的にはpasswordだけを返せば動くと思いますが、それ以外の値も返せば、入力に優先して返した値が使われます。protocolなどを個別に返してもいいし、urlとしてまとめても同じです。
protocol=https host=github.com path=path/to username=lufia password=xxx url=https://lufia:xxx@github.com/path/to quit=0
credential helperの詳細はPro Gitに書かれています。
また、呼び出す側の実装はこのファイルです。
標準で持っているhelperはgit-coreディレクトリに入っています。
% git --exec-path /Library/Developer/CommandLineTools/usr/libexec/git-core % 9 ls -p /Library/Developer/CommandLineTools/usr/libexec/git-core/git-credential-* git-credential-cache git-credential-cache--daemon git-credential-osxkeychain git-credential-store
Factotumを使う
上でみたように、credential helperはただの標準入出力を扱うプログラムなので、echoで固定の文字列を返してもいいし、ネットワークからパスワードを持ってくるコマンドを自作することもできます。ところでPlan 9にはfactotumという認証エージェントが存在していて、これはPlan 9ツールをUnixに移植したplan9portでも使えるので、factotumからパスワードを取り出すcredential helperを実装してみました。完全なソースコードはこちらです。
Factotum
FactotumはSSH秘密鍵やログインパスワードなどを一括して扱うプログラムです。key=value
のリストを管理します。!
で始まる属性は秘密情報扱いとなり、表示されません。
% factotum % echo 'key proto=pass role=client service=git dom=github.com user=lufia !password=xxx' | > 9p write factotum/ctl % 9p read factotum/ctl key proto=pass role=client service=git dom=github.com user=lufia !password?
上記の例ではpassプロトコル(生のパスワードを扱うプロトコル)を使いましたが、これ以外にもp9sk1やrsaなどいくつか用意されています。protoの値によって必須となる属性は変わります。passプロトコルではuserとpassword属性が必須です。任意の属性はいくつ追加しても構いません。ここでは、Git用のパスワードと識別するためにserviceキーと、対象サービスのドメインdom属性を追加しています。
登録された情報を使う場合、proto=pass role=client user=lufia
のように絞り込むための属性を使ってfactotumへアクセスします。ところで、Plan 9のlibauthにはproto=pass
のための関数があるので、それを使えば十分です。
#include <u.h> #include <libc.h> #include <thread.h> #include <auth.h> ... UserPasswd *up; up = auth_getuserpasswd(nil, "proto=pass role=client user=lufia"); if(up == nil) sysfatal("auth_getuserpasswd: %r"); print("username=%s\n", up->username); print("password=%s\n", up->passwd); free(up);
Gitはcredential helperからパスワードを取り出せなかった場合に入力を促して、その内容をcredential helperに保存しようとします。factotumにエントリを登録する場合はfactotum/ctlファイルに書き込めば終わりです。ただし、Plan 9ネイティブの場合は/mntにfactotumが提供するファイルをマウントしているので/mnt/factotum/ctlをopenすればいいんですが、plan9portの場合は直接参照することができないため、lib9pclientを使う必要があります。
#include <u.h> #include <libc.h> #include <thread.h> #include <9pclient.h> ... CFsys *fs; CFid *fid; fs = nsamount("factotum", nil); if(fs == nil) sysfatal("nsamount: %r"); fid = fsopen(fs, "ctl", OWRITE); if(fid == nil) sysfatal("fsopen: %r"); if(fswrite(fid, "key proto=pass ...", n) < 0) sysfatal("fswrite: %r"); fsclose(fid); fsunmount(fs);
上記ではfsopenを使いましたが、代わりにfsopenfdを使うと、CFidの代わりにファイルディスクリプタを受け取ることができて、以降普通のファイルとして扱えるのでこちらの方が便利かもしれません。
Plan 9ネイティブなfactotumはパスワードを扱うだけあって、swapしないようになっていたり、デバッガの接続を禁止していたりと安全な作りになっていますが、plan9portのfactotumにはそういった仕様は盛り込まれていなさそうです。また、factotum単体ではプロセスが死ぬと記憶していたパスワードは消えてしまうので、永続化したい場合はsecstoredと一緒に使いましょう。
macOSの場合
launchd設定例です。
com.bell-labs.plan9.factotum.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>EnvironmentVariables</key> <dict> <key>PLAN9</key> <string>/usr/local/plan9</string> <key>PATH</key> <string>/usr/local/plan9/bin</string> </dict> <key>Label</key> <string>com.bell-labs.plan9.factotum</string> <key>ProgramArguments</key> <array> <string>/usr/local/plan9/bin/factotum</string> </array> <key>RunAtLoad</key> <false/> <key>KeepAlive</key> <dict> <key>OtherJobEnabled</key> <dict> <key>com.bell-labs.plan9.secstored</key> <true/> </dict> <key>SuccessfulExit</key> <false/> </dict> </dict> </plist>
com.bell-labs.plan9.secstored.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>EnvironmentVariables</key> <dict> <key>PLAN9</key> <string>/usr/local/plan9</string> </dict> <key>Label</key> <string>com.bell-labs.plan9.secstored</string> <key>ProgramArguments</key> <array> <string>/usr/local/plan9/bin/secstored</string> <string>-v</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>