Skip to content

ci(release): generate provenance in an isolated reusable workflow (SLSA L3) - #26

Merged
jmgilman merged 1 commit into
masterfrom
ci/slsa-l3-provenance
Jun 28, 2026
Merged

jmgilman merged 1 commit into
masterfrom
ci/slsa-l3-provenance

Conversation

@jmgilman

Copy link
Copy Markdown
Contributor

Summary

Reaches SLSA Build L3 by isolating provenance generation from the build, while keeping GitHub's attestation API (gh attestation verify) — the explicitly chosen tradeoff over slsa-github-generator (which would move provenance off GitHub's API). Follow-up to the mise (#24) and melange/apko (#25) PRs.

Why this is L3

L3 over L2 requires that the signing material be unreachable by the build's user-defined steps (run isolation + signing-key isolation; it does not require hermetic builds). Today the build and attest* run in the same job → L2. Moving the attestation into a reusable workflow (attest.yml, workflow_call) gives it its own execution context and OIDC identity that the build steps can't inject into — GitHub's documented L3 path.

What changed

  • .github/workflows/attest.yml (new, reusable) — does the provenance signing in isolation: actions/attest for the binary checksums (downloaded as an artifact) and actions/attest-build-provenance for the image (by name+digest).
  • release.yml — binary-release-assets drops its in-job actions/attest and uploads checksums.txt as an artifact; container-image-release drops its in-job attest-build-provenance (keeps keyless cosign sign + syft SBOM attestation); two new attest-binaries / attest-image jobs call attest.yml; the summary now verifies provenance against attest.yml (cosign verify still points at release.yml — the signer there is unchanged).
  • Signer follow-through: ghd.toml signer_workflow, stage_ghd_release_assets.py expected_signer (+ its unit test), and release-dry-run.yml expected_signer all move to attest.yml so ghd verification stays consistent.
  • Prose: README "Release Layer" + DELETE_ME updated (also fixes a stale "BuildKit provenance" line from the melange/apko migration).

Scope notes

  • SBOM attestation and the cosign image signature stay in the build job — they're separate controls, not the SLSA provenance. Only provenance needed isolating for L3.
  • L3 = unforgeable provenance, not a trustworthy build. The build job still computes the checksums/digest it passes; the guarantee is that the signed statement can't be tampered with because the key lives only in the isolated workflow.
  • This is GitHub's L3 claim (reusable-workflow isolation), not a slsa-verifier-recognized builder ID — the deliberate trade to keep gh attestation verify.

Verification

  • stage_ghd_release_assets.py unit tests pass with the new signer.
  • All three workflow YAMLs validate.
  • I'll dispatch release-dry-run on this branch (build path + the inline ghd-signer check) — green there means the restructure is sound.
  • Caveat: the attest.yml reusable workflow only runs on a real release.yml invocation (tag/dispatch), so the live attest path is validated by the throwaway prerelease-tag rehearsal we owe before the first real release — that single rehearsal now covers the melange/apko publish, cosign signing, and the L3 attestation at once.

🤖 Generated with Claude Code

…SA L3)

Move the binary-checksums and container-image PROVENANCE attestations out of the
build jobs into a new reusable workflow (attest.yml, workflow_call). A reusable
workflow runs in its own context with its own OIDC identity that the build steps
cannot reach — the SLSA Build L3 isolation requirement (in-job artifact
attestations are L2). Provenance stays on GitHub's attestation API, so
`gh attestation verify` still works; the signer-workflow is now attest.yml.

- attest.yml: actions/attest (binary checksums, via an uploaded artifact) +
  attest-build-provenance (image, by name+digest), keyed on inputs; reuses the
  action SHAs already pinned in the repo.
- release.yml: binary-release-assets drops in-job attest and uploads checksums.txt
  as an artifact; container-image-release drops in-job attest-build-provenance
  (keeps keyless cosign sign + syft SBOM attestation); new attest-binaries +
  attest-image jobs call attest.yml; the summary depends on them and points
  gh-attestation verify at attest.yml (cosign verify still points at release.yml,
  since the cosign signer is unchanged).
- ghd.toml signer_workflow, stage_ghd_release_assets.py expected_signer (+ its
  test fixture), and release-dry-run.yml expected_signer follow the new provenance
  signer so ghd verification stays consistent.
- README/DELETE_ME prose updated (also fixes a stale "BuildKit provenance" line
  left over from the melange/apko migration).

SBOM attestation and the cosign image signature stay in the build job — they are
separate controls, not the SLSA provenance. The tag-triggered attest path runs
only on a real tag; validate with a throwaway prerelease tag before a release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jmgilman
jmgilman merged commit 8d5007d into master Jun 28, 2026
15 checks passed
@jmgilman
jmgilman deleted the ci/slsa-l3-provenance branch June 28, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant