Image Signature Verification Policy with Secrets

This guide demonstrates how to use Kubernetes Secrets to store public keys for Kyverno image signature verification, providing better security and key management compared to embedding keys directly in policies.

TOC

Why Use Secrets for Public Keys?

Using Kubernetes Secrets for storing public keys offers several advantages:

  • Enhanced Security: Keys are stored securely in the Kubernetes Secret store
  • Easy Key Rotation: Update keys without modifying policies
  • Access Control: Use RBAC to control who can access the secrets

Quick Start

1. Generate and Store Keys in Secret

# Generate cosign key pair
cosign generate-key-pair

# Create secret from the public key file
kubectl create secret generic cosign-public-key \
  --from-file=cosign.pub=./cosign.pub \
  --namespace=kyverno

# Verify the secret was created
kubectl get secret cosign-public-key -n kyverno

2. RBAC Configuration for Keyverno

Create Service Account for Kyverno

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kyverno-secret-reader
  namespace: kyverno

Create Role for Secret Access

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: kyverno
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
  resourceNames: ["cosign-public-key", "team-keys"]  # Specific secrets only

Bind Role to Service Account

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: kyverno
subjects:
- kind: ServiceAccount
  name: kyverno-secret-reader
  namespace: kyverno
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

3. Create Policy Using Secret Reference

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-with-secret
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: check-signatures
      match:
        any:
        - resources:
            kinds: [Pod]
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: cosign-public-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        mutateDigest: true

4. Test the Configuration

# Sign an image
cosign sign --key cosign.key registry.company.com/app:v1.0.0

# Apply the policy
kubectl apply -f verify-with-secret.yaml

# Test with signed image (should work)
kubectl run test --image=registry.company.com/app:v1.0.0

# Test with unsigned image (should fail)
kubectl run test-fail --image=nginx:latest

Secret Creation Methods

Method 1: From File

# Create secret from existing cosign public key file
kubectl create secret generic cosign-public-key \
  --from-file=cosign.pub=./cosign.pub \
  --namespace=kyverno

Method 2: From Literal String

# Create secret with inline public key content
kubectl create secret generic cosign-public-key \
  --from-literal=cosign.pub="-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----" \
  --namespace=kyverno

Method 3: From YAML Manifest

apiVersion: v1
kind: Secret
metadata:
  name: cosign-public-key
  namespace: kyverno
  labels:
    app: kyverno
    component: image-verification
type: Opaque
stringData:
  cosign.pub: |
    -----BEGIN PUBLIC KEY-----
    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
    5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
    -----END PUBLIC KEY-----
kubectl apply -f cosign-secret.yaml

Common Use Cases

Scenario 1: Single Team with One Secret

Simple setup where one team manages all image signatures:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: single-team-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-team-signatures
      match:
        any:
        - resources:
            kinds: [Pod, Deployment, StatefulSet, DaemonSet]
      exclude:
        any:
        - resources:
            namespaces: [kube-system, kyverno]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/*"
        - "gcr.io/myproject/*"
        
        failureAction: Enforce
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: team-cosign-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        verifyDigest: true
        required: true

Scenario 2: Multi-Team with Different Secrets

Different teams have their own signing keys and secrets:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: multi-team-verification
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    # Frontend team images
    - name: verify-frontend-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [frontend-*]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/frontend/*"
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: frontend-team-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true
    
    # Backend team images
    - name: verify-backend-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [backend-*]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/backend/*"
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: backend-team-key
                namespace: kyverno
                key: cosign.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true

Scenario 3: Critical Images Requiring Multiple Signatures

High-security environments where multiple teams must sign critical images:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: critical-multi-signature
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-critical-images
      match:
        any:
        - resources:
            kinds: [Pod]
            namespaces: [production]
      
      verifyImages:
      - imageReferences:
        - "registry.company.com/critical/*"
        
        failureAction: Enforce
        
        attestors:
        # Security team signature (required)
        - count: 1
          entries:
          - keys:
              secret:
                name: security-team-key
                namespace: kyverno
                key: security.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        # Development team signature (required)
        - count: 1
          entries:
          - keys:
              secret:
                name: dev-team-key
                namespace: kyverno
                key: development.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        # Release team signature (required)
        - count: 1
          entries:
          - keys:
              secret:
                name: release-team-key
                namespace: kyverno
                key: release.pub
              rekor:
                url: https://rekor.sigstore.dev
        
        mutateDigest: true
        required: true

Scenario 4: Offline Environment with Secrets

Using secrets in air-gapped environments:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: offline-verification-with-secret
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-offline-images
      match:
        any:
        - resources:
            kinds: [Pod, Deployment, StatefulSet, DaemonSet]
      
      verifyImages:
      - imageReferences:
        - "registry.internal.com/*"
        - "airgap.company.com/*"
        
        failureAction: Enforce
        emitWarning: false
        
        attestors:
        - count: 1
          entries:
          - keys:
              secret:
                name: offline-cosign-key
                namespace: kyverno
                key: cosign.pub
              
              # Offline mode configuration
              rekor:
                url: ""                    # Empty URL for offline mode
                ignoreTlog: true           # Ignore transparency log
                ignoreSCT: true            # Ignore SCT
              
              ctlog:
                ignoreTlog: true           # Ignore certificate transparency log
                ignoreSCT: true            # Ignore SCT
        
        mutateDigest: true
        verifyDigest: true
        required: true