Introducing npm package provenance

npmパッケージプロベナンスを導入

Image of Brian DeHamer
   
Image of Philip Harrison

npmパッケージをGitHub Actionsのソースリポジトリやビルド手順に結び付ける方法についてご紹介します。


4月19日より、GitHub Actionsでnpmプロジェクトをビルドする際、”–provenance”フラグを記述することで、パッケージと合わせてプロベナンスを発行できるようになりました。このプロベナンスデータは、ユーザーがパッケージを、そのソースリポジトリと、公開するために使用された特定のビルド手順に紐づけ検証することを可能にします(npmjs.comの例をご覧ください)。

ソースリポジトリと具体的なビルド手順について、GitHubでは以下のようなメタデータを収集しています。

_type: https://in-toto.io/Statement/v0.1
subject:
- name: pkg:npm/sigstore@1.2.0
  digest:
    sha512: 16bf7e5b59e40522190a425047b8c39ffcc8d145cdb15a69fbb9834240a764e2311bda7ac8d5c1c7dc67b47b1f532607139e570e4915577fab61bae4cc079eb0
predicateType: https://slsa.dev/provenance/v0.2
predicate:
  buildType: https://github.com/npm/cli/gha/v2
  builder:
    id: https://github.com/actions/runner
  invocation:
    configSource:
      uri: git+https://github.com/sigstore/sigstore-js@refs/heads/main
      digest:
        sha1: 5b8c0801d1f5d105351a403f58c38269de93f680
      entryPoint: ".github/workflows/release.yml"
    environment:
      GITHUB_EVENT_NAME: push
      GITHUB_REF: refs/heads/main
      GITHUB_REPOSITORY: sigstore/sigstore-js
      GITHUB_REPOSITORY_ID: '495574555'
      GITHUB_REPOSITORY_OWNER_ID: '71096353'
      GITHUB_RUN_ATTEMPT: '1'
      GITHUB_RUN_ID: '4503589496'
      GITHUB_SHA: 5b8c0801d1f5d105351a403f58c38269de93f680
      GITHUB_WORKFLOW_REF: sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
      GITHUB_WORKFLOW_SHA: 5b8c0801d1f5d105351a403f58c38269de93f680
  materials:
  - uri: git+https://github.com/sigstore/sigstore-js@refs/heads/main
    digest:
      sha1: 5b8c0801d1f5d105351a403f58c38269de93f680

パブリックベータ版を利用する方法についてはこちら(英語)をご確認ください。また、なぜ本機能を開発したのかについての詳細もお読みいただけます!

npmサプライチェーンの信頼性を高める

世界最大級のパッケージレジストリのホームであるGitHubでは、npmエコシステムの健全性を維持するため、セキュリティの改善に継続的に取り組んでいます。その責務の一環が、ビルドの基盤となるオープンソースプロジェクトへの信頼構築です。私たちは開発者がソフトウェアサプライチェーンの完全性を確保するために必要なツールを提供したいと考えています。

ソフトウェアサプライチェーンの完全性という問題を解決する唯一の答えはありません。ソフトウェア開発ライフサイクル全体に渡り、さまざまな解決策を必要とします。これまでGitHubでは主に、偶発的な脆弱性の検出と修正に力を入れてきました。脆弱な依存関係に対処するツールが改善されるにつれ、アタッカーはますますソフトウェアサプライチェーンの他の弱点を狙うようになっています。彼らは悪用できる脆弱性が開示されるのを待たず、よく使われる依存関係を直接攻撃することで、プロジェクトに悪意のあるコードを挿入しようとします。

この種の攻撃は、意図的でない脆弱性の発生に比べると比較的まれですが、その数は増えています。標的を定めた意図的な攻撃であることから、その影響が大きくなることが多々あります。ここ数年、UAParser.jsCommand-Option-Argumentrcといった主要なnpmパッケージへの攻撃が顕著になっています。

こうした攻撃では、ソースコードを直接侵害して攻撃することはほとんどなく、むしろ、侵害した認証情報を悪意あるバージョンの発行に使用することが多々あります。

オープンソースモデルが本来持つ透明性は、ソースコード自体への高い信頼性を生み出します。誰もがソースを見て、あらゆる変更を監査できるという事実は、悪意あるコードが検出されずに残存する可能性を低下させます。ただし、ソースコードを信頼しているからといって、発行されたパッケージも同様に信頼するわけではありません。

レジストリからダウンロードしたnpmパッケージの信頼度を高めるには、どのソースが発行済みアーティファクトに変換されたのか、プロセスを可視化する必要があります。

npmエコシステムに対するGitHubの目標は、コードをビルドして発行するプロセスに対して、オープンソースコードそのものと同じ水準の透明性をもたらすことです。

パッケージをソースとビルドに結び付ける

皆さんは恐らく、道で拾った出自が不明なフラッシュドライブを自分のノートPCに挿したりしないでしょう。しかし、オープンソースパッケージでは、まさにこのようなことが四六時中起きています。開発者は日々、あまり熟考することなく、npmレジストリから選んだパッケージをアプリケーションに組み込んでいます。

仮に、利用する個々の依存関係を時間をかけて徹底的に調べようと思ったとしても、それがどこから来てどのように作られたのかという情報は、パッケージにほとんど含まれていません。

npmレジストリのほとんどのパッケージページにはソースリポジトリへのリンクが記載されていますが、この情報は検証されていません。また、特定のコミットを指しているわけでもありません。コードエクスプローラーを使用すると、インストールする前にパッケージの内容を見ることができますが、これはパッケージがどこから来たのかを把握する助けにはなりません。

