Git
Chapters ▾ 2nd Edition

7.14 Git のさまざまなツール - 認証情報の保存

認証情報の保存

SSH を使ってリモートサーバーと接続しているのなら、パスフレーズなしの鍵を使えます。そうすれば、データ転送を安全に行おうとする際に、ユーザー名やパスワードを入力せずにすみます。 一方、HTTP プロトコルの場合はこうはいきません。接続のたびにユーザー名とパスワードが必要です。 さらに大変になるのが二要素認証が必要なシステムの場合です。パスワードと組み合わせて使うトークンはランダムに生成されており、unpronounceable だからです。

さいわい、Git には認証情報の仕組みがあり、上述のような大変さを軽減してくれます。 標準の仕組みで選択可能なオプションは以下のとおりです。

  • デフォルトでは、なにもキャッシュされません。 接続するたび、ユーザー名とパスワードを尋ねられます。

  • “cache” モードにすると、認証情報が一定の間だけメモリーに記憶されます。 パスワードはディスクには保存されません。15分経つとメモリーから除去されます。

  • “store” モードにすると、認証情報がテキストファイルでディスクに保存されます。有効期限はありません。 ということは、パスワードを変更するまで、認証情報を入力しなくて済むのです。 ただし、パスワードが暗号化なしのテキストファイルでホームディレクトリに保存される、というデメリットがあります。

  • Mac を使っているなら、Git の “osxkeychain” モードが使えます。これを使うと、OS のキーチェーン(システムアカウントと紐づく)に認証情報がキャッシュされます。 このモードでも認証情報がディスクに保存され、有効期限切れもありません。ただし先ほどとは違い、保存内容は暗号化(HTTPS 証明書や Safari の自動入力の暗号化と同じ仕組み)されます。

  • Windows を使っているなら、“wincred” という補助ツールがあります。 “osxkeychain” と同じような仕組み(Windows Credential Store)で、重要な情報を管理します。

このオプションを設定するには、以下のように 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 はこれらを順番に検索をかけていき、ヒットした時点で検索を中断します。 認証情報を保存する際は、有効なヘルパー すべて にユーザー名とパスワードが渡されます。それらをどう処理するかはヘルパー次第です。 以下は、複数のヘルパーを有効にする .gitconfig の例になります。USB メモリ上に保存されている認証情報を優先して使うけれど、もし USB メモリが使用不可の場合はパスワードを一定期間キャッシュしておく、という設定です。

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

認証情報保存の裏側

認証情報を保存する仕組みは、いったいどのようにして動作しているのでしょうか。 認証情報ヘルパーの仕組みを操作する基本となるコマンドは 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 とは別のプログラムです。どのプログラムがどのように呼び出されるかは、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-cachegit-credential-store といった名前がつくということです。コマンドライン引数を受け付けるよう設定することもできます。 設定方法は “git-credential-foo [args] <action>.” になります。 なお、標準入出力のプロトコルは git-credential と同じですが、指定できるアクションが少し違ってきます。

  • get はユーザー名/パスワードの組み合わせを要求するときに使います。

  • store はヘルパーのメモリーに認証情報を保持するよう要求するときに使います。

  • erase はヘルパーのメモリーから指定したプロパティの認証情報を削除するよう要求するときに使います。

storeerase のアクションの場合、レスポンスは必要ありません(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 がずらずらと続く形になっています。 なお、osxkeychainwincred ヘルパーは情報を保存するために独自のフォーマットを使用し、cache ヘルパーは独自形式でメモリーに情報を保持します(他のプロセスはこの情報にアクセスできません)。

独自の認証情報キャッシュ

git-credential-store などのプログラムは Git から独立している。」このことを理解すると、どんな プログラムであれ Git 認証情報ヘルパーとして機能できるということに気づくのもそれほど大変ではないと思います。 Git についてくるヘルパーは多くのユースケースに対応していますが、全てに対応できるわけではありません。 ここでは一例として、あなたのチームには全員が共有している認証情報があるとしましょう。デプロイ用の認証情報であればありえるケースです。 この情報は共有ディレクトリに保存されていますが、自分専用の認証情報としてコピーしておきたくはありません。頻繁に更新されるからです。 既存のヘルパーはどれもこの例には対応していません。この用途に合うヘルパーを作るには何が必要か、順を追って見ていきましょう。 まず、このプログラムには必要不可欠な機能がいくつもあります。

  1. 考慮しなければならないアクションは get だけなので、書き込みのアクションである storeerase を受け取った場合は何もせずに終了することにします。

  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 としてパスの通っているところに保存したら、ファイルを実行可能にしましょう。 実際に実行したときの対話型セッションは、以下のようになります。

$ 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

このとおり、Git の認証情報の仕組みを拡張するのはとても単純ですし、個人やチームの悩みを解決するのに役立つはずです。