Image Signature Verification Policy
This guide demonstrates how to configure Kyverno to verify that container images are properly signed before they can run in a Kubernetes cluster. Think of it like checking an ID card - only images with valid "signatures" are allowed in.
TOC
What is Image Signature Verification?
Image signature verification is like having a security guard check IDs at the door. It ensures:
- Images are authentic: They come from who they claim to come from
- Images are untampered: No one has modified them after signing
- Only trusted images run: Unsigned or improperly signed images are blocked
- Audit trail: Track which images were verified and when
Quick Start
1. Generate Keys
# Create a signing key pair (like creating an ID card system)
cosign generate-key-pair
# This creates: cosign.key (private, keep secret) and cosign.pub (public, share freely)
2. Sign Images
# Sign images (like putting an official stamp on it)
cosign sign --key cosign.key registry.company.com/app:v1.0.0
3. Create Basic Verification Policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce # Block unsigned images
background: false
rules:
- name: check-signatures
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.company.com/*" # Check images from company registry
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# Paste the cosign.pub content here
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
mutateDigest: true # Convert tags to secure digest format
4. Test It
# Apply the policy
kubectl apply -f signature-policy.yaml
# Try to run an unsigned image (should fail)
kubectl run test --image=nginx:latest
# Try to run a signed image (should work)
kubectl run test --image=registry.company.com/app:v1.0.0
Common Use Cases
Scenario 1: Multiple Teams Need to Sign Critical Images
For critical applications, both the development team AND security team might need to sign images:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-dual-signatures
spec:
validationFailureAction: Enforce
background: false
rules:
- name: critical-app-signatures
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.company.com/critical/*"
attestors:
# Both teams must sign
- count: 1 # Security team signature
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# Security team's public key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
- count: 1 # Development team signature
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# Development team's public key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyctVd7iEcnessRQjU917hmKO6JWV
GHpDguIyakZA8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5==
-----END PUBLIC KEY-----
mutateDigest: true
Scenario 2: Different Rules for Different Environments
Production needs strict verification, development can be more relaxed:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: environment-specific-verification
spec:
validationFailureAction: Enforce
background: false
rules:
# Strict rules for production
- name: production-must-be-signed
match:
any:
- resources:
kinds:
- Pod
namespaces:
- production
verifyImages:
- imageReferences:
- "*" # All images must be signed
failureAction: Enforce # Block if not signed
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# Production signing key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
mutateDigest: true
# Relaxed rules for development
- name: development-warn-unsigned
match:
any:
- resources:
kinds:
- Pod
namespaces:
- development
- staging
verifyImages:
- imageReferences:
- "registry.company.com/*" # Only check company images
failureAction: Audit # Audit but allow unsigned images
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# Development signing key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyctVd7iEcnessRQjU917hmKO6JWV
GHpDguIyakZA8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5==
-----END PUBLIC KEY-----
mutateDigest: true
Scenario 3: Using Certificates Instead of Keys
For enterprise environments, X.509 certificates might be used:
# Sign with certificate
cosign sign --cert company-cert.pem --cert-chain ca-chain.pem \
registry.company.com/myapp:v1.0.0
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: certificate-verification
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-with-certificates
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.company.com/*"
attestors:
- count: 1
entries:
- certificates:
cert: |-
-----BEGIN CERTIFICATE-----
# Company's signing certificate (replace with real certificate)
MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTcwODI4MTExNzQwWhcNMTgwODI4MTExNzQwWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAuuExVilGcXIZ3ulNuL7wLrA7VkqJoGpB1YPmYnlS7sobTggOGSqMUvqU
BdLXcAo3ZCOXuKrBHBlltvcNdFHynfxOtkAOCZjirD6uQBrNPiQDlgMYMy14QIDAQAB
o1AwTjAdBgNVHQ4EFgQUhKs8VQFhVLp5J4W1sFVLOVgnQxwwHwYDVR0jBBgwFoAU
hKs8VQFhVLp5J4W1sFVLOVgnQxwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF
AAOCAQEAuuExVilGcXIZ3ulNuL7wLrA7VkqJoGpB1YPmYnlS7sobTggOGSqMUvqU
-----END CERTIFICATE-----
rekor:
url: https://rekor.sigstore.dev
mutateDigest: true