GitHubで複数のアカウントを使う方法

# 概要
GitHub上に2つのアカウントを所有し、それぞれのリモートリポジトリにSourceTreeからpushした際に認証エラーが発生した。原因と解決法を以下に書き残す。

# 再現
## 環境
* macOS 10.14.2
* SourceTree 3.0.1

## 手順
GitHubからログアウトし、SourceTree上で**Connect Account**をクリックする。

![Screen Shot 2018-12-31 at 5.32.45.png](https://qiita-image-store.s3.amazonaws.com/0/286440/3511cf61-2ef2-260c-33ae-d64fa3c50ee3.png)

ブラウザが起動し、GitHubのログイン画面が表示されるため、登録したいアカウントでログインする。

![screencapture-github-login-2018-12-31-05_33_11.png](https://qiita-image-store.s3.amazonaws.com/0/286440/0ffe2613-34d4-0e76-d667-f7f4eca5e5b5.png)

**Generate Key**をクリックしてSSHキーを生成する。必要に応じてパスフレーズを設定し、**Save**をクリックして登録を完了させる。これを登録したいアカウント分繰り返す。

![Screen Shot 2018-12-31 at 5.33.40.png](https://qiita-image-store.s3.amazonaws.com/0/286440/c0eb267f-fab6-9661-bbeb-605bd395820b.png)

各アカウント上の適当なリモートリポジトリをcloneして変更をpushすると、一つのアカウントでは成功するが、その他のアカウントでは以下のようなエラーが発生して失敗する。

![Screen Shot 2018-12-31 at 2.28.12.png](https://qiita-image-store.s3.amazonaws.com/0/286440/f84a36ad-e662-6e10-39d5-d20842b7cf30.png)


# 原因
リモートリポジトリを更新する際、別アカウントの秘密鍵を使って公開鍵認証を試みたため、認証エラーが発生した。アカウントと秘密鍵が正しく紐づいていないことが原因である。

# 解決法
## SSH接続チェック（秘密鍵指定なし）
まず、SSH接続が可能であることを確認するため、以下のコマンドを実行する。

```
$ ssh -T git@github.com
Hi {アカウントA}! You've successfully authenticated, but GitHub does not provide shell access.
```

SSH接続は可能であるが、秘密鍵を指定しないとアカウントAが使われた。これは、デフォルトの秘密鍵としてアカウントAのものが選ばれたためである。デフォルトの秘密鍵がどのように決まるかは、[#秘密鍵を指定しない場合の挙動について](#秘密鍵を指定しない場合の挙動について)にて後述する。

## エージェントキー削除
SourceTree上でSSHキーを生成した際に、エージェントキーに追加される秘密鍵を削除する。以下のコマンドでは `-D` オプションで全てのキーを削除しているが、個別に削除する場合は `-d` オプションを使うこと。

```
$ ssh-add -l
4096 SHA256:{ハッシュ} Generated by Sourcetree on macOS for {アカウントA}-GitHub (RSA)
4096 SHA256:{ハッシュ} Generated by Sourcetree on macOS for {アカウントB}-GitHub (RSA)
$ ssh-add -D
All identities removed.
$ ssh-add -l
The agent has no identities.
```

## SSH接続チェック（秘密鍵指定あり）
秘密鍵を指定してアカウントを使い分けるため、`-i` オプションを付けて再試行する。秘密鍵は `~/.ssh` 配下に配置されており、SourceTree上で生成した場合は `{アカウント名}-GitHub` のようなファイル名になっている。

```
$ ls ~/.ssh
{アカウントA}-GitHub	{アカウントB}-GitHub      config
{アカウントA}-GitHub.pub	{アカウントB}-GitHub.pub  known_hosts
$ ssh -T git@github.com -i ~/.ssh/{アカウントA}-GitHub
Hi {アカウントA}! You've successfully authenticated, but GitHub does not provide shell access.
$ ssh -T git@github.com -i ~/.ssh/{アカウントB}-GitHub
Hi {アカウントB}! You've successfully authenticated, but GitHub does not provide shell access.
```

秘密鍵を指定することで、それぞれのアカウントでSSH接続することができた。

## `~/.ssh/config` の修正
`~/.ssh/config` に設定を記述することで、前述のコマンドと同じことをスマートに実現できる。以下の記述例にて、sshコマンド実行時に接続先ホストしか指定していない点に注目する。

```
$ cat ~/.ssh/config
Host {接続先名}
    HostName github.com
    User git
    IdentityFile ~/.ssh/{アカウント名}-GitHub
$ ssh -T {接続先名}
```

上記を踏まえて、`~/.ssh/config` を以下のように修正する。

```diff:~/.ssh/config
# --- Sourcetree Generated ---
Host {アカウント名}-GitHub
	HostName github.com
-	User {アカウント名}
+	User git
	PreferredAuthentications publickey
	IdentityFile /Users/thara/.ssh/{アカウント名}-GitHub
	UseKeychain yes
	AddKeysToAgent yes
# ----------------------------
```

## エージェントキー追加
エージェントキーから削除した秘密鍵を再登録する。

```
$ ssh-add ~/.ssh/{アカウントA}-GitHub
Identity added: /Users/thara/.ssh/{アカウントA}-GitHub (Generated by Sourcetree on macOS for {アカウントA}-GitHub)
$ ssh-add ~/.ssh/{アカウントB}-GitHub
Identity added: /Users/thara/.ssh/{アカウントB}-GitHub (Generated by Sourcetree on macOS for {アカウントB}-GitHub)
$ ssh-add -l
4096 SHA256:{ハッシュ} Generated by Sourcetree on macOS for {アカウントA}-GitHub (RSA)
4096 SHA256:{ハッシュ} Generated by Sourcetree on macOS for {アカウントB}-GitHub (RSA)
```

## SSH接続検証
接続先ホストのみを指定してsshコマンドを実行し、[#SSH接続チェック（秘密鍵指定あり）](#ssh接続チェック秘密鍵指定あり)と同じ結果が得られることを確認する。**接続先が `git@github.com` から `{アカウント名}-GitHub` に置き換わっている**ところが要点である。

```
$ ssh -T {アカウントA}-GitHub
Hi {アカウントA}! You've successfully authenticated, but GitHub does not provide shell access.
$ ssh -T {アカウントB}-GitHub
Hi {アカウントB}! You've successfully authenticated, but GitHub does not provide shell access.
```

## リモートリポジトリの修正
SourceTree上にて、push先の各リモートリポジトリのURLを[#SSH接続検証](#ssh接続検証)の要点を踏まえて以下のように修正する。

![Screen Shot 2018-12-31 at 5.51.18.png](https://qiita-image-store.s3.amazonaws.com/0/286440/a0be9812-58fd-dbe5-73fe-63f3e21e2d69.png)

リモートリポジトリをcloneする場合は、同様にURLを修正してcloneする。

![Screen Shot 2018-12-31 at 4.47.48.png](https://qiita-image-store.s3.amazonaws.com/0/286440/ca016f0f-aa85-fda7-b9f3-91eddb0ec54a.png)

## 動作確認
各リモートリポジトリへ変更をpushし、正常に完了することを確認する。

![Screen Shot 2018-12-31 at 6.29.12.png](https://qiita-image-store.s3.amazonaws.com/0/286440/3909bed8-b5ee-8e3c-7c3f-cb47e00e198a.png)

# 秘密鍵に関わる挙動について
SSH接続の際に秘密鍵を指定しない場合はデフォルトで `~/.ssh/id_rsa` が使われるが、自分の環境では同ファイルが存在しないのにアカウントAがデフォルトとして用いられた。デフォルトの秘密鍵は複数存在し得るのだが、それを知らず理解に苦しんだため、調査結果を備忘録として以下に書き残す。

秘密鍵を指定せず `git@github.com` へSSH接続をした時のログを `-v` オプションを付与して詳細に出力すると、秘密鍵の候補の一覧が確認できる。候補の詳細はopensshの[ソースコード中のコメント](https://github.com/openssh/openssh-portable/blob/eb347d086c35428c47fe52b34588cbbc9b49d9a6/sshconnect2.c#L1470)に記載されている。

```
$ ssh -vT git@github.com
OpenSSH_7.9p1, LibreSSL 2.7.3
...
debug1: rekey after 134217728 blocks
debug1: Will attempt key: Generated by Sourcetree on macOS for {アカウントA}-GitHub RSA SHA256:{ハッシュ} agent
debug1: Will attempt key: Generated by Sourcetree on macOS for {アカウントB}-GitHub RSA SHA256:{ハッシュ} agent
debug1: Will attempt key: /Users/thara/.ssh/id_rsa 
debug1: Will attempt key: /Users/thara/.ssh/id_dsa 
debug1: Will attempt key: /Users/thara/.ssh/id_ecdsa 
debug1: Will attempt key: /Users/thara/.ssh/id_ed25519 
debug1: Will attempt key: /Users/thara/.ssh/id_xmss 
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: Generated by Sourcetree on macOS for {アカウントA}-GitHub RSA SHA256:{ハッシュ} agent
debug1: Server accepts key: Generated by Sourcetree on macOS for {アカウントA}-GitHub RSA SHA256:{ハッシュ} agent
debug1: Authentication succeeded (publickey).
...
```

上記のログより、秘密鍵の候補として最上位に選出されたアカウントAのエージェントキーで認証を行い、成功したことが分かる。エージェントキーが存在する時に秘密鍵を指定せずSSH接続を行なった場合は、登録されている順にエージェントキーで認証を試み、失敗した場合に `~/.ssh/id_rsa` が用いられる。

# まとめ
`~/.ssh/config` の設定を修正し、リモートリポジトリのURLを変えることで、複数のGitHubのアカウントを共存させることができた。最後に、問題発生当初に実施していた手順と処理の流れを以下に示す。

SourceTree上でアカウントAとアカウントBを登録した。
　　↓
両アカウントの公開鍵と秘密鍵が生成された。
同時に、エージェントキーが追加された。
　　↓
両アカウントのリモートリポジトリを `git@github.com:{アカウント名}/{リポジトリ名}` の形式でcloneした。
　　↓
`git@github.com:アカウントA/リポジトリ` へ変更をpush。
秘密鍵が指定されていないためエージェントキーを検索し、先に登録されたアカウントAの秘密鍵が選出される。アカウントと秘密鍵が正しく紐づいているため、処理が正常に完了した。
　　↓
`git@github.com:アカウントB/リポジトリ` へ変更をpush。
上記と同様にアカウントAの秘密鍵が選出される。アカウントと秘密鍵が紐づいていないため、認証エラーが発生して処理が失敗した。