本当にそれでいい?CircleCIにおける複数のPrivateリポジトリのClone方法

There may be security risks in your “clone” of multiple private repositories in CircleCI

CircleCIはGitHubとの親和性が高く、手軽にリポジトリにアクセスし、CIを回すことができます。対象のリポジトリがPrivateの場合でも、Deploy Keyを作成するだけで、簡単にソースコードをチェックアウトすることができます。一方で、このDeploy Keyはひとつのリポジトリしか参照できないため、複数のPrivateリポジトリからCloneする場合には工夫が必要です。

よく見かけるものとして、User Keyを追加するという方法があります。このUser Keyも、CircleCIの設定画面から簡単に登録でき、かつ複数のリポジトリからのClone問題も解決できるので、一見よさそうに見えます。が、本当にそれでよいのでしょうか。今回は、これについて考察したいと思います。

User Keyを登録することのデメリット

Deploy Keyがひとつのリポジトリ専用のSSH Keyであるのに対し、User Keyは名前の通り、ユーザー(あなた)のSSH Keyです。実際にCircleCIでUser Keyを追加した後、GitHubの個人設定画面を確認するとわかりますが、 下記のような名前の鍵が自動で生成されています。

CircleCI: <owner>/<repository>

CircleCIのドキュメントにも記載があるように、GitHubのUser Keyには特定のスコープに用途を制限するような機能はないため、この鍵を使用すればユーザが実行可能な操作はすべて実行できてしまいます。完全に一人で開発しているようなリポジトリであれば問題がないのですが、複数人で開発をしている場合、これは大きな問題になり得ます。CircleCI上で鍵を盗んでしまえば、今の開発とは関係のないユーザ個人のPrivateリポジトリの内容を参照したり、そのユーザに成りすますことができてしまうからです。

解決方法1:複数のDeploy Keyを使用する

CloneするPrivateリポジトリが明確で、gitコマンドなどで明示的にCloneする場合には、各リポジトリのDeploy KeyをCircleCIに登録してあげることで、少し強引ながらも、この問題を解決できます。

ポイントは、CircleCIに登録したDeploy Key (SSH Key)をCloneするリポジトリ毎に正しく使い分ける必要があるため、ホスト名とSSH Keyを紐付ける~/.ssh/configファイルに手を加えるという点です。

ざっくりですが、手順は以下のようになります。

  1. CircleCIのプロジェクトに紐付くGitHubリポジトリのDeploy Keyを、通常の手順通りCircleCIの設定画面のPERMISSIONS-Checkout SSH keysにて登録する。
  2. その他のリポジトリのDeploy Keyを、PERMISSIONS-SSH Permissionsにて手動で登録する。Hostnameにはgithub.com-aのように、github.com以外の文字列を入力する。
  3. .circleci/config.ymlにてadd_ssh_keysコマンドを実行して、手順2で追加したDeploy Keyをコンテナに配置し、~/.ssh/configファイルに反映する。
  4. 手順2で入力したHostnameが正しくgithub.comに接続するよう、下記のようなコマンドで~/.ssh/configを修正する。
    echo "HostName github.com" >> ~/.ssh/config
    echo "StrictHostKeyChecking no" >> ~/.ssh/config
  5. 手順2でDeploy Keyを追加したリポジトリについては、画面で入力したHostnameにホスト名を置き換えた下記のようなコマンドでCloneする。
    git clone git@github.com-a:<owner>/<repository>.git

なお、CircleCIのプロジェクトに紐付くGitHubリポジトリについては、通常通りcheckoutコマンドを使ってCloneすることができます。

ただ、この方法にもひとつ欠点があり、明示的に~/.ssh/configファイルに記述したホスト名を指定できる場合にしか適用できません。例えばGoでは、依存リポジトリを参照する際、GitHubなどのリポジトリ構造をそのまま利用します。このようなケースでは、dep ensureなどで依存関係を自動的に解決しようとするとホスト名を明示的に指定できないため、SSH Keyを使い分けることができずにエラーとなってしまいます。

解決方法2:マシンユーザを作成する

SSH Keyをリポジトリ毎に使い分けようとすると、方法1のようにやや強引な方法をとることになります。また、ケースによっては解決できないこともあります。このような場合には、GitHubにマシンユーザを作成するというのが定石のようです。

  1. GitHubにて、新規でユーザ(マシンユーザ)を作成する。
  2. マシンユーザに対して、必要なリポジトリのRead(またはWrite)権限を付与する。対象のリポジトリに対してはAdmin権限を付与する。
    Admin権限がないと次の手順を実行できないため
  3. マシンユーザでCircleCIにログインして、Checkout KeyとしてUser Keyを設定する。
  4. 自分のアカウントでGitHubにログインし、マシンユーザのAdmin権限をRead(またはWrite)に変更する。

二要素認証などを必須としている場合など、GitHubのユーザを新規作成し適切に管理するのはやや面倒ですが、方法1で問題となったGoの依存性関係の解決も問題なく実施でき、.circleci/config.ymlはずっとシンプルになります。


CircleCIでは、ほぼすべての設定をYAMLファイルに記述して構成管理ができるのでとても便利です。一方でSSH Keyなどの秘匿情報は、当然ながらYAMLファイルには記述できないので、コンソールなどで設定する必要があり、構成管理はできません。だからこそ、こういった設定は可能な限りシンプルにしてあげた方が、メンテナビリティを高めることができるかと思います。そういった意味でも、マシンユーザを利用するのはよい選択肢なのではないでしょうか。