npm パッケージに認証バッジを付けてもらった話 (npm Provenance を試す)

2023/05/08に公開約8,500字

この記事では、npm パッケージの透明性と検証性が向上する新機能である npm package provenance (プロベナンス) について解説します。実際に公開中のパッケージに認証バッジを付けてみて、さらに使用側としてプロベナンスを検証する手順を解説します。

npm パッケージのスクリーンショット。バージョンの隣に GitHub Actions からの認証バッジが付いている
npm パッケージのバージョンの隣に認証バッジが付いている

対象読者

  • npm でパッケージを公開しているのでプロベナンスを付けたい
  • インストールしている依存関係のプロベナンスを検証してみたい
  • npm provenance の仕組みを簡単に知りたい

はじめに

最近、npm のパッケージに悪質なコードを含んで公開されたという問題が起こったのは記憶に新しいのではないでしょうか[1]。この例に限らず、オープンソースサプライチェーン攻撃は日々増加しており大きなリスクとなっています[2]

実は、npm では認証情報さえあれば npm publish とタイプするだけで任意のコードを好きなように公開できます。パッケージを使用しようとしても、どのソースコードからビルドされたのか、どのビルドプロセスを用いられたのかを使用者側から検証できないといった課題があります。

さらには、npm に GitHub へのリンクが載っていますが、このリンクも package.json の情報を転記しているだけで、実際にそのコードが GitHub にあるとは限りません。また、GitHub にあるとしても、そのコードが npm に公開されたコードと一致しているとも限りません。

OSS が信頼される 1 つの理由として、ソースコードや変更履歴が公開されているため透明性が高く、検証しやすいことが挙げられます。しかし、npm のパッケージは同等の透明性と検証性が確保されていません。

これらの背景から、 2023 年 4 月より、npm package provenance がパブリックベータ版で提供開始されました。この機能を用いると npm のパッケージにプロベナンスが発行されます。パッケージを使用するユーザーが、ビルド元のコミットやビルドプロセスなどを検証できるようにすることで、透明性と検証性が向上しています。

今回は、私がメンテナーである npm パッケージへプロベナンスを付け、さらに使用側として検証してみました。

プロベナンスを付けてみた

まずは、私がメンテナーである npm パッケージにプロベナンスを付けてみます。

ワークフローの変更

プロベナンスを付与する方法は npm 公式ドキュメント に記載されています。現状、ベータ版であるため対応している CD サービスは GitHub Actions のみです。

すでに GitHub Actions でパブリッシュするワークフローを組んでいれば、必要な作業は id-token の生成権限付与と npm publish--provenance を追加するだけです。

実際の差分はこちらで確認できます。

workflow.yml
name: Publish (latest)
on:
  release:
    types: [created]
jobs:
  build:
    runs-on: ubuntu-latest
    environment: npm
+    permissions:
+      contents: read
+      id-token: write
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18.x'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
-      - run: npm publish
+      - run: npm publish --provenance
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

パブリッシュ

その後、GitHub のリリースをすることでワークフローをトリガーします。

npm パッケージのページを開いてみると、新バージョンに認証バッジとプロベナンスが追加されていました。

npm パッケージのスクリーンショット。Provenance という項目が追加され、GitHub Actions によってこのバージョンが署名されていることを示している
npm パッケージに Provenance が追加され認証バッジが付いている

すでに GitHub Actions のワークフローを組んでいた場合、鍵を生成するなどの複雑な作業は不要で数分で対応できることがわかりました。開発者体験がとても良いですね。

実際の npm ページは下記から確認できます。

https://www.npmjs.com/package/gatsby-plugin-fix-fouc/v/1.0.3-beta.0

プロベナンスを検証してみた

無事にプロべナンスを追加できたので、使用者側として検証してみます。

検証できる情報

まずは、npm ページについたプロベナンスのカードを見ると主に下記の情報がわかります。

  • ソースコードのコミットハッシュ
  • ビルドした際に使用されたワークフロー
  • ワークフローの実行結果
  • など

