[GitHub Actions] Secrets や書き込み権限が必要な Workflow を Dependabot からも使えるようにする
先月、とある GitHub リポジトリで使っている Dependabot が送ってくる Pull request の CI が軒並み落ちるようになり、状況を見てみるとどうやらリポジトリの Secrets がうまく Workflow のジョブに渡せていない事が原因の様でした。
公式ブログによると 2021 年 3 月 1 日以降、 Dependabot が特定のいくつかのトリガーにより開始した Workflow (※) では Secrets が読み込めなくなり、また GITHUB_TOKEN についても書き込み権限が剥奪され、リポジトリへの書き込み操作ができなくなってしまった様です。
具体的には、 Dependabot からの Pull request は書き込み権限を有さないユーザーからの物と同様 (Fork 経由と同様) になっている様で、元々このポリシーについては去年の記事 Keeping your GitHub Actions and workflows secure: Preventing pwn requests でも言及されています。
またこの件は、 Dependabot の公式リポジトリでも Issue が立てられて議論されています。
今回はこの問題に対処した方法を紹介します。
※記事にもある通り、 pull_request
, pull_request_review
, pull_request_review_comment
, push
が対象
免責
今回の記事ではセキュリティに関わる内容を取り扱っています。今回私が使った対処法はあくまで一例であり、セキュリティが完璧に担保される事は保証していませんので予めご了承の上、同様の手法を使う場合は自己責任にてよろしくお願い致します。
前提
さて、次節より具体的な解決法を記しますがその前にいくつか前提をお知らせしたいと思います。
- リポジトリ: github.com にホストされているプライベートリポジトリ
- Dependabot: GitHub に統合された後の、github.com 上で動作している bot
- 試していないけど dependabot.com 版はこれまで通り何もしなくても動いていると予想
- Workflow について: ジョブ内において、予めリポジトリに登録していた AWS のクレデンシャルと、 Slack の Webhook URL を必要とし、これを Secret 経由で渡していた
どのように解決したか
先述した記事にも書いてあるのですが、具体的には pull_request_target
を使って解決しました。
今回はサンプルリポジトリ (Public) を使って説明したいと思います。
https://github.com/issei-m/dependabot-secrets-test
今回、改修後の Workflow の設定は以下のようになっています:
on:
push:
branches: [main]
pull_request:
branches: [main]
pull_request_target:
branches: [main]
jobs:
main:
name: Run main jobs
runs-on: ubuntu-latest
# push, pull_request は Dependabot 以外のユーザーのみ、
# pull_request_target は Dependabot のみが実行できる
if: |
(github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]') ||
(github.event_name != 'pull_request_target' && github.actor != 'dependabot[bot]')
steps:
- name: Checkout
if: ${{ github.event_name != 'pull_request_target' }}
uses: actions/checkout@v2
# pull_request_target 駆動の場合、コンテキストは Pull request のターゲットブランチ (main ブランチ) になるので、
# 当該 Pull request の HEAD コミットを明示的に指定しないと変更内容に対する CI を実行できない
- name: Checkout PR
if: ${{ github.event_name == 'pull_request_target' }}
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
# ...
※ dependabot[bot]
ユーザーは Dependabot が所有している bot ユーザーですので、サンプルリポジトリ上では issei-m2
が bot の代わりとして定義されていますので読み替えて下さい。
実際の挙動を見てみましょう。
まずは、書き込み権限を有するユーザー (多くのプライベートリポジトリの場合、開発者は全員これに該当すると思われる) からの Pull request:
[General] Pull request from a member who has writeable permission (or admin)
Pull request の description にも記載していますが、この Workflow では pull_requesss
トリガー経由でのみジョブが実行され、 pull_request_target
経由の場合は Skip されています:
また、実行された Workflow では SENSITIVE_INFO
Secret が適切に渡っており、ジョブで動かしている run.js が内容を出力できてる事が分かります。これは今まで通りで特に問題がありません:
※余談ですが、通常ジョブに渡された Secret の内容は秘匿性保護の為コンソールには出力されないようになっていますが、単にコンソールに出力された内容から Secret の内容を空欄に置換しているだけの様なので、 run.js のようにすると内容を出力する事が可能です。
次に、 Dependabot からの Pull request (を模したもの): Pull request from a Dependabot
先程と異なり、 pull_request_target
経由のジョブのみ実行されており、反対に pull_request
経由での物は Skip となっています:
しかし Workflow では先程と同様、 SENSITIVE_INFO
が渡ってきている事が分かります:
これでやりたかった事は実現できました。
最後に、読み込み権限しか持たないユーザーからの Pull request です。これは Fork からの Pull request を含みます: Pull request from a member who has read-only permission
最初の Action 同様、 pull_request
経由の Workflow のみジョブが実行されており、そこでは SENSITIVE_INFO
は渡ってきていない事が分かります:
また、上に記述した YAML の設定にもコメントで記載したとおり、 pull_request_target
駆動の Workflow は Pull request のターゲットブランチでのコンテキストで実行されます。これは、 Workflow 自体の設定ファイルもこのブランチ上の物が使われます。従って、仮にこれらのユーザーに設定ファイルを (Dependabot ユーザーでなくても起動できる様) 書き換えられたとしても、 pull_request
駆動の Workflow しか起動できないので、 secret は守られます。
プライベートリポジトリではあまり使わないとは思いますが、このプラクティスはオープンソースの方で役立つかもしれません。
注意点
さて、今回の方法は Workflow を起動した人物によって挙動を変えています。つまり、 Dependabot が作った Pull request 用の Workflow を、Actions メニュー等で他者が再起動した場合、 1 個目や 3 個目の様に pull_request
駆動の物だけが実行されます。この場合でも Workflow には Secret が渡ってこないので注意が必要です。
幸い、 Dependabot には @dependabot recreate
と言うコマンドが有り、この内容を Pull request で書き込み権限を有するユーザーがコメントすれば新しい Pull request が作られるのでこの方法で Workflow を再起動できます。
以上、 Secrets や書き込み権限が必要な Workflow を Dependabot でも使える方法でした。
おまけ: Dependabot の Pull request の CI がパスした時に自動でマージする
これも紹介した Issue に書かれていますが、 workflow_run
トリガーを使うと、 Dependabot が CI をパスした時に自動的にマージを行う事ができます。具体的には以下のような Workflow を別途用意します:
name: Dependabot Auto-merge
on:
workflow_run:
workflows:
- Main # CI を実行しているワークフローの名前を記載する
types:
- completed
jobs:
auto_merge:
runs-on: ubuntu-latest
if: |
github.actor == 'dependabot[bot]' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Automerge Dependabot
uses: actions/github-script@v4.0.2
with:
# 予め WRITABLE_GITHUB_TOKEN Secret に書き込み権限を持つユーザーのアクセストークンを登録しておく
github-token: ${{ secrets.WRITABLE_GITHUB_TOKEN }}
script: |
const output = `@dependabot squash and merge`;
github.issues.createComment({
issue_number: ${{ github.event.workflow_run.pull_requests[0].number }},
owner: "${{ github.event.repository.owner.login }}",
repo: "${{ github.event.repository.name }}",
body: output
})
on.workflow_run.workflows[0]
には、 CI で使っている Workflow の name
で指定している名前を指定します。
仕組みは単純で、単に Dependabot が作った Pull request の CI がパスした際に、その Pull request に @dependabot squash and merge
とコメントするだけです。これは Dependabot のコマンドの 1 つで、内容の通り Pull request のマージを指示します:
尚、この workflow_run
による Workflow はデフォルトブランチ (main) のコンテキストで実行される為安全なので、 Secrets が有効かつ、 GITHUB_TOKEN
も書き込み権限になっています。
但し、 Dependabot へのマージ指示コマンドは、コメント書き込み者が当該リポジトリに push する権限を持っていなければならず、渡される GITHUB_TOKEN
はその権限を持たないので、 actions/github-script
に渡す github-token
には別途用意した書き込み権限を持つユーザーのアクセストークンを Secret 経由で渡す必要があります:
コメントを残す