GitHubでPull Requestを作成することはもはや当たり前のように行われています。 最近は多くの場合、対象リポジトリをcloneした後でfeature branchを作成し、当該のfeature branchを用いてPull Requestを作成するという流れが一般的になったように思われますが、リポジトリをforkし、fork元へPull Requestを作成するという流れもまだまだ多くの場面で利用されています。
また、Pull Requestに反応する形でCIジョブが実行され、その結果をPull Requestにコメントするという運用も多くみられるようになりました。僕も、テストカバレッジをPull Requestにコメントしたいんだわ。
TL;DR
パブリックリポジトリでカバレッジレポートをPull Requestにコメントする場合、次のようなワークフローを組むのが良いっぽい。
pull_request
イベントでテストを実行して、カバレッジレポートをArtifactとしてアップロードする- その後に
workflow_run
イベントを起動させ、カバレッジレポートをダウンロードし、その内容をPull Requestにコメントする
問題
この両者を組み合わせるとき、つまりfork元へPull Requestを作成し、それをトリガとしてコメントを投稿するCIジョブが実行される場合に問題が発生することがあります。Pull Requestへのコメントができないんだわ。
Couldn't write a comment to the pull request. Please make sure your job has the permission 'pull-request: write'.
Original Error was: [HttpError] - Resource not accessible by integration
これは、GitHub Actionsが次のようにpull_request
イベントをトリガとしてCIジョブを実行している場合に発生します。
on: pull_request: types: [opened, synchronize]
原因
pull_request
イベントをトリガとしていることを前提として、fork先のリポジトリからfork元リポジトリへPull Requestを作成した場合、GITHUB_TOKEN
の権限は読み取り専用になります。
読み取り専用なのだから当然、Pull Requestにコメントを投稿することはできません。
これはなんでそうなっているかというとセキュリティ上の設計によるものです。悪意のある第三者が、悪意のあるコードをCIジョブで実行させたときの懸念をできるだけ抑えたいですもんね。
Due to the dangers inherent to automatic processing of PRs, GitHub’s standard pull_request workflow trigger by default prevents write permissions and secrets access to the target repository.
強制的に書き込み権限を与えれば解決するんだけど、それはセキュリティ上のリスクを伴います。 GitHub Actionsのワークフローファイルに悪意のあるコードを入れたら、それがCIジョブで実行されるわけなので、書き込み権限を如何様にも使えます。悪意のあるコメントでリポジトリを埋めることも、リポジトリ自体に悪さすることも可能ですね。
それならpull_request_target
イベントを使えば…
いくつか回避策はあります。そのうちで代表的なのはpull_request
ではなくpull_request_target
イベントをトリガとしてCIジョブを実行することでしょう。この場合、利用されるGITHUB_TOKENには書き込み権限も付与されます。
pull_request_target
はfork元リポジトリのコンテキストで動作するので、Pull Requestを送ってきた人の任意のコードが実行されないというメリットもある(というか、こちらの方がpull_request_target
イベントが導入された背景であり、その結果として書き込み権限を付与できるようになった順という理解)。
pull_request_target イベントによってワークフローがトリガーされると、パブリック フォークからトリガーされた場合でも、GITHUB_TOKEN にはリポジトリの読み取り/書き込みアクセス許可が付与されます。
であれば、pull_request_target
イベントを使えばいいよね、という話になりますが、そうは問屋が下さない。先の
pull_request_target
はfork元リポジトリのコンテキストで動作する
という記述は、CIでチェックアウトされ実行されるコードが、(修正を含まない)fork元リポジトリのコードであるということを意味しています。わかりづらいと思うので、こちらをみていただけると良いと思う。
要するに、Pull Requestに含まれる修正を含むコードはチェックアウトされないので、それに対してテストを実行しても意味がない。
workflow_runの登場
僕の目的に対してpull_request_target
イベントは使えません。
pull_request
イベントをトリガとするCIジョブでテストを実行し、カバレッジレポートを取得する。ここまでは良いのですが、コメントができない。書き込み権限を付与してコメントすることは可能ですが、そうすると悪意のあるユーザが悪意のあるコード込みでPull Requestを送ってきた時に、リポジトリにヤバい内容が混入したり、ヤバい内容がコメントされたりするリスクが出てくる。
そこで、pull_request
イベントでテストを実行し、カバレッジレポートをArtifactとしてアップロードする。可能な限り、ここでコードの安全性も確認する。その後にworkflow_run
イベントを起動(連鎖)させ、カバレッジレポートをダウンロードし、その内容をPull Requestにコメントするという方法を取れば良い。
workflow_run
イベントにおいては、もはやPull Requestに含まれるコードとは隔離されています。このため、GITHUB_TOKEN
に書き込み権限を与えてもリスクは低い。
感想
今までずっと、特段の考慮をしないまま書き込み権限を与えてきたことが多いので、なるほどなと思いました。 余談だけど、リポジトリの設定には、fork先からのPull Requestに対してCIジョブを実行させるか否かの承認設定なんてものもあるので、別観点でのセーフガードになります。