また、プロベナンスに対する Sigstore による署名ログも付与されています。これらは npm のサイトからリンクされています。

ソースコードのコミットハッシュ

ワークフローがトリガーされたコミットのハッシュにリンクされています。

ビルド元のソースコードやコミット履歴を検証できます。

https://github.com/bicstone/gatsby-plugin-fix-fouc/tree/5abb98d705367364fa88a9a4e6a4e0f13801bf2c

ビルドした際に使用されたワークフロー

使用されたワークフローのソースコードにリンクされています。

ビルド時のコマンドを確認することで、ソースコードからのビルド手順を検証できます。

https://github.com/bicstone/gatsby-plugin-fix-fouc/actions/runs/4871672535/workflow

ワークフローの実行結果

GitHub Actions のワークフロー実行結果にリンクされています。

私のパッケージでは Deployment protection rules において承認を必須にしており、承認履歴も残っています。 (アカウントを乗っ取れば誰でも承認できるのであくまでも参考程度)

https://github.com/bicstone/gatsby-plugin-fix-fouc/actions/runs/4871672535/attempts/1

プロベナンスに対する Sigstore による署名ログ

プロベナンスは、 Sigstore によって署名されています。

署名のログは Rekor で閲覧できます。この公開された署名により、プロベナンスや発行済みのパッケージが改ざんされた場合に検出ができます。

https://search.sigstore.dev/?logIndex=19589594

npm CLI から検証

さらに、依存関係で使用しているパッケージでプロベナンスやパッケージが改ざんされていないかどうかを、 npm CLI 9.5 以上であれば検証できます。ちなみに yarn や pnpm などでは執筆時点では検証できません[3]。最近 npm CLI 回帰の流れを耳にすることがありますが、また 1 つ理由が増えましたね。

$ npm audit signatures
audited 1302 packages in 14s
1302 packages have verified registry signatures
1 package has a verified attestation
  • x packages have verified registry signatures は npm による署名を用いて、パブリッシュされたものから改ざんされていないことを検証しています。 (以前からある機能)
  • x package has a verified attestation はプロベナンスの署名を用いて、プロべナンスの情報やパッケージが改ざんされていないことを検証しています。

CD プロセスに npm audit signatures を追加することで、改ざんされていないかどうかをデプロイのたびに検証できます。

プロベナンスの仕組み

仕組みについては GitHub 公式ブログで解説されています。Sigstore の仕組みを書くだけで 1 記事書けるレベルなので、今回は概要だけ解説します。詳しくは公式ブログをご覧ください。

https://github.blog/2023-04-19-introducing-npm-package-provenance/

まず、GitHub Actions 内で下記のようなプロベナンスが作成されます。

プロベナンスの例

_type: https://in-toto.io/Statement/v0.1
subject:
  - name: pkg:npm/gatsby-plugin-fix-fouc@1.0.3-beta.0
    digest:
      sha512: >-
        a38cd31ada1fb30cf24b8ec672c152930ea660f5add6773a33953c2d4650a60fb42e1720a44ce0acbf02fc16b8314c8c353e27eccf518315366823d4ea04fb83
