🎎

SSHエージェント転送を使いつつ複数のGitHubアカウントとSSH鍵を使い分ける

2021/07/21に公開
4

3行まとめ

  • gitコマンドで使用するsshコマンドとそのオプションはcore.sshcommandで設定できる。
  • SSHエージェントに渡ったSSH鍵の情報はafssh(ssh-agent-filter)でフィルタリングできる。
  • core.sshcommandafsshを組み合わせることで、SSHエージェント転送を使いつつSSH鍵を使い分けることができる。

背景

皆さん、gitGitHub、使ってますか?(もちろん使っているからこの記事を読んでいるのでしょうが)
GitHub(やGitLab)を使う時に時折困るのが「複数のアカウント」、「複数のSSH鍵」の使い分けです。

ご存じの通り、GitHubへのアクセスにはSSH、またはHTTPSを使います。
SSHは環境(例えばマシン)毎に認証情報を使い分けることができ、個別に無効化できてセキュアなため、SSHでアクセスしている方が多いかと思います。(公開リポジトリの場合、HTTPSを使うこともあるかもしれませんが)

$ git clone git@github.com:user/repo.git

例えば上記のようなコマンドを実行することで、userユーザのrepoリポジトリをSSH経由でcloneすることができます。

解決したい問題

git@github.com:user/repo.gitというリポジトリのアドレスからも分かるとおり、github.comというSSHサーバにgitというユーザでアクセスしています。
ではどうやってアカウントを識別するのかと言うと、SSH公開鍵を使って識別します。(普通のSSHサーバは「ユーザ名+SSH公開鍵」で識別します)

上記の通り共通のユーザを使う仕様のため、user1user2という2つのGitHubアカウントがあった場合、その2つのアカウントに同じSSH公開鍵を登録することができません。(登録できてしまったら、アカウントを識別できなくなります)

この挙動は、ssh git@github.comコマンドを実行することで確認することができます。「Hi user1!」の部分が識別されたアカウント名です。

local$ ssh git@github.com
PTY allocation request failed on channel 0
Hi user1! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

この問題の解決方法は後述しますが、これに「SSHエージェント転送」が加わると話がさらにややこしくなります。
本記事では「SSHエージェント転送を使いつつ、複数のGitHubアカウントを使い分ける方法」について説明していきます。

前提知識

まずは、本記事に出てくる用語の簡単な解説です。とても簡単な説明なので、詳細は適宜検索をお願いします。

SSH鍵ペア

SSHでは公開鍵暗号方式が用いられます。その時に使う暗号鍵は「公開鍵」と「秘密鍵」があり、この2つの組み合わせを「鍵ペア」と呼びます。

デフォルトでは、SSH秘密鍵は~/.ssh/id_rsa、SSH公開鍵は~/.ssh/id_rsa.pubというパスに存在します。(RSA暗号の場合)

「SSH秘密鍵」、「SSH公開鍵」については、以降は単に「秘密鍵」、「公開鍵」と呼びます。

ポイント:

  • 秘密鍵はその名の通り、秘密にしておく必要があります。(公開してはいけません)
  • 公開鍵はその名の通り、公開しても大丈夫です。
  • 秘密鍵はマシンから取り出してはいけません。(複数のマシンで共有してはいけません)
  • 秘密鍵には必ずパスフレーズを設定し、暗号化しましょう。

SSHエージェント

SSHエージェント(ssh-agent)は、SSHを使う時の便利ツールです。モダンなOS(macOS、Ubuntuなど)では標準で動作しています。

SSH接続の際には秘密鍵の暗号化を解く(復号する)必要がありますが、その際に入力するのが「パスフレーズ」です。
秘密鍵を利用する度(SSH接続する度)にパスフレーズを入力するのは大変なので、これをSSHエージェントが肩代わりしてくれます。

SSHエージェントに鍵を登録するには、ssh-addコマンドを使います。

# 鍵を登録する(デフォルトのパスに存在する場合はファイルパスを省略できます)
local$ ssh-add

# パスを指定して鍵を登録する
local$ ssh-add ~/.ssh/user1.id_rsa

SSHエージェントに登録された鍵は、ssh-add -lコマンドで確認することができます。

local$ ssh-add -l
3072 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user1@mac.local (RSA)
3072 SHA256:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user2@mac.local (RSA)

SSHエージェントに登録された鍵は、ssh-add -Dコマンドで登録を解除する(忘れさせる)ことができます。

local$ ssh-add -D
local$ ssh-add -l
The agent has no identities.

ポイント:

  • SSHエージェントを使用すると、パスフレーズを何度も入力する必要がなくなります。
  • SSHエージェントを使用する場合、離席する場合は必ず画面をロックするか、鍵の登録を解除しましょう。

SSHエージェント転送

ローカルマシンに存在する秘密鍵を使い、ローカルマシンからリモートマシンAにSSH接続する場合は、上記のSSHエージェントが有効に機能します。

