Add multiple SSH deploy keys with same hostname on CircleCI
tl;dr
対象の現象は CircleCIとGithubに登録するSSH鍵は間違っていないのに権限系のエラーが出る です。
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
このエラーのパターンの1つとして、以下のことを確認しました。
- CircleCIで同じドメインのSSH鍵を複数追加すると
.ssh/config
中の優先順位で後から追加された鍵が利用できない - 同じドメインの複数のprivate repositoryにアクセスする場合はMachine Userを作成することが推奨されている
- Machine Userを作りたくない場合のワークアラウンドを調査
CircleCI
CIを実行してくれるサービスです。
ここではCircleCIの概要について詳しくは述べませんが、github等と連携して読み込んだソースコードの自動テストやbuildをする基盤を提供しています。
https://circleci.com/
SSH Permission
CircleCI + GithubにおけるSSH Permissionのおさらいです。
bitbucketや他のgit registryでもおよそ同じことが言えると思いますが、確認はしていません。
知ってる人は読み飛ばしてOK
CircleCI に SSH 公開鍵を登録する必要があるケースは、以下の 2 パターンです。
- バージョン管理システムからコードをチェックアウトする
- 実行中のプロセスが他のサービスにアクセスできるようにする
公式サイトに記載されているように、privateなsubmoduleや依存先repositoryをビルド時にひっぱるために鍵が必要です。
鍵には2種類あり、CircleCIのプロジェクトごとに設定することができます。
- Deploy Key
- User Key
Deploy Key
Deploy KeyはGithubの概念です。
Githubのrepositoryごとに作成し、repository単位でRead/Writeアクセスを制御します。
例えば以下の画像では、あるrepositoryに2つのDeploy Keyが追加されています。
CircleCIという名前の鍵はCircleCIでこのrepositoryを追加したときに自動で作成されたものです。checkout keyとも呼ばれます。
公式ドキュメント
あるrepositoryのDeploy Keyの秘密鍵をCircleCIのProjectに追加することによって、ProjectはそのrepositoryのRead/Write権限を得ることができます。
Deploy Keyを登録するためには、Projectの設定画面のSSH Permissionsで追加します。その際、Hostname欄でどのドメインで利用する鍵かを指定することができます。Hostnameを空欄にすると全てのドメインで利用されます。
User Key
User KeyもGithubの概念です。
User Keyはユーザごとに作成し、Githubのユーザと同等のRead/Write権限を持つため、User Keyを使用すると様々なrepositoryへのアクセスが可能です。
CircleCIでUser Keyを利用するためには、Projectの設定画面のCheckout SSH keysを選択し、Create and add [Githubユーザ名] user keyを実行します。するとGithubのUser Keyが作成されその秘密鍵がProjectに追加されます。
User Keyはユーザと同じ権限を持つ便利な鍵です。一方CircleCIでは追加された秘密鍵をProject内から見ることができてしまうため、チームのProjectやconfigを把握していないProjectに自身のUser Keyを追加する事には慎重になる必要があります。
CircleCIでのSSH鍵の使用
追加した鍵はDeploy keyもUser keyも次のようにして利用できます。
https://circleci.com/docs/2.0/add-ssh-key/
version: 2
jobs:
deploy-job:
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "SO:ME:FIN:G:ER:PR:IN:T"
- run:
name: pull submodule
command: git submodule update --init
問題
1つのProjectで複数のprivate repositoryを扱う場合はどのようにしたら良いでしょうか?
例えば次のようなケースを考えます。
Github上にある2つのprivate repository(submodule_1
, submodule_2
)をsubmoduleとして持つrepository(circleci_test
)があったときに、CircleCIでsubmoduleを含めてcircleci_testをbuildするためにはどのような設定したら良いでしょうか?
$ cat .gitmodules
[submodule "submodule_1"]
path = submodule_1
url = git@github.com:ymaki/submodule_1.git
[submodule "submodule_2"]
path = submodule_2
url = git@github.com:ymaki/submodule_2.git
それぞれのDeploy KeyをCircleCIに追加しても機能しない
まずは次の画像のように2つのrepositoryのDeploy KeyをそれぞれCircleCIに追加するということを考えると思います。
上記画像のように別々の秘密鍵をCircleCIのcircleci_test
Projectに追加した上で
-
7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b
の公開鍵をGithubのsubmodule_1
のDeploy Keyに設定 -
29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7
の公開鍵をGithubのsubmodule_2
のDeploy Keyに設定
します。
そして以下のようなconfig.ymlを実行します。
imageにpython:stretch
を利用していますが、Debianであればそう変わらないと思うので何でもいいです。
version: 2
jobs:
build:
docker:
- image: python:stretch
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- "29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7"
- run:
name: pull submodule
command: git submodule update --init
すると、下記の画像のようにadd_ssh_keysは成功するのですがsubmoduleを読み込む部分で 何故か 失敗してしまいます。
# !/bin/bash -eo pipefail
git submodule update --init
Submodule 'submodule_1' (git@github.com:ymaki/submodule_1.git) registered for path 'submodule_1'
Submodule 'submodule_2' (git@github.com:ymaki/submodule_2.git) registered for path 'submodule_2'
Cloning into '/home/circleci/project/submodule_1'...
Cloning into '/home/circleci/project/submodule_2'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
fatal: clone of 'git@github.com:ymaki/submodule_2.git' into submodule path '/home/circleci/project/submodule_2' failed
Failed to clone 'submodule_2'.
よく見るとsubmodule_1へのアクセスは成功していますがsubmodule_2へのアクセスは失敗していることを心に留めておきます。
現象の調査
CircleCIはdebugのために実行中のcontainerにSSH接続できる機能を提供しています。
便利なので活用していきましょう。
https://circleci.com/docs/2.0/ssh-access-jobs/
先程失敗したjobのcontainerに入ってみます。
circleci@a6bbcb66dd92:~# ls
project
circleci@a6bbcb66dd92:~# cd project/
circleci@a6bbcb66dd92:~/project# ls
submodule_1
先程失敗したところで止まっているため、submodule_1
は引けてますが、submodule_2
がありません。
まずは失敗したjobと同じコマンドを実行してみます。
circleci@a6bbcb66dd92:~/project# git submodule update --init
Cloning into '/home/circleci/project/submodule_2'...
Warning: Permanently added the RSA host key for IP address '140.82.114.4' to the list of known hosts.
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
fatal: clone of 'git@github.com:ymaki/submodule_2.git' into submodule path '/home/circleci/project/submodule_2' failed
Failed to clone 'submodule_2'. Retry scheduled
Cloning into '/home/circleci/project/submodule_2'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
fatal: clone of 'git@github.com:ymaki/submodule_2.git' into submodule path '/home/circleci/project/submodule_2' failed
Failed to clone 'submodule_2' a second time, aborting
jobの結果と同じように失敗しました。
上記のgitコマンドはsubmodule_2
を引く際、sshプロトコルをを利用しています。
その際に利用するconfigや鍵はsshコマンドと同じく ~/.ssh
のものを利用しているため、ここを調べます。
circleci@10e8c95f8f07:~# ls .ssh/
config id_rsa id_rsa_29ff6cd6d55fcc92b93390d96333c6f7 id_rsa_7a50914d0c1a98e17cc4784d873a397b known_hosts
鍵が3本とconfig, known_hostsがあります。
鍵のファイル名のsuffixがfingerprintと一致するため、これがそれぞれのDeploy Keyだと想像できます。ちなみにid_rsa
はcircleci_test
のcheckout keyです。
~/.ssh/config
を見ましょう。
circleci@10e8c95f8f07:~# cat .ssh/config
Host github.com
IdentitiesOnly yes
IdentityFile /home/circleci/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
Host github.com
IdentitiesOnly yes
IdentityFile /home/circleci/.ssh/id_rsa_29ff6cd6d55fcc92b93390d96333c6f7
~/.ssh/config
の中に Host github.com
行が2行あることがわかりました。
これはマズいです。
OpenSSHのssh_config(5)によると、次のように書いてあります。
For each parameter, the first obtained value will be used.
https://man.openbsd.org/ssh_config
https://linux.die.net/man/5/ssh_config
つまり各パラメータについて先に出てきた値が使われあとの値は無視されます。
今回の場合はgithub.comへのsshアクセスはどのrepositoryへのアクセスかによらず最初のブロックの
IdentityFile /root/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
が使われることになります。
ちなみに、次のようにssh-agentに鍵が登録されています。
1行目がcircleci_test
の読み込み権限を持つcheckout key、2行目3行目がそれぞれsubmoduleのDeploy Keyと思われますが、Host github.com
ブロックでIdentitiesOnly yes
が設定されているためこれらの鍵は使われません。
circleci@26b1a44d8d1b:~$ ssh-add -l
2048 SHA256:Iv1F93xHKVfdpsngi5Zo6PuaQ+8aYTlxTIb1DbBp+M4 (RSA)
2048 SHA256:R+dN1DTPqR6CCIfFzDrWOAKUvRhDZ0/SkMeoYihNIrc (RSA)
2048 SHA256:SZX2IepedP70lftv2gN8bhgMe/6TOkJqaQLVlniXWzg (RSA)
上記より、submodule_2
へのアクセスにsubmodule_1
のdeploy keyを利用しようとして Permission Denied
となってしまった。というのが今回起きた現象だとわかりました。
つまり、CircleCIのadd_ssh_keys
の仕様として、同じドメイン用の秘密鍵を複数追加して同じcontainerの中で利用しようとしても、最初の鍵しか読まれずあとは失敗します。
CircleCIにおける鍵の扱い調査
対策を考える前にもう少し調査する必要があります。
~/.ssh/config
へ意図しない設定がされていることが原因でしたが次の3点の状態を確認しておきます。
- containerが立ち上がった直後
- checkout後
- add_ssh_keys後
containerが立ち上がった直後
次のconfigを実行してcontainerが立ち上がった直後の状態を確認します。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- run:
name: fail
command: "false"
circleci@26b1a44d8d1b:~$ ls .ssh
ls: cannot access '.ssh': No such file or directory
circleci@26b1a44d8d1b:~$ ssh-add -l
2048 SHA256:Iv1F93xHKVfdpsngi5Zo6PuaQ+8aYTlxTIb1DbBp+M4 (RSA)
2048 SHA256:R+dN1DTPqR6CCIfFzDrWOAKUvRhDZ0/SkMeoYihNIrc (RSA)
2048 SHA256:SZX2IepedP70lftv2gN8bhgMe/6TOkJqaQLVlniXWzg (RSA)
初期状態では.sshフォルダすらありませんが、ssh-agentにDeploy Keyは登録されているようです。
1行目がcheckout key, 2行目と3行目はおそらくsubmoduleのDeploy Keyです。
Projectの設定でcheckout keyをUser Keyにしておくと、1行目がUser Keyになります。
次のようにssh-agentに追加されている秘密鍵ではcircleci_test
にはアクセスできるがsubmodule_1
とsubmodule_2
にはアクセスできないという結果になりました。
circleci@184428427e0a:~$ git clone git@github.com:ymaki/circleci_test.git
Cloning into 'circleci_test'...
remote: Enumerating objects: 51, done.
remote: Counting objects: 100% (51/51), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 51 (delta 11), reused 50 (delta 10), pack-reused 0
Receiving objects: 100% (51/51), 5.20 KiB | 0 bytes/s, done.
Resolving deltas: 100% (11/11), done.
circleci@184428427e0a:~$ git clone git@github.com:ymaki/submodule_1.git
Cloning into 'submodule_1'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
circleci@184428427e0a:~$ git clone git@github.com:ymaki/submodule_2.git
Cloning into 'submodule_2'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
これはssh-agentの1行目のcheckout keyがgit@github.com
に対してsshプロトコルでは認証成功する一方でsubmoduleのRead権限は持っていないため、ERROR: Repository not found.
となっています。もしsshでdenyされていれば次の鍵が試されるのですが、sshプロトコルとしては認証成功しているため2本目3本目の鍵は試行されない、ということだと推測しています。
ちなみに自身のUser Keyを追加してCircleCI上でpreferred keyに設定するとssh-agentの1行目の秘密鍵がUser Keyになり、submodule_1
, submodule_2
の読み出しが可能になります。
repository | アクセス |
---|---|
circleci_test | 可 |
submodule_1 | 不可 |
submodule_2 | 不可 |
checkout後
checkout stepで実行されているシェルスクリプトは実行結果に乗っています。
Checkout code
を展開するとシェルスクリプトが見えますが、関係しているのは次の行です。
mkdir -p ~/.ssh
echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
' >> ~/.ssh/known_hosts
(umask 077; touch ~/.ssh/id_rsa)
chmod 0600 ~/.ssh/id_rsa
(cat <<EOF > ~/.ssh/id_rsa
$CHECKOUT_KEY
EOF
)
- .sshフォルダの作成
- known_hostsの設定
- checkout keyの挿入
が行われています。 $CHECKOUT_KEY
については ここには記載がないのですが、ほぼ間違いなくcheckout key(=秘密鍵)の文字列だと推測できます。
IdentityFile
Specifies a file from which the user's RSA or DSA authentication identity is read. The default is ~/.ssh/identity for protocol version 1, and ~/.ssh/id_rsa and ~/.ssh/id_dsa for protocol version 2.
~/.ssh/id_rsa
というファイル名で秘密鍵を保存しているので、ssh-agentに秘密鍵が追加されていない場合でも circleci_test
repositoryからソースコードを読み込むことが可能になります。
ちなみにcheckout stepで追加した ~/.ssh/id_rsa
ファイルが無くても先程確認したとおりssh-agentに既に鍵が追加されているためcircleci_test
にアクセスできるはずです。以下でその確認を行いました。
以下のconfigでcheckout後の状態を作り、ssh接続します。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- checkout
- run:
name: fail
command: "false"
checkout stepによって~/.ssh/id_rsa
, ~/.ssh/known_hosts
ファイルが作成されています。
ssh-add -l
の結果は変わりません。
circleci@c9932af704b4:~$ ls ~/.ssh/
id_rsa known_hosts
circleci@c9932af704b4:~$ ssh-add -l
2048 SHA256:Iv1F93xHKVfdpsngi5Zo6PuaQ+8aYTlxTIb1DbBp+M4 (RSA)
2048 SHA256:R+dN1DTPqR6CCIfFzDrWOAKUvRhDZ0/SkMeoYihNIrc (RSA)
2048 SHA256:SZX2IepedP70lftv2gN8bhgMe/6TOkJqaQLVlniXWzg (RSA)
checkout stepで追加された~/.ssh/id_rsa
を削除してgit@github.com:ymaki/circleci_test.git
を読み出せることを確認します
circleci@c9932af704b4:~$ rm ~/.ssh/id_rsa ~/.ssh/known_hosts
circleci@c9932af704b4:~$ git clone git@github.com:ymaki/circleci_test.git
Cloning into 'circleci_test'...
The authenticity of host 'github.com (140.82.114.4)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,140.82.114.4' (RSA) to the list of known hosts.
remote: Enumerating objects: 49, done.
remote: Counting objects: 100% (49/49), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 49 (delta 10), reused 49 (delta 10), pack-reused 0
Receiving objects: 100% (49/49), 4.97 KiB | 0 bytes/s, done.
Resolving deltas: 100% (10/10), done.
成功しました。
checkout stepで追加した~/.ssh/id_rsa
がなくても circleci_test
にアクセスできました。
(試しに消してみたのですがCIでは非対話環境なのでknown_hostsは必要です)
上記より、checkout stepではcheckout keyの追加を行っていますが今回の現象にはあまり関係がなさそうです。
repository | アクセス |
---|---|
circleci_test | 可 |
submodule_1 | 不可 |
submodule_2 | 不可 |
add_ssh_keys後
最後にadd_ssh_keys stepの確認をします。
残念ながらadd_ssh_keys stepについては内部の処理が不明です。
https://circleci.com/docs/2.0/configuration-reference/#add_ssh_keys
そのためあまり良くないのですが外側から観測するしかなさそうです。
次の.circleci/config.yml
を実行してadd_ssh_keys
前後の変化を確認してみます。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- run:
name: fail
command: "false"
jobがfailしたらsshで接続し、ssh関連の確認をします。
circleci@253954aa16fb:~$ ssh-add -l
2048 SHA256:Iv1F93xHKVfdpsngi5Zo6PuaQ+8aYTlxTIb1DbBp+M4 (RSA)
2048 SHA256:R+dN1DTPqR6CCIfFzDrWOAKUvRhDZ0/SkMeoYihNIrc (RSA)
2048 SHA256:SZX2IepedP70lftv2gN8bhgMe/6TOkJqaQLVlniXWzg (RSA)
ssh-agentに追加された鍵は変わりません。
circleci@253954aa16fb:~$ ls .ssh/
config id_rsa_7a50914d0c1a98e17cc4784d873a397b
~/.ssh/config
が追加されています。
また、add_ssh_keys stepでは、checkout keyではなく指定したfingerprintのDeploy Keyがsuffix付きで挿入されています。
circleci@253954aa16fb:~$ cat ~/.ssh/config
Host github.com
IdentitiesOnly yes
IdentityFile /home/circleci/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
~/.ssh/config
では、指定したHostnameに対して次の設定をしています。
- ssh-agentに追加された鍵を使わず、configで指定したファイルの鍵を利用する
- 秘密鍵ファイルの指定
ssh-agentに鍵が登録されていますが、github.com
についてIdentitiesOnly yes
が設定されていますのでssh-agentの鍵は利用しません。
この状態ではgithub.comへの全てのsshアクセスに/home/circleci/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b(submodule_1の秘密鍵)
を利用します。
github.com
へのアクセスがsubmodule_1
で成功、submodule_2
, circleci_test
で失敗することを確認します。
circleci@253954aa16fb:~$ git clone git@github.com:ymaki/submodule_1.git
Cloning into 'submodule_1'...
The authenticity of host 'github.com (140.82.113.4)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,140.82.113.4' (RSA) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
circleci@253954aa16fb:~$ git clone git@github.com:ymaki/submodule_2.git
Cloning into 'submodule_2'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
circleci@253954aa16fb:~$ git clone git@github.com:ymaki/circleci_test.git
Cloning into 'circleci_test'...
ERROR: Repository not found.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
予想通りの結果になりました。
repository | アクセス |
---|---|
circleci_test | 不可 |
submodule_1 | 可 |
submodule_2 | 不可 |
add_ssh_keysを複数回
それではadd_ssh_keys stepを複数回実行した場合はどうなるかというと、先述の通り単純に~/.ssh/config
に追記されます。
circleci@10e8c95f8f07:~# ls .ssh/
config id_rsa id_rsa_29ff6cd6d55fcc92b93390d96333c6f7 id_rsa_7a50914d0c1a98e17cc4784d873a397b known_hosts
circleci@10e8c95f8f07:~# cat .ssh/config
Host github.com
IdentitiesOnly yes
IdentityFile /home/circleci/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
Host github.com
IdentitiesOnly yes
IdentityFile /home/circleci/.ssh/id_rsa_29ff6cd6d55fcc92b93390d96333c6f7
繰り返しになりますが、.ssh/config
は先に記述したものが優先される & IdentitiesOnly yes
のため、2つ目のブロックの秘密鍵は使用されず、1つ目のブロックの秘密鍵/home/circleci/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
のみが利用されます。
repository | アクセス |
---|---|
circleci_test | 不可 |
submodule_1 | 可 |
submodule_2 | 不可 |
(おまけ)add_ssh_keysの後にcheckout
これまでの話から、add_ssh_keys stepの後にcheckout stepを実施した場合が気になります。
add_ssh_keys stepでsubmodule_1
の秘密鍵を使用する設定になってしまうので、checkout stepでcheckout keyを挿入してもcircleci_test
の読み出しが失敗する気がします。
試してみましょう。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- checkout
- run:
name: fail
command: "false"
無事失敗しました。
複数の鍵でなくてもgithub.com(またはbitbucket.org)ドメインの鍵を追加する際には注意が必要そうです。
対策
挙動が見えてきたので対策を考えます。
(x) 1つのDeploy Keyを複数のGithub repositoryに追加する
CircleCIの~/.ssh/config
に同じドメインの設定が複数記述されるのが問題なので、Deploy Keyを1つにしてGithubの複数repositoryにそのDeploy Keyを登録しようという発想です。
この案は、Githubでは複数repositoryに単一のDeploy Keyを登録できないという制約により棄却されます。
登録しようとしてもError: Key already in use
とエラーが出て登録できませんでした。
https://help.github.com/ja/articles/error-key-already-in-use
(x) IdentitiesOnly yes
を削除して秘密鍵をssh-addする
add_ssh_keysではIdentitiesOnly
パラメータによってssh-agentが持つ鍵を利用不可にしていますが、これをnoに変更することでssh-agentの秘密鍵を利用できるようにしようという作戦です。
こんな感じでしょうか。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- "29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7"
- run:
name: use ssh-agent
command: |
sed -i -e 's/IdentitiesOnly yes/IdentitiesOnly no/g' ~/.ssh/config
for filename in $(grep 'id_rsa_' ~/.ssh/config | cut -d' ' -f4); do ssh-add ${filename}; done
- run:
name: pull submodule
command: git submodule update --init
と、やってみたのですが、失敗してしまいました。

