投稿

git の HTTPS リモートを SSH に自動書き換えして macOS キーチェーンの確認を止める

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.git

SSH 鍵が 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:".insteadOf

SSH 権限がないリポをクローンしたいとき

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.git

GIT_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 を入れてから、あのダイアログを一度も見ていない。もっと早くやればよかった、というのが正直なところ。

トレンドのタグ