You can scan every container image in your pipeline. You can generate SBOMs for every build. You can sign images with cosign and verify them locally. None of it matters if Kubernetes will happily deploy an unsigned, unscanned, vulnerability-riddled image when someone runs kubectl apply.
Supply chain policy engines are the enforcement layer. They sit between the developer's kubectl apply and the scheduler's pod creation, rejecting workloads that do not meet your security requirements. Without them, every other supply chain security investment is advisory.
The Enforcement Gap
Consider a typical pipeline:
- Developer pushes code
- CI builds a container image
- Scanner finds 3 critical CVEs
- Scanner reports findings to a dashboard
- Developer deploys the image anyway
- Image runs in production with 3 critical CVEs
Step 4 is where most organizations stop. They have visibility. They lack enforcement. The dashboard shows red, but nothing prevents the deployment.
Policy engines close this gap by intercepting Kubernetes API requests and validating them against your security requirements before the pod is created.
How Kubernetes Admission Works
Kubernetes admission controllers intercept API requests after authentication and authorization but before persistence to etcd:
kubectl apply → API Server → Authentication → Authorization → Admission → etcd
Two types of admission webhooks:
- Mutating: Modify the request (e.g., inject sidecar containers, add labels)
- Validating: Accept or reject the request (e.g., block unsigned images)
Supply chain policy engines typically use validating admission webhooks. Some also use mutating webhooks to inject metadata or modify image references.
Sigstore and Cosign: The Foundation
Before you can enforce signatures, you need a signing infrastructure. Sigstore provides the standard toolchain:
Signing Images
# Keyless signing (uses OIDC identity)
cosign sign registry.example.com/myapp@sha256:abc123
# Key-based signing
cosign generate-key-pair
cosign sign --key cosign.key registry.example.com/myapp@sha256:abc123
Attaching Attestations
# Attach vulnerability scan results
cosign attest --predicate scan-results.json \
--type vuln \
registry.example.com/myapp@sha256:abc123
# Attach SBOM
cosign attest --predicate sbom.spdx.json \
--type spdx \
registry.example.com/myapp@sha256:abc123
Verification
# Verify signature
cosign verify registry.example.com/myapp@sha256:abc123
# Verify attestation
cosign verify-attestation \
--type vuln \
registry.example.com/myapp@sha256:abc123
Kyverno for Supply Chain Policies
Kyverno is a Kubernetes-native policy engine that uses custom resources instead of a separate policy language.
Require Image Signatures
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.example.com/*"
attestors:
- entries:
- keyless:
issuer: https://token.actions.githubusercontent.com
subject: "https://github.com/myorg/*"
This rejects any pod using an image from your registry that is not signed by your GitHub Actions pipeline.
Require Vulnerability Scan Attestation
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-vuln-scan
spec:
validationFailureAction: Enforce
rules:
- name: check-vulnerabilities
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.example.com/*"
attestations:
- type: https://cosign.sigstore.dev/attestation/vuln/v1
conditions:
- all:
- key: "{{ scanner.result.summary.criticalCount }}"
operator: Equals
value: "0"
- key: "{{ scanner.result.summary.highCount }}"
operator: LessThanOrEquals
value: "5"
This allows deployment only if the vulnerability scan attestation shows zero critical CVEs and five or fewer high CVEs.
Require SBOM Attestation
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-sbom
spec:
validationFailureAction: Enforce
rules:
- name: check-sbom
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.example.com/*"
attestations:
- type: https://spdx.dev/Document
conditions:
- all:
- key: "{{ spdxVersion }}"
operator: NotEquals
value: ""
No SBOM attached to the image? No deployment.
OPA Gatekeeper for Supply Chain Policies
OPA Gatekeeper uses Rego, a purpose-built policy language. It is more flexible than Kyverno but has a steeper learning curve.
Restrict Image Registries
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith(container.image, input.parameters.registries[_])
msg := sprintf("Image '%v' is not from an allowed registry", [container.image])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: allowed-registries
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
registries:
- "registry.example.com/"
- "cgr.dev/chainguard/"
Block Images with Tags (Require Digests)
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredigest
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not contains(container.image, "@sha256:")
msg := sprintf("Image '%v' must use a digest, not a tag", [container.image])
}
Tags are mutable. An attacker who compromises your registry can overwrite a tag with a malicious image. Digests are immutable. Requiring digests ensures the exact image you built and scanned is the one that runs.
Connaisseur: Dedicated Image Verification
Connaisseur is a focused admission controller that only does image signature verification:
# helm values.yaml
validators:
- name: cosign
type: cosign
trustRoots:
- name: default
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
policy:
- pattern: "registry.example.com/*"
validator: cosign
with:
trustRoot: default
- pattern: "*"
validator: deny
This is simpler than Kyverno or Gatekeeper when image verification is your only requirement.
Building a Complete Supply Chain Gate
A production-grade supply chain policy should enforce:
- Image source: Only from approved registries
- Image reference: By digest, not tag
- Signature: Valid cosign signature from your build pipeline
- Vulnerability scan: Attestation present, critical CVE count is zero
- SBOM: Present and in a standard format (SPDX or CycloneDX)
- Base image: From an approved list (Chainguard, distroless, internal base)
- Build provenance: SLSA attestation linking the image to source code
Each of these is a policy rule. Together, they create a comprehensive gate that blocks images that have not passed through your full supply chain security pipeline.
Rollout Strategy
Do not go from zero to full enforcement overnight.
- Audit mode first. Set policies to
Audit(Kyverno) ordryrun(Gatekeeper). See what would be blocked without actually blocking it. - Fix the pipeline. Add signing, scanning, and SBOM generation to CI/CD.
- Enforce on new namespaces. Start with new projects that can build compliance in from the start.
- Migrate existing workloads. Work through existing images, adding signatures and attestations.
- Full enforcement. Flip to
Enforcemode once all workloads comply.
How Safeguard.sh Helps
Safeguard.sh integrates with Kubernetes admission controllers to provide visibility into policy enforcement across your clusters. The platform tracks which images pass and fail your supply chain gates, identifies patterns in policy violations (specific teams, registries, or image types that consistently fail), and provides remediation guidance. For organizations building their supply chain policy from scratch, Safeguard.sh provides pre-built policy templates aligned with SLSA levels and NIST SSDF requirements, accelerating the path from advisory scanning to actual enforcement.