CKS — Image Signing & Verification

9 minute read

Image Signing & Verification

CKS Exam Domain: Supply Chain Security (20%)

Image scanning tells you if an image has known vulnerabilities. Image allowlisting restricts where images come from. But neither answers the question: Has this image been tampered with since it was built?

Image signing provides cryptographic proof of image integrity and provenance. If your cluster only accepts signed images, an attacker who compromises a registry, replaces a tag, or injects a malicious layer is blocked — the signature won’t match.


1. Why Image Signing Matters

Real-World Supply Chain Attacks

Attack Year What Happened How Signing Helps
SolarWinds 2020 Attacker injected backdoor into signed build artifacts Signing alone wasn’t enough — the build pipeline was compromised before signing. Need build provenance (SLSA).
Codecov 2021 Docker image modified in CI, exfiltrated env vars Signed images after build would detect modification
ua-parser-js 2021 npm maintainer account hijacked, malicious version published Signed artifacts would show different publisher
Log4Shell 2021 Vulnerable library in thousands of images SBOM + signature reveals affected images quickly

The Trust Gap

Without signing:

  Build Server ──→ Registry ──→ Kubernetes
                      │
                      └── Attacker replaces image at registry
                          (tag overwrite, registry compromise)
                          Kubernetes pulls malicious image unknowingly

With signing:

  Build Server ──→ Sign ──→ Registry ──→ Kubernetes ──→ Verify Signature
                                                            │
                                              Signature mismatch → REJECT

2. Cosign — Container Image Signing

Cosign is part of the Sigstore project. It is the standard tool for signing and verifying container images.

Install Cosign

# macOS
brew install cosign

# Linux
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Or download binary
COSIGN_VERSION=v2.2.4
curl -LO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64
chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign

3. Key-Based Signing (Traditional)

Generate a Key Pair

cosign generate-key-pair

This creates two files:

  • cosign.key — private key (password-protected)
  • cosign.pub — public key (distributed to verifiers)

Security: Protect the private key. Store it in a KMS (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault). Never commit it to Git.

Sign an Image

# Sign the image with the private key
cosign sign --key cosign.key registry.example.com/app:v1.0

# The signature is stored as a separate tag in the same registry:
#   registry.example.com/app:sha256-<digest>.sig

Verify a Signed Image

# Verify using the public key
cosign verify --key cosign.pub registry.example.com/app:v1.0

# Output shows the signature payload:
# Verification for registry.example.com/app:v1.0 --
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - The signatures were verified against the specified public key

# If verification fails:
cosign verify --key cosign.pub registry.example.com/app:v2.0-tampered
# Error: no matching signatures

Sign with Annotations

# Add metadata to the signature
cosign sign --key cosign.key \
  -a "team=platform" \
  -a "pipeline=github-actions" \
  -a "commit=$(git rev-parse HEAD)" \
  registry.example.com/app:v1.0

4. Keyless Signing with Sigstore

Keyless signing removes the need to manage long-lived signing keys. Instead, it uses short-lived certificates tied to an OIDC identity (GitHub, Google, Microsoft).

How Keyless Signing Works

┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  Developer   │───→│ OIDC Provider│───→│   Fulcio      │───→│   Rekor      │
│  (identity)  │    │ (GitHub/     │    │  (CA - issues │    │ (Transparency│
│              │    │  Google)     │    │  short-lived  │    │  Log - public│
│              │    │              │    │  cert)        │    │  record)     │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
                                              │
                                              ▼
                                    Certificate valid for
                                    ~10 minutes (ephemeral)
                                    Tied to OIDC identity

Components:

  • Fulcio — Certificate Authority that issues short-lived signing certificates based on OIDC tokens
  • Rekor — Transparency log that records every signing event (tamper-evident, append-only)
  • OIDC Provider — Identity provider (GitHub Actions, Google, Azure AD)

Keyless Sign

# In an interactive terminal (opens browser for OIDC auth)
cosign sign registry.example.com/app:v1.0
# Opens browser → authenticate → signs with ephemeral key → records in Rekor

# In CI (non-interactive, uses ambient OIDC token)
# GitHub Actions provides ACTIONS_ID_TOKEN_REQUEST_URL automatically
COSIGN_EXPERIMENTAL=1 cosign sign registry.example.com/app:v1.0

