RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

pinactを利用して簡単にGitHub Actionsにおけるサプライチェーン攻撃被害のリスクを軽減する

はじめに

昨今、パッケージなどのエコシステムをターゲットとしたサプライチェーン攻撃が増加しています。

各種プログラミング言語向けのパッケージマネージャーやレジストリにおいては、インストールするパッケージのバージョンを固定したり、チェックサムを検証したりすることにより、サプライチェーン攻撃被害のリスクを軽減する仕組みが導入されています。

もちろんGitHub Actionsにおいても、サードパーティー製のワークフローを利用する場合に、サプライチェーン攻撃の被害を受けるリスクが生じますが、このような仕組みを導入するには少々作業が必要になります。

そこで本記事では、pinactを利用して簡単にGitHub Actionsにおけるサプライチェーン攻撃被害のリスクを軽減する方法を紹介します。なお、本記事で紹介する内容は、弊社で最近実施されたものです。

なぜ実施したのか?

弊社の一部リポジトリで利用しているtj-actions/changed-filesにおいて、サプライチェーン攻撃が発生しました:

www.stepsecurity.io

具体的には、tj-actions/changed-filesの運用のためのボットで利用していたPATが流出し、それを悪用して攻撃者による悪意のあるコミットが紛れ込み、各タグなども改竄されてしまったようです。

時差の関係もあって運よく被害は発生しなかったのですが、仮に被害が発生した際の影響は大きなものになりえます。今後のリスク軽減のために、以下で紹介する対策を実施することにしました。

実施したこと

pinactの導入

pinactとは?

GitHub Actionsのワークフローにおける各種依存アクションのバージョンを固定してくれるCLIツールです。

github.com

使い方

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によってサードパーティー製ワークフローのコミットハッシュによる参照を強制することで、意図せず改竄されてしまったバージョンのワークフローが実行されるリスクを軽減することができます。

pinactの導入に関する選択肢

今回、pinactの導入に当たって実現したいことは以下の内容です。

  1. CI (GitHub Actions) でpinact run --checkを実行したい。
    • pinact run --checkを実行すると、GitHub Actionsのワークフローにおいてバージョンが固定されていない依存アクションを検出してくれます。
  2. 目的はサプライチェーン攻撃へのリスクを低下させることなので、できるだけ安全な方法でpinactを導入したい。

その上で、pinactを導入する方法としては以下のあたりが選択肢として考えられそうです。

  1. Homebrew
    1. メリット
      1. 公式からHomebrew/actionsが提供されており、GitHub Actionsからの利用が容易である。
      2. pinactによって推奨されるインストール方法の一つである。
      3. 使い慣れているユーザーが比較的多いと思われる。
    2. デメリット
      1. インストールするツールのバージョンを固定できない。
  2. aqua
    1. メリット
      1. 公式からaquaproj/aqua-installerが提供されており、GitHub Actionsからの利用が容易である。
      2. pinactによって推奨されるインストール方法の一つである。
      3. 依存ツールのバージョンの固定が可能である(後述)。
      4. aqua-checksums.jsonによるチェックサムの管理・検証が可能である。
    2. デメリット
      1. Homebrewや後述するmiseと比較すると、まだ使用例は多くないと思われる。
  3. mise
    1. メリット
      1. 作者によってjdx/mise-actionが提供されており、GitHub Actionsからの利用が容易である。
      2. aquaバックエンドを利用すればpinactを導入可能である。
      3. aquaと同様に、依存ツールのバージョンの固定が可能である。
      4. 今回導入予定のpinact以外に、Node.jsなどのさまざまなランタイムのバージョン管理もできる。
    2. デメリット
      1. aquaバックエンドは、実験的サポートの段階である(※)。

今回の目的とメリットを踏まえると、aquamiseがよさそうです。miseaquaバックエンドはまだ実験的サポート(※) であったことと、aqua-checksums.jsonによるチェックサム管理の仕組みがあることなどから、本記事ではaquaを試してみることにしました。

※ ⚠️ 記事の執筆を開始した当初は、miseaquaバックエンドはまだ実験的サポートの段階でしたが、本記事の公開時点ではすでに実験的という表記は削除されています (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-checksumaqua-checksums.jsonを更新しておく必要があります。

$ aqua update-checksum

actionlintを導入する

pinactに加えて、actionlintもGitHub Actionsにおけるセキュリティを改善する上で有用なツールです。今回はあわせて導入します(actionlintについてはすでにWeb上に情報が十分にあるため、詳細は割愛します)。

actionlintaquaでも導入可能です。

$ aqua g -i rhysd/actionlint

pinactactionlintをGitHub Actionsで実行する

aquaによってpinactactionlintを導入し、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に関するサプライチェーン攻撃へのリスクを軽減することが期待されます。

おわりに

以下のブログ記事では、今回紹介したものよりもさらに踏み込んだ対策が紹介されています(ちなみにこの記事は、今回紹介したaquapinactの作者の方によって書かれています)。

zenn.dev

参考になる記事だと思いますので、興味がありましたらぜひ上記の記事もご覧ください。