Image Validation

Validate image signatures for your deployments

Overview

Kyverno is available on the cluster and lets you enforce that only container images signed with a key you control may start in your namespace. This is a self-service guardrail against accidentally deploying an unsigned, tampered, or wrong-registry image.

You can create policies of kind NamespacedImageValidatingPolicy in your own namespace. The cluster-scoped equivalent (ImageValidatingPolicy) is reserved for cluster administrators.

Note: Because the policy lives in your namespace, anyone with write access to that namespace can also modify or delete it. Treat it as a safety net for your own workflows, not as a boundary against privileged users.

Prerequisites

  • A namespace on the cluster (testuser-dev in the examples below)
  • cosign v3.x installed locally
  • Push access to a repository in harbor.uni-muenster.de

Create a signing key pair

cosign generate-key-pair

This writes cosign.key (encrypted with the passphrase you enter) and cosign.pub.

Sign your image

Always sign by digest, not by tag. Tags are mutable; a signature bound to a tag proves nothing after the tag moves.

IMAGE=harbor.uni-muenster.de/testuser/testimage
docker push $IMAGE:v1
DIGEST=$(crane digest $IMAGE:v1)

cosign sign --key cosign.key $IMAGE@$DIGEST

Verify locally before involving the cluster:

cosign verify --key cosign.pub $IMAGE@$DIGEST

Create the policy

Replace the namespace, the image glob, and the public key with your own values:

apiVersion: policies.kyverno.io/v1
kind: NamespacedImageValidatingPolicy
metadata:
  name: verify-images
  namespace: testuser-dev
spec:
  validationActions:
    - Deny
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: [CREATE, UPDATE]
        resources: [pods]
  matchImageReferences:
    - glob: "harbor.uni-muenster.de/testuser/testimage*"
  attestors:
    - name: local
      cosign:
        key:
          data: |
            -----BEGIN PUBLIC KEY-----
            MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOolTpYvhO5y6Xmyi97WMadLgtJlf
            ETaNiQ5G7OHWFSFoTEP2XPlbiGiGMk1rsiXvCCbfyktnt+AWQm+ERaQUpQ==
            -----END PUBLIC KEY-----
  validations:
    - expression: >-
        images.containers.all(img, verifyImageSignatures(img, [attestors.local]) > 0) &&
        images.initContainers.all(img, verifyImageSignatures(img, [attestors.local]) > 0) &&
        images.ephemeralContainers.all(img, verifyImageSignatures(img, [attestors.local]) > 0)
      message: "Image signature verification failed - must be signed with the local cosign key."
kubectl apply -f verify-images.yaml
kubectl get namespacedimagevalidatingpolicy -n testuser-dev

Test the policy

A signed image should start normally:

kubectl -n testuser-dev run signed --image=$IMAGE@$DIGEST

An unsigned one should be rejected:

kubectl -n testuser-dev run unsigned --image=harbor.uni-muenster.de/testuser/testimage:unsigned
Error from server: admission webhook "ivpol.validate.kyverno.svc-fail" denied the request:
Image signature verification failed - must be signed with the local cosign key.