Keyless Verify

# Verify with identity constraints
cosign verify \
  --certificate-identity=build@company.com \
  --certificate-oidc-issuer=https://accounts.google.com \
  registry.example.com/app:v1.0

# Verify GitHub Actions signature
cosign verify \
  --certificate-identity=https://github.com/company/repo/.github/workflows/build.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  registry.example.com/app:v1.0

5. The Transparency Log (Rekor)

Rekor is a tamper-evident, append-only log that provides public accountability for signing events.

Search the Log

# Search for signing entries for an image
rekor-cli search --artifact registry.example.com/app:v1.0

# Get details of a specific entry
rekor-cli get --uuid <entry-uuid>

# Verify an entry hasn't been tampered with
rekor-cli verify --artifact registry.example.com/app:v1.0

Why Transparency Logs Matter

  • Accountability — Every signature is publicly recorded. A compromised key’s misuse is visible.
  • Tamper detection — If an entry is modified, the Merkle tree proof fails.
  • Incident response — After a breach, you can see exactly which images were signed by a compromised identity.

6. Admission Control for Signed Images

Signing is only useful if the cluster enforces signature verification. Without admission control, unsigned images still run.

Kyverno: Verify Image Signatures

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  rules:
  - name: verify-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
          - kyverno
    verifyImages:
    - imageReferences:
      - "registry.example.com/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----

Kyverno: Verify Keyless Signatures

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-keyless-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-keyless
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "registry.example.com/*"
      attestors:
      - entries:
        - keyless:
            subject: "https://github.com/company/repo/.github/workflows/build.yml@refs/heads/main"
            issuer: "https://token.actions.githubusercontent.com"
            rekor:
              url: https://rekor.sigstore.dev

Connaisseur

Connaisseur is a dedicated admission controller for image signature verification. It supports Cosign, Notary v1/v2, and custom validators.

# Connaisseur Helm values
validators:
- name: cosign-validator
  type: cosign
  trustRoots:
  - name: company-key
    key: |
      -----BEGIN PUBLIC KEY-----
      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
      -----END PUBLIC KEY-----

policy:
- pattern: "registry.example.com/*"
  validator: cosign-validator
  with:
    trustRoot: company-key
- pattern: "*"
  validator: deny          # Deny everything else

7. SBOM and SLSA: Beyond Signing

Software Bill of Materials (SBOM)

An SBOM lists every component in a container image — OS packages, application libraries, versions. It enables rapid vulnerability response.

# Generate SBOM with Trivy
trivy image --format spdx-json --output sbom.json registry.example.com/app:v1.0

# Generate SBOM with Syft
syft registry.example.com/app:v1.0 -o spdx-json > sbom.json

# Attach SBOM to image with Cosign
cosign attach sbom --sbom sbom.json registry.example.com/app:v1.0

# Sign the SBOM
cosign sign --key cosign.key --attachment sbom registry.example.com/app:v1.0

SLSA (Supply Chain Levels for Software Artifacts)

SLSA (pronounced “salsa”) is a framework for supply chain integrity. It defines levels of assurance:

Level Requirements Protects Against
SLSA 1 Build process documented Ad hoc builds
SLSA 2 Hosted build service, signed provenance Tampered builds
SLSA 3 Hardened build platform, non-falsifiable provenance Compromised build
SLSA 4 Hermetic, reproducible build, two-party review Insider threats

8. CI/CD Signing Pipeline

Complete GitHub Actions Example

name: Build, Sign, and Push
on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write       # Required for keyless signing

jobs:
  build-sign-push:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Install Cosign
      uses: sigstore/cosign-installer@v3

    - name: Login to Registry
      uses: docker/login-action@v3
      with:
        registry: registry.example.com
        username: $
        password: $

    - name: Build and Push
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: registry.example.com/app:$

    - name: Sign Image (keyless)
      run: |
        cosign sign \
          --yes \
          registry.example.com/app:$

    - name: Verify Signature
      run: |
        cosign verify \
          --certificate-identity=https://github.com/$/.github/workflows/build.yml@refs/heads/main \
          --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
          registry.example.com/app:$

Summary