predicateType: https://slsa.dev/provenance/v0.2
predicate:
  buildType: https://github.com/npm/cli/gha@v1
  builder:
    id: https://github.com/npm/cli@9.5.1
  invocation:
    configSource:
      uri: >-
        git+https://github.com/bicstone/gatsby-plugin-fix-fouc@refs/tags/v1.0.3-beta.0
      digest:
        sha1: 5abb98d705367364fa88a9a4e6a4e0f13801bf2c
      entryPoint: >-
        bicstone/gatsby-plugin-fix-fouc/.github/workflows/publish-next.yml@refs/tags/v1.0.3-beta.0
    parameters: {}
    environment:
      GITHUB_ACTOR_ID: "47806818"
      GITHUB_EVENT_NAME: release
      GITHUB_REF: refs/tags/v1.0.3-beta.0
      GITHUB_REF_TYPE: tag
      GITHUB_REPOSITORY: bicstone/gatsby-plugin-fix-fouc
      GITHUB_REPOSITORY_ID: "528440344"
      GITHUB_REPOSITORY_OWNER_ID: "47806818"
      GITHUB_RUN_ATTEMPT: "1"
      GITHUB_RUN_ID: "4871672535"
      GITHUB_RUN_NUMBER: "10"
      GITHUB_SHA: 5abb98d705367364fa88a9a4e6a4e0f13801bf2c
      GITHUB_WORKFLOW_REF: >-
        bicstone/gatsby-plugin-fix-fouc/.github/workflows/publish-next.yml@refs/tags/v1.0.3-beta.0
      GITHUB_WORKFLOW_SHA: 5abb98d705367364fa88a9a4e6a4e0f13801bf2c
  metadata:
    buildInvocationId: 4871672535-1
    completeness:
      parameters: false
      environment: false
      materials: false
    reproducible: false
  materials:
    - uri: git+https://github.com/bicstone/gatsby-plugin-fix-fouc
      digest:
        sha1: 5abb98d705367364fa88a9a4e6a4e0f13801bf2c

このプロベナンスには、該当のコミットやワークフローの情報、これらのハッシュなどの情報を含んでいます。このプロべナンスは下記のような手順で署名、検証、公開されます。

  1. GitHub Actions
    • 使い捨ての公開鍵と秘密鍵のキーペアを生成
    • OpenID Connect (OIDC) サービスを用いて、ID token を取得
    • プロべナンスを生成
    • Sigstore の認証局 (Fulcio) に ID token, 公開鍵を送信
  2. Fulcio (認証局)
    • ID token を検証
    • 検証が成功すれば、短命の署名証明書を発行して GitHub Actions に返す
  3. GitHub Actions
    • 秘密鍵でプロべナンスに署名
    • 署名したプロベナンスと署名証明書を Sigstore の台帳 (Rekor) にアップロード
  4. Rekor (台帳)
    • 署名したプロベナンスと署名証明書を検証
    • 検証が成功すれば、台帳に記録して公開
  5. GitHub Actions
    • npm にパッケージ, 署名したプロべナンス, 署名証明書を送信
  6. npm (リポジトリ)
    • 送信されたパッケージ, 署名されたプロべナンス, 署名証明書を検証
      • 署名証明書に添付されている Identity が GitHub Actions になっているか
      • プロべナンスの署名が有効で、改ざんされていないか
      • パッケージのハッシュ値がプロべナンスと一致し、改ざんされていないか
      • など
    • 検証が成功すれば新バージョンを発行

下記はイメージです。

まとめ

オープンソースサプライチェーン攻撃が増加している背景から、npm package provenance という新機能が導入されました。この機能で、npm パッケージの透明性や検証性を向上させることができます。

(認証情報が漏れる前提で) OSS のメンテナーを信じずに、ソースコードとビルドプロセスを直接検証できるようにするというのが素晴らしいと考えています。まさしく OSS の本質ですね。

内部では複雑なフローが動いていますが、とても簡単な手順でプロべナンスを付けられ、検証できるように工夫されていました。OSS のメンテナーが秘密鍵を管理する必要がない仕組みはよく考えられていて、業界はオープンソースサプライチェーン攻撃に対する取り組みに本気なのだと改めて感じさせられました。

他のプラットフォームにも導入されていき、将来的には、プロベナンス付きで npm パッケージを公開することがスタンダードになるのではないかと考えています。

脚注
  1. Infinite loop causing Denial of Service in colors · GHSA-5rqg-jm4f-cqx7 · GitHub Advisory Database / Embedded malware in ua-parser-js · GHSA-pjwm-rvh2-c87w · GitHub Advisory Database など ↩︎

  2. 8th Annual State of the Software Supply Chain Report | Sonatype ↩︎

  3. Generating provenance statements | npm Docs ↩︎

Discussion

ログインするとコメントできます