では、そのリモートマシンAから、さらに別のリモートマシンBにSSH接続する場合はどうでしょう?

残念ながらデフォルトではSSHエージェントは機能せず、リモートマシンAに存在する秘密鍵が利用されます。
リモートマシンAで鍵ペアを生成し、その公開鍵をリモートマシンBに登録することでSSH接続できるようになりますが、マシンが増えると組み合わせが膨大になり、鍵ペアの管理が大変です。

そこで利用するのが「SSHエージェント転送」です。ローカルマシンで動作しているSSHエージェントを、SSH接続先でも利用するための機能です。

リモートマシンA、リモートマシンBの両方にローカルマシンの公開鍵を登録しているとします。
まず、ローカルマシンからリモートマシンAへはそのままSSH接続できます。SSHエージェント転送を有効にすることで、さらにリモートマシンAからリモートマシンBにローカルマシンの秘密鍵を使って接続できます。

具体的にはsshコマンドに-Aオプションを付加するか、~/.ssh/configForwardAgent yesを指定します。

具体例は以下の通りです。

# SSHエージェントに登録されている秘密鍵の一覧を確認する
local$ ssh-add -l
3072 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user1@mac.local (RSA)
3072 SHA256:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user2@mac.local (RSA)

# ローカルマシンの秘密鍵を使い、リモートマシンAにSSH接続する(SSHエージェント転送を有効化)
local$ ssh -A remote-a.example.com

# SSHエージェントに登録されている秘密鍵の一覧を確認する(SSHエージェント転送の確認)
remote-a$ ssh-add -l
3072 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user1@mac.local (RSA)
3072 SHA256:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user2@mac.local (RSA)

# ローカルマシンの秘密鍵を使い、リモートマシンAからリモートマシンBに接続する
remote-a$ ssh remote-b.example.com

ローカルマシンでGitHubアカウントを使い分ける

ここから本題です。まずはSSHエージェント転送を使わない単純な例を考えてみます。

前述の通り、GitHubでは公開鍵を使ってアカウントを識別するため、複数のGitHubアカウントを使い分ける場合は、GitHubアカウント毎に鍵ペアを生成する必要があります。

普段、鍵ペアを生成する場合は、ssh-keygenコマンドをオプションを指定せずに実行します。

$ ssh-keygen

途中でインタラクティブに保存先のファイルパスを聞いてくれるのでこれでも良いのですが、-fオプションで保存先のファイルパスを明示することもできます。

$ ssh-keygen -f ~/.ssh/user1.id_rsa
$ ssh-keygen -f ~/.ssh/user2.id_rsa

user1user2のそれぞれの鍵ペアが生成できました。SSHエージェントに登録しておきましょう。

$ ssh-add ~/.ssh/user1.id_rsa
$ ssh-add ~/.ssh/user2.id_rsa

gitコマンドで使用する鍵を使い分ける方法は、私の知っている限りでは以下の2つがあります。

  • core.sshcommandを使う方法
  • ホスト名を書き換え、~/.ssh/configで鍵を指定する方法(github-user1.comなどホスト名を書き換える)

今回は前者の方法について説明します。(後者の方法は、ホスト名の書き換えが面倒なので使ったことがありません)

user1user2のそれぞれのアカウントで、それぞれの鍵を使い、git cloneする例は以下の通りです。

local$ git clone \
  --config user.name="user1" \
  --config user.email="user1@example.com" \
  --config core.sshcommand="ssh -i ~/.ssh/user1.id_rsa -o IdentitiesOnly=yes" \
  git@github.com:user1/repo1.git

local$ git clone \
  --config user.name="user2" \
  --config user.email="user2@example.com" \
  --config core.sshcommand="ssh -i ~/.ssh/user2.id_rsa -o IdentitiesOnly=yes" \
  git@github.com:user2/repo2.git

core.sshcommandは、gitコマンドが使用するsshコマンドやオプションを指定するための設定です。
user.nameuser.emailの指定は任意ですが、アカウントを使い分けたい場合にはこれらも使い分けたいことがほとんどかと思います。

鍵を使い分ける以外にも、-vオプションを指定することでデバッグメッセージを表示させ、SSHに関する問題を調査する場合などに便利です。
git cloneの時に指定する以外にも、git configで設定することもできます。

local$ git config core.sshcommand "ssh -v"
local$ git config --list
# 設定値が表示される

local$ git pull
# SSHのデバッグメッセージが表示される

SSHエージェント転送を使いつつ、GitHubアカウントを使い分ける

前述の通り、core.sshcommandを使うことで、ローカルマシンでGitHubアカウントを使い分けることができました。
では、SSHエージェント転送を使った場合はどうでしょうか?

残念ながらcore.sshcommandの中で秘密鍵のファイルパスを指定しており、リモートマシン上からはこれらのファイルにアクセスすることができません。
かといって秘密鍵ファイルをリモートマシンAにコピーする(鍵を共有する)のもセキュリティ的に望ましくありません。