Concept Purpose Tool
Key-based signing Sign with long-lived key pair cosign sign --key
Keyless signing Sign with OIDC identity, no key management cosign sign (Sigstore)
Transparency log Public, tamper-evident record of signatures Rekor
Admission enforcement Reject unsigned images at API server Kyverno, Connaisseur
SBOM List all components in an image Trivy, Syft
SLSA Framework for build provenance levels Build pipeline attestation

Practice Questions

Question 1

Generate a Cosign key pair, sign the image registry.example.com/api:v3.0, and verify the signature. Show all commands.

Show Answer
# 1. Generate key pair
cosign generate-key-pair
# Creates: cosign.key (private), cosign.pub (public)
# Prompts for password

# 2. Sign the image
cosign sign --key cosign.key registry.example.com/api:v3.0
# Enter password for cosign.key

# 3. Verify the signature
cosign verify --key cosign.pub registry.example.com/api:v3.0
# Output:
# Verification for registry.example.com/api:v3.0 --
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - The signatures were verified against the specified public key

Question 2

What is the difference between key-based signing and keyless signing with Sigstore?

Show Answer
Aspect Key-Based Keyless (Sigstore)
Key management You manage a long-lived key pair No long-lived keys; uses ephemeral certificates
Identity Whoever possesses the private key OIDC identity (email, GitHub Actions workflow)
Certificate None (just a key) Short-lived cert from Fulcio (~10 min)
Transparency Optional Mandatory — recorded in Rekor
Key rotation Manual, error-prone Not needed — certificates expire automatically
Compromise risk Key leak → attacker can sign anything Token theft → limited window (cert expires quickly)
Verification Public key Certificate identity + OIDC issuer

Keyless is preferred for CI/CD because there’s no private key to store, rotate, or leak. The identity is tied to the build pipeline, providing stronger provenance guarantees.

Question 3

Your cluster runs Kyverno. Write a policy that only allows images from registry.company.com/* that are signed by your team’s Cosign public key.

Show Answer
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  rules:
  - name: verify-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "registry.company.com/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...your-key...
              -----END PUBLIC KEY-----

Any Pod pulling from registry.company.com/* without a valid Cosign signature matching this public key will be denied.

Question 4

Explain how Rekor (the transparency log) helps during a security incident where a signing key is compromised.

Show Answer

When a signing key is compromised, Rekor enables:

  1. Timeline reconstruction — Search Rekor for all entries signed by the compromised key:
    rekor-cli search --public-key cosign.pub
    

    This returns every image this key ever signed, with timestamps.

  2. Scope determination — Compare the signing timestamps against the known compromise window. Any signatures made during the compromise period are suspect.

  3. Tamper evidence — Rekor entries cannot be modified or deleted (Merkle tree backed). If an attacker tried to inject a false entry, the tree proof would fail.

  4. Non-repudiation — The attacker cannot deny they used the key — the evidence is in the public log.

  5. Revocation action — Once you know which images were signed during the compromise window:
    • Update admission policies to reject those specific image digests
    • Re-sign legitimate images with a new key
    • Rotate the key pair
    • Alert all clusters consuming those images

Without Rekor, you have no visibility into what was signed or when.

Question 5

What is an SBOM and why is it important for container security?

Show Answer

An SBOM (Software Bill of Materials) is a comprehensive inventory of every component inside a container image — OS packages, application libraries, frameworks, and their exact versions.

Why it matters:

  1. Rapid CVE response — When a new CVE is disclosed (e.g., Log4Shell), search SBOMs to instantly identify every image that contains the vulnerable library, without scanning each image again.

  2. License compliance — Identify all open-source licenses in production images.

  3. Supply chain visibility — Know exactly what’s running in your cluster, down to the dependency level.

  4. Attestation — Attach the SBOM to the image as a signed artifact:
    cosign attach sbom --sbom sbom.json registry.example.com/app:v1.0
    cosign sign --key cosign.key --attachment sbom registry.example.com/app:v1.0
    
  5. Regulatory requirements — US Executive Order 14028 requires SBOMs for software sold to the federal government.

Generate with Trivy or Syft:

trivy image --format spdx-json --output sbom.json myimage:v1.0
syft myimage:v1.0 -o spdx-json > sbom.json

Updated: