gitのcredential.helperを理解してCodeCommitの403エラーを回避してみた

2019.03.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、平野です。

先日下記のブログが投稿されたのとほぼ同じタイミングで私も同じ状態に陥っていたため、対応策を探していました。

gitでのCodeCommitへの操作が403エラーになった原因を調査した記録

対応策がいろいろあるようですが、credential.helperの動作を調べたところ、 ある程度納得できる対処できるができました。

問題の状況

CodeCommitにクロスアカウントでアクセスする際には CodeCommitのマニュアル に書かれている通り設定すればよく、結果として、globalなり、localなりに

[credential]
helper=!aws codecommit credential-helper --profile MyCrossAccountAccessProfile $@

が設定されているはずです。

しかし、MacのgitでCodeCommitを使用している場合、 初回はうまくいくのですが、それからしばらく時間が経つと認証を通らなくなってしまいます。

対処法

まず先に対処法を書きます。 root権限が必要な部分を触るのでお気をつけ下さい。

/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig を編集します。

[credential]
helper = osxkeychain

を以下のように変更します。

[credential]
helper = osxkeychain_wrapper

次にgit-credential-osxkeychain_wrapperを作成します。

#!/bin/bash

if grep -q ' codecommit credential-helper ' ${GIT_DIR}/config; then
exit
fi

readonly OSXKEYCHAIN_SCRIPT="/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-credential-osxkeychain"
eval "${OSXKEYCHAIN_SCRIPT} \"$@\""

このファイルを/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-credential-osxkeychain_wrapperとして保存します。 (フルパスは人によって異なるかもしれないので、後の解説を参照)

2019/11/11追記 上記のファイルには実行権限が必要なので、以下のように実行権限をつけます。

sudo chmod +x git-credential-osxkeychain_wrapper

追記終わり

以上で完了です。

問題の整理

gitの設定について

gitの現在の設定はgit config -lで確認できます。 CodeCommitの設定を行った状態では、以下のようになります。

$ git config -l
credential.helper=osxkeychain
credential.helper=!aws codecommit credential-helper --profile MyCrossAccountAccessProfile $@
(credential.helper以外は省略)

上記のように、同じ項目(ここではcredential.helper)が複数表示されることがあります。 これはgitの設定箇所が複数あり、ローカルな設定ほど下に書かれます。 gitはこの設定を上から順に見ていきますので、ここに表示される設定は後勝ちとなります。

credential.helperの場合

しかし、credential.helperに関しては、 Gitのマニュアル に、

サーバーの認証情報が必要になると Git はこれらを順番に検索をかけていき、ヒットした時点で検索を中断します。

と書かれている通り、上から評価して、 ヒットした時点で中断 されてしまいます。 なのでcredential.helperについては、上位で認証情報が得られた場合、下位のものは無視されてしまうようです。

CodeCommitのリポジトリにアクセスする際、初回はキーチェーンにそれに関連する情報がないので、 credential.helper=osxkeychainの部分はスルーされ、 aws codecommit credential.helperのコマンドが実行されます。 そして、そこで得られた認証情報がキーチェーンに登録されます。

その後しばらくはキーチェーンを参照することで正しい認証情報が得られるので、 aws codecommit credential.helperコマンドを実行することなくリモートリポジトリに接続することができます。 しかし、CodeCommitの認証システムでは15分たつと新しい認証情報が必要になりますので、 それ以降はキーチェーンの情報では認証を通ることができません。

また、credential.helperは、認証を実際に通過できたかは関知しないので、 この状態ではキーチェーンは古い情報しか出さない上に、aws codecommit credential.helperのコマンドも実行されなくなってしまいます。

解決方法の模索

誰がOSのキーチェーン使用を設定している?

最下位のcredential.helperがどこで設定されているかは以下のコマンドで参照できます。

git config --show-origin --get credential.helper

ローカルなどからcredential.helperの設定を外した状態で見てみると、 /Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig というファイルで設定されていることがわかります。

中身は以下のようになっていました。

[credential]
helper = osxkeychain

ここでcredential.helperとしてOSのキーチェーンを使用するように書かれています。

osxkeychainをラップする

gitの認証情報の保存に関する情報 を見ると、gitの認証情報の保持の仕方が解説されています。

マニュアルによると、osxkeychainという記述があった場合、 保存されている認証情報を取得するためには

git-credential-osxkeychain get

というコマンドが実行されるようです。 またosxkeychainという記述はgit-credential-に後続する部分だけを省略して指定できる機能を使っているようです。 そしてこのファイルは /Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-credential-osxkeychain という場所にありました。1

ということで、git-credential-osxkeychainをラップしたプログラムを用意します。 CodeCommitかどうかを判別し、CodeCommitだった場合は何もせずに終了とし、 そうでない場合にはgit-credential-osxkeychainへ移譲を行うようにします。 このラッパーが何もせずに終了した場合には、より下位のcredential.helperの評価に進みます。

CodeCommitかどうかの判別方法

git-credential-osxkeychain_wrapperを実行するプロセスでは、GIT_DIRという環境変数が設定されており、 それが現在のレポジトリの.gitディレクトリをさすようになっています。 なので、そこから簡単にconfigファイルの場所を知ることができます。

あとはconfigファイル内でCodeCommitのcredential.helperを使用しているかを確認すればOKです。

Xcodeを更新した場合

2019/11/11追記 Xcodeを新しいバージョンに上げたとところ、 上記で設定したファイルが全てデフォルトの状態に戻っていたようでした。 再び同様に設定した所同じように動作することが確認できました。

やはり、公式的にはあまりオススメされないやり方ではあるかと思いますので、 その点はご留意頂きたいと思います。 追記終わり

まとめ

Macで、CodeCommitにhttpsで接続した場合、15分経つと接続ができなくなってしまう問題が生じます。 そこで、CodeCommitを使用した場合にはOSのキーチェーンを使用せず、 よりローカルな設定に任せる方法をご紹介しました。

credential.helperがグローバルな設定を先に評価してしまい、 ローカルな設定にたどり着けないという挙動はなかなか影響が大きく、 CodeCommit以外にも同様な問題に当たる可能性はあるかなと思います。 その際にも、上記の方法で回避が可能になるかと思いますので、お試し頂ければと思います。

以上、誰かの参考になれば幸いです。


  1. これは単純にfindでコマンドで探しました。