git の HTTPS リモートを SSH に自動書き換えして macOS キーチェーンの確認を止める
まとめ
- macOS で git を HTTPS 経由で使うと、push/pull のたびにキーチェーンの確認ダイアログが出ることがある。原因は credential helper がキーチェーンから資格情報を読み出すため
- 個別リポは
git remote set-urlで SSH に切り替えれば、キーチェーンを経由しなくなる - グローバル config に
insteadOfを入れると、HTTPS の GitHub URL を git が自動で SSH に書き換える。今後クローンするリポにも効くので恒久対処になる - public リポは SSH 権限がなくても clone できる。HTTPS でしか読めない private リポだけは
GIT_CONFIG_GLOBAL=/dev/nullで一時的にグローバル config を無視して clone する
毎回キーチェーンの確認が出て面倒
git を HTTPS で使っていると、push や pull のたびに「”git-credential-osxkeychain” がキーチェーンに保存されている機密情報を使おうとしています」というダイアログが出ることがある。パスワードを入れて「許可」を押しても、しばらくするとまた出てくる。自分もこれが地味に鬱陶しくて、毎回手を止められるのが嫌だった。
これは macOS の credential helper(osxkeychain)の挙動だ。git を HTTPS で使うと、GitHub のトークンやパスワードをログインキーチェーンに保存し、通信のたびにそこから読み出そうとする。その読み出しをキーチェーンが確認してくる、という流れになっている。
毎回出る典型的な原因は2つある。
- ダイアログで「許可」を押している。これは今回だけの許可なので、次回また聞かれる。「常に許可」を押せば以降は出なくなる
- Homebrew などで
gitバイナリが更新され、コード署名が変わった。キーチェーンは署名が変わると別のアプリとみなして再確認してくる
「常に許可」で止まることも多いが、バイナリを更新するたびに再確認されるのは変わらない。そもそもキーチェーンを触らない構成にするのが根本的だ。SSH に切り替えればいい。
まず個別リポを SSH に切り替える
HTTPS を使っているリポを洗い出す。origin の URL を見て https:// で始まるものを拾う。
for gitdir in $(find ~/code ~/work -maxdepth 4 -name '.git' -type d 2>/dev/null); do
repo="${gitdir%/.git}"
url=$(git -C "$repo" remote get-url origin 2>/dev/null)
case "$url" in
https://*) echo "$repo -> $url" ;;
esac
done見つかったら SSH の URL に張り替える。
git remote set-url origin git@github.com:owner/repo.gitSSH 鍵が GitHub に登録済みなら、これで認証確認が通る。
ssh -T git@github.com
# Hi <username>! You've successfully authenticated, ...この確認が出れば鍵は有効だ。あとは git ls-remote origin などが素通りする。
恒久対処: HTTPS を SSH に自動書き換えする
個別に張り替えても、新しくクローンしたリポや、CLI が HTTPS でリモートを作った場合はまた HTTPS に戻ってしまう。そこで git の insteadOf を使う。GitHub の HTTPS URL を、git が操作時に自動で SSH に書き換えるようにする。
git config --global url."git@github.com:".insteadOf "https://github.com/"これで挙動が変わる。
git clone https://github.com/owner/repo.gitしても、実際の接続は SSH になる- CLI が HTTPS のリモートを作っても、git 操作時に SSH に書き換わる
- 既存の HTTPS リモートも、リモート設定を変えずにそのまま SSH で通信される
書き換えは通信のたびに裏で行われる。リモート設定そのものは HTTPS のまま残るが、git ls-remote --get-url で確認すると SSH の URL が返ってくる。
git ls-remote --get-url https://github.com/owner/repo.git
# git@github.com:owner/repo.gitこれでキーチェーンを経由しなくなるので、確認ダイアログは出なくなる。
トレードオフも一応ある。この設定は github.com の全リポに効くので、他人の public リポにも SSH で接続しにいく。SSH 鍵が登録済みなら public リポの読み取りは問題なく通るので実害はないが、SSH のポート 22 が塞がれたネットワークだと繋がらなくなる。その場合は SSH の 443 番フォールバックを設定すればいい。
# ~/.ssh/config
Host github.com
Hostname ssh.github.com
Port 443元に戻したくなったら unset する。
git config --global --unset url."git@github.com:".insteadOfSSH 権限がないリポをクローンしたいとき
insteadOf を入れると「HTTPS でしか読めないリポはどうなるのか」が気になる。ここは2つのケースを分けて考える必要がある。
public リポは権限不要で通る
SSH の読み取りは、そのリポの権限とは関係なく、鍵が登録された GitHub ユーザーなら誰でも通る。public リポの clone に per-repo の SSH 権限は要らない。自分が collaborator でない他人の public リポでも、SSH で普通に clone できる。
だから insteadOf が入っていても、public リポは何もせずそのまま clone できる。ここは心配しなくていい。
HTTPS トークンでしか読めない private リポ
唯一ハマるのがこれだ。SSH collaborator ではないが、HTTPS のトークンでは読める private リポ。insteadOf が SSH に書き換えてしまうので、Permission denied になる。
この時だけ、グローバル config を一時的に無視して clone する。
GIT_CONFIG_GLOBAL=/dev/null git clone https://github.com/owner/repo.gitGIT_CONFIG_GLOBAL に /dev/null を指定すると、そのコマンドだけグローバル config(insteadOf を含む)を読まなくなる。書き換えが効かないので HTTPS のまま接続する。実際に確認するとこうなる。
git ls-remote --get-url https://github.com/owner/repo.git
# git@github.com:owner/repo.git (通常は書き換わる)
GIT_CONFIG_GLOBAL=/dev/null git ls-remote --get-url https://github.com/owner/repo.git
# https://github.com/owner/repo.git (書き換わらない)この時は HTTPS 認証が動くのでキーチェーンやトークンの入力を求められるが、レアケースの一発だけなので許容範囲だろう。なお、CLI の clone コマンドも内部で git clone を呼ぶことが多く、その場合は insteadOf が効く。HTTPS を強制したいなら素の git に上の環境変数を付けて使う。
おわりに
キーチェーンの確認が煩わしかったのは、HTTPS と credential helper の組み合わせが原因だった。SSH に寄せればキーチェーンを触らなくなる。個別のリポは remote set-url、今後のリポまで含めるなら insteadOf のグローバル設定、という二段構えで塞げる。権限のないリポの clone も、public はそのまま通るし、private だけ環境変数で一時回避すればいい。
自分は insteadOf を入れてから、あのダイアログを一度も見ていない。もっと早くやればよかった、というのが正直なところ。