Github における ssh 接続の認証認可の制約とその対処

Published: 2022/9/4


Github 上のリポジトリに対して git プロトコル、つまり git clone git@github.com:user/repo.git のような形で操作を行うとき、複数の ssh 秘密鍵を利用していていて、かつ、それぞれの鍵に与えられているアクセス権限が異なるような場合、 ssh と github の制約により、上手くリポジトリへのアクセスが行えない場合がある。 具体的な例は以下の2つがある。

  1. 複数 Github アカウントのリポジトリを同じ PC のユーザーアカウントから操作しようとする場合
  2. CI などにてその git リポジトリへの操作が Deploy Key によって行われている場合

この制約の詳細を説明した上で、それぞれのケースへの対処方法をこの記事にまとめる。

制約: github は ssh の鍵自体でユーザーの認証を行う

github は、特に git プロトコルによってアクセスを行っているとき、そのアクセスに利用された鍵情報でもってユーザー認証とする。 この制約のため、ある ssh の鍵が一つあったとき、それを SSH 鍵として登録できる Github ユーザーは最大1人、という制約が課される。 その実、一つの ssh 鍵を複数アカウントに登録しようとしても、「他で利用されている」としてエラーになる。

結果、複数 Github アカウントをある一個人が運用する場合にはそれぞれのアカウントで鍵を別々に生成して登録することになり、権限が異なる ssh 鍵を複数持つことになる。

問題: github は複数 ssh 鍵をあんまり上手くサポートしない

github は ssh プロトコルで git リポジトリにアクセスされた場合、認証に成功した ssh 鍵に対して、内部の(ユーザーの)権限テーブルとの突合を行い、その結果により git repository に対する操作に対して許可するかどうかを判定する。

ssh-agent などにより複数鍵を登録している場合、認証が成功するまで登録された鍵候補達から認証の retry を OpenSSH は行ってくれるが、一度認証が成功した後はどちらかというと Github 側でできることはほぼなく、本来アクセス権限がない、つまり認可されないアクセスを行おうとした際には、エラー的にその時点で ssh のセッションを abort させる、ということを行うことになる。 というか、なっている。

フローを纏めると以下の通り。

  1. 登録された ssh 鍵たちに対して、 github 全体に登録された鍵と合致するものを探す
    • すべての鍵認証に失敗した場合: git@github.com: Permission denied (publickey). で終了
  2. 最初に認証に成功した鍵を利用して、 git の操作を行おうとする。
    • アクセス権限がない鍵、つまり認可されない鍵であると、例えば fatal: Could not read from remote repository. により失敗する

このため、権限が異なる複数の ssh 鍵を運用しようとすると、認証で使われた鍵が必ずしもアクセスしたいリポジトリに対する権限を持っているわけではないことになり、本来ならばアクセスできるはずのリポジトリに対して、アクセスエラーが発生するようになる。

対処方法: ローカル開発環境の場合

問題の根本は、 Github 上にホストされたリポジトリはすべて github.com というホスト名を持っているため、そのままでは Github の ssh 接続の際に利用する ssh 秘密鍵を指定できない、というところから来ている。 なので、リポジトリごとに利用される鍵を設定できるようにしてしまえばよく、そのための手法はいくつかのパターンがある。

以下、 git@github.com:user1/repo1.git~/.ssh/key1git@github.com:user2/repo2.git~/.ssh/key2 を用いてアクセスを行おうとしているとする。

ホスト名を独自定義してそこから git 操作する

~/.ssh/config に ssh の接続に利用するホストそれぞれに対する設定を行うことができるが、そこで、独自のホスト名を定義して、そこで実際に利用される鍵を指定する方法。

Host github-user1
  HostName github.com
  IdentityFile ~/.ssh/key1

Host github-user2
  HostName github.com
  IdentityFile ~/.ssh/key2

上記のように github の ssh 接続の情報を設定し、 git clone git@github-user1:user1/repo1.git のようにその設定した Host 設定を用いて git の操作を行っていく方法。

SSH に利用されるコマンドをカスタマイズする

ある一定のバージョン以降の git では、 ssh の際に利用される ssh のコマンドをカスタマイズできる。

  • 環境変数でこれを行う方法: GIT_SSH_COMMAND='ssh -i ~/.ssh/key1' のように環境変数の設定を行っておくことで、この例では ~/.ssh/key1 を強制的に利用させることができる。
    • ssh-agent を利用している場合、そちらの鍵が使われてしまう場合がある。 対策として、 GIT_SSH_COMMAND='ssh -o IdentityAgent=none -i ~/.ssh/key1' として、 ssh-agent を利用しないようにすると良い。
  • git config にてこれを行う方法: core.sshCommandgit config にして設定してやれば、その設定に従い、 ssh を行う。例: git config core.sshCommand 'ssh -i ~/.ssh/key1'

これらと、例えば環境変数ベースで行うならば、 direnv 等を組合せていくことで、複数鍵を別々の Github アカウントに登録している場合であってもほぼストレスなく運用はできるようになるはず。

問題: CI の場合

いくつかの CI SaaS は、 github 連携を行えば勝手にリポジトリからのチェックアウトを行えるように設定を行ってくれる。 その際、その実装にあたり利用されるのが Deploy Key であった場合には、 Deploy Key は唯一つのリポジトリへのアクセスしか実現できないため、CI 上で複数プライベートリポジトリのチェックアウト等を行おうとした場合に、やはり複数鍵問題が発生する。 このような挙動を示す CI サービスとして、具体的には CircleCI が挙げられる。

ローカル環境の場合に示した対策方法が CI においても有効ではあるが、やはりどの方法も多少の面倒くささがあり、特に、開発者の環境では問題なく clone できていたリポジトリや git submodule たちが、 CI 上ではそのセットアップに一手間かけていかなければならない、というのは少々面倒臭い。

Github 社や CircleCI 社は、この問題に対して基本的には Machine User を推奨している。 これは、 CI で利用するための github アカウントを一つ用意して、そのユーザーに対して CI で利用するための ssh 鍵を紐付けることで、 Deploy Key によって発生する ssh 認可問題を回避するための方法となる。

そうではなく、あくまで Deploy Key で頑張る場合や、組織に紐付いたアクセストークンを利用して https 認証で git 操作を行いたいような場合には、例えば以下の記事などが参考にできる。

(circleci で deploy key ベースで複数リポジトリを扱う方法)

(Github Apps からアクセストークンを発行する方法。アクセストークンは、それを用いて https 認証で git 操作が可能。)


Tags: githubgitsshcircleci

関連記事