本文へスキップ
webkida wiki
戻る

【GitHub】SSH認証の仕組みと複数アカウント

更新日:

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/configIdentityFilessh-agent に載っている鍵、~/.ssh/id_ed25519 のようなデフォルト名の順で候補になります。

includeIf

Gitの設定を、ローカルのディレクトリ条件で読み込む仕組みです。gitdir: に指定するのはGitHub上のパスではなく、PC上のフォルダです。ここではコミット作者(user.name / user.email)の切り替えに使います。

コミット作者

コミットに記録される user.nameuser.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クライアント)configssh-agentid_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 のファイルを総当たりするわけではありません。鍵候補は主に次から決まります。

  1. ~/.ssh/configIdentityFile
  2. ssh-agent に載っている鍵
  3. ~/.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 gitgit はGitHubアカウント名ではありません。GitHubへのSSHではSSHユーザー名は git 固定で、普段使うアカウントか別アカウントかは、提示する鍵で決まります。

IdentitiesOnly yes は、agent に載っている別の鍵を先に試させない指定です。別名でつなぐときに、デフォルト鍵が先に通ってしまうのを避けられます。

github-other で認証するときの流れ

ssh -T git@github-othergit 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 は別レイヤー。両方揃えて初めて、認証も作者も別アカウントになる。

まとめ


この記事をシェア:

前の記事
【Search Console】初回設定でハマったサイトマップとインデックス
次の記事
【Cursor】サブエージェントとバックグラウンド実行