GitでGitHubにpushするとき、裏ではSSHが鍵を1本選んで認証しています。普段はその鍵が1本で足りるので、どれが使われているかを意識することは少ないです。
アカウントがもう1つ要ると、同じ github.com に別の鍵を渡す必要が出ます。ここを雑にすると、意図しないアカウントでつながったり、コミットに残る名前だけが食い違ったりします。
先にSSHが鍵をどう選ぶかを見て、そのあと別アカウント用の Host alias と、コミット作者をフォルダで切り替える includeIf につなげます。
用語
SSH鍵
秘密鍵と公開鍵のペアです。Mac側に秘密鍵、GitHub側に公開鍵を置きます。Macが公開鍵を提示し、GitHubが登録を確認したうえでチャレンジに署名してもらい、公開鍵で検証して「どのアカウントか」を決めます。
Host alias
~/.ssh/config に書く、自分用の接続名です。実在するサーバー名ではなく、HostName github.com と組み合わせて「この名前で接続したら、この鍵を使う」と決めます。別アカウント用の名前は github-other にします。
IdentityFile
SSHが提示する秘密鍵のパスです。~/.ssh/config の IdentityFile、ssh-agent に載っている鍵、~/.ssh/id_ed25519 のようなデフォルト名の順で候補になります。
includeIf
Gitの設定を、ローカルのディレクトリ条件で読み込む仕組みです。gitdir: に指定するのはGitHub上のパスではなく、PC上のフォルダです。ここではコミット作者(user.name / user.email)の切り替えに使います。
コミット作者
コミットに記録される user.name と user.email です。SSH認証(どのGitHubアカウントとして push するか)とは独立しています。別アカウントの鍵で push しても、Git設定が普段のままだと作者は普段の名前のままです。
SSH認証の仕組み
Mac側には秘密鍵と公開鍵があります。
~/.ssh/id_ed25519 ← 秘密鍵
~/.ssh/id_ed25519.pub ← 公開鍵
公開鍵をGitHubの普段使うアカウントに登録しておくと、GitHubはその鍵をそのアカウントに紐付けます。Mac側に「id_ed25519 = このアカウント」という直接の定義があるわけではありません。
Mac → 公開鍵を提示 → GitHubが登録を確認 → チャレンジに署名 → アカウントが決まる
【接続時】Mac → GitHub
Mac:「まず公開鍵を提示するね。候補の鍵を順に出すよ」
【接続時】GitHub
GitHub:「その公開鍵、登録あるよ。署名して。この鍵は普段使うアカウントだ。Hi username!」
Macはアカウント名を送っているのではなく、公開鍵を提示し、通った鍵だけ秘密鍵で署名します。GitHubは提示された公開鍵1本ずつ照合し、登録があれば署名を検証します。アカウント側が登録済みの鍵を順に試す動きではありません。
誰が鍵を順番に試すのか
「署名をGitHubに送ったあと、GitHubが登録済みの公開鍵を順番に試すのでは?」と感じることがあります。順番に試しているのは主にMac側のSSHクライアントです。
| 側 | 動き |
|---|---|
| Mac(SSHクライアント) | config → ssh-agent → id_ed25519 など、候補の鍵を順に提示する |
| GitHub | 提示された公開鍵1本について「登録ある?」を調べ、あればチャレンジ→署名検証する |
GitHubは「別アカウントに登録されている鍵を全部なぞる」わけではありません。Macが渡してきた公開鍵1本に対して、DBを引いて当たりを付けます。GitHubは最初から「普段使うアカウントで入りたいのか、別アカウントか」を知りません。どの公開鍵が提示されたかだけで、アカウントが決まります。
1本目が未登録なら GitHub は拒否し、Mac が次の鍵を提示します。通った時点で認証は終わりです。
Mac: 公開鍵A を提示(Offering public key)
GitHub: A は未登録 → 却下
Mac: 公開鍵B を提示
GitHub: B は登録あり → チャレンジを返す
Mac: 秘密鍵B で署名
GitHub: 公開鍵B で検証 → OK → Hi username!
秘密鍵そのものは送りません。公開鍵を先に出し、通った鍵だけ署名のやり取りになります。
確認は次です。
ssh -T git@github.com
成功すると Hi USERNAME! You've successfully authenticated... のように、GitHubが認識したユーザー名が出ます。
なぜ id_ed25519 が使われるのか
SSHは ~/.ssh のファイルを総当たりするわけではありません。鍵候補は主に次から決まります。
~/.ssh/configのIdentityFilessh-agentに載っている鍵~/.ssh/id_ed25519や~/.ssh/id_rsaなどの標準的なデフォルト鍵名
id_ed25519 はGitHub専用の名前ではなく、SSHの標準的なデフォルト鍵名です。GitLabや自前サーバーなど、他サービスでも同じ鍵を登録して使っていることがあります。
どの鍵が候補かを見るには次です。
ssh -G github.com | grep -i identityfile
実際にどの鍵を提示し、どれが通ったかは、詳細ログで確認できます。
ssh -vT git@github.com
鍵関連だけ抜くなら次です。
ssh -vT git@github.com 2>&1 | grep -E "Offering public key|Server accepts key"
Offering public key は提示した候補、Server accepts key はGitHubが受け入れた鍵です。
flowchart LR candidates["鍵の候補<br/>config / agent / デフォルト名"] --> offer["Offering public key"] offer --> github["GitHub が公開鍵を照合"] github --> accept["Server accepts key"] accept --> account["アカウントが決まる"]
図: SSHは候補の鍵を提示し、GitHub側の公開鍵照合でアカウントが決まる。
複数アカウントのSSH設定
方針は、普段使うアカウントはデフォルトのまま、別アカウントだけ例外を足すです。既存の id_ed25519 はそのまま残します。
別アカウント用の鍵を作る
ssh-keygen -t ed25519 -C "OTHER_ACCOUNT_EMAIL" -f ~/.ssh/id_ed25519_github_other
生成物は次です。
~/.ssh/id_ed25519_github_other ← 秘密鍵
~/.ssh/id_ed25519_github_other.pub ← 公開鍵
macOSで公開鍵をコピーします。
pbcopy < ~/.ssh/id_ed25519_github_other.pub
GitHubの別アカウントで、Settings → SSH and GPG keys に貼り付けます。
~/.ssh/config に Host alias を足す
普段使う側は明示しなくて構いません。別アカウントだけ足します。
Host github-other
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github_other
IdentitiesOnly yes
github-other は実在するサーバーではなく、自分で付けた Host alias です。実際の接続先は HostName github.com です。
User git の git はGitHubアカウント名ではありません。GitHubへのSSHではSSHユーザー名は git 固定で、普段使うアカウントか別アカウントかは、提示する鍵で決まります。
IdentitiesOnly yes は、agent に載っている別の鍵を先に試させない指定です。別名でつなぐときに、デフォルト鍵が先に通ってしまうのを避けられます。
github-other で認証するときの流れ
ssh -T git@github-other や git push(remote が git@github-other:...)のとき、Mac は ~/.ssh/config を見て github-other 用の鍵だけ を候補にします。IdentitiesOnly yes があるので、ssh-agent に載っている普段の鍵は先に出しません。
【ssh -T git@github-other 実行時】Mac
config を読むね。Host は
github-other、使う鍵はid_ed25519_github_otherだけ。IdentitiesOnly yesだから agent の鍵は出さないよ
【同じタイミング】Mac → GitHub
公開鍵
id_ed25519_github_otherを提示する(Offering public key)
【同じタイミング】GitHub
その公開鍵、別アカウントに登録してあるね。チャレンジ返すから署名して
【同じタイミング】Mac → GitHub
秘密鍵
id_ed25519_github_otherで署名したよ
【同じタイミング】GitHub
署名 OK。Hi other-user! 別アカウントとして認証完了
git push も同じです。remote が git@github-other:OTHER_ACCOUNT/my-repo.git なら、push のたびに SSH は github-other 経由で接続し、上と同じ鍵だけが使われます。
git remote -v
# origin git@github-other:OTHER_ACCOUNT/my-repo.git (push)
git push
# 内部では git@github-other へ SSH 接続 → id_ed25519_github_other で認証
詳細ログで確認する例です。
ssh -vT git@github-other 2>&1 | grep -E "Offering public key|Server accepts key|Authenticated to"
出力のイメージは次のとおりです(フィンガープリントは環境ごとに異なります)。
debug1: Offering public key ED25519 SHA256:xxxxxxxx ... id_ed25519_github_other
debug1: Server accepts key: ... id_ed25519_github_other
Authenticated to github.com ([140.82.121.4]:22) using "publickey".
Hi other-user! You've successfully authenticated, but GitHub does not provide shell access.
Offering public key が1行だけなのは、IdentitiesOnly yes で候補が1本に絞られているためです。普段の id_ed25519 は出ていません。
一方、IdentitiesOnly yes がなく agent に普段の鍵が載っていると、別アカウント用 Host でも先に id_ed25519 が提示され、普段使うアカウントで認証されてしまうことがあります。
debug1: Offering public key ED25519 SHA256:aaaaaaaa ... id_ed25519
debug1: Server accepts key: ... id_ed25519
Hi default-user! ...
別アカウントのリポジトリなのに push 権限がない、といった症状のときは、remote の Host が github-other か、IdentitiesOnly yes が付いているかを疑うと早いです。
認証確認は次です。
# 普段使うアカウント
ssh -T git@github.com
# 別アカウント
ssh -T git@github-other
最終的な対応はこうなります。
github.com → ~/.ssh/id_ed25519 → 普段使うアカウント
github-other → ~/.ssh/id_ed25519_github_other → 別アカウント
【別アカウントへつなぐとき】Mac → GitHub
Mac:「
github-otherなので、id_ed25519_github_otherだけ提示するよ」
【別アカウントへつなぐとき】GitHub
GitHub:「その公開鍵は別アカウントに紐づいている。署名 OK。Hi other-user!」
別アカウントのリポジトリを clone する
GitHubが表示する通常のSSH URLは次です。
git@github.com:OTHER_ACCOUNT/repo.git
別アカウントとして認証するには、Host 部分を alias へ変えます。
git clone git@github-other:OTHER_ACCOUNT/repo.git
ここでの OTHER_ACCOUNT はリポジトリのownerです。認証ユーザーの指定ではありません。認証アカウントはSSH鍵で決まります。
cloneすると、そのURLが remote origin に保存されます。
git remote -v
origin git@github-other:OTHER_ACCOUNT/repo.git (fetch)
origin git@github-other:OTHER_ACCOUNT/repo.git (push)
以降は普通に git fetch / git pull / git push すれば、別アカウントの鍵が使われます。
既存リポジトリの remote を切り替える場合は次です。
git remote set-url origin git@github-other:OTHER_ACCOUNT/repo.git
Privateリポジトリでも同じです。cloneできれば、少なくとも別アカウントのSSH認証とread権限は通っています。write権限がなければ、cloneできても push は失敗します。
HTTPSは別物
git clone https://github.com/OTHER_ACCOUNT/repo.git
もできますが、これはSSHではなくHTTPS認証です。
URL中の OTHER_ACCOUNT は repo owner であって、別アカウントとして認証する指定ではありません。Privateリポジトリでは、Personal Access Token、Git Credential Manager、macOS Keychain など、HTTPS側の credential が使われます。
したがって、ここまでのSSH設定:
github.com → 普段使うアカウント
github-other → 別アカウント
は https://github.com/... には効きません。GitHubの緑ボタンからHTTPSでcloneすると、~/.ssh/config の分岐は通りません。
整理すると、認証の経路は次の2本です。
GitHubへの認証
├─ SSH → SSH鍵(github.com / github-other)
└─ HTTPS → credential / token
コミット作者
└─ Git config → user.name / user.email
コミット作者をフォルダで切り替える
別アカウント用URLでcloneしても、コミット作者は自動では切り替わりません。globalの user.name / user.email が普段使うアカウントなら、次の食い違いが起きます。
GitHubへのpush認証 → 別アカウント
コミット作者 → 普段使うアカウント
確認は次です。
git config user.name
git config user.email
別アカウント用のローカルフォルダでは、Gitの作者もそちらへ切り替えます。
例として、別アカウントのリポジトリを ~/work/github-other/ 配下に置くとします。
~/work/
├── repo-default/
└── github-other/
├── repo-1/
├── repo-2/
└── nested/repo-3/
別アカウント用の設定 ~/.gitconfig-other です。
[user]
name = OTHER_NAME
email = OTHER_EMAIL
メインの ~/.gitconfig です。
[user]
name = DEFAULT_NAME
email = DEFAULT_EMAIL
[includeIf "gitdir:~/work/github-other/"]
path = ~/.gitconfig-other
これで次のように分かれます。
~/work/repo-default/ → 普段使うアカウントの作者
~/work/github-other/repo-1/ → 別アカウントの作者
~/work/github-other/repo-2/ → 別アカウントの作者
~/work/github-other/nested/... → 別アカウントの作者
gitdir:~/work/github-other/ はGitHub上のパスではなく、PC上のローカルパスです。親フォルダを指定すれば、その配下のGitリポジトリに適用されます。
どのファイルから読まれているかは、次で確認できます。
git config user.name
git config user.email
git config --show-origin --get user.email
includeIf は SSH 認証を切り替えない
たとえば次のようにすると、フォルダは別アカウント用でも、SSH Host は github.com のままです。
cd ~/work/github-other
git clone git@github.com:OTHER_ACCOUNT/repo.git
普段使うアカウントの鍵が使われます。別アカウントでcloneするには、これまでどおり Host alias が必要です。
git clone git@github-other:OTHER_ACCOUNT/repo.git
役割は完全に別です。
~/.ssh/config
→ GitHubへのSSH認証アカウントを決める
→ どの公開鍵 / 秘密鍵を使うかの話
Git config / includeIf
→ commitに記録する user.name / user.email を決める
→ リポジトリのコミット作者の話
別アカウントでの典型的な作業は次です。
cd ~/work/github-other
git clone git@github-other:OTHER_ACCOUNT/repo.git
cd repo
git config user.name
git config user.email
git remote -v
期待する状態です。
user.name / user.email → 別アカウント
origin → git@github-other:OTHER_ACCOUNT/repo.git
以降は普通に git commit / git push すれば、コミット作者もGitHub認証も別アカウントになります。
flowchart TB
subgraph ssh ["SSH 認証"]
direction LR
hostDefault["github.com"] --> keyDefault["id_ed25519"]
hostOther["github-other"] --> keyOther["id_ed25519_github_other"]
keyDefault --> accDefault["普段使うアカウント"]
keyOther --> accOther["別アカウント"]
end
subgraph git ["コミット作者"]
direction LR
global["global Git config"] --> authorDefault["普段使う作者"]
includeIf["includeIf gitdir"] --> authorOther["別アカウントの作者"]
end
図: SSHの Host と Git の includeIf は別レイヤー。両方揃えて初めて、認証も作者も別アカウントになる。
まとめ
- GitHubへのSSH認証は、Macが公開鍵を提示し、GitHubが登録と署名を検証してアカウントを決めます。順番に試すのはMac側の候補鍵で、GitHubが登録済み鍵を全部なぞるわけではありません。
- 鍵の候補は
~/.ssh/config、ssh-agent、id_ed25519などのデフォルト名です。既存のデフォルト鍵は、他サービスでも使っていることがあるので残します。 - 別アカウントは、別鍵と Host alias(
github-other)を足します。clone URLの Host を alias に変えると、その remote が以降の push / pull に使われます。 https://github.com/...にはSSH configは効きません。HTTPSは token / credential の経路です。- コミット作者は
user.name/user.emailです。includeIfでフォルダ単位に切り替えられますが、SSH認証は切り替えません。