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-----
ctlog:
insecureIgnoreTlog: true # Skip transparency log verification
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."
Note: we are skipping the Tlog verification since access to the external Sigstore servers is blocked in the clusters. As we are not using keyless signing the cryptographic verification against your public key is unaffected by this.
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.