The verification contract
A verifier accepts a claim only under a specified identity, artifact, and scope. The CI verifier evaluates the newly built digest and its current source revision. Admission evaluates a requested Pod image against a configured registry and policy. They overlap but do not implement identical predicates.
- Both paths check the trusted keyless identity and digest-bound artifact claims.
- CI additionally matches the invocation source commit and source material against github.sha.
- Admission checks builder, workflow entry point and canonical source URI without that commit/material predicate.
- Shared workflow/policy authority is a correlated failure dependency; these are not automatically independent roots.
Solid arrows indicate the stated handoff, not a claim of independent trust. Optional relationships are described in the text equivalent. Historical components are labeled in the caption.
Exact expected identity
Section titled “Exact expected identity”The keyless certificate subject is https://github.com/devSatym/gcp-supply-chain-security/.github/workflows/sign-attest.yml@refs/heads/main; the issuer is https://token.actions.githubusercontent.com. Kyverno specifies Rekor at https://rekor.sigstore.dev. Cosign’s CLI verification uses its certificate identity/issuer arguments and default transparency verification behavior; the workflow does not pass a flag to skip transparency checks.
The reusable signing workflow is the producer. The cloud service account authorizes access to the registry, while the Sigstore certificate identifies the GitHub workflow. These are distinct assertions from distinct exchanges even though both start from GitHub OIDC.
CI and admission comparison
Section titled “CI and admission comparison”| Property | CI verify.yml |
Kyverno block-unsigned-images |
|---|---|---|
| Image binding | Full digest image reference; signature’s docker-manifest-digest must equal build output |
verifyDigest: true, required: true, mutateDigest: false on matched images |
| Signing subject / issuer | Exact expected workflow on main and GitHub issuer | Same fixed canonical subject and issuer |
| Metadata location | COSIGN_REPOSITORY environment value |
Explicit metadata repository value |
| SPDX statement | Cosign verifies spdxjson; parsed predicate type https://spdx.dev/Document; subject digest match; reports package count |
Trusted signed attestation of that type; no package-content conditions |
| Provenance statement | Cosign verifies slsaprovenance; parsed predicate type https://slsa.dev/provenance/v0.2; subject digest match |
Trusted signed attestation of that type and digest-bound image verification |
| Builder | Exact https://github.com/actions/runner |
Same exact predicate condition |
| Entry point | Exact .github/workflows/sign-attest.yml |
Same exact predicate condition |
| Source URI | git+https://github.com/<current repository>@refs/heads/main |
Fixed canonical repository URI on main |
| Source commit | invocation.configSource.digest.sha1 == github.sha |
No explicit predicate condition for source commit |
| Material binding | At least one material with expected repository URI and current commit | No explicit materials condition |
| Evaluation scope | The build output digest in this workflow run | Pod images matching the protected GAR pattern, outside excluded namespaces |
The admission policy’s three explicit provenance conditions evaluate at the predicate body. They do not add the two CI source-commit/material filters. A CI pass is consequently stronger on those fields, while admission checks the actual image requested later. There is no separate signed “CI passed” statement that admission validates; it trusts the configured signing workflow’s output and its own narrower policy.
Scope and failure behavior
Section titled “Scope and failure behavior”The policy pattern is europe-west1-docker.pkg.dev/valiant-house-502004-k2/supply-chain-security/*. Namespaces kube-system, kyverno, argocd, crossplane-system, and cert-manager are excluded. A different registry, an unmatched path, or an excluded namespace is outside this check. The policy is Enforce with background reporting, but background evaluation does not retroactively evict workloads.
The single-image unsigned Pod capture records rejection of a regular container. The mixed and dedicated initContainer fixtures establish rejection of an unsigned initContainer alongside a signed regular container; they do not isolate an unsigned regular sidecar. Ephemeral-container updates and controller/autogeneration variants lack equivalent fresh execution evidence here. Do not infer coverage of every mutation path from an image-verification feature’s general capability.
Absent, wrong, or unreadable statements can prevent a protected image from passing the configured checks. Registry or verifier outages raise an availability question: actual webhook failurePolicy and deployed version/configuration must be inspected. Enforce alone is insufficient evidence of fail-closed outage behavior.
What these checks do not prove
Section titled “What these checks do not prove”An authentic SBOM proves the trusted producer attached an inventory, not that the inventory is complete or packages are safe. Workflow-generated provenance declares its builder/source inputs; this schema alone does not establish a SLSA assurance level or an isolated, tamper-proof builder. A compromised workflow could sign malicious content with the expected identity and fabricate matching assertions.
CI and admission share GitHub, the signing workflow, registry metadata, and Sigstore trust dependencies. Checking twice catches handoff errors and later unauthorized selections; it is not two independent proofs that a trusted producer is honest. Runtime observation covers another time and failure class, while the decisions page records remaining trade-offs.