はじめに
昨今、パッケージなどのエコシステムをターゲットとしたサプライチェーン攻撃が増加しています。
各種プログラミング言語向けのパッケージマネージャーやレジストリにおいては、インストールするパッケージのバージョンを固定したり、チェックサムを検証したりすることにより、サプライチェーン攻撃被害のリスクを軽減する仕組みが導入されています。
もちろんGitHub Actionsにおいても、サードパーティー製のワークフローを利用する場合に、サプライチェーン攻撃の被害を受けるリスクが生じますが、このような仕組みを導入するには少々作業が必要になります。
そこで本記事では、pinactを利用して簡単にGitHub Actionsにおけるサプライチェーン攻撃被害のリスクを軽減する方法を紹介します。なお、本記事で紹介する内容は、弊社で最近実施されたものです。
なぜ実施したのか?
弊社の一部リポジトリで利用しているtj-actions/changed-files
において、サプライチェーン攻撃が発生しました:
具体的には、tj-actions/changed-files
の運用のためのボットで利用していたPATが流出し、それを悪用して攻撃者による悪意のあるコミットが紛れ込み、各タグなども改竄されてしまったようです。
時差の関係もあって運よく被害は発生しなかったのですが、仮に被害が発生した際の影響は大きなものになりえます。今後のリスク軽減のために、以下で紹介する対策を実施することにしました。
実施したこと
pinact
の導入
pinact
とは?
GitHub Actionsのワークフローにおける各種依存アクションのバージョンを固定してくれるCLIツールです。
使い方
pinact
はHomebrewなどで導入可能です。
$ brew install pinact
導入したら、以下を実行します。
$ pinact run
すると、リポジトリ内の各種ワークフローにおける依存アクションを検出し、以下のようにバージョンの定義を書き換えてくれます。
steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 with: node-version: '22' - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
このように、pinact
によってサードパーティー製ワークフローのコミットハッシュによる参照を強制することで、意図せず改竄されてしまったバージョンのワークフローが実行されるリスクを軽減することができます。
pinact
の導入に関する選択肢
今回、pinact
の導入に当たって実現したいことは以下の内容です。
- CI (GitHub Actions) で
pinact run --check
を実行したい。pinact run --check
を実行すると、GitHub Actionsのワークフローにおいてバージョンが固定されていない依存アクションを検出してくれます。
- 目的はサプライチェーン攻撃へのリスクを低下させることなので、できるだけ安全な方法で
pinact
を導入したい。
その上で、pinact
を導入する方法としては以下のあたりが選択肢として考えられそうです。
- Homebrew
- メリット
- 公式からHomebrew/actionsが提供されており、GitHub Actionsからの利用が容易である。
- pinactによって推奨されるインストール方法の一つである。
- 使い慣れているユーザーが比較的多いと思われる。
- デメリット
- インストールするツールのバージョンを固定できない。
- メリット
aqua
- メリット
- 公式から
aquaproj/aqua-installer
が提供されており、GitHub Actionsからの利用が容易である。 - pinactによって推奨されるインストール方法の一つである。
- 依存ツールのバージョンの固定が可能である(後述)。
aqua-checksums.json
によるチェックサムの管理・検証が可能である。
- 公式から
- デメリット
- Homebrewや後述する
mise
と比較すると、まだ使用例は多くないと思われる。
- Homebrewや後述する
- メリット
mise
- メリット
- 作者によってjdx/mise-actionが提供されており、GitHub Actionsからの利用が容易である。
aqua
バックエンドを利用すればpinact
を導入可能である。aqua
と同様に、依存ツールのバージョンの固定が可能である。- 今回導入予定の
pinact
以外に、Node.jsなどのさまざまなランタイムのバージョン管理もできる。
- デメリット
aquaバックエンドは、実験的サポートの段階である(※)。
- メリット
今回の目的とメリットを踏まえると、aqua
かmise
がよさそうです。mise
のaqua
バックエンドはまだ実験的サポート(※) であったことと、aqua-checksums.json
によるチェックサム管理の仕組みがあることなどから、本記事ではaqua
を試してみることにしました。
※ ⚠️ 記事の執筆を開始した当初は、mise
のaqua
バックエンドはまだ実験的サポートの段階でしたが、本記事の公開時点ではすでに実験的という表記は削除されています (af36cfd)。 今回はaqua
を採用しましたが、mise
も選択肢として有望だと思います。
aqua
とは
CLIツール向けのパッケージマネージャーで、サプライチェーン攻撃に対する対策が強く意識されているのが特徴です。
- slsa-verifierによりパッケージが検証される(SLSAはサプライチェーン攻撃への保護を目的としたフレームワーク・仕様です)
- チェックサムが検証される
- プロジェクトごとに依存ツールのバージョンが固定される
導入方法
ローカルに導入する際は、Homebrewや公式のインストーラーなどで導入可能です。
$ brew install aqua
GitHub Actionsで
aqua
を利用したい場合は、aquaproj/aqua-installer
を使用します。
- uses: aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with: aqua_version: 'v2.46.0' skip_install_aqua: "true" - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: '~/.local/share/aquaproj-aqua' key: v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys: | v2-aqua-installer-${{runner.os}}-${{runner.arch}}-
aqua
の設定
まず、設定ファイルであるaqua.yaml
を生成します。
$ aqua init
(推奨)aqua.yaml
を生成したら、まずチェックサムの検証を有効化することを推奨します。
# aqua.yaml checksum: # See https://github.com/aquaproj/aquaproj.github.io/blob/4709985f3f10c1c257fc812d9f791ab595cad266/docs/reference/config/checksum.md#require_checksum for details enabled: true require_checksum: true # 以下は必要に応じて調整します (`aqua update-checksum`の実行時に該当の環境向けのチェックサムが登録されます) supported_envs: - darwin - linux/amd64
このaqua.yaml
と後述するaqua-checksums.json
は、バージョン管理に含めます。
パッケージの追加
aqua g -i <パッケージ名>
でパッケージを追加できます(aqua
のパッケージレジストリはこちらにあります)。
# 例) pinactを追加 $ aqua g -i suzuki-shunsuke/pinact # 例) actionlintを追加 $ aqua g -i rhysd/actionlint
すると、aqua.yaml
にパッケージの定義が追加されます。
packages: - name: suzuki-shunsuke/pinact@v2.0.4 - name: rhysd/actionlint@v1.7.7
aqua.yaml
で定義されている各種パッケージをインストールするには、下記コマンドを実行します。
$ aqua i
(推奨)aqua.yaml
でチェックサムの検証を有効化している場合、依存パッケージの追加や更新などを行なった際に、aqua update-checksum
でaqua-checksums.json
を更新しておく必要があります。
$ aqua update-checksum
actionlint
を導入する
pinact
に加えて、actionlint
もGitHub Actionsにおけるセキュリティを改善する上で有用なツールです。今回はあわせて導入します(actionlint
についてはすでにWeb上に情報が十分にあるため、詳細は割愛します)。
actionlint
はaqua
でも導入可能です。
$ aqua g -i rhysd/actionlint
pinact
とactionlint
をGitHub Actionsで実行する
aqua
によってpinact
とactionlint
を導入し、GitHub Actionsによって実行を自動化します。
name: Lint workflows on: push: branches: - main paths: - '.github/**/*.yml' - '.github/**/*.yaml' pull_request: branches: - main paths: - '.github/**/*.yml' - '.github/**/*.yaml' jobs: lint: name: Lint workflows runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with: aqua_version: 'v2.46.0' skip_install_aqua: "true" - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: '~/.local/share/aquaproj-aqua' key: v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys: | v2-aqua-installer-${{runner.os}}-${{runner.arch}}- - name: Run pinact run: pinact run --check - name: Run actionlint run: actionlint
一連の対策によってGitHub Actionsに関するサプライチェーン攻撃へのリスクを軽減することが期待されます。
おわりに
以下のブログ記事では、今回紹介したものよりもさらに踏み込んだ対策が紹介されています(ちなみにこの記事は、今回紹介したaqua
やpinact
の作者の方によって書かれています)。
参考になる記事だと思いますので、興味がありましたらぜひ上記の記事もご覧ください。