Posted at

CircleCIで同じHostnameのSSH鍵を複数追加したい

Add multiple SSH deploy keys with same hostname on CircleCI


tl;dr

対象の現象は CircleCIとGithubに登録するSSH鍵は間違っていないのに権限系のエラーが出る です。


error

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 パターンです。


  1. バージョン管理システムからコードをチェックアウトする

  2. 実行中のプロセスが他のサービスにアクセスできるようにする


公式サイトに記載されているように、privateなsubmoduleや依存先repositoryをビルド時にひっぱるために鍵が必要です。

鍵には2種類あり、CircleCIのプロジェクトごとに設定することができます。

1. Deploy Key

2. User Key


Deploy Key

Deploy KeyはGithubの概念です。

Githubのrepositoryごとに作成し、repository単位でRead/Writeアクセスを制御します。

例えば以下の画像では、あるrepositoryに2つのDeploy Keyが追加されています。

CircleCIという名前の鍵はCircleCIでこのrepositoryを追加したときに自動で作成されたものです。checkout keyとも呼ばれます。

公式ドキュメント

スクリーンショット 2019-08-15 10.51.53.png

あるrepositoryのDeploy Keyの秘密鍵をCircleCIのProjectに追加することによって、ProjectはそのrepositoryのRead/Write権限を得ることができます。

Deploy Keyを登録するためには、Projectの設定画面のSSH Permissionsで追加します。その際、Hostname欄でどのドメインで利用する鍵かを指定することができます。Hostnameを空欄にすると全てのドメインで利用されます。

スクリーンショット 2019-08-15 11.31.49.png


User Key

User KeyもGithubの概念です。

User Keyはユーザごとに作成し、Githubのユーザと同等のRead/Write権限を持つため、User Keyを使用すると様々なrepositoryへのアクセスが可能です。

スクリーンショット 2019-08-15 11.01.14.png

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を追加する事には慎重になる必要があります。

スクリーンショット 2019-08-15 10.57.00.png


CircleCIでのSSH鍵の使用

追加した鍵はDeploy keyもUser keyも次のようにして利用できます。

https://circleci.com/docs/2.0/add-ssh-key/


.circleci/config.yml


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に追加するということを考えると思います。

スクリーンショット 2019-08-15 12.03.53.png

上記画像のように別々の秘密鍵をCircleCIのcircleci_testProjectに追加した上で

- 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であればそう変わらないと思うので何でもいいです。


.circleci/config.yml

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を読み込む部分で 何故か 失敗してしまいます。

スクリーンショット 2019-08-15 13.33.36.png


エラー内容

#!/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/

スクリーンショット 2019-08-15 13.58.02.png

先程失敗した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_rsacircleci_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が立ち上がった直後の状態を確認します。


add_ssh_keys前

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_1submodule_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.


https://linux.die.net/man/5/ssh_config

~/.ssh/id_rsaというファイル名で秘密鍵を保存しているので、ssh-agentに秘密鍵が追加されていない場合でも circleci_test repositoryからソースコードを読み込むことが可能になります。

ちなみにcheckout stepで追加した ~/.ssh/id_rsa ファイルが無くても先程確認したとおりssh-agentに既に鍵が追加されているためcircleci_testにアクセスできるはずです。以下でその確認を行いました。

以下のconfigでcheckout後の状態を作り、ssh接続します。


.circleci/config.yml

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前後の変化を確認してみます。


.circleci/config.yml

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に対して次の設定をしています。

1. ssh-agentに追加された鍵を使わず、configで指定したファイルの鍵を利用する

2. 秘密鍵ファイルの指定

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"

無事失敗しました。

スクリーンショット 2019-08-15 17.46.35.png

複数の鍵でなくても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

と、やってみたのですが、失敗してしまいました。

スクリーンショット 2019-08-15 23.23.47.png

先述しましたが、ssh接続する際にssh-agentに最初に追加されたcircleci_testのcheckout keyで認証に成功しますがsubmodule_1, submodule_2へのアクセス権が無いため認可に失敗。ただしssh認証自体は成功しているので次の鍵の試行は行わないという流れのようです。


(x) 自身のUser Keyを利用する

circleci_testsubmodule_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になります。


.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"
- 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が重複しないため、複数のブロックの設定が活きます。


~/.ssh/config

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に秘密鍵を登録しておきます。

スクリーンショット 2019-08-15 22.12.54.png

そして、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は次のようになります。


~/.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


これを実行すると、成功したようです。

スクリーンショット 2019-08-15 22.56.03.png

この方法は手を入れる箇所が多いですが、成功はします。

今回は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