gitをsshで使うconfigの書き方|鍵を使い分けて別の相手に繋ぐ

git config sshで調べる人が詰まっているのは、たいてい同じ場所です。鍵が2つ以上あって、どちらを使うかをgitに伝える方法が分からない。あるいは、仕事の相手と個人の相手で違う鍵と違うメールアドレスを使いたいのに、片方の設定がもう片方に漏れる。この記事では、設定の置き場所を~/.ssh/config側とgitの設定側に分けたうえで、どちらに何を書くのが正しいかを整理します。

gitはsshを呼ぶだけで、鍵の選び方は決めていない

最初に分けておくべきなのは、責任の境目です。gitがsshのアドレスのリポジトリに繋ぐとき、gitは自分で暗号の処理をせず、外部のsshのコマンドを呼びます。どの鍵をどのホストに出すかを決めているのは、呼ばれた側のsshです。つまり「gitの設定で鍵を指定する」と考えて探し始めると、見つからない場所を掘ることになります。

この分担は、SSHの証明書やCAを扱った経験があると素直に飲み込めます。gitはただの利用者で、認証の作法はsshの設定ファイルが持っている、という形です。したがって手を入れる順番は、まず~/.ssh/config、次にどうしても足りない部分だけをgit側、になります。

公開されている解説でも、出発点は接続先ごとの設定ファイルに置かれています。

まず GitHub へ接続する ~/.ssh/config の設定を見ていきます。GitHub で認証に使用する SSH キーは登録済みで秘密鍵は ~/.ssh/github へ配置している想定です。 出典: zenn.dev

同じ解説では、別のアカウントを使いたい場合には~/.ssh/configの設定とgitのリモートの接続情報の両方を変えることで対応できる、という整理がされています。片方だけ直して動かないという相談の多くは、この2か所のうち1か所しか触っていない状態です。

~/.ssh/config は「先に書いたものが勝つ」

設定を書く前に、読まれ方の癖を1つだけ覚えておく必要があります。sshは設定を3か所から取り、その順番はコマンドの引数、利用者の設定ファイル、システム全体の設定ファイルです。そして、注記のないかぎり各項目は最初に得られた値が使われます。

これは上書きの仕組みに慣れた目には逆に見えます。後から書いた行で前の行を打ち消すことはできません。マニュアルにも、ホストごとの細かい指定はファイルの先頭に近い場所へ、全体の既定値は末尾へ置くべきだと書かれています。Host * の塊をファイルの頭に置くと、その下に書いた個別の設定が効かなくなります。

もう1つ例外があります。IdentityFile は、複数書くと後の行が前の行を打ち消さず、試す鍵の一覧に積み上がります。この振る舞いは他の項目とは違うと明記されています。鍵が増えていくうちに意図しない鍵まで出してしまうのは、この積み上がりが原因です。

Host に書く名前はパターンとして扱われ、単独の * はすべての相手への既定値になります。先頭に感嘆符を付けると否定になり、ワイルドカードの例外を作れます。

鍵を使い分ける最小の設定

同じ相手に複数のアカウントがある場合の基本形は、Host にあだ名を付け、実際の接続先を HostName で指定する形です。

Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_personal
    IdentitiesOnly yes

あとはリポジトリ側のURLを、github.com ではなく github-work のほうに向けます。こうすると、同じサービスの同じドメインでも、あだ名ごとに違う鍵が出ます。

ここで効いているのが IdentitiesOnly です。この指定を yes にしないと、設定に書いた鍵のほかに、鍵の管理をしている仕組みが持っている鍵も候補として出されます。鍵をいくつも預けている環境では、意図した鍵より先に別の鍵が受け付けられてしまい、間違ったアカウントとして扱われます。マニュアルにも、鍵の管理側が多くの鍵を提供している状況のための指定だと書かれています。既定値は no なので、書かなければ効きません。

指定を省いた場合に試される鍵の名前も決まっています。/.ssh/id_rsa、/.ssh/id_ecdsa、/.ssh/id_ecdsa_sk、/.ssh/id_ed25519、~/.ssh/id_ed25519_sk の順です。この名前のどれかで置いてあると、設定を書かなくても繋がってしまうため、鍵を分けたつもりで分かれていない状態に気付きにくくなります。接続先のポートを変えている相手には Port を書きます。既定値は22番です。

