Step 2

Artifact Attestation & Provenance

On this page

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.sh to validate your Exercise 2 completion.

← β†’ to navigate between steps