TT Lab
はじめる
学ぶ 学習パス コース

ビルドは緑だったのに、あのライブラリを入れたのは誰か

署名は合っているが、そのビルダーを許可した覚えがない

TT Labで続きを見る

目標

リリースのバンドルに来歴証明を付けて署名し、証拠が足りないバンドルをデプロイ前に止めるゲートを、プログラムで作ります。止まる場合を3つ、自分で作って確認します。

なぜ重要なのか

署名は「誰が作ったか」に答え、SBOMは「何が入っているか」に答えます。ところが、事故のレポートに最も多く残る文は、その3つのどれでもありません。「このバイト列がどこから来たのか、誰も知らない」です。来歴証明は、その質問の答えを、ビルドの時点で書いておくことです。何を入力に使い、どのコミットから、どのビルダーが、いつ作ったのか。そして、その記録は、デプロイ前に読まれなければ存在しないのと同じです。ゲートがやることがそれで、ゲートの価値は、何を止めたかではなく、なぜ止めたかを人が読めるかから生まれます。最後の場合を必ず自分で作ってみてください。署名も合っていて、ダイジェストも合っているのに、そのビルダーを許可した覚えがないバンドルです。

ステップ

  1. /root/gate/releaseにアーティファクトとSBOMを集めて、/root/gate/subject.jsonを書きます。
  2. /root/gate/release/provenance.jsonにin-toto Statementを書きます。
  3. 材料のダイジェストとビルド時刻を加えます。
  4. 鍵ペアを作って、/root/gate/release/provenance.json.sigとして署名します。
  5. /root/gate/allowed-builders.txtと/root/gate/release-gate.shでゲートを作ります。
  6. 正常なバンドルの判定を/root/gate/06-pass.jsonに残します。
  7. /root/gate/unsigned・/root/gate/tampered・/root/gate/rogueを作って、/root/gate/07-reject.jsonに残します。
  8. 4つの判定を/root/gate/08-audit.jsonlに監査記録として残します。

参考

リリースのバンドルを1か所に集める

/root/gate/release/の下に、/opt/fixtures/sbom/release/paygate-1.4.2.jsをpaygate-1.4.2.jsとして、/opt/fixtures/sbom/sbom/paygate-1.4.2.cdx.jsonをsbom.cdx.jsonとしてコピーしてください。そのあと/root/gate/subject.jsonに、in-totoのsubject配列を保存してください。項目は1つで、nameはファイル名、digestは{"sha256": 計算した値}です。

来歴証明は、いつも「何に対する」証明です。subjectがその「何」であり、名前ではなくダイジェストで指して初めて、あとですり替えを見つけられます。

ビルドが何を何から作ったかを書く

/root/gate/release/provenance.jsonにin-toto Statementを保存してください。_typeは"https://in-toto.io/Statement/v1"、subjectはステップ1の配列、predicateTypeは"https://slsa.dev/provenance/v1"です。predicate.buildDefinitionには、buildType "https://labhub.example/buildtypes/npm-bundle/v1"とexternalParametersを置きますが、externalParametersには、source(uri "git+https://git.internal/payments/paygate"、digest {"gitCommit": "9f2c1d7a4b6e8035c1a2d4f6b8093e5a7c1d2f40"})とentryPoint "npm run build"を入れます。predicate.runDetails.builder.idは"https://labhub.example/builders/paygate-ci@v3"です。

必須はbuildDefinitionとrunDetailsで、その中でbuildType・externalParameters・builderが必須です。buildTypeがURIなのは、「この欄をどう読むべきか」を指すアドレスだからです。

材料と時刻を書く

/root/gate/release/provenance.jsonのpredicate.buildDefinitionにresolvedDependenciesを加えてください。項目はuriとdigestを持ち、2つの材料はfile:///opt/fixtures/sbom/release/package-lock.jsonとfile:///opt/fixtures/sbom/sbom/paygate-1.4.2.cdx.jsonで、digestは{"sha256": そのファイルのハッシュ}です。uriの辞書順に入れてください。そして、predicate.runDetails.metadataに、invocationId "build-2026-09-11-0007"、startedOn "2026-09-11T03:40:00Z"、finishedOn "2026-09-11T03:52:00Z"を書きます。

