Image Validation
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.