先述しましたが、ssh接続する際にssh-agentに最初に追加されたcircleci_test
のcheckout keyで認証に成功しますがsubmodule_1
, submodule_2
へのアクセス権が無いため認可に失敗。ただしssh認証自体は成功しているので次の鍵の試行は行わないという流れのようです。
(x) 自身のUser Keyを利用する
circleci_test
やsubmodule_1
, submodule_2
にRead/Write権限のある自身のUser KeyをCircleCIのProjectに追加する案です。
成功はするのですが、Projectに不必要に広い権限を与えてしまっているため、非推奨です。
秘密鍵は実行中のcontainerの~/.ssh/
以下に挿入されるため、例えば同じProjectに参加している他人があなたの権限を持つ秘密鍵を覗き、Projectに関係ないprivateなrepositoryを読み書きする事ができてしまいます。
(o) Machine UserのUser Keyを利用する
If your server needs to access multiple repositories, you can create a new GitHub account and attach an SSH key that will be used exclusively for automation. Since this GitHub account won't be used by a human, it's called a machine user.
Machine Userと呼ばれる権限管理のためのアカウントを新たに作成します。
必要な権限をMachine Userに付与した上でProjectにMachine UserのUser Keyを追加することで、必要なrepositoryだけを引けるようになります。
CircleCIはこの方法を推奨しているようです。
Your best bet, for fine-grained access to more than one repo, is to create what GitHub calls a machine user. Give this user exactly the permissions your build requires, and then associate its user key with your project on CircleCI.
discussionやドキュメントにも記載があります。
https://discuss.circleci.com/t/multiple-ssh-deploy-keys-with-same-hostname/4288/2
https://circleci.com/docs/2.0/gh-bb-integration/#controlling-access-via-a-machine-user
必要十分な権限を与えられる点は良いのですが、Project毎に権限のためだけのgithubアカウントを作成して管理するのか…? という疑問があり、個人的にはあまりやりたくありません。
(o) add_ssh_keysの後に.ssh/config
を掃除する
根本の原因はadd_ssh_keys
した際に.ssh/config
に同一名のブロックが現れることが問題でした。
そこで、「add_ssh_keys -> githubアクセス -> configを消去」を1セットにして1つずつ鍵を使うとうまく行く気がします。
具体的には次のようなconfigになります。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- run:
name: pull submodule
command: git submodule update --init submodule_1
- run:
name: remove ssh keys
command: rm ~/.ssh/config
- add_ssh_keys:
fingerprints:
- "29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7"
- run:
name: pull submodule
command: git submodule update --init submodule_1
- run:
name: remove ssh keys
command: rm ~/.ssh/config
CircleCIのversion2.1以降でcommandsが使える場合は、次のようにしてcommandにまとめてしまうのが良いでしょう。
version: 2.1
commands:
checkout_submodule:
description: "Checkout a submodule"
parameters:
fingerprint:
type: string
module_name:
type: string
steps:
- add_ssh_keys:
fingerprints:
- << parameters.fingerprint >>
- run:
name: pull submodule
command: git submodule update --init << parameters.module_name >>
- run:
name: remove ssh keys
command: rm ~/.ssh/config
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- checkout
- checkout_submodule:
module_name: "submodule_1"
fingerprint: "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- checkout_submodule:
module_name: "submodule_2"
fingerprint: "29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7"
少しhackyですが、submoduleの依存関係を取り除くケースではworkします。
ただし、こちら言及されているように、go get
など複数のprivate repositoryへのアクセスが同時に自動で行われる場合には適用できません。
(o) ./ssh/config
を書き換えてalias名でアクセスする
ここで言及されている案で、add_ssh_keysで追加されるconfigにHostName
を追加することで、Host
をaliasとして使おうという作戦です。
~/.ssh/config
が次のようになります。
Hostが重複しないため、複数のブロックの設定が活きます。
Host some_alias_name.com
HostName github.com
IdentitiesOnly yes
IdentityFile /root/.ssh/id_rsa_7a50914d0c1a98e17cc4784d873a397b
Host some_another_alias_name.com
HostName github.com
IdentitiesOnly yes
IdentityFile /root/.ssh/id_rsa_29ff6cd6d55fcc92b93390d96333c6f7
具体的にやってみます。
まずは重複しない適当な名前でCircleCIのProjectに秘密鍵を登録しておきます。
そして、add_ssh_keys stepの直後で~/.ssh/config
を書き換えます。(add alias of ssh target hostname
)
さらに、ssh targetのHostをsubmodule1.github.com
のように変更したので、git submodule update
する際にもgithub.com
ではなくsubmodule1.github.com
を見に行く必要があります。(modify submodule hostname to alias name
)
~/.circleci/config.ymlは次のようになります。
version: 2
jobs:
build:
docker:
- image: circleci/python:stretch
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "7a:50:91:4d:0c:1a:98:e1:7c:c4:78:4d:87:3a:39:7b"
- "29:ff:6c:d6:d5:5f:cc:92:b9:33:90:d9:63:33:c6:f7"
- run:
name: add alias of ssh target hostname
command: |
sed -i -e 's/Host submodule1.github.com/Host submodule1.github.com\n HostName github.com/g' ~/.ssh/config
sed -i -e 's/Host submodule2.github.com/Host submodule2.github.com\n HostName github.com/g' ~/.ssh/config
- run:
name: modify submodule hostname to alias name
command: |
git config submodule.submodule_1.url git@submodule1.github.com:ymaki/submodule_1.git
git config submodule.submodule_2.url git@submodule2.github.com:ymaki/submodule_2.git
- run:
name: pull submodule
command: git submodule update --init
これを実行すると、成功したようです。