材料にダイジェストを書いておけば、「同じ入力でもう一度作れば同じものが出るか」を、あとで問えます。名前だけを書くと、その質問を投げられません。

来歴証明に署名してバンドルに入れる

prime256v1の鍵ペアを/root/gate/release-2026.keyと/root/gate/release-2026.pubに作り、その秘密鍵で/root/gate/release/provenance.jsonにSHA-256で署名して、/root/gate/release/provenance.json.sigに保存してください。

署名されていない来歴証明は、SLSAのドキュメントがBuild L1に置いている段階です。ミスは防げますが、偽造は防げません。署名が付いて初めて、「ビルドのあとに手を加えたか」を問えます。

ゲートをプログラムで書く

/root/gate/allowed-builders.txtに許可するビルダーを1行に1つずつ書き(今回は次の1つだけ: https://labhub.example/builders/paygate-ci@v3)、/root/gate/release-gate.shを作ってください。bash /root/gate/release-gate.sh <묶음 디렉터리>で呼び出すと、bundle・verdict・reasonsを入れたJSON1つを標準出力に出し、止めるものがあれば1で終了します(プレースホルダーはバンドルのディレクトリです)。見るのは4つです。sbom.cdx.jsonのcomponentsが空でないか(なければsbom_missing)、provenance.json.sigがあって/root/gate/release-2026.pubで検証できるか(なければsignature_missing、違えばsignature_invalid)、provenance.jsonのsubjectダイジェストが、バンドル内のそのファイルの実際のハッシュと同じか(違えばsubject_mismatch)、builder.idが許可リストにあるか(なければbuilder_not_allowed)。reasonsは辞書順に重複なしで入れます。

ゲートの価値は、「何を止めたか」ではなく、「なぜ止めたかを人が読めるか」から生まれます。理由をコードで残せば、ダッシュボードも振り返りも、そのコードで数えます。

正常なバンドルが通ることを確認する

bash /root/gate/release-gate.sh /root/gate/releaseを実行して、その出力を/root/gate/06-pass.jsonに保存してください。verdictはpass、reasonsは空の配列である必要があります。

先に通ることを確認せず、止めるほうから試すと、ゲートが「常に止める」ものでも、成功したように見えます。

証拠が足りないバンドルを3つ作って止める

/root/gate/releaseをコピーして、/root/gate/unsigned(署名ファイルだけを消したもの)・/root/gate/tampered(paygate-1.4.2.jsの内容を変えたもの)・/root/gate/rogue(provenance.jsonのbuilder.idを"https://labhub.example/builders/laptop@v1"に変えて、同じ鍵でもう一度署名したもの)を作ってください。3つをそれぞれゲートに通して、/root/gate/07-reject.jsonにunsigned・tampered・rogueを保存します。各値には、exit(終了コード)とreasons(ゲートが出した理由の配列)を入れます。

3つ目がこのラボの核心です。署名は問題なく、ダイジェストも合っているのに、あのビルダーを私たちが許可した覚えがありません。署名だけを見るゲートは、これを通してしまいます。

判定を監査記録に残す

4つの判定を/root/gate/08-audit.jsonlに、1行ずつJSONとして残してください。行はbundle名の辞書順(release・rogue・tampered・unsigned)で、各行はbundle(ディレクトリ名だけ)・verdict・reasons・builder・artifact_sha256・checked_at("2026-09-11T04:00:00Z")を入れます。artifact_sha256は、そのバンドル内のpaygate-1.4.2.jsの実際のハッシュです。

ゲートが止めたという事実は、その瞬間に画面にだけ残ります。あとで「なぜあのとき止まったのか」を問うには、判定とその根拠を一緒に書いておく必要があります。