Git, CI/CD & GitOpsLXX · ProvenanceVerify
Consuming and verifying attestations — the verifier side and the policy
What you'll learn
- Verify a GitHub-generated attestation with gh attestation verify
- Pin the OIDC subject, the OIDC issuer, and the source repository in the verifier policy
- Author a Sigstore policy file that enumerates the accepted signers and sources
- Diagnose why a verification fails when the OIDC subject drift after a workflow rename
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The attestation is load-bearing only when the verifier checks it. An attestation the team generates but never verifies is an attestation the team could have not generated; the artifact is admitted without the policy being enforced. The verifier side is where the SLSA level is earned. The verifier fetches the attestation, checks the signature, checks the OIDC subject, checks the source, and admits or denies.
The verifier command
The gh attestation verify command is the platform-native
verifier for GitHub-generated attestations:
gh attestation verify ./dist/app-binary \
--owner org \
--repo app \
--signer-workflow 'org/app/.github/workflows/build.yml@refs/heads/main'
The command fetches the attestation from the GitHub
attestations API for the artifact digest, verifies the
signature against the Fulcio certificate in the Sigstore
bundle, checks the OIDC subject against the pinned workflow
path, and reports the attestation summary. The same command
can verify a container image instead of a file. The
--signer-workflow flag is the policy; the wildcard org/.*
is too permissive; the specific path is the policy.
The Sigstore policy file
The verifier policy can be authored as a Sigstore policy file and stored in the repository. The policy file enumerates the accepted signers, the expected sources, and the artifact shapes:
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: app-provenance
spec:
images:
- glob: "ghcr.io/org/app:*"
authorities:
- name: github-actions
keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "^https://github.com/org/app/.github/workflows/build.yml@refs/heads/main$"
The policy is the contract. The verifier fetches the
attestation, checks the OIDC issuer against issuer, checks
the OIDC subject against the subjectRegExp, and admits or
denies. The policy file is version-controlled, reviewed, and
deployed through the same change-management process as the
application code.
flowchart LR
A["Artifact"] --> B["Verifier"]
B --> C["Fetch attestation"]
C --> D["Check signature"]
D --> E["Check OIDC issuer"]
E --> F["Check OIDC subject"]
F --> G["Check source repo"]
G --> H["Admit"]
G --> I["Deny"]
The chain is signature, issuer, subject, source. Each step is a check; each check is a line in the policy file.
The producer and the verifier share a pin
The producer (the workflow) declares its identity by existing; the verifier declares the accepted identities by pinning. The two declared identities must match. The mismatch modes are a workflow path change, a source branch change, and a CI provider migration (the OIDC issuer URL changes). The fix is the same in every case: update the verifier pin to match the new identity, deploy the policy, and re-verify. The pin is the policy; the policy is the artifact.
The policy is the audit deliverable
The audit team consumes the policy file, not the binary. The
policy file documents which workflow can sign (the
subjectRegExp), which source repository is trusted (the
images glob), which OIDC issuer is trusted (the issuer
field), and which signing key was used (the keyless block).
The four lines are the policy; the audit team asks for the
policy file; the team hands over the policy file; the audit is
complete.
Production discipline
- Pin the workflow path, the OIDC issuer, and the source repository in the policy. Three pins; three lines.
- Wire the verifier into the admission controller. The policy is not enforced until the verifier runs.
- Version the policy file in the repository. The policy is a code artifact; the policy is reviewed.
- Update the policy when the producer changes. A mismatch is a verified reject; the policy must follow the producer.
Cross-course references
- Git, CI/CD & GitOps — Part LXX-05 (GitHub Actions Artifact Attestations) is the producer side; LXX-06 is the verifier side.
- Git, CI/CD & GitOps — Part LXIX-06 (Verify on Deploy) is the cosign verification pattern; LXX-06 extends it to attestations.
- Container Security for Production Sysadmins — Part VII (Admission Control) is the enforcement side that the verifier feeds.
Quiz
Knowledge check · 4 questions
Q1. Which flag on `gh attestation verify` scopes the artifact search to a specific GitHub owner?
Q2. Authoring a Sigstore policy file that pins the workflow path is not sufficient to satisfy the SLSA L2 verifier requirement.
Q3. Name the three pins the Sigstore policy file declares for a keyless GitHub Actions attestation, and what each one pins.
Q4. Diagnose why every verification is failing after the team renamed the workflow file, and recommend the fix.
Team T renames the workflow from `build.yml` to `build-push.yml` and updates the path in the repository. The build workflow runs successfully; the attestations are generated. The admission controller rejects every pod with the message 'OIDC subject does not match pinned workflow'. The team cannot deploy.
Passing score: 75%. Answers are checked in this browser.