※2021年7月30日追記: sshコマンドの-iオプションには秘密鍵のファイルパスだけではなく、公開鍵のファイルパスも指定できる旨、 IWAMOTO Kouichi さんよりコメントを頂きました。情報ありがとうございます!

まずは、SSHエージェント転送が有効な状態で、リモートマシンA上でのgit cloneを試してみましょう。

local$ ssh-add ~/.ssh/user1.id_rsa
local$ ssh-add ~/.ssh/user2.id_rsa
local$ ssh -A remote-a.example.com

remote-a$ git clone git@github.com:user1/repo1.git
# 成功する

remote-a$ git clone git@github.com:user2/repo2.git
...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

鍵の登録順序に依存しますが、上記の場合、user1では成功し、user2では失敗しています。
これはSSHエージェントで利用可能な鍵のうち、最初に該当した鍵(user1の鍵)が使われているため、片方は成功し、もう片方は失敗しています。

この問題は、afsshコマンド(ssh-agent-filter)を使用することで対応することができます。
afsshコマンドは、Ubuntuではssh-agent-filterパッケージとして提供されており、aptコマンドでインストール可能です。

remote-a$ sudo apt update
remote-a$ sudo apt install ssh-agent-filter

afsshコマンドでは、SSHエージェントに登録されている鍵を、様々な条件でフィルタリング(選択)することができます。
例えば、-c--comment)オプションでは、鍵のコメントでフィルタすることができます。

remote-a$ git clone \
  --config user.name="user1" \
  --config user.email="user1@example.com" \
  --config core.sshcommand="afssh --comment user1@mac.local --" \
  git@github.com:user1/repo1.git

remote-a$ git clone \
  --config user.name="user2" \
  --config user.email="user2@example.com" \
  --config core.sshcommand="afssh --comment user2@mac.local --" \
  git@github.com:user2/repo2.git

これで「SSHエージェント転送を使いつつ、GitHubアカウントを使い分ける」が実現できました。めでたしめでたし。

最後に

今回の例以外にもcore.sshcommandafsshコマンドを活用できる場面は色々あるかと思います。
どうぞよいリモート開発ライフを!

Discussion

いわもと こういちいわもと こういち

残念ながらcore.sshcommandの中で秘密鍵のファイルパスを指定しており、リモートマシン上からはこれらのファイルにアクセスすることができません。
かといって秘密鍵ファイルをリモートマシンAにコピーする(鍵を共有する)のもセキュリティ的に望ましくありません。

実は -i で公開鍵を指定する事が出来ます。例えばリモートマシンAに公開鍵 ~/.ssh/user1.id_rsa.pub を置いておき、ssh -i ~/.ssh/user1.id_rsa.pub -o IdentitiesOnly=yes とするとssh-agentに登録されている秘密鍵の内、公開鍵user1.id_rsa.pubに対応した秘密鍵使って認証を行ってくれます。

これはSSHエージェントで利用可能な鍵のうち、最初に該当した鍵(user1の鍵)が使われているため、片方は成功し、もう片方は成功しています。

後者は失敗でしょうか?

Yuya KatoYuya Kato

sshコマンドの-iオプションについて、本文に追記させて頂きました。また、誤字も修正しています。重ねて情報に感謝します。

Yuya KatoYuya Kato

IWAMOTO Kouichi さん、情報ありがとうございます!この記事を書いて良かったです。

実は -i で公開鍵を指定する事が出来ます。例えばリモートマシンAに公開鍵 ~/.ssh/user1.id_rsa.pub を置いておき、ssh -i ~/.ssh/user1.id_rsa.pub -o IdentitiesOnly=yes とするとssh-agentに登録されている秘密鍵の内、公開鍵user1.id_rsa.pubに対応した秘密鍵使って認証を行ってくれます。

なんと、公開鍵を指定できるのですね。情報、ありがたいです。

後者は失敗でしょうか?

誤字ですね💦修正しておきます。

everpeaceeverpeace

最近Github Enterprise Managed Usersを使うようになったので、アカウントの切り替えをどうしようかと悩んでいて、秘密鍵を指定してsshcommandを切り替えるのは簡単なんですが、エージェント転送して転送先でもってなると、やはりエージェントで秘密鍵のcommentとかで出し分けるssh-agent wrapperを書かなきゃいけないのかな・・・と思って検索していた所、こちらの記事を発見しました(ドンピシャ情報でした)。あとはgitconfigのIncludeIfとかを使って'core.sshCommand'を指定してやれば、ほぼ手動切替はゼロな環境にできそうです!めちゃくちゃ有益情報をありがとうございました!!

あと、Macでもssh-agent-filter使えるようにhomebrewでインストールできるようにしました。

https://github.com/everpeace/homebrew-ssh-agent-filter/

もしよかったら記事に追記戴けると他のMacユーザの方も嬉しいかなと思いますのでご検討頂けたらと思います🙇