この方法は手を入れる箇所が多いですが、成功はします。
今回はsubmoduleだったのでgit config
コマンドでURLを変更しましたが、例えばgo get
の場合はソースコード中のURLを変更するのでしょうか? ちょっと考えたくありません…。
まとめ
CircleCIでは同じドメインの複数Deploy Keyの追加はサポートされていません。
以下の案を検討しましたが適用できるケースとできないケースがあり、適用できないケースではおとなしくMachine Userを作成するのが良さそうです。
それぞれ一長一短なので許容できるデメリットを考えて(特に推奨されていない方法は自己責任で)利用してください。
また、セキュリティリスクや他に良さそうな方法があったら教えて下さい🙇
失敗または脆弱
- 単一のDeploy Keyを使い回す案
- 自分のUser Keyを利用する案
-
IdentitiesOnly no
+ssh-add
案
成功
- Machine Userを作成しそのUser Keyを利用する案(公式推奨)
- add_ssh_keyのあと都度
.ssh/config
を削除する案 -
./ssh/config
を書き換えてaliasでアクセスする案
参考
https://circleci.com/docs/2.0/
https://discuss.circleci.com/
https://man.openbsd.org/ssh_config
https://linux.die.net/man/5/ssh_config
https://help.github.com/ja/articles/error-key-already-in-use
https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#_the_ssh_protocol
https://medium.com/veltra-engineering/there-may-be-security-risks-in-your-clone-of-multiple-private-repositories-in-circleci-6ae6368eacb9