CI/CD Supply Chain Security
Proves the artifact, not just the source: keyless Sigstore signing, SBOM attestation, and a Kyverno policy that enforces both at cluster admission.

Proves the artifact, not just the source: keyless Sigstore signing, SBOM attestation, and a Kyverno policy that enforces both at cluster admission.

The secure CI/CD pipeline proves the source is clean. This repo proves the artifact is: that the container image a cluster is about to run was built by this pipeline, hasn't been tampered with since, and ships with a verifiable bill of materials. Without artifact signing, a clean source scan and a build step are not a complete chain of custody: anything that can push to your registry can substitute an image between build and deploy.
The mechanism is Sigstore. The pipeline signs every image keylessly with Cosign using the workflow's OIDC identity, attaches an SPDX SBOM as a signed attestation, and a Kyverno policy refuses to admit anything to the cluster that can't produce both.
There is no private key to manage or leak. Cosign requests a short-lived certificate from Fulcio bound to the GitHub Actions OIDC identity, signs the image digest, and logs the signature to the Rekor transparency log. Verification checks the certificate identity and issuer rather than a key you have to rotate, store securely, and recover if lost.
Everything operates on the image digest, never a mutable tag. The thing that gets signed is exactly the thing that was built. A tag can be moved after signing; a digest can't.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-signed-images
spec:
validationFailureAction: Enforce
failurePolicy: Fail
background: false
rules:
- name: check-cosign-signature
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences:
- "ghcr.io/<org>/*"
attestors:
- count: 1
entries:
- keyless:
subject: "https://github.com/<org>/*"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
mutateDigest: true
required: true
- name: require-sbom-attestation
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences:
- "ghcr.io/<org>/*"
attestations:
- type: https://spdx.dev/Document
attestors:
- count: 1
entries:
- keyless:
subject: "https://github.com/<org>/*"
issuer: "https://token.actions.githubusercontent.com"
Four jobs run sequentially after build:
The Kyverno policy then enforces the same checks at admission: a signed image from the pipeline is admitted; an arbitrary nginx:latest is rejected. policy/test/pods.yaml has one of each for kyverno apply to test against, which is what the admission-policy-check workflow runs on every PR that touches the policy file.
Why keyless is the right default for CI: there is no private key to store, rotate, or recover. The trust anchor is the GitHub OIDC identity that already exists. The workflow asks for id-token: write and Cosign handles the rest. The transparency log means every signature is auditable after the fact.