UID2-6764: Add SLSA build provenance attestations to docker publish workflows#228
UID2-6764: Add SLSA build provenance attestations to docker publish workflows#228BehnamMozafari wants to merge 9 commits intomainfrom
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Attestation runs after the docker push but before the changelog/release steps. Without continue-on-error, an attest failure leaves a half-finished release: image pushed, no GitHub Release created. Tolerate attest failures during the v3 rollout so consumers aren't stuck mid-release if attestation breaks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This reverts commit 80a5560.
|
Can we do a real smoke test without "Attest build provenance" step being skipped? |
| with: | ||
| subject-name: ${{ inputs.docker_registry }}/${{ inputs.docker_image_name }} | ||
| subject-digest: ${{ steps.push.outputs.digest }} | ||
| push-to-registry: true |
There was a problem hiding this comment.
for both these attestations, is it possible to add a verification step that runs gh attestation verify?
There was a problem hiding this comment.
Done — extracted into a new actions/attest_image composite action that runs both actions/attest@v4.1.0 and gh attestation verify against the just-pushed digest. Both shared workflows now call it. See PR description for the full smoke-test evidence (run 25542801315, external verify exit 0).
| env: | ||
| NODE_OPTIONS: --max-http-header-size=32768 | ||
| with: | ||
| subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ inputs.append_image_name }} |
There was a problem hiding this comment.
do we have potential case sensitivity issue here? image name has previously been lowercased but subject name is not?
There was a problem hiding this comment.
Real concern, partial fix needed. actions/attest@v4.1.0 already auto-lowercases subject-name when push-to-registry: true (src/main.ts passes downcaseName: inputs.pushToRegistry; src/subject.ts applies .toLowerCase()). However gh attestation verify does not lowercase the URI — so the new in-line verify step still needs a lowercased value. The new attest_image composite action lowercases once at the top and reuses for both signing and verifying. The smoke test's first run actually caught a real case-sensitivity failure (docker push rejected the mixed-case tag), so the concern was correct.
| if: ${{ inputs.not_snapshot == 'true' }} | ||
| uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 | ||
| env: | ||
| NODE_OPTIONS: --max-http-header-size=32768 |
There was a problem hiding this comment.
is this (and the one above) still required? If yes, can we add a comment explaining why
There was a problem hiding this comment.
Kept and commented. The new actions/attest_image/action.yaml has: # Mirrors actions/attest-build-provenance, prevents oversized OCI registry auth-challenge headers triggering HPE_HEADER_OVERFLOW.
| with: | ||
| subject-name: ${{ inputs.docker_registry }}/${{ inputs.docker_image_name }} | ||
| subject-digest: ${{ steps.push.outputs.digest }} | ||
| push-to-registry: true |
There was a problem hiding this comment.
Is it worth extracting this duplication into a small composite action?
There was a problem hiding this comment.
Done — actions/attest_image/action.yaml is the single implementation. Both shared workflows call IABTechLab/uid2-shared-actions/actions/attest_image@v3 instead of inlining the attest block.
Addresses jon8787's review comments on PR #228: - #2 verify step: attest_image now calls 'gh attestation verify' immediately after signing so misconfigured signatures fail at build time, not consumer pull time. - #3 case sensitivity: lowercase the image ref once and reuse it for both signing and verifying. actions/attest@v4 already lowercases subject-name internally when push-to-registry is true (verified at the pinned commit 59d8942 in src/main.ts and src/subject.ts), but 'gh attestation verify' does NOT lowercase the OCI URI we pass it; doing it ourselves keeps the signed name and the verified URI byte-identical. - #4 NODE_OPTIONS comment: brief comment explaining why we mirror actions/attest-build-provenance's defensive HTTP header bump. - #5 extract: pulled the attest+verify pair into a single composite action so the Java workflow and the non-Java composite action share one implementation. Adds .github/workflows/test-attest-image.yaml: a manually-dispatched smoke test that builds a throwaway image and exercises the full attest+verify path. Use this whenever attest_image or actions/attest@v4 changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop after merge — only here so the smoke test can run before the workflow file lands on main (gh workflow run / API dispatch require the file to exist on the default branch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github.repository is mixed case; docker rejects mixed-case tags at push time. Compute a lowercased ref once and reuse it for the push tag, the attest_image input, and the independent re-verify command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…st is green Run 25542801315 verified the attest+verify path end-to-end. Reverting to workflow_dispatch only so the test stops auto-firing and remains as an on-demand regression check after merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds SLSA build-provenance attestation to every non-snapshot image published by the shared docker workflows.
actions/attest_imagewraps the full attest+verify path: it lowercases the image ref once, callsactions/attest@v4.1.0(pinned to59d8942), and immediately runsgh attestation verifyagainst the just-pushed digest.shared-publish-java-to-docker-versioned.yamlandactions/shared_publish_to_docker/action.yamlnow callattest_image@v3instead of inlining the attest block.id-token: writeandattestations: write.not_snapshotguard.test-attest-image.yamlvalidates the composite action end-to-end without depending on a real consumer publish.Closes UID2-6764. Spike was UID2-5763.
Review-comment responses
test-attest-image.yamlworkflow exercises the full attest+verify path. Run25542801315succeeded — see "Smoke test evidence" below.gh attestation verifystepattest_imageso every release verifies in CI before any consumer pulls.subject-nameactions/attest@v4already auto-lowercasessubject-namewhenpush-to-registry: true(verified insrc/main.ts→downcaseName: inputs.pushToRegistry, applied atsrc/subject.tsline 47). However,gh attestation verifydoes not lowercase the OCI URI we pass it. To keep the signed name and the verify URI byte-identical,attest_imagelowercases once at the top and reuses the value for both. (The smoke test caught a real case-sensitivity failure in the first run when${{ github.repository }}evaluated toIABTechLab/...anddocker pushrejected it — proves the concern is real.)NODE_OPTIONSattest_image/action.yaml:Mirrors actions/attest-build-provenance, prevents oversized OCI registry auth-challenge headers triggering HPE_HEADER_OVERFLOW.actions/attest_image/action.yamlis the single implementation.Smoke test evidence
Run 25542801315 —
test-attest-image.yamlonbmz-UID2-6764-artifact-attestation, all 9 steps green in 38s.Test image:
ghcr.io/iabtechlab/uid2-shared-actions/test-attest@sha256:e008cbdd1c67eee898020ad96d56ff0d42d762585ef4c1153479abaf5a4112bbVerified certificate identity:
{ "ref": "refs/heads/bmz-UID2-6764-artifact-attestation", "workflow": "https://github.com/IABTechLab/uid2-shared-actions/.github/workflows/test-attest-image.yaml@refs/heads/bmz-UID2-6764-artifact-attestation", "builder": "github-hosted", "predicateType": "https://slsa.dev/provenance/v1" }The chain matches end to end: image digest → SLSA v1 provenance → workflow file at the exact ref → github-hosted runner identity.
Pre-merge cleanup (resolved)
push:trigger ontest-attest-image.yaml(added so the test could run before the workflow file exists onmain) removed in commit688a818. Workflow now only runs viaworkflow_dispatch(after merge to main, since the dispatch API requires the file on the default branch).Test plan
IABTechLab/uid2-admin(Java path) — run 25421656856 — workflow succeeded, "Attest build provenance" step skipped (proves thenot_snapshotguard works).gh attestation verifysucceeds.v3float is promoted; verified digests will be recorded in the UID2-6764 ticket.Caller-repo follow-up — already opened (one PR each, all open as of 2026-05-08)
Each grants
id-token: write+attestations: write(plus the implicit defaults the publish job already relied on). They're additive and harmless until this PR merges andv3is promoted.SDK images are explicitly out of scope; follow-up ticket to be filed separately.