k8s-security.pro
#25 Supply Chain Security enterprise

Cosign Image Signing & Verification

Enforce container image signing and verification using Cosign/Sigstore with Kyverno admission policies to prevent deployment of unsigned images.

CIS Benchmark
5.4.1
MITRE ATT&CK
T1195.002T1525

Overview

This template enforces container image signing and verification using Cosign (part of the Sigstore project) combined with Kyverno admission control. Every container image deployed to your cluster must be cryptographically signed, preventing attackers from injecting malicious images by compromising a registry or mutating tags.

Security threat addressed: Without image signature verification, an attacker who gains access to your container registry can replace legitimate images with malicious ones. Tag mutability means myapp:v1.0 can be silently overwritten with malware. Cosign signatures guarantee the image was built by your trusted CI/CD pipeline.

When to use: Implement this in production clusters after establishing a signing workflow in your CI/CD pipeline. Start by signing images with cosign sign, then deploy these policies to enforce verification.

Threat Model

  • Registry compromise defense: Even if an attacker gains write access to your registry, unsigned or incorrectly signed images are rejected at admission.
  • Tag mutability protection: Signatures are tied to the image digest (SHA256), not the tag. Overwriting a tag breaks the signature.
  • Supply chain integrity: Guarantees that only images built by your authorized CI/CD pipelines are deployed.

MITRE ATT&CK:

  • T1195.002 — Supply Chain Compromise: Prevents deployment of compromised images from the supply chain.
  • T1525 — Implant Internal Image: Blocks deployment of attacker-crafted images that weren’t built by trusted pipelines.

YAML Source

# Kyverno ClusterPolicy — Verify image signatures (key-based)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
  labels:
    app.kubernetes.io/name: k8s-security
    app.kubernetes.io/part-of: k8s-security-pro
    app.kubernetes.io/managed-by: k8s-security-pro
  annotations:
    policies.kyverno.io/title: Verify Image Signatures
    policies.kyverno.io/category: Supply Chain Security
    policies.kyverno.io/severity: high
    policies.kyverno.io/description: >-
      Verifies that all container images are signed with Cosign using the
      configured public key. Unsigned or incorrectly signed images are blocked.
spec:
  validationFailureAction: Enforce
  background: false
  rules:
  - name: verify-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "registry.example.com/*"  # ACTION REQUIRED: Your registry
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              ACTION REQUIRED: Paste your Cosign public key here
              Generated with: cosign generate-key-pair
              -----END PUBLIC KEY-----

---
# Kyverno ClusterPolicy — Verify keyless signatures (Sigstore)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-keyless-signature
  labels:
    app.kubernetes.io/name: k8s-security
    app.kubernetes.io/part-of: k8s-security-pro
    app.kubernetes.io/managed-by: k8s-security-pro
  annotations:
    policies.kyverno.io/title: Verify Keyless Signatures (Sigstore)
    policies.kyverno.io/category: Supply Chain Security
    policies.kyverno.io/severity: high
spec:
  validationFailureAction: Enforce
  background: false
  rules:
  - name: verify-keyless
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "ghcr.io/your-org/*"  # ACTION REQUIRED: Your GHCR org
      attestors:
      - count: 1
        entries:
        - keyless:
            subject: "https://github.com/your-org/*"  # ACTION REQUIRED
            issuer: "https://token.actions.githubusercontent.com"
            rekor:
              url: https://rekor.sigstore.dev

---
# Kyverno ClusterPolicy — Require image digest (no tags)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-image-digest
  labels:
    app.kubernetes.io/name: k8s-security
    app.kubernetes.io/part-of: k8s-security-pro
    app.kubernetes.io/managed-by: k8s-security-pro
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-digest
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images must use a digest (sha256) instead of a tag."
      pattern:
        spec:
          containers:
          - image: "*@sha256:*"

Deployment

  1. Generate Cosign keys: cosign generate-key-pair
  2. Sign your images in CI/CD: cosign sign --key cosign.key registry.example.com/myapp@sha256:abc123
  3. Update the public key in the ClusterPolicy
  4. Apply policies: kubectl apply -f 25_cosign_image_signing.yaml
  5. Test with an unsigned image (should be rejected)

Verification

# Check policies are active
kubectl get clusterpolicy

# Test with signed image (should pass)
kubectl run test --image=registry.example.com/signed-image@sha256:abc123

# Test with unsigned image (should fail)
kubectl run test-unsigned --image=registry.example.com/unsigned-image:latest
# Expected: admission webhook denied the request

# Verify signature manually
cosign verify --key cosign.pub registry.example.com/myapp@sha256:abc123

Further Reading

Get Full Access to This Template

This template is included in the Enterprise tier and above.

View Pricing Plans