Kubernetes Security

Kubernetes Supply Chain Policy Engines: Enforcing What Gets Deployed

Scanning for vulnerabilities means nothing if you cannot enforce the results. Supply chain policy engines in Kubernetes turn security findings into hard deployment gates.

Alex
Security Architect
6 min read

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:

  1. Developer pushes code
  2. CI builds a container image
  3. Scanner finds 3 critical CVEs
  4. Scanner reports findings to a dashboard
  5. Developer deploys the image anyway
  6. 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:

  1. Image source: Only from approved registries
  2. Image reference: By digest, not tag
  3. Signature: Valid cosign signature from your build pipeline
  4. Vulnerability scan: Attestation present, critical CVE count is zero
  5. SBOM: Present and in a standard format (SPDX or CycloneDX)
  6. Base image: From an approved list (Chainguard, distroless, internal base)
  7. 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.

  1. Audit mode first. Set policies to Audit (Kyverno) or dryrun (Gatekeeper). See what would be blocked without actually blocking it.
  2. Fix the pipeline. Add signing, scanning, and SBOM generation to CI/CD.
  3. Enforce on new namespaces. Start with new projects that can build compliance in from the start.
  4. Migrate existing workloads. Work through existing images, adding signatures and attestations.
  5. Full enforcement. Flip to Enforce mode 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.