必要なのは、npmパッケージから、そのパッケージが派生した正確なソースコードのコミットまでのつながりを直接把握する方法です。絵画の所有者を年代順に追跡している美術史家のごとく、GitHubはパッケージのプロベナンスステートメントを必要とします。このステートメントでは、最終的なアーティファクトの組み立てに使われた元のソースとビルド手順を、検証可能な記録で確認できます。

SLSA(Supply-chain Levels for Software Artifacts)の仕様はまさにこうした目的のために作られたもので、npmのプロベナンスステートメントに使われています。SLSAのプロベナンススキーマでは、subject(発行されたnpmパッケージ)は入力のmaterials(ソースリポジトリとコミットSHA)から発生し、buildConfig (パッケージのビルド/発行のために実行したステップ)で処理されたものとして記述されます。これら3つの値は、発行されたパッケージがどのようにソースから派生したかを理解するために必要な情報を正確に教えてくれます。

コードを信頼できるようにする

このようにして、GitHubは取得する情報および、それを記録するためのフォーマットに関するアイデアを得ました。次のステップは、プロベナンスステートメントを介して検証可能な署名を作成することですが、パッケージのプロベナンスを証明するには、誰を信頼すればよいのでしょうか?

パッケージの署名には、一般的にメンテナーが直接管理するキーが使われます。こうすることで、パッケージが本当にそのキーの所有者によって作られたものであることを利用者が検証できます。ただし、この方法はキーのセキュリティ侵害に対して脆弱であり、ソースコードと発行されたパッケージを結び付ける検証可能な手段にはなりません。

GitHubの目標は、個々のメンテナーに署名を依存するのではなく、ソースコードとビルドプロセスを直接信頼できるものにすることです。

この実現に向けては、信頼できるCI/CDプラットフォームでパッケージをビルドする必要があります。これにより、ビルドをトリガーした特定のコミットや、最終的なアーティファクトの発行に使用した手順を可視化できます。そして、この情報により、ビルドの監査可能性が高まり、コードを改ざんしようとする試みを大幅に把握しやすくなります。

さらに、CI環境とジョブのID(OpenID Connectトークン形式)を用いてプロベナンスステートメントに暗号署名を適用し、パッケージの利用者が検証できる方法でデータの有効性を証明することができます。

プロベナンスに署名するため、GitHubではSigstoreプロジェクトが提供するツールを活用しています。Sigstoreはパブリック認証局を運営しており、適合するCI/CDプロバイダーからOIDCトークンを受け取り、応答として短時間有効なX.509署名証明書を発行します。

パッケージのプロベナンス生成処理の一部として、GitHubではプロベナンスステートメントに署名するために使い捨てのキーペアを作成し、SigstoreのFulcio CAを呼び出して、そのキーとCIジョブのIDをバインドする署名証明書をリクエストします。キーを管理する必要はありませんが(署名は生成されるとすぐに削除されます)、署名証明書を提示された人は署名を検証できるだけでなく、その署名を作成したCIジョブのIDも確認できます。

Sigstoreのパブリック認証局を利用するには、対応しているクラウドCI/CDプロバイダー上で実行する必要があります。現在、GitHub Actionsでは対応していますが、できるだけ多くのCI/CDプラットフォームでの対応の促進に取り組んでいます。

まとめると、対応するCI/CDプロバイダーからプロベナンス付きで発行される場合、以下の手順が実行されます。

検証

パッケージのプロベナンスを生成するだけでは、道半ばに過ぎません。本当に影響力のあるものにするには、信頼できるソースにより証明されたプロベナンスであることを利用者が検証できるツールも必要です。

署名プロセスの一部として、プロベナンス証明はSigstoreのRekorサービスにアップロードされます。公開された、この改ざん不可能な透明性ログにより、後で誰かがプロベナンスや発行済みのパッケージの内容を変更しようとしても、その行為を検出できます。

プロベナンス証明がRekorに投稿されると、発行されるパッケージと合わせてnpmレジストリに送信されます。レジストリは、署名と署名証明書に添付されたIDをチェックし、誰もプロベナンスを偽装しようとしていないことを確認した後、発行されたバージョンを受け入れます。

プロベナンスと一緒に発行されたパッケージは、バージョン番号の横に新しいバッジが付いた状態でnpmjs.comの画面上に表示されます。

開発者はnpm CLI(npm 9.5.0以降で利用可能)を使用して、インストールされた依存関係のプロベナンスの完全性も検証できます。

npm audit signatures

今後の展望

npmパッケージのプロベナンスを一般提供できるようにするため、GitHubはさらに多くの改良に取り組んでいます。

  • SLSAプロベナンス仕様のバージョン1.0を採用
  • 他のクラウドCI/CDプロバイダーと協力して、プロベナンス署名のサポートを追加
  • 想定されるソースリポジトリとコミットが存在することを検証
  • CI/CD環境とnpmレジストリ間のアクセスを管理する新しいツールを開発

意図的なサプライチェーン攻撃を防ぐことは、GitHubだけでは不可能です。GitHubはOpenSFFの創設メンバーであり、ソフトウェアリポジトリを保護するワーキンググループに積極的に参加し、同様の機能を他のプラットフォームやパッケージエコシステムに導入することを目指しています。業界全体で一丸となり、オープンソースサプライチェーンを保護するこれらの取り組みに尽力しています。