それでは GitHub Actions Best Practice 2025 というタイトルで発表させていただきます。よろしくお願いします。
最初に自己紹介ですが、私 freee 株式会社で SRE をやっております、鈴木と申します。
主に zenn で色々ブログを書いています。
GitHub Actions に関するブログも書いているので是非御覧ください。
GitHub Actions やコマンドラインツールなど、趣味で様々な OSS の開発をしています。
本日の登壇の中でも幾つか紹介させていただきます。
本日は GitHub Actions のベストプラクティス、特にセキュリティ周りの話をしたいと思います。
まず一般的なセキュリティのプラクティスの話をした後に、自分の経験に基づく応用的な話をします。
その後、セキュリティ以外の DX 周りの話も若干します。
まず GitHub の Organization の GitHub Actions token に関する設定です。
GitHub Actions token を用いた PR の作成や approve を禁止しましょう。
また、 default で Read-only にしましょう。
GitHub Actions token で PR の作成ができると、
PR を作って悪意のあるコードを追加して自分で approve してマージするといったことが簡単に出来てしまいます。
なので禁止しましょう。
次に default branch の Branch Rulesets を適切に設定しましょう。
PR を必須にしたり、 code owner のレビューを必須にしたり、
最新のコミットに対するレビューを必須にしたり、 CI がパスしていることを必須にしましょう。
GitHub Actions token の permissions を明示的に設定し、必要最小限にしましょう。
何も権限がいらない場合は空の map を設定しましょう。
GitHub App を使って Access token を生成する場合も repositories や permissions を必要最小限にしましょう。
GitHub App から Access token を生成する公式の App は permissions が設定できないため、こちらの github-app-token という action がオススメです。
secrets の scope を最小限にしましょう。
workflow や job の環境変数として渡すのではなく、 secret が必要な step にだけ渡すようにしましょう。
actions/checkout の persist-credentials を false にしましょう。
persist-credentials が true だとリポジトリの checkout に使われる access token や SSH key が Git の設定に永続化されます。
そうすると、後続の任意の step が token にアクセスでき、セキュリティ的によろしくありません。
しかも、 persist-credentials はデフォルトで true です。
なので、明示的に false にしましょう。
disable-checkout-persist-credentials というツールを使うと workflow の修正を自動化出来ます。
persit-credentials を false にした状態で `git pull` や push をしたい場合、 `gh auth setup-git` を使う方法と、 GitHub API を使う方法の 2 つ方法があります。
gh auth setup-git は GitHub CLI を使って Git の認証をするようにするコマンドです。
gh auth setup-git を実行しておくと環境変数で access token を渡すことで git の認証ができるようになります。
必要な step にだけ access token を渡せば良いのでより安全になります。
ghcp や commit-action というツールを使って GitHub API で commit を生成・ push することが出来ます。
GitHub App の access token を使うことで GPG key なしでコミットに署名が出来ます。
action のバージョンを full commit hash で固定しましょう。
Git の tag は後から作り直すことができるので危険ですし、メジャーバージョンしか指定していないとバージョンが勝手に変わって何もしてないのに CI が壊れることがあります。
pinact というツールを使うと workflow の修正を自動化出来ます。
pinact は action の update にも対応しており、最新に update しつつバージョンを固定することが出来ます。
これは手元で workflow 書いてるときにも便利です。
action の最新バージョンが幾つかなんて一々覚えてないですし調べるのも面倒ですが、
とりあえず v1 みたいな適当なバージョンをしておいて workflow をざっと書いて、最後に pinact を実行すれば最新に update しつつバージョンを固定出来ます。
pinact-action という action を使うと CI で自動でバージョンを固定出来ます。
job の timeout-minutes を明示的に設定しましょう。
timeout-minutes は job のタイムアウトで、デフォルトで 360 分ですが、これは長すぎるので job ごとに適切に設定しましょう。
ghatm というツールを使うと workflow の修正を自動化出来ます。
multi-gitter というツールを使うと、 pinact や ghatm, disable-checkout-persist-credentials というツールを GitHub Organizatino の全リポジトリにまとめて実行して PR 作成しマージして修正する事ができます。
ghalint や lintnet という linter を使ってここまで紹介した best practice が守られているかチェックすることが出来ます。
CI でこれらのツールを実行することで best practice を強制することが出来ます。
GitHub Actions の Linter といえば actionlint も便利です。
ghalint が主に security 的なベストプラクティスのチェックに焦点を当てているのに対し、
actionlint は主に syntax check に焦点を当てています。
actionlint は shellcheck を実行してシェルスクリプトの潜在的なバグを見つけることが出来ます。
また reviewdog と連携して 分かりやすくエラーをレポート出来ます。
workflow や job を如何に構造化するかお話します。
理想的には、全ての変更は CI でテストされるべきであり、全てのテストがパスするべきです。
つまり、全ての job が pass するべきです。
しかし、 test や build, format といった様々な job を実行する場合、それら全ての job を Branch Rulesets の `Required Checks` に追加するのは大変です。
job を追加・リネーム・削除するたびに Branch Rulesets の修正が必要になりますし、修正を忘れると job が失敗しているのに PR がマージできるようになってしまいます。
そこで、まず `pull_request` workflow をなるべくひとつにまとめましょう。
workflow を分けると、 workflow を追加したりするたびに Required Checks を更新する必要が出てきてしまいます。
複数の workflow で required check を共有するのはやめましょう。
例えば workflow A, B で status-check という required Check を共有する場合、
B の test を実行中に A の status-check が完了すると PR がマージできるようになってしまいます。
auto-merge が有効になっていると B を待たずに PR がマージされてしまいます。
Workflow の Path filter を使うと、その Workflow の job は実行されない場合があるので Required Checks に追加できなくなってしまいます。
そのため、 dorny/paths-filter のような action を使い、 job level で skip するようにしましょう。
また、 dorny/paths-filter を使えば job 単位でフィルタを設定できるため、 filter の異なる workflow をマージする場合でも filter が使えます。
workflow ごとに Required Check を 1 つだけ追加しましょう。
このように、一つの workflow で色々な job を実行しつつ、 Required Check にはこの status-check という job だけ追加します。
そうすると workflow に job を追加したりしても Branch Rulesets を修正する必要がなくなります。
この場合、 status-check という job は他の全ての job が成功したら成功し、そうでなければ失敗しなければなりません。
このような job をどのように定義したら良いでしょうか?
まず駄目なパターンですが、全ての job を status-check の needs に追加し、どれかが失敗したら status-check も失敗するようにします。
このパターンには幾つか問題があります。
まず needs をメンテしないといけませんし、 needs に job を追加するのを忘れると job が失敗しているのに PR がマージできてしまいます。
更に特定の job だけリトライすることで、他の job が失敗したままでも status-check を skip させることができる GitHub Actions の bug のような挙動があります。
そこでこの問題を解決するために、 workflow_call を使って新しく workflow を作成し、 pull_request workflow の全 job を新しい workflow に移します。
そして pull_request workflow から呼び出すようにします。
そして pull_request workflow に status-check job を追加し、 workflow_call が失敗したら status-check が失敗するようにします。
こうすると needs をメンテする必要がなくなりますし、 job の status 関連のバグもなくなります。
PR をレビューなしでマージするのを防ぎましょう。
自分以外が作成した PR に自分で commit を追加して自分で approve するであるとか、
自分の PR を Machine User や Bot を使って approve するみたいな不正を防ぎましょう。
まず既に説明した通り Branch Rulesets を設定しましょう。
codeowner の approve を必須にすることで Bot を使った approve を防ぐことが出来ます。
そして pull_request_review や merge_group event を使って PR のコミッターや approver のバリデーションをしましょう。
PR にコミットした人以外が approve しているか、あるいは複数人が approve していれば良しとし、そうでなければ PR をマージできないようにします。
このように pull_request_review event で workflow を実行し、この job を Required Check に追加します。
deny-self-approve-action という action を使って validation することが出来ます。
CI によってコードを自動で修正すると便利です。
その際、先述の通り ghcp や commit-action を使うと簡単に署名付きの commit を生成できます。
ここで主に OSS のような public repository の話をします。
public repository では fork repository から PR が作成されますが、 fork からの pull request で実行される workflow では write 権限がありませんし、 secret にもアクセスできません。
そのため、 commit を push して自動で修正することも難しいです。
pull_request_target を使うとできるようになりますが、悪意のあるコードが fork からの PR 経由で実行される可能性があり、非常に危険です。
そこで autofix.ci という GitHub App 及び Action を使うとこの問題を解決できます。
Workflow に権限を与えず GitHub App 経由でコードを修正することで fork からの PR でも安全にコードを修正できます。
OSS のメンテをされている方には非常にお勧めの App です。
使い方も簡単で、 App をインストールしたうえで autofix.ci という名前の workflow を追加し、
workflow でコードを修正したうえで最後に専用の action を実行するだけです。
autofix.ci を仕事で使うのは難しいけど、 autofix.ci みたいに簡単にコードを修正したい場合、 commit-action が便利です。
autofix.ci のように job の最後に commit-action を実行するだけでコードを修正できます。
次に CI のパフォーマンスの可視化の話です。
パフォーマンスの改善を行うにはまず可視化しないといけません。
GitHub Actions のモニタリングには GitHub Actions Usage Metrics や Performance Metrics が使えます。
このように workflow や job がどのくらい実行されてどのくらい時間がかかってるか見ることが出来ます。
GitHub Actions Usage Metrics や Performance Metrics は特別なセットアップ無しで簡単に使えますし、大雑把に状況を把握するのには便利です。
しかし、グラフで可視化したりボトルネックを詳細に調べたりするのには向いていません。
そこで CIAnalyzer という OSS を使うと、データを BigQuery に集めて LookerStudio で可視化することが出来ます。
ダッシュボードを自由にカスタマイズできますし、 workflow から job, step というふうにボトルネックをドリルダウンして調べることが出来ます。
CI の結果をよりわかりやすくし、 CI が失敗した場合にも開発者が困らないようにし、より効率よく問題を解決できるようにしましょう。
github-comment というツールを使うと、コマンドが失敗した際に失敗したコマンドとその出力をコメントすることが出来ます。
そうすると CI のログを見なくてもどのコマンドが失敗したのか分かるようになります。
また CI が失敗したときにどうすればいいのかガイドをコメントして開発者が困らないようにすることも出来ます。
単に CI を失敗させるだけでなく、このように開発者フレンドリーにすることでお互い幸せになれます。
最後にまとめです。
本日は GitHub Actions の主にセキュリティ関連のベストプラクティスについてお話しました。
基本的で良く知られたプラクティスから、自分の経験に基づく応用的なプラクティスについてもお話しました。
また、これらのプラクティスを実践するための様々なツールを紹介しました。
今日お話したプラクティスをいきなり全て実践するのは難しいかもしれませんが、まず action のバージョンを full commit hash で固定しましょうみたいな一般的なプラクティスから実践していきましょう。
また、最後に本日は時間の関係で割愛したのですが、
GitHub Actions の Workflow や workflow で実行するスクリプトの改竄を防ぐ仕組みについてもブログを書いています。
ご興味のある方は読んでみてください。