🐧

GitHub Actions でスクリプト等の改竄を防ぐ

2024/02/10に公開

以前 GitHub Actions で pull_request_target event を使って workflow の改竄を防ぎセキュリティを改善する方法について書きました。

https://zenn.dev/shunsuke_suzuki/articles/secure-github-actions-by-pull-request-target

ただこの記事でも書きましたが、 workflow を改竄を防ぐだけではなく、 workflow から実行しているスクリプトなどの改竄を防ぐ必要があります。

ただし、 workflow の改竄が出来ないと言っても、 feature branch のスクリプトなどを workflow から実行している場合、そのスクリプトを改竄することで任意のコマンドが実行できてしまうケースもありますので、その点には注意が必要です。
スクリプトや action を実行したい場合、別のリポジトリで管理するか、 default branch などから checkout したものを実行する必要があるでしょう。

本記事ではこのスクリプトなどの改竄を防ぐ方法について書きたいと思います。
上記の記事を前提としているので、上記の記事を読んでいない方はまずそちらを読んでください。
workflow は pull_request_target によって実行しており、よって workflow 自体は改竄できないものとします。

「スクリプトなど」が指すものですが、シェルスクリプトや Python, Ruby, Go と言ったプログラミング言語で書かれたスクリプト、また Makefile やツールの設定ファイルを指します。
ツールによっては設定ファイルを改竄することで危険なコードを実行できるものもあるので、そういった設定ファイルの改竄も防ぐ必要があるでしょう。

スクリプトなどの改竄を防ぐ方法

スクリプトなどの改竄を防ぐ方法としては大きく 2 つあるでしょう。

  1. スクリプトの内容を workflow に移し、改竄できないようにする
  2. スクリプトを改竄できない信頼できる場所で管理し、そこから取得するようにする

単一のシェルスクリプトであれば 1 も可能でしょう。
run step にシェルスクリプトをコピペできます。
またシェルスクリプト以外でも無理やりスクリプトの内容を workflow にコピペし、それをファイルに出力するようにすれば改竄できなくなります。
ただメンテナンス性がかなり悪いでしょうし、ローカル開発でも使いたいようなスクリプトの場合不向きでしょう。
なので主に 2 について考えます。

「スクリプトを改竄できない信頼できる場所」ですが、直接 commit を push することができずレビューが必須な branch (主に default branch) であれば信頼できると言って良いでしょう(もちろん適切に branch protection rule や CODEOWNERS を設定してください)。

この場合も主に二つあります。

  1. 同じリポジトリの default branch
  2. 別リポジトリの default branch

スクリプトを別リポジトリで管理するか否か

同じリポジトリで管理するか別リポジトリで管理するかは一長一短ありますが、本記事では同じリポジトリで管理する方法に焦点を当てます。
なぜかというと導入が楽だからです。
リポジトリを分ける方式の場合、以下のようなことをやる必要があります。

  • リポジトリを別に作成
  • リポジトリの設定を適切に行う
  • 別リポジトリへのアクセス権限を適切に付与
  • スクリプトなどを別リポジトリに移す
  • ローカル開発でスクリプトを使用する場合、各開発者がスムーズにスクリプトを取得できるようにし、案内する
    • ローカル開発で使うスクリプトが適切に更新されるようにする

中々面倒だと思います。
面倒が故に、セキュリティ的に重要であるにも関わらず後回しにされてしまうこともあるでしょう。
今回紹介する同じリポジトリで管理する方式は Action を実行するだけでとりあえず出来てしまうので、導入が容易であり素早く問題に対応できるでしょう。

スクリプトを同じリポジトリで管理して CI 実行時に default branch と同期させる

指定したファイル・ディレクトリを指定したブランチ (default branch を想定) と同期するシンプルな Action を作りました。

https://github.com/suzuki-shunsuke/simple-sync-action

action.yaml を見てもらえればわかる通り、大したことはしていません。

  1. リポジトリを指定したブランチで checkout
  2. 指定したファイル・ディレクトリを rsync で同期
  3. checkout したリポジトリを削除

これだけのことですが、 CI でスクリプトを実行する前に default branch とスクリプトを同期することで、 review, approve なしで危険なコードが実行されることを防ぐことができます。

このアプローチの欠点は、やはり pull request をマージする前に実際にスクリプトを実行して変更をテストすることが難しいことです。
これはそもそも改竄を防ぐことが目的なので仕方ないことではあります。
やるとしたら、前述の記事でも説明した通り、 secret や OIDC による強い権限を持った token を取得できないようにした上で pull_request event などを使って テストを実施する必要があるでしょう。

さいごに

前回の記事の補足として、 GitHub Actions でスクリプトの改竄を防ぐ方法を紹介しました。
今回の仕組みは workflow 自体を改竄できると意味がないので、 pull_request_target や workflow_run などと併用する必要があります。
CI で強めの secret を渡してスクリプトなどを実行する場合がある必要がある際に導入すると良いでしょう。
逆に強めの secret を必要としないようなケースでは導入する必要もないでしょう。

Discussion