Attempt #1
Windowsランナーでssh-agent
Windowsランナーを使う予定のない人は、以降まったく読む価値が無いことに注意。
別のプライベートリポジトリのクローン
あるリポジトリAについてGitHub Actionsのジョブを実行中に、Aと別のプライベートリポジトリBをチェックアウトまたはクローンするステップを記述したい。Windowsランナーに限らないが、簡単な方法はマーケットプレイスのactions/checkoutを次のように使うことだろう:
steps:
- name: Checkout private repository
uses: actions/checkout@v4
with:
repository: maroontress-tomohisa/private-repository-example
ssh-key: ${{secrets.PRIVATE_REPO_DEPLOY_KEY}}
path: private-repository-example
- name: Print README.md
shell: bash
run: |
cat private-repository-example/README.md
「リポジトリB」にアクセスするために、デプロイキーを使用することにする。リポジトリBのURLはgit@github.com:maroontress-tomohisa/private-repository-example.git
としよう。そして、リポジトリBには適切にデプロイキーのパブリックキーが設定され、リポジトリAのシークレットに適切にデプロイキーのプライベートキーが設定されていて、それをワークフロー内ではsecret.PRIVATE_REPO_DEPLOY_KEY
で参照できるようになっている、とする。
最初のステップでリポジトリBをprivate-repository-example
にチェックアウトする。次のステップで確認のため、リポジトリBのREADME.md
を出力する。
結果は次のようになった:
Run actions/checkout@v4
Syncing repository: maroontress-tomohisa/private-repository-example
Getting Git version info
Temporarily overriding HOME='D:\a\_temp\d3f120f5-cde6-43a2-b847-7146107be8b8' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
"C:\Program Files\Git\bin\git.exe" config --global --add safe.directory D:\a\try_out_github_actions\try_out_github_actions\private-repository-example
Initializing the repository
Disabling automatic garbage collection
Setting up auth
Determining the default branch
Fetching the repository
Determining the checkout info
Checking out the ref
"C:\Program Files\Git\bin\git.exe" log -1 --format='%H'
'3cf9492e7ffb20ea5246a8edf1bec8d3aab293a4'
⋮
# An Example of Private Repository
成功だ。最後の行はREADME.md
の中身そのものである。
このように、actions/checkoutを使えば、別のプライベートリポジトリをチェックアウトすることができる。しかし、ローカル環境でも同じようにリポジトリBをチェックアウトする必要がある場合、何らかのスクリプトを用意して、そのスクリプトでチェックアウトしたい。そうすると、ワークフローファイルに記述したものはGitHub Actionsでしか実行できないので、二重に管理することになる。ワークフローファイルをローカルで実行できるように変換する何かを作るのも良さそうだが、他の方法も調べてみよう。
ssh-agentで別のプライベートリポジトリのクローン
同じことを、今度はssh-agent
を用いてやってみる。そうではなく、~/.ssh/config
にエントリを追加してもできるが、それは後のセクションで試してみる。
まず、Windowsランナーにインストールされているssh-agent
関連のコマンドがどのようになっているのかを確認する。
- name: Check commands
shell: bash
run: |
ls -l `which ssh`
ls -l `which ssh-add`
ls -l `which ssh-agent`
ls -l `which git`
結果は次のようになった:
-rwxr-xr-x 1 runneradmin 197121 958822 Aug 30 09:46 /usr/bin/ssh
-rwxr-xr-x 1 runneradmin 197121 441485 Aug 30 09:46 /usr/bin/ssh-add
-rwxr-xr-x 1 runneradmin 197121 415546 Aug 30 09:46 /usr/bin/ssh-agent
-rwxr-xr-x 4 runneradmin 197121 3830264 Aug 30 09:49 /mingw64/bin/git
Windowsのssh-agent
の実装はいくつかあるが、パスにあるのはGit for Windowsのものである。
ssh-agent
を素直に実行すると次のようになる:
- name: Start ssh-agent
shell: bash
run: |
eval `ssh-agent`
echo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
echo SSH_AGENT_PID="$SSH_AGENT_PID" >> "$GITHUB_ENV"
最後の二行は、後に続くステップで環境変数SSH_AUTH_SOCK
とSSH_AGENT_PID
が有効になるように$GITHUB_ENV
に追記している(詳細は環境変数の設定を参照)。
結果は次のようになった:
Agent pid 433
ssh-agent
がバックグラウンドで動作したので、次にデプロイキーをssh-add
コマンドでエージェントに登録する:
- name: Add a deploy key
shell: bash
run: |
mkdir -p $HOME/.ssh
echo "${{secrets.PRIVATE_REPO_DEPLOY_KEY}}" > $HOME/.ssh/PRIVATE_REPO_DEPLOY_KEY
ssh-add $HOME/.ssh/PRIVATE_REPO_DEPLOY_KEY
一旦、~/.ssh
ディレクトリを作成して、そこにファイルとしてプライベートキーを保存した。
結果は次のようになった:
Identity added: /c/Users/runneradmin/.ssh/PRIVATE_REPO_DEPLOY_KEY (git@github.com:maroontress-tomohisa/private-repository-example.git)
無事、登録できた。カッコ内にSSHキーのコメントが表示されている。このデプロイキーは、コメントにリポジトリのURLを指定して作成した。これは現時点では意味が無い(後のセクションで、複数のプライベートリポジトリを読み出すときに用いる)ので、ここでは無視する。
念のため、次のようにssh-add -l
で登録されたキーを確認する:
- name: List fingerprints
shell: bash
run: |
ssh-add -l
結果は次のようになった:
3072 SHA256:EHYsJhMvV2X03sbEYcAH3w7MNft1lra8M/ZSF0XMr5k git@github.com:maroontress-tomohisa/private-repository-example.git (RSA)
これでgit clone
できるはずである。次のようにクローンしてみよう:
- name: Clone the private repository (which fails)
continue-on-error: true
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-repository-example.git
cat private-repository-example/README.md
最後の行は、クローンに成功した場合にREADME.md
を表示したい、という意図だが、成功しないので意味が無い。結果は次のようになった:
Cloning into 'private-repository-example'...
Host key verification failed.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Error: Process completed with exit code 128.
これは簡単で、github.com
のSSHパブリックキーが~/.ssh/known_hosts
に含まれてないのでエラーになる。そもそも~/.ssh/known_hosts
が存在してないのだ。次のようにknown_hosts
を作成しよう:
- name: Perform workarounds (create ~/.ssh/known_hosts)
shell: bash
run: |
rm -rf private-repository-example
cat << EOF > $HOME/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
もう一度、次のようにクローンしてみよう:
- name: Clone a private repository
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-repository-example.git
cat private-repository-example/README.md
結果は次のようになった:
Cloning into 'private-repository-example'...
# An Example of Private Repository
成功した。
☕
少し前までは、この段階で環境変数
GIT_SSH
の設定が必要だったのだが、現在はその必要が無くなった。GitHub Actionsの世界は少しずつ良くなっているようだ。
別のプライベートLFSリポジトリのクローン
今度は、別のプライベートリポジトリCをクローンしてみよう。ただし、リポジトリCはGit LFSを用いている。そう、説明はなかったが、前のセクションのリポジトリBはLFSを使っていなかった。
リポジトリCのURLはgit@github.com:maroontress-tomohisa/private-lfs-repository-example.git
とする。今度も同様に、リポジトリCには適切にデプロイキーのパブリックキーが設定され、リポジトリAのシークレットに適切にデプロイキーのプライベートキーが設定されていて、それをワークフロー内ではsecret.PRIVATE_LFS_REPO_DEPLOY_KEY
で参照できるようになっているとしよう。
素直に考えると、前のセクションと同じように、URLとシークレットの変数だけ変更すればできそうだ。次のようにステップを実行する:
steps:
- name: Start ssh-agent
shell: bash
run: |
mkdir -p $HOME/.ssh
cat << EOF > $HOME/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
eval `ssh-agent`
echo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
echo SSH_AGENT_PID="$SSH_AGENT_PID" >> "$GITHUB_ENV"
- name: Add a deploy key
shell: bash
run: |
echo "${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}" > $HOME/.ssh/PRIVATE_LFS_REPO_DEPLOY_KEY
ssh-add $HOME/.ssh/PRIVATE_LFS_REPO_DEPLOY_KEY
- name: List fingerprints
shell: bash
run: |
ssh-add -l
- name: Clone a private repository with LFS
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
cat private-lfs-repository-example/README.md
unzip -v private-lfs-repository-example/empty.zip
プライベートリポジトリCのルートにはempty.zip
があるので、最後の行でそれを確認できれば成功だ。結果は次のようになった:
Agent pid 56
⋮
Identity added: /c/Users/runneradmin/.ssh/PRIVATE_LFS_REPO_DEPLOY_KEY (git@github.com:maroontress-tomohisa/private-lfs-repository-example.git)
⋮
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
⋮
Cloning into 'private-lfs-repository-example'...
# An Example of Private Repository with LFS
Archive: private-lfs-repository-example/empty.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
0 Stored 0 0% 2023-10-06 06:44 00000000 empty
-------- ------- --- -------
0 0 0% 1 file
成功した。
☕
少し前までは、この段階でも環境変数
GIT_SSH
の設定が必要だったのだが、現在はその必要が無くなった。
なお、actions/checkoutを用いる場合は、次のようにオプションlfs: true
の追加が必要になることに注意:
steps:
- name: Checkout private LFS repository
uses: actions/checkout@v4
with:
repository: maroontress-tomohisa/private-lfs-repository-example
ssh-key: ${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}
lfs: true
path: private-lfs-repository-example
別の複数のプライベートリポジトリをクローンする
次に、二つのリポジトリBとCを、リポジトリAのジョブから読み出したい。actions/checkoutを使うなら二つのステップを並べれば良いが、ssh-agent
を使ってもできるのだろうか。
ssh-agent
にssh-add
で同じホストのキーを複数追加することはできる。しかし、ssh
は、接続に失敗したら次のキーを試す、のように接続先に順番にキーを適用するだけである。マーケットプレイスのwebfactory/ssh-agentの説明を引用すると:
ただし、注意点が1つあります。SSHサーバーは、多数の一致しないキーが提示された後に接続試行を中断する可能性があります。そのため、たとえば、6つの異なるキーがssh-agentにロードされていて、5つの不明なキーの後にサーバーが中断した場合、最後のキー(正しいキーかもしれません)は決して試されません。
だから、ssh-agent
を用いて同じホストの複数のリポジトリをチェックアウトするなら、次のように「デプロイキーの登録、チェックアウト、デプロイキーの削除」を繰り返す必要がある:
- リポジトリBのキーを
ssh-add
で登録 - リポジトリBをクローン
- リポジトリBのキーを
ssh-add -d
で削除 - リポジトリCのキーを
ssh-add
で登録 - リポジトリCをクローン
- リポジトリCのキーを
ssh-add -d
で削除 - ⋮
ところで、ローカルでビルドする際は、デプロイキーを用いることなく、二つのリポジトリをクローンできるのが望ましい。ワークフローファイルでもローカルと同じような手順でチェックアウトしたいので、ssh-agent
を用いるのをやめて、別の方法を試してみよう。
リポジトリBとCは、同じホストgithub.com
にあるので、ちょっとした工夫が必要になる(GitHub Actionsに限った話ではなく、良く知られた話なので、詳細はChatGPTにでも尋ねてほしい)。要約すると、git config
のurl.<base>.insteadOf
の機能と、~/.ssh/config
のHost
及びHostname
の機能を使って、Gitのレイヤーでリポジトリ毎にユニークな偽ホスト名を割り当て、SSHのレイヤーで偽ホスト名をgithub.com
に変換し、同時にその偽ホストに対応するリポジトリのSSHキーを関連付ける、という技法である。具体的には次のようになる:
steps:
- name: Create ~/.ssh/known_hosts
shell: bash
run: |
mkdir -p $HOME/.ssh
cat << EOF > $HOME/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
- name: Add deploy keys
shell: bash
run: |
add_key() {
key="$HOME/.ssh/$1"
win_key="$(cygpath -w $key)"
echo "$2" > "$key"
ssh-keygen -y -f $key > $key.pub
read a b comment < $key.pub
echo comment: $comment
url="${comment%.*}"
echo url: $url
host_path="${url#*@}"
new_host_path="$1.${host_path}"
new_url="git@$new_host_path"
echo git config --global url."${new_url}".insteadOf "${url}"
git config --global url."${new_url}".insteadOf "${url}"
cat << EOF >> $HOME/.ssh/config
Host ${new_host_path%%:*}
HostName github.com
IdentityFile $win_key
IdentitiesOnly yes
EOF
}
add_key PRIVATE_REPO_DEPLOY_KEY "${{secrets.PRIVATE_REPO_DEPLOY_KEY}}"
add_key PRIVATE_LFS_REPO_DEPLOY_KEY "${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}"
- name: Print git config
shell: bash
run: git config --global --list
- name: Print ssh config
shell: bash
run: cat $HOME/.ssh/config
- name: Clone a private repository
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-repository-example.git
cat private-repository-example/README.md
- name: Clone another private repository with LFS
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
cat private-lfs-repository-example/README.md
unzip -v private-lfs-repository-example/empty.zip
少し長いが、それほど難しくはない。Add deploy keys
のステップで、git config
による設定変更と~/.ssh/config
のエントリ作成を、デプロイキーごとに実施する。前のセクションで言及したSSHキーのコメントはここで使用する。鍵にコメントとしてURLを埋め込んでおくことで、「キーとURL」の組みを記述せずに済んでいる。結果は次のようになった:
comment: git@github.com:maroontress-tomohisa/private-repository-example.git
url: git@github.com:maroontress-tomohisa/private-repository-example
git config --global url.git@PRIVATE_REPO_DEPLOY_KEY.github.com:maroontress-tomohisa/private-repository-example.insteadOf git@github.com:maroontress-tomohisa/private-repository-example
comment: git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
url: git@github.com:maroontress-tomohisa/private-lfs-repository-example
git config --global url.git@PRIVATE_LFS_REPO_DEPLOY_KEY.github.com:maroontress-tomohisa/private-lfs-repository-example.insteadOf git@github.com:maroontress-tomohisa/private-lfs-repository-example
⋮
url.git@PRIVATE_REPO_DEPLOY_KEY.github.com:maroontress-tomohisa/private-repository-example.insteadof=git@github.com:maroontress-tomohisa/private-repository-example
url.git@PRIVATE_LFS_REPO_DEPLOY_KEY.github.com:maroontress-tomohisa/private-lfs-repository-example.insteadof=git@github.com:maroontress-tomohisa/private-lfs-repository-example
⋮
Host PRIVATE_REPO_DEPLOY_KEY.github.com
HostName github.com
IdentityFile C:\Users\runneradmin\.ssh\PRIVATE_REPO_DEPLOY_KEY
IdentitiesOnly yes
Host PRIVATE_LFS_REPO_DEPLOY_KEY.github.com
HostName github.com
IdentityFile C:\Users\runneradmin\.ssh\PRIVATE_LFS_REPO_DEPLOY_KEY
IdentitiesOnly yes
⋮
Cloning into 'private-repository-example'...
# An Example of Private Repository
⋮
Cloning into 'private-lfs-repository-example'...
# An Example of Private Repository with LFS
Archive: private-lfs-repository-example/empty.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
0 Stored 0 0% 2023-10-06 06:44 00000000 empty
-------- ------- --- -------
0 0 0% 1 file
git config
の設定内容と、~/.ssh/config
の中身を確認してから、リポジトリBとCをチェックアウトした。正しくチェックアウトできている。
☕
GitHub公式ドキュメントでも“1つのサーバー上で複数のリポジトリを利用する”で複数のデプロイキーを扱う別の方法を紹介している。しかし、その方法だと、ローカルの
~/.ssh/config
も変更することになるし、git clone
の際にリポジトリのURLとして偽のURLを指定することになる。また別の面倒を引き寄せたくなければ、避けたほうが良い。
webfactory/ssh-agentで別の複数のプライベートリポジトリをクローンする
しかし、このような「工夫」をワークフローファイルに記述するのは煩雑だ。同じことをマーケットプレイスのwebfactory/ssh-agentを次のように使って実現してみよう:
steps:
- name: webfactory/ssh-agent
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: |
${{secrets.PRIVATE_REPO_DEPLOY_KEY}}
${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}
結果は次のようになった:
Starting ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-QrHWdhFo8ceQ/agent.1095
SSH_AGENT_PID=1096
Adding private key(s) to agent
Identity added: (stdin) (git@github.com:maroontress-tomohisa/private-repository-example.git)
Identity added: (stdin) (git@github.com:maroontress-tomohisa/private-lfs-repository-example.git)
Key(s) added:
3072 SHA256:EHYsJhMvV2X03sbEYcAH3w7MNft1lra8M/ZSF0XMr5k git@github.com:maroontress-tomohisa/private-repository-example.git (RSA)
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
Configuring deployment key(s)
Added deploy-key mapping: Use identity 'C:\Users\runneradmin/.ssh/key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39' for GitHub repository maroontress-tomohisa/private-repository-example
Added deploy-key mapping: Use identity 'C:\Users\runneradmin/.ssh/key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9' for GitHub repository maroontress-tomohisa/private-lfs-repository-example
簡単になったが、副作用としてssh-agent
がバックグラウンドで開始する。次のステップで確認してみる:
- name: List fingerprints
shell: bash
run: ssh-add -l
結果は次のようになった:
3072 SHA256:EHYsJhMvV2X03sbEYcAH3w7MNft1lra8M/ZSF0XMr5k git@github.com:maroontress-tomohisa/private-repository-example.git (RSA)
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
git config
の設定内容と、~/.ssh/config
の中身も確認してみよう:
- name: Print git config
shell: bash
run: git config --global --list
- name: Print ssh config
shell: bash
run: cat $HOME/.ssh/config
結果は次のようになった:
url.git@key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39.github.com:maroontress-tomohisa/private-repository-example.insteadof=https://github.com/maroontress-tomohisa/private-repository-example
url.git@key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39.github.com:maroontress-tomohisa/private-repository-example.insteadof=git@github.com:maroontress-tomohisa/private-repository-example
url.git@key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39.github.com:maroontress-tomohisa/private-repository-example.insteadof=ssh://git@github.com/maroontress-tomohisa/private-repository-example
url.git@key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9.github.com:maroontress-tomohisa/private-lfs-repository-example.insteadof=https://github.com/maroontress-tomohisa/private-lfs-repository-example
url.git@key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9.github.com:maroontress-tomohisa/private-lfs-repository-example.insteadof=git@github.com:maroontress-tomohisa/private-lfs-repository-example
url.git@key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9.github.com:maroontress-tomohisa/private-lfs-repository-example.insteadof=ssh://git@github.com/maroontress-tomohisa/private-lfs-repository-example
⋮
Host key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39.github.com
HostName github.com
IdentityFile C:\Users\runneradmin/.ssh/key-43749e3def49002289a25278b6d8d5a0b8fed7f2c33f26750fe6233c114a1c39
IdentitiesOnly yes
Host key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9.github.com
HostName github.com
IdentityFile C:\Users\runneradmin/.ssh/key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9
IdentitiesOnly yes
git config
に関してはエントリが前のセクションの三倍に増えているが、git@github.com:
で始めるSSH形式のURLに加えて、HTTPS形式のURL、ssh://
で始まるSSH+GIT形式のURLも変換の対象になっている。あとは、偽ホストの名前にハッシュ値が使われてることを除いて、前のセクションの説明と違いは無い。
ではクローンしてみよう:
- name: Clone the private repository (which fails)
shell: bash
continue-on-error: true
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-repository-example.git
結果は次のようになった:
Cloning into 'private-repository-example'...
Host key verification failed.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Error: Process completed with exit code 128.
webfactory/ssh-agentの説明によると、次のように~/.ssh/known_hosts
を生成するという記述がある:
This action
- ⋮
- configures known_hosts for GitHub.com.
しかし、Windowsランナーではどうやらうまくできないようだ。自分で作成してもう一度試してみよう:
- name: Perform workarounds
shell: bash
run: |
mkdir -p $HOME/.ssh
cat << EOF > $HOME/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
- name: Clone a private repository
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-repository-example.git
cat private-repository-example/README.md
- name: Clone another private repository with LFS
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
cat private-lfs-repository-example/README.md
unzip -v private-lfs-repository-example/empty.zip
結果は次のようになった:
Cloning into 'private-repository-example'...
# An Example of Private Repository
⋮
Cloning into 'private-lfs-repository-example'...
# An Example of Private Repository with LFS
Archive: private-lfs-repository-example/empty.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
0 Stored 0 0% 2023-10-06 06:44 00000000 empty
-------- ------- --- -------
0 0 0% 1 file
成功した。そして、やっと本題に入れる。
🚧
webfactory/ssh-agentを本格的に使用する場合、リポジトリをそれぞれのオーガナイゼーションにコピー(フォーク)してから使うべきである。攻撃者がwebfactoryのアカウントを乗っ取り、マーケットプレイスのアクションを悪意をもって書き換え、プライベートキーとそのプライベートリポジトリのURLを抜いてしまう、というリスクを排除するためである。これはwebfactoryに限らず、GitHub公式のアクション以外の全てのサードパーティ製アクションに当てはまることだ。
参考までに、アクションのリポジトリをプライベートにコピーして使う設定例を次に示す:
コピーしたアクションを使用するプライベートリポジトリの設定
Settings ➜ Actions ➜ General ➜ Actions permissions で “Allow your-organization, and select non-your-organization, actions and reusable workflows” を選択して保存する。
![]()
このようにプライベートリポジトリを設定すると、自分のオーガナイゼーションのアクション(コピーしたサードパーティアクション)、GitHub公式のアクション、マーケットプレイスの「verified creator」のアクションだけが利用可能になる。それ以外のサードパーティアクションを指定するとワークフロー実行時にエラーになる。
もしくは、自分のオーガナイゼーションのリポジトリにコピーする代わりに、バージョンをSHA-1ハッシュで指定しても良い。ただし、その場合でも根本的なビジネスのリスクとして、依存している公開されているアクションが突然消失する可能性(例: 開発元の作成者がリポジトリを消してしまう、など)を認識しておくべきである。さらに、プライベートなコピーにしろSHA-1ハッシュ指定にしろ、使うアクションがその依存関係により参照する別の「何か」が問題にならないかについて注意する必要もある。セキュリティ強化に関する詳細は、公式の「Using third-party actions — Security hardening for GitHub Actions」を参照。
混ぜるな危険
最後に、マーケットプレイスのactions/checkoutとwebfactory/ssh-agentを組み合わせて、プライベートリポジトリをチェックアウトしてみよう。最初に、webfactory/ssh-agentでリポジトリCをチェックアウトするための準備として、デプロイキーを次のように登録する:
steps:
- name: webfactory/ssh-agent
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: |
${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}
⋮
- name: Perform workarounds
shell: bash
run: |
mkdir -p $HOME/.ssh
cat << EOF > $HOME/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
結果は次のようになった:
Starting ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-bGHWNwHc6WoG/agent.1649
SSH_AGENT_PID=1650
Adding private key(s) to agent
Identity added: (stdin) (git@github.com:maroontress-tomohisa/private-lfs-repository-example.git)
Key(s) added:
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
Configuring deployment key(s)
Added deploy-key mapping: Use identity 'C:\Users\runneradmin/.ssh/key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9' for GitHub repository maroontress-tomohisa/private-lfs-repository-example
⋮
あとはリポジトリCをチェックアウトするだけだが、その前にactions/checkoutでリポジトリBをチェックアウトする:
- name: Checkout a private repository with actions/checkout
uses: actions/checkout@v4
with:
repository: maroontress-tomohisa/private-repository-example
ssh-key: ${{secrets.PRIVATE_REPO_DEPLOY_KEY}}
path: private-repository
結果は次のようになった:
Syncing repository: maroontress-tomohisa/private-repository-example
Getting Git version info
Copying 'C:\Users\runneradmin\.gitconfig' to 'D:\a\_temp\d52f99d9-b40f-4405-a929-b1a18384d9db\.gitconfig'
Temporarily overriding HOME='D:\a\_temp\d52f99d9-b40f-4405-a929-b1a18384d9db' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
"C:\Program Files\Git\bin\git.exe" config --global --add safe.directory D:\a\try_out_github_actions\try_out_github_actions\private-repository
Initializing the repository
Disabling automatic garbage collection
Setting up auth
Determining the default branch
Fetching the repository
Determining the checkout info
Checking out the ref
"C:\Program Files\Git\bin\git.exe" log -1 --format='%H'
'3cf9492e7ffb20ea5246a8edf1bec8d3aab293a4'
成功した。続いて、リポジトリCをクローンしてみよう:
- name: List fingerprints after actions/checkout (which fails)
continue-on-error: true
shell: bash
run: ssh-add -l
- name: Clone another private repository with LFS (which fails)
continue-on-error: true
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
cat private-lfs-repository-example/README.md
unzip -v private-lfs-repository-example/empty.zip
本来は不要だが、git clone
の前のステップではssh-add
を用いてssh-agent
の状態を確認している。結果は次のようになった:
Error connecting to agent: Bad file descriptor
Error: Process completed with exit code 2.
⋮
Cloning into 'private-lfs-repository-example'...
Load key "C:\\Users\\runneradmin/.ssh/key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9": error in libcrypto
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Error: Process completed with exit code 128.
git clone
は失敗した。その前のgit-add -l
も失敗している。actions/checkoutを実行した結果、バックグラウンドで実行中のssh-agent
の状態が壊れた(プロセスは存在しているが、プロセス間通信のステートがおかしくなった)のだろう。
とりあえず、次のようにssh-agent
を再起動してから、再度クローンしてみよう:
- name: Perform more workarounds (kill ssh-agent to restart)
shell: bash
run: |
eval `ssh-agent -k`
# The following lines are placebos (because we can't unset env.*):
echo SSH_AUTH_SOCK= >> "$GITHUB_ENV"
echo SSH_AGENT_PID= >> "$GITHUB_ENV"
# See https://github.com/actions/runner/issues/1126
- name: webfactory/ssh-agent
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: |
${{secrets.PRIVATE_LFS_REPO_DEPLOY_KEY}}
- name: List fingerprints (after restarting ssh-agent)
shell: bash
run: ssh-add -l
- name: Clone another private repository with LFS
shell: bash
run: |
git clone --depth 1 git@github.com:maroontress-tomohisa/private-lfs-repository-example.git
cat private-lfs-repository-example/README.md
unzip -v private-lfs-repository-example/empty.zip
結果は次のようになった:
unset SSH_AUTH_SOCK;
unset SSH_AGENT_PID;
echo Agent pid 1650 killed;
⋮
Starting ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-nR7bVfNdAwqI/agent.766
SSH_AGENT_PID=767
Adding private key(s) to agent
Identity added: (stdin) (git@github.com:maroontress-tomohisa/private-lfs-repository-example.git)
Key(s) added:
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
Configuring deployment key(s)
Added deploy-key mapping: Use identity 'C:\Users\runneradmin/.ssh/key-aabbaf196b10644a69c23360df933575c9e9d496cfc251dabf9b22ee13d7bea9' for GitHub repository maroontress-tomohisa/private-lfs-repository-example
⋮
3072 SHA256:61EFfTJR56r9rX3u9EGG/HrvPcejWJTR0VLssfIpBzg git@github.com:maroontress-tomohisa/private-lfs-repository-example.git (RSA)
⋮
Cloning into 'private-lfs-repository-example'...
# An Example of Private Repository with LFS
Archive: private-lfs-repository-example/empty.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
0 Stored 0 0% 2023-10-06 06:44 00000000 empty
-------- ------- --- -------
0 0 0% 1 file
ssh-agent
を再起動すると、確かに全てのエラーは消えた。さて、なぜこのようなことが起きるのか調べてみよう。
actions/checkoutが何故起動済みのssh-agent
を「壊す」のだろうか。actions/checkoutのログを見てみると、次の行に気づく:
▾Setting up auth
⋮
"C:\Program Files\Git\bin\git.exe" config --local core.sshCommand "\"C:\Windows\System32\OpenSSH\ssh.exe\" -i \"$RUNNER_TEMP/455b31bf-8bfa-4d87-bb2e-3d92b700ba9a\" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o \"UserKnownHostsFile=$RUNNER_TEMP/455b31bf-8bfa-4d87-bb2e-3d92b700ba9a_known_hosts\""
なんと、actions/checkoutは、デプロイキーを指定した場合、SSHにC:\
を使用するようだ。Windowsにはssh
の実装がいくつかあるが、OpenSSH版の実装とGit for Windows版の実装は互換性が無い。OpenSSHのssh
とGit for Windowsのssh-agent
を一緒に使うことはできない。互換性が無い理由はちゃんと調べてないが、おそらくプロセス間通信で使うプロトコルが違うのだろう(だとすると、何故同じ環境変数を使っているのだろう? よく経緯はわからないが、まともではないことは間違いない)。
まとめると、Windowsランナーの場合:
bash
からssh-agent
を起動すると、Git for Windowsの実装の実行ファイルがバックグラウンドで動作する(ように環境変数PATH
が設定されている)。- actions/checkoutはオプションでデプロイキーを指定すると、
git
がSSHの実装としてC:\
を使用するようになる。Windows\ System32\ OpenSSH\ ssh.exe - OpenSSHの
ssh.exe
は環境変数SSH_AUTH_SOCK
とSSH_AGENT_PID
の値を見て、OpenSSH版ssh-agent
が実行中と判断して、プロセス間通信を開始する。しかし、起動しているのはGit for Windows版のエージェントなので、状態が壊れる(多分)。
次のような回避方法がある:
- 先にactions/checkout、その後に
ssh-agent
(またはwebfactory/ssh-agent)という順番になるように徹底する。 ssh-agent
はOpenSSH版(C:\
)を使う。Windows\ System32\ OpenSSH\ ssh-agent.exe - 複数のデプロイキーを使うだけなら、
ssh-agent
を起動しないようにする。
最初の二つの選択肢は、別の混乱を招く可能性が高いので、やめておいた方が良い。そもそも、webfactory/ssh-agentは、その名前が表すように、ssh-agent
を使うためのものだ。だから、複数のデプロイキーを扱うためだけに使うのは悪用なのではないだろうか。パッと探しただけだと、マーケットプレイスには複数のデプロイキーを扱うだけのアクションは見つからなかった。だとすると、アクションを自作(して、公開)するのも良さそうだ。