Supply Chain Security · 2026
Container Provenance
End-to-end GitOps container provenance pipeline using Sigstore + K8s admission webhooks. Stops npm/registry-style supply-chain attacks via keyless signing - stolen tokens can't publish trusted artifacts without CI OIDC identity.
- Sigstore
- Cosign
- Kubernetes
- ArgoCD
- GitHub Actions
- Helm
- Golang
- ▸ Lab
The problem
If a CI pipeline is compromised, the attacker can push a malicious image to the same registry path your cluster trusts. The cluster has no way to know - it pulls the image by tag, and the tag points wherever the attacker says.
This is a different class of problem from "the cluster is misconfigured." The cluster is doing exactly what it was told. The trust boundary was upstream, and nothing inside the cluster reaches across it.
The approach
I wanted three guarantees, in order:
- Every image gets signed in CI, with no long-lived private key anywhere.
- The cluster admission controller refuses to schedule pods backed by unsigned images.
- Every signing event is auditable in a transparency log I don't run.
Sigstore covers (1) and (3) directly. Kyverno on the cluster side handles (2) by hooking into the admission webhook.
Architecture
┌─────────────┐ keyless OIDC ┌────────┐ write ┌───────┐
│ GitHub CI │ ─────────────▶ │ Fulcio │ ────────▶ │ Rekor │
└─────┬───────┘ └────────┘ └───────┘
│ docker push ▲
▼ │ verify
┌─────────────┐ ┌────────┴──────────┐
│ Registry │ ◀── pull during admission ──── │ Kyverno (in-cluster) │
└─────────────┘ └───────────────────┘
│ allow / deny
▼
┌─────────────┐
│ kube-apiserver
└─────────────┘
A pod request hits the API server. Kyverno runs at the validating admission webhook, pulls the image's signature reference, asks Rekor "did this signature happen?", and compares the signing identity to the policy. A mismatch is a hard reject.
Implementation highlights
CI signs the image
permissions:
id-token: write # cosign keyless
- run: cosign sign --yes ghcr.io/me/app:${{ github.sha }}The id-token permission is the magic. GitHub mints a short-lived OIDC
token, Fulcio uses it to issue a certificate tied to the workflow
identity, and the signature lands in Rekor.
Cluster enforces the policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-signature
match: { resources: { kinds: ["Pod"] } }
verifyImages:
- imageReferences: ["ghcr.io/me/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/me/app/.github/workflows/release.yml@*"
issuer: "https://token.actions.githubusercontent.com"Enforce means a non-matching pod is rejected, not just audited. The
keyless attestor pins the policy to a specific workflow file at a
specific repository - a different repo signing under the same identity
won't satisfy it.
Results
- Demo: unsigned image → admission rejected with a clear
kubectl describereason. Signed image (after re-tagging) → pod scheduled. - Audit trail: every signing event landed in the public Rekor log and was retrievable by image digest.
- CI overhead: ~3 seconds added to the release job. Negligible.
What I learned
The conceptual win was understanding that "trust" in supply-chain security isn't a property of an image; it's a property of a chain of custody that can be replayed and verified. The image is the artefact; the signature is the receipt; Rekor is the notary. Each part is verifiable on its own, and together they tell you exactly who, what, when.
The production-grade extension list would include attestations (SBOMs, provenance per SLSA), per-repo verification keys, and a policy versioning strategy. But "verified at admission" alone moves the threat model.