Artifact Attestation & Provenance
Exercise 2: Artifact Attestation & Provenance
Goal: Understand WHAT attestation contains, prove provenance with consumer-side verification, demonstrate tamper detection, and connect to the SLSA supply chain integrity framework.
π Open
docs/provenance-verification.mdβ record your provenance findings and tamper test results.
Threat Framing
Your OIDC workflow builds a container image and pushes it to a registry. But what if someone modifies the image in the registry between build and deploy? Or pushes a different image with the same tag?
Without attestation, you have no way to know. The deployment proceeds with a potentially compromised image.
Step 1: Understand Whatβs Inside an Attestation
Before running anything, understand the STRUCTURE of an attestation. Itβs not just βa signatureβ β itβs structured provenance metadata proving the complete chain of who/what/where/when/how:
Attestation Structure (SLSA Provenance v1.0):
ββ Subject βββββββββββββββββββββββββββββββββββββββββββββββ
β WHAT: Container image digest (sha256:abc123...) β
β The specific artifact being attested β
ββ Builder βββββββββββββββββββββββββββββββββββββββββββββββ€
β WHO built it: GitHub Actions (ephemeral, isolated) β
β Trust basis: Hosted runner managed by GitHub β
ββ Source ββββββββββββββββββββββββββββββββββββββββββββββββ€
β WHERE: Repository, commit SHA, branch, trigger event β
β Verifiable: "Was this triggered by a reviewed PR?" β
ββ Build Config ββββββββββββββββββββββββββββββββββββββββββ€
β HOW: Workflow file, steps, inputs, environment β
β Reproducible: Same inputs β same verifiable artifact β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
When you run
gh attestation verify, youβre verifying ALL of these fields β not just checking a signature.
Step 2: Review the Attestation Step in the OIDC Workflow
The .github/workflows/build-deploy-oidc.yml workflow already includes an attestation step. Examine it:
- name: Attest Build Provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: $/$
subject-digest: $
push-to-registry: true
This step:
- Signs the container image digest with a Sigstore-based attestation
- Records which commit, workflow, and trigger produced this artifact
- Pushes the attestation to the container registry alongside the image
Step 3: Run the Workflow
# Trigger a workflow run that builds and attests the image
gh workflow run build-deploy-oidc.yml --ref main
# Wait for completion
gh run watch
Step 4: Consumer-Side Verification
Now verify the attestation from the consumer side β as if you were a deployment pipeline or auditor pulling this image:
# Verify the attestation of the built image
gh attestation verify oci://${ACR_NAME}.azurecr.io/supply-chain-demo:latest \
--owner {owner}
Expected output includes the provenance metadata. Examine it to answer these questions:
| Question | Where to Find It |
|---|---|
| (a) What commit produced this artifact? | buildConfig.source.digest.sha1 or materials[0].digest |
| (b) What workflow built it? | buildConfig.invocation.configSource.entryPoint |
| (c) Was it triggered by a reviewed PR or a direct push? | metadata.buildInvocationID and trigger event type |
# Detailed provenance inspection
gh attestation verify oci://${ACR_NAME}.azurecr.io/supply-chain-demo:latest \
--owner {owner} \
--format json | jq '.verificationResult.signature.certificate'
Step 5: Tampering Proof β βThe Auditorβs Questionβ
An auditor asks: βHow do you know the image in production wasnβt modified after it was built?β Your answer: βWe can verify. Watch.β
Prove that attestation detects unauthorized modifications:
# Re-tag the image (simulating a tampering scenario)
docker pull ${ACR_NAME}.azurecr.io/supply-chain-demo:latest
docker tag ${ACR_NAME}.azurecr.io/supply-chain-demo:latest \
${ACR_NAME}.azurecr.io/supply-chain-demo:tampered
docker push ${ACR_NAME}.azurecr.io/supply-chain-demo:tampered
# Now verify the "tampered" tag β this should FAIL or show no attestation
gh attestation verify oci://${ACR_NAME}.azurecr.io/supply-chain-demo:tampered \
--owner {owner}
The verification fails because no attestation exists for this digest β proving that unattested or modified images are detectable.
Side-by-side result:
| Image | gh attestation verify |
Meaning |
|---|---|---|
Original (:latest) |
β Verified | Provenance chain intact β this IS what was built |
Tampered (:tampered) |
β Failed | No attestation for this digest β modified or unauthorized |
Step 6: Chain of Custody β Bridge to Exercise 3
Attestation doesnβt exist in isolation. Itβs one link in a chain of custody that spans the entire delivery pipeline:
Source Code β Build β ATTEST β Registry β Deploy (VERIFY) β Runtime (MONITOR)
β β β β β β
WS2 GitHub Exercise Container Admission Exercise 3
Guardrails Actions 2 (here) Registry Policy (Defender)
In production, you would enforce attestation verification at deployment time β a Kubernetes admission controller that BLOCKS deployments without valid provenance. This ensures the chain is unbroken from source to runtime.
Exercise 3 shows the other end of this chain: how Defender for Cloud traces FROM runtime BACK to source, using the provenance that attestation provides.
Step 7: Update THREAT-MODEL.md
Now fill in rows 4-5 of THREAT-MODEL.md:
| # | Attack Vector | Target Asset | Threat Actor | Current Control | Gap? |
|---|---|---|---|---|---|
| 4 | Build artifact tampered with in CI/CD pipeline | A3 β Container Images | T3 β CI/CD | β OIDC (no static secrets) + Artifact attestation | Covered (WS3) |
| 5 | Deployment artifact doesnβt match reviewed code | A4 β Deployed App | T3 β CI/CD | β
Artifact attestation + gh attestation verify |
Covered (WS3) |
# Edit THREAT-MODEL.md to fill in rows 4-5
# Replace the "?" placeholders with the mitigations above
Key Insight
Artifact attestation answers the question: βIs whatβs running in production actually what we reviewed and approved?β Without it, you are trusting the pipeline blindly. With it, every artifact carries cryptographic proof of its origin.
NIST Callout
NIST SP 800-204D states that each activity in the CI/CD pipeline must maintain integrity against unauthorized modification. Artifact attestation is how we prove that integrity β every build is signed, every deployment is verifiable.
SLSA Framework Alignment
GitHub artifact attestations achieve SLSA Level 3 (Supply-chain Levels for Software Artifacts), an industry standard developed by the OpenSSF:
| SLSA Level | Requirement | GitHub Attestations |
|---|---|---|
| L1 | Provenance exists (metadata recorded) | β |
| L2 | Signed provenance from hosted build | β |
| L3 | Hardened build platform + verifiable provenance | β (GitHub Actions) |
| L4 | Two-person review + advanced isolation | Partially (with branch protections from WS2) |
When an auditor asks βWhat SLSA level do you achieve?β β the answer is Level 3, backed by GitHub artifact attestations with Sigstore-based signing.
Tip: Run
scripts/verify-exercise2.shto validate your Exercise 2 completion.