From e1cd367a6af14b0dbe03b311908fd8927b45f885 Mon Sep 17 00:00:00 2001 From: riiim Date: Tue, 10 Nov 2026 10:47:14 +0000 Subject: [PATCH 1/2] new post --- src/content/blog/slsa-build-l2-to-l3.md | 258 ++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 src/content/blog/slsa-build-l2-to-l3.md diff --git a/src/content/blog/slsa-build-l2-to-l3.md b/src/content/blog/slsa-build-l2-to-l3.md new file mode 100644 index 0000000..dcb54ea --- /dev/null +++ b/src/content/blog/slsa-build-l2-to-l3.md @@ -0,0 +1,258 @@ +--- +title: SLSA Build L2からL3へ +pubDate: '2026-11-10' +description: actions/attestのSLSA Build L2から再利用ワークフローでのL3強化と検証について +author: RiiiM +tags: [Tech, Security, CI/CD] +--- + +前回はGitHub Releaseのバイナリ差し替えに備えて +actions/attestでSLSA provenanceを作り`gh attestation verify`で検証するところまでやった + +https://www.rowicy.com/blog/verify-release-binary + +SLSA provenanceでの課題はビルドステップまで侵害されたら正規のattestationが出てしまう、これの対策はあるだろうか + +## SLSA Buildレベルをあげる + +SLSAでは成果物の成熟度モデルとして +Build trackがある + +| レベル | 要件 | 防ぐもの | +| --- | --- | --- | +| Build L0 | なし | なし | +| Build L1 | ビルド方法を示すprovenanceがある | ビルドミス| +| Build L2 | ホストされたビルド基盤がprovenanceを生成・署名 | ビルド後の改ざん | +| Build L3 | 堅牢化されたビルド基盤 | ビルド中の改ざん | + +L3でビルド基盤に求められるのは2つ + +- ワークフローが影響し合わないようにする +- provenanceの署名に使う秘密情報に、ビルドステップから触れないようにする + +### GitHub actions/attestのレベルは? + +artifact attestation単体ではSLSA v1.0のL2に該当 + +前回のワークフローはビルド基盤のランナーからprovenanceを生成・署名していた + +```yaml +# .github/workflows/build.yml (前回) + +name: build-and-attest + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write + + steps: + - uses: actions/checkout@ + - uses: actions/setup-go@ + with: + go-version: '1.24' + - name: Build + run: | + mkdir -p dist + go build -o dist/slsa-workshop main.go + - name: Generate provenance + uses: actions/attest@ + with: + subject-path: dist/slsa-workshop +``` + +`id-token: write`の権限はjobの全ステップに及ぶため、 +侵害されると同じjobのOIDCトークンで署名されうる + +これをBuild track L3にするには、分離をする + +GitHub Actionsで実践するなら以下 + +1. ビルドとattestを再利用ワークフローに移す +2. 呼び出し側ワークフローで必要権限とともに呼び出し + + +### 再利用ワークフロー + +```yaml "build:" "attest:" {29-32,52-57} +# .github/workflows/reusable-build.yml + +name: reusable-build + +on: + workflow_call: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read # id-token: writeを持たせない + outputs: + digest: ${{ steps.hash.outputs.digest }} + + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + + - uses: actions/setup-go@ + with: + go-version: '1.24' + cache: false # 信頼できないワークフローのキャッシュを読まないようにキャッシュを切る + + - name: Build + run: | + mkdir -p dist + go build -o dist/slsa-workshop main.go + + - name: Digest + id: hash + run: echo "digest=sha256:$(sha256sum dist/slsa-workshop | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + + - name: Upload binary + uses: actions/upload-artifact@ + with: + name: slsa-workshop + path: dist/slsa-workshop + + attest: + needs: build + runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write + artifact-metadata: write + + steps: + - name: Generate provenance + uses: actions/attest@ + with: + subject-name: slsa-workshop + subject-digest: ${{ needs.build.outputs.digest }} +``` + +再利用ワークフローの中の`github`コンテキストは呼び出し側のものになる + +### 呼び出しワークフロー + +前回のbuild.ymlは呼び出すだけのワークフローになる + +```yml +# .github/workflows/build.yml +name: build-and-attest + +# 省略 +permissions: + contents: read + +jobs: + build: + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write + uses: ./.github/workflows/reusable-build.yml +``` + +再利用ワークフローは、呼び出し側が渡した以上の権限を持てないので呼び出し側のjobにも`id-token: write`などが要る + +これで分離はできた +しかし、L3が防ぐのは署名鍵の盗難とprovenanceの偽造で、ワークフロー定義を書き換えられる場合には耐えない +侵害されたビルドが悪意あるバイナリを正しくハッシュし、それに正規のprovenanceを出すことまでは防げない + +そこまで防ぐなら再利用ワークフローを別リポジトリに置くなどの工夫が考えられる + +## 利用側の検証ポリシーを締める + +前回の検証コマンドをもう一度 + +```zsh +% gh attestation verify ./slsa-workshop --repo riiimparm/slsa-test +... +- Subject Alternative Name must match regex: (?i)^https://github\.com/riiimparm/slsa-test/ +``` + +バイナリ生成元のリポジトリだけ指定しているが、 +これはデフォルトではSANはリポジトリ内ワークフローなら何でも通る + +攻撃者がリポジトリに`evil.yml`を足して適当なブランチで動かしても通る + +SANを厳格にするオプションがあるのでそれで検証を強めるほうがいいかもしれない + +| フラグ | 照合先 | +| ---------------------------- | ------------------------------------------ | +| `--signer-workflow` | 署名したワークフロー(SAN)`[host/]//`で指定 | +| `--signer-repo` | 署名したワークフローのリポジトリ | +| `--signer-digest` | 署名したワークフローのコミット(Build Signer Digest) | +| `--source-ref` | ビルド元のgit ref(Source Repository Ref) | +| `--source-digest` | ビルド元のコミット(Source Repository Digest) | +| `--deny-self-hosted-runners` | self-hosted runnerで作られたものを拒否 | +| `--cert-identity` | SANの完全一致 | + +再利用ワークフローで署名した場合、`--signer-workflow`か`--signer-repo`を使うのがよさそう + +以下で「slsa-testのv0.2.0タグから、reusable-build.ymlがGitHubホストのrunnerでビルド・署名したもの」だけが通る + +```zsh +% gh attestation verify ./slsa-workshop \ + --repo riiimparm/slsa-test \ + --signer-workflow riiimparm/slsa-test/.github/workflows/reusable-build.yml \ + --source-ref refs/tags/v0.2.0 \ + --deny-self-hosted-runners +The following policy criteria will be enforced: +- Predicate type must match:..................... https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:........ https://github.com/riiimparm +- Source Repository URI must match:.............. https://github.com/riiimparm/slsa-test +- Source repo ref must match:.................... refs/tags/v0.2.0 +- Subject Alternative Name must match regex:..... ^https://github.com/riiimparm/slsa-test/.github/workflows/reusable-build.yml +- OIDC Issuer must match:........................ https://token.actions.githubusercontent.com +- Action workflow Runner Environment must match : github-hosted + +✓ Verification succeeded! +``` + + +## 再利用ワークフローを別リポジトリに置く + +ワークフロー定義の書き換え耐性として、再利用ワークフローを別リポジトリに移す案がある + +```yaml +# .github/workflows/build.yml +jobs: + build: + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write + uses: <別リポジトリ>/.github/workflows/go-build.yml@<コミットSHA> +``` + +しかしこのリポジトリ管理も増える +ここで外部ビルダー(slsa-github-generator)を呼ぶ方法もあるが、現状推奨されていない +slsa-github-generatorは積極的にはメンテナンスされておらず、GitHub artifact attestationsへ移行するよう案内されている + +## 今できること + +現実的な範囲ではここらへん + +- ビルドと署名のjobを分離する(署名権限をビルドステップから外す) +- 再利用ワークフローを別リポジトリに置く(定義の書き換えを少し難しくする) +- 検証時に `--signer-workflow` で署名したワークフローを固定する + From 83d87d94f1f870fb06326f990c8c8fa60a195b4e Mon Sep 17 00:00:00 2001 From: riiim400th Date: Wed, 11 Nov 2026 03:05:22 +0900 Subject: [PATCH 2/2] new_post --- src/content/blog/slsa-build-l2-to-l3.md | 280 +++++++++++++----------- 1 file changed, 152 insertions(+), 128 deletions(-) diff --git a/src/content/blog/slsa-build-l2-to-l3.md b/src/content/blog/slsa-build-l2-to-l3.md index dcb54ea..5510213 100644 --- a/src/content/blog/slsa-build-l2-to-l3.md +++ b/src/content/blog/slsa-build-l2-to-l3.md @@ -1,9 +1,12 @@ --- -title: SLSA Build L2からL3へ +title: GitHub Releaseの不正差し替えに備える検証 ~ SLSA Build Track ~ pubDate: '2026-11-10' description: actions/attestのSLSA Build L2から再利用ワークフローでのL3強化と検証について author: RiiiM -tags: [Tech, Security, CI/CD] +tags: + - Tech + - Security + - CI/CD --- 前回はGitHub Releaseのバイナリ差し替えに備えて @@ -11,32 +14,42 @@ actions/attestでSLSA provenanceを作り`gh attestation verify`で検証する https://www.rowicy.com/blog/verify-release-binary -SLSA provenanceでの課題はビルドステップまで侵害されたら正規のattestationが出てしまう、これの対策はあるだろうか +SLSAはソースコード、コンテナイメージ、ファームウェア、ファイル、ディレクトリなど、改変されない成果物全般の成果過程を証明する仕組み -## SLSA Buildレベルをあげる +SLSAの成果物をAttestasion(署名付きの主張)と呼ぶ +これには成果物の来歴情報がつく -SLSAでは成果物の成熟度モデルとして -Build trackがある +この来歴証明をProvenanceと呼ぶ -| レベル | 要件 | 防ぐもの | -| --- | --- | --- | -| Build L0 | なし | なし | -| Build L1 | ビルド方法を示すprovenanceがある | ビルドミス| -| Build L2 | ホストされたビルド基盤がprovenanceを生成・署名 | ビルド後の改ざん | -| Build L3 | 堅牢化されたビルド基盤 | ビルド中の改ざん | +リリースバイナリで言うとビルドのワークフロー、コミットSHAなどが情報として入り、ワークフローがこれに署名する -L3でビルド基盤に求められるのは2つ +これを利用者側で検証するときにProvenanceの照合がずれたら不正リリースを疑うことができる -- ワークフローが影響し合わないようにする -- provenanceの署名に使う秘密情報に、ビルドステップから触れないようにする +## Build track -### GitHub actions/attestのレベルは? +SLSAには成果物の成熟度モデルがある -artifact attestation単体ではSLSA v1.0のL2に該当 +いまでいうリリースバイナリの生成ステップ -前回のワークフローはビルド基盤のランナーからprovenanceを生成・署名していた +Build trackとよばれる -```yaml +| レベル | 要件 | 防ぐもの | +| -------- | --------------------------------- | -------------- | +| Build L0 | なし | なし | +| Build L1 | Provenanceがある | ビルド来歴の不透明さ | +| Build L2 | Provenanceがあり
ビルド基盤がそれを生成・署名する | Provenanceの改ざん | +| Build L3 | Provenanceがあり
ビルド基盤を堅牢化している | ビルドの干渉 | + +L3での堅牢化は具体的に以下 + +- ビルド間の隔離。並行・過去の別ビルドから影響を受けないこと +- provenanceの署名に使う秘密情報に、ビルドステップから触れないようにすること + +## Build Level上げ + +前回のワークフローは同一jobからprovenanceを生成・署名していた + +```yaml "id-token: write" "steps" # .github/workflows/build.yml (前回) name: build-and-attest @@ -58,41 +71,38 @@ jobs: attestations: write artifact-metadata: write - steps: - - uses: actions/checkout@ - - uses: actions/setup-go@ - with: - go-version: '1.24' - - name: Build - run: | - mkdir -p dist - go build -o dist/slsa-workshop main.go - - name: Generate provenance - uses: actions/attest@ - with: - subject-path: dist/slsa-workshop + steps: + - uses: actions/checkout@ + - uses: actions/setup-go@ + with: + go-version: '1.24' + - name: Build + run: | + mkdir -p dist + go build -o dist/slsa-workshop main.go + - name: Generate provenance + uses: actions/attest@ + with: + subject-path: dist/slsa-workshop ``` -`id-token: write`の権限はjobの全ステップに及ぶため、 -侵害されると同じjobのOIDCトークンで署名されうる - -これをBuild track L3にするには、分離をする +`id-token: write`の権限は全ステップに及ぶ +そのためトークン盗取が署名の偽造につながる -GitHub Actionsで実践するなら以下 +これの対策としてBuildとGenerate provenanceをjob分離する -1. ビルドとattestを再利用ワークフローに移す -2. 呼び出し側ワークフローで必要権限とともに呼び出し +### job分離 +わけてみた -### 再利用ワークフロー - -```yaml "build:" "attest:" {29-32,52-57} -# .github/workflows/reusable-build.yml - -name: reusable-build +```yml {13,31} "id-token: write" +# .github/workflows/build.yml +name: build-and-attest on: - workflow_call: + push: + branches: [main] + workflow_dispatch: permissions: contents: read @@ -101,33 +111,20 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read # id-token: writeを持たせない - outputs: - digest: ${{ steps.hash.outputs.digest }} - + contents: read steps: - uses: actions/checkout@ - with: - persist-credentials: false - - uses: actions/setup-go@ with: go-version: '1.24' - cache: false # 信頼できないワークフローのキャッシュを読まないようにキャッシュを切る - + cache: false # 自動キャッシュをクリア - name: Build run: | mkdir -p dist go build -o dist/slsa-workshop main.go - - - name: Digest - id: hash - run: echo "digest=sha256:$(sha256sum dist/slsa-workshop | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - - - name: Upload binary - uses: actions/upload-artifact@ + - uses: actions/upload-artifact@ with: - name: slsa-workshop + name: dist path: dist/slsa-workshop attest: @@ -137,29 +134,52 @@ jobs: id-token: write attestations: write artifact-metadata: write - steps: + - uses: actions/download-artifact@ + with: + name: dist + path: dist - name: Generate provenance uses: actions/attest@ with: - subject-name: slsa-workshop - subject-digest: ${{ needs.build.outputs.digest }} + subject-path: dist/slsa-workshop ``` -再利用ワークフローの中の`github`コンテキストは呼び出し側のものになる +`id-token: write` を署名側だけに付与しており、権限もスコープを絞る + +### 切り出し -### 呼び出しワークフロー +当たり前だが今のワークフロー内を書き換えればビルドステップも書き換わる -前回のbuild.ymlは呼び出すだけのワークフローになる +`go build -o dist/slsa-workshop main.go`にパラメータを好きに追加でき、ビルドに関わる環境変数も入れ放題、チェックアウトして別コミットのビルドもできる + +build jobが侵害されたらそのまま署名まで通る +attestはそれを防がない + +ここでbuild と attestだけ取ったワークフローを別リポジトリの再利用可能なワークフローに切り出し、管理を分ける ```yml -# .github/workflows/build.yml +# <別リポジトリ>/.github/workflows/build.yml name: build-and-attest -# 省略 -permissions: - contents: read +on: + workflow_call: + +(...先程同様の内容...) +jobs: + build: + runs-on: ubuntu-latest + (...) + + attest: + needs: build + (...) +``` + +元のワークフローはそれを呼ぶだけにする + +```yaml {8} jobs: build: permissions: @@ -167,85 +187,90 @@ jobs: id-token: write attestations: write artifact-metadata: write - uses: ./.github/workflows/reusable-build.yml + uses: <別リポジトリ>/.github/workflows/build.yml@ ``` -再利用ワークフローは、呼び出し側が渡した以上の権限を持てないので呼び出し側のjobにも`id-token: write`などが要る +buildに不要な`id-token`や`attestations: write`を呼び出し側で付与しているが、 +再利用ワークフローのbuild job内でもpermissionsを定義していなければ使えないので、呼び出し元でまとめて権限を付与してok -これで分離はできた -しかし、L3が防ぐのは署名鍵の盗難とprovenanceの偽造で、ワークフロー定義を書き換えられる場合には耐えない -侵害されたビルドが悪意あるバイナリを正しくハッシュし、それに正規のprovenanceを出すことまでは防げない +再利用ワークフローの利点として再利用性もだが、 +呼び出し先で定義した変数経由でしかビルドに介入できないことがあげれる -そこまで防ぐなら再利用ワークフローを別リポジトリに置くなどの工夫が考えられる +ここまできてL3といえる -## 利用側の検証ポリシーを締める +## 利用側の検証ポリシー -前回の検証コマンドをもう一度 +別リポジトリの再利用ワークフローに切り出したバイナリを検証してみる -```zsh +前回のコマンドを打ってみる + +```zsh {9,12} % gh attestation verify ./slsa-workshop --repo riiimparm/slsa-test -... +Loaded digest sha256:2701e4f26f820d52d3335d3aaa9f097f7945bdfa2c844a9cd6a4ccc25f4cee88 for file://slsa-workshop +Loaded 1 attestation from GitHub API + +The following policy criteria will be enforced: +- Predicate type must match:................ https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:... https://github.com/riiimparm +- Source Repository URI must match:......... https://github.com/riiimparm/slsa-test - Subject Alternative Name must match regex: (?i)^https://github\.com/riiimparm/slsa-test/ +- OIDC Issuer must match:................... https://token.actions.githubusercontent.com + +✗ Sigstore verification failed ``` -バイナリ生成元のリポジトリだけ指定しているが、 -これはデフォルトではSANはリポジトリ内ワークフローなら何でも通る +失敗した + +SANが別リポジトリワークフローになったからだ -攻撃者がリポジトリに`evil.yml`を足して適当なブランチで動かしても通る +--repoの指定だけだとそのリポジトリのワークフローを探してしまう -SANを厳格にするオプションがあるのでそれで検証を強めるほうがいいかもしれない +SANを指定するオプションがあるのでそちらを足して検証する -| フラグ | 照合先 | -| ---------------------------- | ------------------------------------------ | +| フラグ | 照合先 | +| ---------------------------- | ------------------------------------------------ | | `--signer-workflow` | 署名したワークフロー(SAN)`[host/]//`で指定 | -| `--signer-repo` | 署名したワークフローのリポジトリ | -| `--signer-digest` | 署名したワークフローのコミット(Build Signer Digest) | -| `--source-ref` | ビルド元のgit ref(Source Repository Ref) | -| `--source-digest` | ビルド元のコミット(Source Repository Digest) | -| `--deny-self-hosted-runners` | self-hosted runnerで作られたものを拒否 | -| `--cert-identity` | SANの完全一致 | +| `--signer-repo` | 署名したワークフローのリポジトリ | +| `--signer-digest` | 署名したワークフローのコミット(Build Signer Digest) | +| `--cert-identity` | SANの完全一致 | -再利用ワークフローで署名した場合、`--signer-workflow`か`--signer-repo`を使うのがよさそう +> [!note] また、同リポジトリに再利用ワークフローを置く場合、これらをつけなくてもSANの検証は通るが、SAN値に入るワークフローrefの前方一致しか見ない +>(上にもある通り`Subject Alternative Name must match regex: (?i)^https://github\.com/riiimparm/slsa-test/` ) +> そのため攻撃者がリポジトリに`evil.yml`を差し込み、そこから署名しても検証が通る -以下で「slsa-testのv0.2.0タグから、reusable-build.ymlがGitHubホストのrunnerでビルド・署名したもの」だけが通る +再利用ワークフローで署名した場合、`--signer-workflow`か`--signer-repo`を使うのがよさそう -```zsh -% gh attestation verify ./slsa-workshop \ +```zsh "riiimparm/slsa-test-reusable" {16} +% gh attestation verify ./slsa-workshop \ --repo riiimparm/slsa-test \ - --signer-workflow riiimparm/slsa-test/.github/workflows/reusable-build.yml \ - --source-ref refs/tags/v0.2.0 \ - --deny-self-hosted-runners + --signer-repo riiimparm/slsa-test-reusable \ + --source-ref refs/tags/v0.3.2 +Loaded digest sha256:2701e4f26f820d52d3335d3aaa9f097f7945bdfa2c844a9cd6a4ccc25f4cee88 for file://slsa-workshop +Loaded 1 attestation from GitHub API + The following policy criteria will be enforced: -- Predicate type must match:..................... https://slsa.dev/provenance/v1 -- Source Repository Owner URI must match:........ https://github.com/riiimparm -- Source Repository URI must match:.............. https://github.com/riiimparm/slsa-test -- Source repo ref must match:.................... refs/tags/v0.2.0 -- Subject Alternative Name must match regex:..... ^https://github.com/riiimparm/slsa-test/.github/workflows/reusable-build.yml -- OIDC Issuer must match:........................ https://token.actions.githubusercontent.com -- Action workflow Runner Environment must match : github-hosted +- Predicate type must match:................ https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:... https://github.com/riiimparm +- Source Repository URI must match:......... https://github.com/riiimparm/slsa-test +- Source repo ref must match:............... refs/tags/v0.3.2 +- Subject Alternative Name must match regex: (?i)^https://github\.com/riiimparm/slsa-test-reusable/ +- OIDC Issuer must match:................... https://token.actions.githubusercontent.com ✓ Verification succeeded! -``` - -## 再利用ワークフローを別リポジトリに置く +The following 1 attestation matched the policy criteria -ワークフロー定義の書き換え耐性として、再利用ワークフローを別リポジトリに移す案がある - -```yaml -# .github/workflows/build.yml -jobs: - build: - permissions: - contents: read - id-token: write - attestations: write - artifact-metadata: write - uses: <別リポジトリ>/.github/workflows/go-build.yml@<コミットSHA> +- Attestation #1 + - Build repo:..... riiimparm/slsa-test + - Build workflow:. .github/workflows/build.yml@refs/tags/v0.3.2 + - Signer repo:.... riiimparm/slsa-test-reusable + - Signer workflow: .github/workflows/reusable-build.yml@fce2a39b6a6ec8975652c6b991ce717db34c4cdc ``` -しかしこのリポジトリ管理も増える -ここで外部ビルダー(slsa-github-generator)を呼ぶ方法もあるが、現状推奨されていない +## 外部ビルダー + +別リポジトリだと管理が増える +信頼できるビルド先として外部ビルダー(slsa-github-generator)のワークフロー呼ぶ方法もあるが、現状推奨されていない slsa-github-generatorは積極的にはメンテナンスされておらず、GitHub artifact attestationsへ移行するよう案内されている ## 今できること @@ -254,5 +279,4 @@ slsa-github-generatorは積極的にはメンテナンスされておらず、Gi - ビルドと署名のjobを分離する(署名権限をビルドステップから外す) - 再利用ワークフローを別リポジトリに置く(定義の書き換えを少し難しくする) -- 検証時に `--signer-workflow` で署名したワークフローを固定する - +- 検証時に `--signer-workflow`か`--signer-repo` \ No newline at end of file