gitの側から鍵を指定したいとき

~/.ssh/config を触れない事情があるときや、1つのリポジトリだけ例外にしたいときは、git側から呼び出すコマンドを差し替えられます。設定の名前は core.sshCommand です。

git config --local core.sshCommand "ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes"

この指定があると、取得と送信のときにsshの代わりに指定したコマンドが使われます。環境変数の GIT_SSH_COMMAND と同じ形で書き、環境変数が設定されている場合はそちらが優先されます。つまり一時的に別の鍵で試したいだけなら、設定を書き換えずに環境変数を前置きするだけで済みます。

どちらを選ぶかの目安は、その設定を他の道具にも効かせたいかどうかです。~/.ssh/config に書けば、gitだけでなく scp や rsync や端末からの直接の接続にも効きます。core.sshCommand はgitの中だけの話です。鍵の使い分けが恒久的な事情なら前者、そのリポジトリ限りの事情なら後者が向きます。

相手ごとに名前とメールアドレスを切り替える

鍵が分かれても、コミットに残る名前とメールアドレスが1つのままだと、仕事のリポジトリに個人用のアドレスが残ります。これはsshではなくgit側の設定で解きます。使うのは条件付きの読み込みです。

[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

gitdir に続けて書いたものはパターンとして扱われ、.git の場所が一致したときだけ指定したファイルが読み込まれます。仕事のリポジトリを置くフォルダを1つ決めておけば、その下では自動的に仕事用のメールアドレスが使われます。パターンには通常のワイルドカードのほかに、複数の階層にまたがる指定も使えます。

条件には他の種類もあります。特定のブランチがチェックアウトされているときだけ読み込む指定と、指定したURLのリモートが存在するときだけ読み込む指定です。後者は、置き場所ではなく相手先で切り替えたい場合に向きます。

もう1つ覚えておくと効くのが、URLの書き換えです。url..insteadOf を使うと、ある文字列で始まるURLをすべて別の形に読み替えさせられます。複数の指定が当たる場合は、いちばん長く一致したものが使われます。チームで共有しているリポジトリのURLを書き換えずに、自分の手元だけあだ名の側へ向けたいときに使えます。送信のときだけ別のURLにしたい場合は pushInsteadOf があります。

パスフレーズを毎回打たないための2つの指定

鍵にパスフレーズを付けていると、接続のたびに入力を求められます。macOSでは、これを2つの指定で片付けられます。

Host *
    AddKeysToAgent yes
    UseKeychain yes

AddKeysToAgent を yes にすると、ファイルから読み込んだ鍵とそのパスフレーズが、動いている鍵の管理の仕組みに自動で預けられます。ask にすると預ける前に確認が入り、confirm にすると鍵を使うたびに確認が入ります。時間の指定を書くと、その時間が過ぎたところで自動的に取り消されます。既定値は no です。

UseKeychain はmacOS固有の指定です。鍵を使うときにキーチェーンの中からパスフレーズを探すかどうか、そして利用者が入力したパスフレーズが正しいと確認できたあとにキーチェーンへ保存するかどうかを決めます。既定値は no で、PKCS11Provider との併用はできないと明記されています。

この2つは全体の既定値として書くことが多いため、Host * の塊に入れることになります。先に書いた値が勝つという癖があるので、この塊はファイルの末尾に置きます。

思った鍵が出ていないときに見る3点

設定を書いたのに効かない場合、原因はほぼ3つに絞れます。

1つ目は、ファイルの権限です。~/.ssh/config は悪用の余地があるため厳しい権限が必要で、利用者本人が読み書きでき、他者から書き込めない状態でなければならないと明記されています。秘密鍵のほうはさらに厳しく、他者からアクセスできる状態になっている秘密鍵ファイルは黙って無視されます。鍵を別のMacからコピーしてきた直後に効かないのは、この無視が起きている場合が多いです。

2つ目は、どの設定が実際に適用されたかです。sshには、HostとMatchの塊を評価したあとの設定を表示して終了する指定が用意されています。設定ファイルを読み返して推測するより、評価済みの結果を出させたほうが確実です。書いたはずの IdentityFile が出てこないなら、その塊に届いていないということです。

3つ目は、接続の途中で何が起きたかです。詳細を出す指定を付けると、接続と認証と設定の問題を追うための経過が表示されます。この指定は重ねられ、最大は3段です。どの鍵を順に出して、どこで断られたかがここで分かります。

この3点を確かめる順番も決まっています。権限、評価済みの設定、接続の経過。前の2つは接続しなくても確かめられるので、相手のサーバーに何度も繋ぎに行く前に済ませたほうが速くなります。

設定が育ってきたら、ファイルを分ける

相手が増えると~/.ssh/configは長くなります。読み込む側には分割の仕組みが用意されています。

Include conf.d/*.conf

Include には複数のパスを並べられ、ワイルドカードも使えます。絶対パスでない場合、利用者の設定ファイルから読み込むときは~/.sshの中にあるものとして扱われます。展開の順番は文字の並びの順です。Match や Host の塊の中に書くこともできるため、条件付きで読み込む使い方もできます。

分け方の目安は、持ち回るかどうかです。会社から配られた設定と自分で書いた設定を1つのファイルに混ぜると、Macを入れ替えるときにどこまで移せばよいかが分からなくなります。相手の組織ごとにファイルを分けておけば、必要な分だけ持っていけます。

設定ファイルを開く窓と、動作を確かめる窓が分かれている

設定を直す作業の実際の手間は、書く内容よりも往復の回数にあります。~/.ssh/config をエディタで開き、端末に切り替えて接続を試し、どの鍵が出たかを確かめ、またエディタに戻る。ファイルの権限が原因で無視されていた場合は、フォルダの中身を見る窓も開くことになります。1回の修正で3つの窓を行き来する形です。

フォルダとターミナルとAIが同じ窓にある状態なら、設定ファイルを開いている場所がそのまま端末の現在の場所になり、直した直後にその場で接続を試せます。設定が長くなってIncludeで分けたあとも、どのファイルが読み込まれているかをフォルダの側で見ながら確かめられます。

道具の側で何ができるかはできることにまとまっています。Finderや他のファイル管理との違いは他のファイル管理との比較にあり、外出先から手元のMacの作業を確かめる使い方はiPhone・iPadから続きをに整理されています。費用の目安は料金、判断に迷いやすい点はよくある質問を見てください。

設定を書き始める前に決めることは1つです。鍵を分ける単位を、相手のサービスにするのか、リポジトリの置き場所にするのか。サービスで分けるなら Host のあだ名、置き場所で分けるなら includeIf が主役になります。ここを決めずに両方を書き足していくと、どちらが効いているのか分からない設定ファイルになります。

よくある質問

同じサービスで2つのアカウントを使い分けるには、どこを直しますか?

~/.ssh/config と、リポジトリのリモートのURLの2か所です。設定ファイルで Host にあだ名を付け、HostName に本来の接続先、IdentityFile にその鍵を書き、IdentitiesOnly を yes にします。そのうえでリモートのURLをあだ名のほうへ向けます。片方だけ直すと、意図しない鍵が出て別のアカウントとして扱われます。

IdentitiesOnly を書かないと、何が起きますか?

設定に書いた鍵のほかに、鍵の管理の仕組みが持っている鍵も候補として出されます。鍵を複数預けている環境では、意図した鍵より先に別の鍵が受け付けられ、別のアカウントとして認証が通ってしまいます。既定値は no なので、鍵を使い分けたいときは明示的に yes を書いてください。

特定のリポジトリだけ別の鍵にしたいときは、どうしますか?

そのリポジトリの中で core.sshCommand を設定すると、gitが呼ぶsshのコマンドを差し替えられます。鍵のパスと IdentitiesOnly の指定をそのまま書けます。一時的に試すだけなら、環境変数の GIT_SSH_COMMAND を前置きする形でも同じことができ、環境変数のほうが設定より優先されます。

仕事と個人でコミットのメールアドレスを分けるには、どの設定を使いますか?

gitの条件付きの読み込みを使います。includeIf の gitdir に置き場所のパターンを書き、その下で読み込むファイルにメールアドレスを書いておくと、そのフォルダ以下のリポジトリでだけ設定が効きます。置き場所ではなく相手先で分けたい場合は、リモートのURLを条件にする指定も用意されています。

記事一覧へ戻る