Home DevOps Cosign and Image Signing: Stop Deploying Untrusted Containers
Advanced 7 min · July 11, 2026

Cosign and Image Signing: Stop Deploying Untrusted Containers

Cosign image signing with Sigstore — keyless signing via Fulcio CA + Rekor transparency log, cosign sign/verify, KMS integration (Azure, AWS, GCP, Vault), SLSA provenance attestation, GitHub Actions OIDC integration, Notary v2 (Notation) comparison, and Kyverno admission control for signed images..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 11, 2026
last updated
1,787
articles · all by Naren
Before you start⏱ 25 min
  • Docker image and registry basics
  • Understanding of public-key cryptography (signing, verification)
  • CI/CD pipeline experience (GitHub Actions, GitLab CI)
  • Basic Kubernetes admission controller concepts
 ● Production Incident 🔎 Debug Guide
Quick Answer

Sign an image with Cosign keyless mode: cosign sign ghcr.io/myuser/myapp:latest. This opens a browser for OIDC authentication (GitHub, Google, Microsoft), Fulcio issues a short-lived certificate, Cosign signs the image, and the signature is stored in the registry. Verify: cosign verify ghcr.io/myuser/myapp:latest uses the Rekor log to verify the signature without needing your public key. For CI/CD: cosign sign --oidc-issuer https://token.actions.githubusercontent.com --oidc-client-id sigstore ghcr.io/myuser/myapp:latest signs using the GitHub Actions OIDC token. For key-based signing: cosign generate-key-pair && cosign sign --key cosign.key ghcr.io/myuser/myapp:latest. Integrate with Kyverno admission controller to block unsigned images: cosign verify --key k8s://ns/signing-secret image:tag.

✦ Definition~90s read
What is Cosign Image Signing and Supply Chain Security?

Cosign is a container image signing, verification, and storage tool that is part of the Sigstore project (Linux Foundation). It enables developers to sign container images and other OCI artifacts using a simple CLI, with support for keyless signing via the Fulcio certificate authority and the Rekor transparency log.

Think of Cosign as a notary for your container images.

Cosign stores signatures as OCI artifacts alongside the image in the container registry (using .sig, .att, .sbom tags). It supports KMS integration (Azure Key Vault, AWS KMS, GCP KMS, HashiCorp Vault) for enterprise key management, and can attach arbitrary in-toto attestations (SLSA provenance, vulnerability reports, SBOMs).

Cosign integrates with Kubernetes admission controllers (Kyverno, OPA) to enforce that only signed images are deployed. The keyless mode eliminates the hardest part of image signing — key management — by using short-lived certificates from Fulcio that are authenticated via OIDC (OpenID Connect) and logged in Rekor for auditability.

Plain-English First

Think of Cosign as a notary for your container images. When you ship a package, you stamp it with a wax seal (the signature). Anyone receiving the package can check the seal to verify it's really from you and hasn't been tampered with. Traditional signing is like having a personal wax stamp that you guard with your life (a private key). If you lose it, someone can forge your seal. Cosign's keyless mode is like a digital notary office: you show your government ID (OIDC identity from GitHub/Google), the notary (Fulcio) stamps a temporary seal valid for only 10 minutes, you stamp your package, and the notary keeps a public record (Rekor) of the transaction. If anyone later claims the package is from you, anyone can check the public record to confirm.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You're deploying 50 containers a day from your CI pipeline. You scan each image for vulnerabilities, run tests, and promote through staging to production. But how do you know the image rolling out to production right now is exactly the one your CI built? How do you prove it hasn't been tampered with in the registry, during transit, or by a compromised registry admin? That's where image signing comes in.

Image signing with Cosign provides cryptographic proof of an image's origin and integrity. A signature on an image proves: (1) who signed it (the signer's identity verified via OIDC), (2) when it was signed (timestamp from Fulcio certificate or Rekor entry), and (3) the image hasn't been altered since signing (the signature covers the image manifest digest). Combined with an admission controller (Kyverno), you can enforce that every image deployed to your cluster has a valid signature from an approved identity.

Cosign is part of the Sigstore project, which has become the de-facto standard for container image signing since 2023. It's used by all major cloud providers, integrated into GitHub Actions and GitLab CI, and supported by all major registries (Docker Hub, GHCR, Harbor, ECR, GCR, ACR). The key innovation is keyless signing: using OIDC and short-lived certificates instead of long-lived private keys, eliminating the key management burden that made previous signing tools (Notary v1, GPG-based signing) impractical.

This guide covers Cosign in production: keyless signing with Fulcio and Rekor, KMS integration for enterprise key control, SLSA provenance attestations, CI/CD integration with GitHub Actions OIDC, a comparison with Notary v2 (Notation), and Kyverno admission policies for enforcing signed images. By the end, you'll have a complete image signing pipeline that ensures every container in your cluster is verifiably from your CI pipeline and unmodified.

Sigstore Ecosystem: Fulcio, Rekor, and Cosign Explained

Sigstore is not just Cosign — it's three components that work together to provide a complete signing infrastructure. Understanding each component's role is essential before deploying production signatures.

Fulcio is the certificate authority (CA) that issues short-lived X.509 certificates for signing. When you run cosign sign, it authenticates you via OIDC (Google, GitHub, Microsoft, or any OIDC provider), requests a certificate from Fulcio, and Fulcio issues a certificate binding your OIDC identity to a temporary key pair. The certificate is valid for 10 minutes. Fulcio's root CA is publicly known, so anyone can verify that a certificate was issued by Sigstore's trusted CA.

Rekor is the transparency log — an append-only, immutable ledger of all signing events. When Cosign signs an image, it creates an entry in Rekor containing the signature, the certificate, and the image digest. This serves as a public record: anyone can query Rekor to verify 'was image X signed by identity Y on date Z?' The Rekor log is verifiable using Merkle tree proofs, similar to Certificate Transparency. Rekor provides the public audit trail that makes keyless signing work — even if the short-lived certificate expires, the Rekor entry proves the signing occurred.

Cosign is the CLI tool that orchestrates the signing workflow. It generates a temporary key pair, gets it signed by Fulcio, runs the actual signing operation (signing the image manifest digest), stores the signature in the registry as an OCI artifact (.sig tag), and submits the signing record to Rekor. On the verification side, Cosign reads the signature from the registry, fetches the certificate from Rekor (or from the signature artifact), and verifies the signature against Fulcio's root CA.

The data flow: (1) cosign sign → (2) OIDC authentication → (3) Fulcio cert issuance → (4) Image digest signature → (5) Signature pushed to registry (.sig) → (6) Entry created in Rekor. Verification: (1) cosign verify → (2) Fetch .sig from registry → (3) Fetch cert from Rekor → (4) Verify cert against Fulcio root → (5) Verify signature against image digest.

sigstore-components.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// io.thecodeforge — DevOps tutorial

# Install Cosign (macOS)
brew install cosign

# Linux: https://github.com/sigstore/cosign/releases

# Check Sigstore infrastructure status
curl -s -o /dev/null -w "%{http_code}" https://fulcio.sigstore.dev/api/v2/healthCheck
# Should return 200

curl -s -o /dev/null -w "%{http_code}" https://rekor.sigstore.dev/api/v1/log
# Should return 200

# Keyless sign an image (interactive, opens browser for OIDC)
export COSIGN_EXPERIMENTAL=1
cosign sign ghcr.io/myuser/myapp:latest

# Verify the signature
cosign verify ghcr.io/myuser/myapp:latest

# Check Rekor entry for the signing event
cosign verify --rekor-url https://rekor.sigstore.dev ghcr.io/myuser/myapp:latest

# Get the Rekor entry UUID
rekor-cli search --artifact sha256:<image-digest>

# View the Rekor entry details
rekor-cli get --uuid <entry-uuid>
# Shows: signature, certificate, image digest, timestamp, OIDC identity

# For air-gapped: use key-based signing instead
cosign generate-key-pair
cosign sign --key cosign.key ghcr.io/myuser/myapp:latest
cosign verify --key cosign.pub ghcr.io/myuser/myapp:latest
Interview Gold: Why Keyless Signing Is Revolutionary
Before Sigstore, image signing failed because of key management: generating, storing, rotating, and revoking keys was too complex. Keyless signing solves this by using OIDC instead of keys. The Fulcio certificate expires in 10 minutes. The Rekor log provides long-term auditability. The only thing you need to protect is your OIDC identity (e.g., GitHub account with 2FA). This is why Cosign adoption exploded while Notary v1 never gained traction.
Production Insight
A large enterprise initially resisted keyless signing because 'we need to control our own keys.' They deployed key-based signing with AWS KMS. After 6 months, the operational burden of key rotation (each team needed new keys, revoked old ones, distributed public keys to every cluster) led them to switch to keyless. The tipping point: a forgotten key rotation left 30% of images unverifiable for 2 weeks before anyone noticed.
Key Takeaway
Sigstore has three components: Fulcio (CA for short-lived certs), Rekor (transparency log), and Cosign (CLI). Keyless signing uses OIDC instead of keys — no key management required. The Rekor log provides a permanent public audit trail.
docker-cosign-image-signing THECODEFORGE.IO Sigstore Ecosystem Architecture Layered components for image signing and verification Client Tools Cosign CLI | Notary v2 CLI Signing Services Fulcio CA | Rekor Transparency Log Key Management Azure Key Vault | AWS KMS | GCP KMS Registry OCI-compliant Registry Admission Control Kyverno Policy Engine THECODEFORGE.IO
thecodeforge.io
Docker Cosign Image Signing

Cosign Sign and Verify: Keyless and Key-Based Workflows

Cosign supports two signing workflows: keyless (recommended, no key management) and key-based (for air-gapped environments or compliance requirements).

Keyless signing: cosign sign <image> triggers an OIDC flow. In interactive mode, it opens a browser. In CI/CD, it reads the OIDC token from the environment. GitHub Actions provides ACTIONS_ID_TOKEN_REQUEST_TOKEN automatically. GitLab CI provides CI_JOB_JWT_V2. The resulting signature is stored in the registry as <image>.sig. Verification: cosign verify <image> reads the signature, fetches the Fulcio certificate from Rekor, and verifies.

Key-based signing: cosign generate-key-pair creates cosign.key (private) and cosign.pub (public). cosign sign --key cosign.key <image> signs with the private key. cosign verify --key cosign.pub <image> verifies with the public key. For CI/CD, store the private key in a KMS (Azure Key Vault, AWS KMS, GCP KMS, HashiCorp Vault) and use --kms flag: cosign sign --key azurekms://mykeyvault/key <image>.

Verification options: cosign verify checks the signature is valid. Add --check-claims to verify OIDC identity claims in the certificate. Add --cert-email <email> to require a specific email. Add --cert-oidc-issuer <issuer> to verify the OIDC issuer. In Kyverno policies, these checks are configured as conditions.

Multiple signatures: an image can have multiple signatures from different signers (CI pipeline, security team, QA). cosign verify checks all signatures by default. Use --signature to check a specific signature tag.

Signature storage: Cosign stores signatures as OCI artifacts named <image>:<digest>.sig. Some registries require the --allow-insecure-registry flag. Harbor, GHCR, Docker Hub, and all major registries support Cosign signatures.

cosign-sign-verify.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// io.thecodeforge — DevOps tutorial

# === KEYLESS WORKFLOW ===

# Sign (interactive)
export COSIGN_EXPERIMENTAL=1
cosign sign ghcr.io/myuser/myapp:latest

# Sign (GitHub Actions CI)
cosign sign \
  --oidc-issuer https://token.actions.githubusercontent.com \
  --oidc-client-id sigstore \
  ghcr.io/myuser/myapp:latest

# Verify
cosign verify ghcr.io/myuser/myapp:latest

# Verify with identity check
cosign verify \
  --cert-identity https://github.com/myuser/myapp/.github/workflows/ci.yml@refs/heads/main \
  --cert-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/myuser/myapp:latest

# === KEY-BASED WORKFLOW ===

# Generate keys
cosign generate-key-pair

# Sign with key
cosign sign --key cosign.key ghcr.io/myuser/myapp:latest

# Verify with public key
cosign verify --key cosign.pub ghcr.io/myuser/myapp:latest

# === KMS-BASED WORKFLOW ===

# Sign with Azure Key Vault
cosign sign --key azurekms://mykeyvault.vault.azure.net/mykey ghcr.io/myuser/myapp:latest

# Sign with AWS KMS
cosign sign --key awskms:///arn:aws:kms:us-east-1:123456789:key/mykey ghcr.io/myuser/myapp:latest

# Sign with GCP KMS
cosign sign --key gcpkms://projects/myproject/locations/global/keyRings/myring/cryptoKeys/mykey/versions/1 ghcr.io/myuser/myapp:latest

# Sign with HashiCorp Vault
cosign sign --key hashivault://mykey ghcr.io/myuser/myapp:latest
Senior Shortcut: Identity-Based Verification in Kyverno
Don't just verify that an image is signed — verify WHO signed it. In Kyverno, use cert-identity and cert-oidc-issuer to require that images are signed by your CI pipeline's OIDC identity. This prevents an attacker from signing images with their own Sigstore account and deploying them to your cluster.
Production Insight
A team used key-based signing but stored the private key in a CI secret. The secret was exposed in a build log. The attacker signed a malicious image with the 'authorized' key. Because verification only checked 'is there a valid signature?' (not 'who signed it?'), the malicious image passed Kyverno admission. The fix: switch to keyless signing where verification checks the OIDC identity, and add identity constraints to Kyverno policies.
Key Takeaway
Use keyless signing for most environments (no key management). Use key-based signing with KMS for air-gapped environments or compliance. Always verify both the signature AND the signer's identity.

SLSA Provenance Attestations: Proving How Your Images Were Built

Image signing proves the image hasn't been tampered with, but it doesn't tell you HOW the image was built. SLSA (Supply-chain Levels for Software Artifacts) provenance attestations fill this gap by attaching verifiable metadata about the build process: the source repository, build platform, build command, builder identity, and build timestamp.

Cosign supports attaching in-toto attestations to images. The cosign attest command takes a predicate (the attestation payload) and attaches it as an OCI artifact (.att tag). The standard predicate for build provenance is SLSA, stored at slsa.dev/provenance/v1.

A SLSA provenance attestation includes: builder identity (e.g., https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0), source repository URL, source revision (commit SHA), build command, build platform (OS, architecture), and build timestamp. This enables downstream verifiers to check: 'Was this image built from the expected repository at the expected commit by the expected builder?'

Generating SLSA provenance: In GitHub Actions, use the slsa-framework/slsa-github-generator action to generate SLSA provenance for your build artifacts. The output is a signed SLSA provenance attestation. Attach it with cosign attest --predicate <slsa.json> --type slsa.dev/provenance/v1 <image>.

Verifying attestations: cosign verify-attestation --type slsa.dev/provenance/v1 <image> checks that the attestation is valid and signed. Kyverno can verify attestations at admission time: checking the source repository matches your allowed list, the builder matches your CI pipeline, and the build was triggered by an approved event.

SLSA levels: Level 1 (provenance exists), Level 2 (provenance is generated by a hosted build platform), Level 3 (provenance resists tampering with strong cryptographic guarantees). Cosign + GitHub Actions (with OIDC) achieves SLSA Level 3 because the attestation is signed by the OIDC identity, preventing tampering.

slsa-provenance.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# io.thecodeforge — DevOps tutorial

name: Build and Sign with SLSA Provenance
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .

      - name: Push image
        run: docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

      # Generate SLSA provenance using the official generator
      - name: Generate SLSA provenance
        uses: slsa-framework/slsa-github-generator/.github/actions/generate-provenance@v2.0.0
        id: provenance
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: sha256:$(docker inspect --format='{{.RepoDigests}}' ghcr.io/${{ github.repository }}:${{ github.sha }} | grep -o 'sha256:[^"]*' | cut -d: -f2)

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

      - name: Sign image and attach SLSA provenance
        env:
          COSIGN_EXPERIMENTAL: 1
        run: |
          cosign sign ghcr.io/${{ github.repository }}:${{ github.sha }}
          cosign attest \
            --predicate ${{ steps.provenance.outputs.provenance-path }} \
            --type slsa.dev/provenance/v1 \
            ghcr.io/${{ github.repository }}:${{ github.sha }}

      # Tag as latest only after signing and provenance
      - name: Tag image
        run: |
          cosign copy ghcr.io/${{ github.repository }}:${{ github.sha }} \
                    ghcr.io/${{ github.repository }}:latest
Interview Gold: SLSA Levels Explained
SLSA Level 1: provenance exists (anyone can generate it). Level 2: provenance is generated by a hosted build platform (e.g., GitHub Actions, GitLab CI) with version-controlled build scripts. Level 3: provenance is signed by the build platform's OIDC identity and is tamper-proof. Cosign + Keyless signing achieves Level 3 because the attestation is signed by the CI pipeline's OIDC identity, which can't be forged by developers.
Production Insight
A banking client required SLSA Level 3 for all production deployments due to regulatory compliance. They implemented Cosign keyless signing with SLSA provenance in GitHub Actions. The audit trail: (1) Rekor shows the signing event. (2) SLSA attestation shows the source commit and builder. (3) Kyverno verifies both the Cosign signature and the attestation at admission. This satisfied their regulator's requirement for 'cryptographic proof of software supply chain integrity.'
Key Takeaway
SLSA provenance attestations show how an image was built: source repo, commit, builder, and build command. Cosign attest attaches signed SLSA predicates as OCI artifacts. Kyverno can verify attestations at admission. Cosign + GitHub Actions achieves SLSA Level 3.
Cosign vs Notary v2 (Notation) Choosing the right image signing tool Cosign Notary v2 (Notation) Keyless Signing Supported via Sigstore Not supported natively KMS Integration Azure, AWS, GCP, HashiCorp Limited to Azure Key Vault SLSA Provenance Built-in attestations Requires external tooling Admission Control Kyverno native support Requires custom policies Standard Compliance OCI artifact spec Notary v2 spec THECODEFORGE.IO
thecodeforge.io
Docker Cosign Image Signing

KMS Integration: Azure, AWS, GCP, and HashiCorp Vault

For organizations that require centralized key management (compliance, air-gapped environments, or existing KMS infrastructure), Cosign supports signing and verification keys stored in hardware security modules (HSMs) and cloud KMS providers.

Cosign's KMS integration uses the --key <kms-provider>://<key-path> flag. The KMS holds the private key; Cosign requests signing operations from the KMS without the key ever leaving the HSM. This satisfies compliance requirements where private keys must never be stored on disk.

Azure Key Vault: cosign sign --key azurekms://mykeyvault.vault.azure.net/mykey version <image>. Prerequisites: Azure CLI login, key in AKV with sign/verify permissions. Cosign uses the Azure SDK for authentication.

AWS KMS: cosign sign --key awskms:///arn:aws:kms:us-east-1:123456789:key/mykey <image>. Use a KMS key with Sign and Verify usage. Cosign supports asymmetric keys (RSA_2048, ECC_NIST_P256, ECC_SECG_P256K1).

GCP KMS: cosign sign --key gcpkms://projects/myproject/locations/global/keyRings/myring/cryptoKeys/mykey/versions/1 <image>. Requires Cloud KMS API enabled and appropriate IAM permissions.

HashiCorp Vault: cosign sign --key hashivault://mykey <image>. Configure Vault's transit engine with a key named mykey. Cosign uses VAULT_ADDR and VAULT_TOKEN for authentication.

KMS verification: cosign verify --key <kms-provider>://<key-path> <image>. Verification can be done by anyone with access to the KMS public key (no secret required).

Best practice: use KMS for key-based signing in regulated environments. Use automatic key rotation features (AWS KMS automatic rotation, GCP KMS rotation period). Audit all signing operations via KMS CloudTrail (AWS) or Audit Logs (GCP).

cosign-kms.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// io.thecodeforge — DevOps tutorial

# === Azure Key Vault ===
# Prerequisites: az login, key in AKV
az login

# Create a key in AKV
az keyvault key create --vault-name mykeyvault --name mykey --kty RSA --size 3072

# Sign with Azure KMS
cosign sign --key azurekms://mykeyvault.vault.azure.net/mykey ghcr.io/myuser/myapp:latest

# Verify
cosign verify --key azurekms://mykeyvault.vault.azure.net/mykey ghcr.io/myuser/myapp:latest

# === AWS KMS ===
# Create KMS key (asymmetric, sign/verify)
aws kms create-key --key-usage SIGN_VERIFY --customer-master-key-spec RSA_3072

# Sign with AWS KMS
cosign sign --key awskms:///arn:aws:kms:us-east-1:123456789:key/mykey-id ghcr.io/myuser/myapp:latest

# Verify
cosign verify --key awskms:///arn:aws:kms:us-east-1:123456789:key/mykey-id ghcr.io/myuser/myapp:latest

# === GCP KMS ===
# Create key ring and key
gcloud kms keyrings create myring --location global
gcloud kms keys create mykey --location global --keyring myring --purpose asymmetric-signing --default-algorithm rsa-sign-pkcs1-3072-sha256

# Sign with GCP KMS
cosign sign --key gcpkms://projects/myproject/locations/global/keyRings/myring/cryptoKeys/mykey/versions/1 ghcr.io/myuser/myapp:latest

# Verify
cosign verify --key gcpkms://projects/myproject/locations/global/keyRings/myring/cryptoKeys/mykey/versions/1 ghcr.io/myuser/myapp:latest

# === HashiCorp Vault ===
# Prerequisites: Vault transit engine enabled, key created
# vault secrets enable transit
# vault write -f transit/keys/mykey type=rsa-3072

# Sign with Vault
export VAULT_ADDR=https://vault.example.com:8200
cosign sign --key hashivault://mykey ghcr.io/myuser/myapp:latest

# Verify
cosign verify --key hashivault://mykey ghcr.io/myuser/myapp:latest
Senior Shortcut: KMS + Keyless = Best of Both
Some organizations use KMS for the CI/CD signing key AND keyless OIDC for developer verification. CI/CD signs with the KMS key (centralized control, audited). Developers verify signatures with cosign verify --key k8s://ns/signing-secret (the public key stored in a Kubernetes secret). This gives centralized control over signing while making verification accessible to everyone.
Production Insight
A fintech company needed FIPS 140-2 compliance for their signing infrastructure. They used AWS KMS with an FIPS-validated HSM. Cosign's AWS KMS integration worked transparently. The key never left the HSM, satisfying the compliance requirement. For verification, they distributed the public key via a Kubernetes secret that Kyverno read for admission control.
Key Takeaway
Cosign integrates with Azure Key Vault, AWS KMS, GCP KMS, and HashiCorp Vault for centralized key management. The private key never leaves the HSM. KMS is ideal for regulated environments requiring FIPS compliance and audit trails.

Cosign vs Notary v2 (Notation): Choosing the Right Signing Tool

Both Cosign and Notation sign container images, but they differ in architecture, key management, and ecosystem integration. Choosing between them depends on your organization's key management infrastructure and compliance requirements.

Cosign (Sigstore) uses OCI artifact-based signatures (.sig tags). Signatures are stored as separate artifacts in the registry, referenced by the image manifest digest. Supports keyless signing via Fulcio/OIDC and key-based signing with KMS. Has built-in attestation support for vulnerability reports, SLSA provenance, and SBOMs. Integrates natively with Kyverno admission policies. Large ecosystem: all major CI/CD platforms support it, all major registries store its signatures.

Notation (Notary v2) uses OCI 1.1 referrers API to embed signatures directly in the OCI manifest as referrers. Uses X.509 certificates managed via a trust store and trust policy. Designed for enterprise PKI environments where X.509 is already the standard. Requires certificate management (CAs, certificates, CRLs). No keyless mode — requires actual certificates. Integrates with Kyverno (via notations attestors).

Comparison: Cosign is easier to get started with (keyless, no certificate management) and better for cloud-native teams. Notation is better for enterprise PKI environments with existing certificate infrastructure. Cosign's attestation support is more mature. Notation's signature storage (referrers API) is more OCI-native and doesn't create separate tags.

Recommendation: Start with Cosign keyless for most teams. Switch to Notation only if: you need X.509 PKI integration (existing CA), OCI-native signature storage (no .sig tags), or compliance requirements mandate X.509 certificates. Both can be used simultaneously (Harbor and Kyverno support both).

notation-usage.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// io.thecodeforge — DevOps tutorial

# === Notation (Notary v2) Setup ===

# Install Notation
# macOS: brew install notation

# Generate test certificate
notation cert generate-test --default "registry.example.com"

# Sign image
notation sign --key "registry.example.com" registry.example.com/myapp:latest

# Verify
notation verify registry.example.com/myapp:latest

# List signatures
notation ls registry.example.com/myapp:latest

# === Kyverno Policy for Notation ===
# apiVersion: kyverno.io/v2beta1
# kind: ClusterPolicy
# metadata:
#   name: require-notation-signature
# spec:
#   rules:
#     - name: verify-notation
#       match:
#         any:
#           - resources:
#               kinds:
#                 - Pod
#       verifyImages:
#         - imageReferences:
#             - "registry.example.com/*"
#           attestors:
#             - entries:
#                 - notations:
#                     certs: "|"  # trust store reference
#                     # Trust policy defines which CAs are trusted
When to Use Notation vs Cosign
Use Notation if: you have an existing X.509 PKI, need OCI-native signature storage (referrers API), or have compliance requirements for X.509 certificates. Use Cosign if: you want keyless signing via OIDC, prefer the Sigstore ecosystem, need built-in attestations for SLSA/SBOM/vulnerability reports, or are starting fresh with no PKI overhead.
Production Insight
A government contractor needed to sign images with certificates issued by their existing DoD PKI. Notation was the natural choice: their CA issued certificates, Notation used them directly, and Kyverno verified against their trust store. Cosign's keyless approach wouldn't work because their CI systems didn't have OIDC access to public providers. Notation's X.509 model aligned perfectly with their existing infrastructure.
Key Takeaway
Cosign (keyless, OIDC, attestations) vs Notation (X.509 PKI, OCI referrers). Start with Cosign for most teams. Use Notation for enterprise PKI environments. Both work with Kyverno admission control.

Kyverno Admission Control: Enforcing Signed Images at Deploy Time

An image signature is only useful if you check it before deployment. Kyverno is the Kubernetes admission controller that verifies Cosign signatures and attestations at pod creation time, blocking unsigned or incorrectly-signed images.

Kyverno's verifyImages rule configuration: specify the image reference pattern (e.g., ghcr.io/myrepo/*), the attestor (Cosign public key, KMS key, or keyless), and optional conditions (check OIDC issuer, subject, or certificate attributes). When a pod references an image matching the pattern, Kyverno pulls the image's Cosign signature from the registry, verifies it, and only admits the pod if verification succeeds.

Keyless verification in Kyverno: use verifyImages with attestors: entries: keys: kms: to trigger keyless verification. Kyverno connects to Fulcio and Rekor to verify the signature. Add cert-identity and cert-oidc-issuer to require specific OIDC identities.

Key-based verification: store the public key in a Kubernetes secret and reference it: keys: publicKeys: k8s://namespace/secret-name. Kyverno reads the secret and uses it for verification.

Best practices: (1) Use failureAction: Enforce for production namespaces, Audit for dev. (2) Verify both signature AND identity: check cert-oidc-issuer matches your CI platform. (3) Exclude system images (kube-system, etc.) from verification. (4) Use image reference patterns to apply different policies to different registries. (5) For migration, start with Audit mode: it reports violations without blocking.

kyverno-cosign-policy.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# io.thecodeforge — DevOps tutorial

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-cosign-keyless
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
            - "registry.example.com/*"
          attestors:
            - entries:
                - keyless:
                    # Require signing by specific OIDC identity
                    subject: "https://github.com/myorg/myapp/.github/workflows/ci.yml@refs/heads/main"
                    issuer: "https://token.actions.githubusercontent.com"
          # Optional: also verify SLSA provenance attestation
          attestations:
            - predicateType: https://slsa.dev/provenance/v1
              conditions:
                - expression: "predicate.buildDefinition.buildType == 'https://actions.github.io/build'"

---
# Policy for key-based verification (using KMS)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-kms
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-cosign-kms
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keys:
                    kms: "azurekms://mykeyvault.vault.azure.net/mykey"
Production Trap: Kyverno Admission Timeouts with Remote Verification
Cosign verification requires Kyverno to fetch the signature from the registry and verify against Fulcio/Rekor. During a cluster-wide rollout (e.g., 1000 pods at once), this creates 1000+ registry API calls and external verification requests. This can slow admission to the point of timeout. Mitigations: (1) Use a registry cache (Harbor proxy cache). (2) Increase Kyverno pod resources. (3) Use imagePullSecrets to pre-authenticate to the registry. (4) Consider verifying signatures in CI and attesting the result, then verifying the attestation locally.
Production Insight
A large-scale deployment of Kyverno with Cosign verification hit admission delays during a helm upgrade of 500 pods. The solution: pre-fetch signatures using a sidecar in the Kyverno pod that cached verification results. Subsequent verifications used the cache. This reduced admission latency from 8 seconds to 200ms per pod. The cache was invalidated when image tags changed.
Key Takeaway
Kyverno enforces Cosign signature verification at Kubernetes admission time. Use keyless mode with identity checks. Start in Audit mode for migration. Cache verification results to avoid admission delays during large rollouts.
● Production incidentPOST-MORTEMseverity: high

The Registry Compromise That Cosign Would Have Caught in Seconds

Symptom
A payment processing microservice started returning 'connection refused' on outbound HTTPS calls. The application logs showed 'certificate verify failed' errors for the payment gateway API.
Assumption
Team assumed the payment gateway changed their TLS certificates. They spent 4 hours updating trust stores and redeploying. The issue persisted because the malicious image had replaced the legitimate payment gateway's CA bundle with a self-signed one that routed traffic through an attacker's proxy.
Root cause
An attacker obtained push credentials to the private registry via a leaked robot account token in a CI log. They pushed a new image with the same tag (payment-service:latest) that included a modified CA bundle and a reverse proxy that intercepted outbound traffic. The image was identical in size and layering to the legitimate one, so registry admins didn't notice.
Fix
1) Pulled the compromised image, revoked the leaked robot token, and rotated all registry credentials. 2) Deployed Cosign with keyless signing in CI: every build signs the image with the CI pipeline's OIDC identity. 3) Deployed Kyverno admission controller with a policy that blocks any image without a valid Cosign signature from the CI pipeline's OIDC issuer. 4) Added digest-pinned deployments (image@sha256:...) instead of tag-based references. 5) Set up a monitoring alert for any push to the registry that doesn't have a corresponding Cosign signature within 60 seconds.
Key lesson
  • Tag-based image references are vulnerable to substitution attacks. Always pin by digest or verify signatures at admission time.
  • A leaked CI credential can silently replace production images. Image signing with Cosign provides the cryptographic proof that the image in the registry came from your CI pipeline.
  • Kubernetes admission controllers (Kyverno) that verify Cosign signatures at deploy time would have blocked this attack before it reached a single node.
Production debug guideSymptom → Root cause → Fix3 entries
Symptom · 01
cosign sign fails with 'error: failed to sign' or 'no matching credentials'
Fix
1. Verify OIDC authentication: cosign sign in interactive mode opens browser. In CI, ensure OIDC token is available: echo $ACTIONS_ID_TOKEN_REQUEST_TOKEN. 2. Check registry authentication: docker login or cosign login. 3. For keyless, ensure you have a working OIDC provider (GitHub, Google, Microsoft). 4. Check Fulcio availability: curl https://fulcio.sigstore.dev/api/v2/.
Symptom · 02
cosign verify returns 'error: unable to verify' when used with Kyverno admission controller
Fix
1. Test verification manually: cosign verify <image>. 2. Check Kyverno policy configuration for the correct key source. 3. Ensure the image hasn't been mutated since signing (e.g., by a mutating webhook). 4. Check if the registry supports Cosign signatures (OCI referrers API). Harbor and GHCR do; some old registries may not.
Symptom · 03
Cosign signature verification passes locally but fails in CI
Fix
1. Verify the CI environment has network access to Rekor: curl https://rekor.sigstore.dev. 2. Check if CI is behind a proxy that blocks Rekor/Fulcio. 3. For air-gapped CI, use key-based signing and distribute the public key to the CI environment. 4. Set COSIGN_REKOR_URL if using a private Rekor instance.
FeatureCosign (Sigstore)Notation (Notary v2)Docker Content Trust (Notary v1)
Signature storageOCI artifact (.sig tag)OCI 1.1 referrers APISeparate Notary server
Key managementKeyless (OIDC) + KMS + localX.509 certificates + trust storeLocal keys + delegation
Keyless modeYes (Fulcio + OIDC)NoNo
Attestation supportBuilt-in (SLSA, SBOM, vuln)Limited (experimental)None
Admission integrationKyverno, OPA, GatekeeperKyverno (notations attestors)Limited
CI/CD integrationGitHub, GitLab, JenkinsGitHub, GitLabLimited
Ecosystem maturityMature (CNCF sandbox)Stable (OCI standard)Deprecated
Best forCloud-native, keyless, attestationsEnterprise PKI, X.509 complianceLegacy systems
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
sigstore-components.shbrew install cosignSigstore Ecosystem
cosign-sign-verify.shexport COSIGN_EXPERIMENTAL=1Cosign Sign and Verify
slsa-provenance.ymlname: Build and Sign with SLSA ProvenanceSLSA Provenance Attestations
cosign-kms.shaz loginKMS Integration
notation-usage.shnotation cert generate-test --default "registry.example.com"Cosign vs Notary v2 (Notation)
kyverno-cosign-policy.yamlapiVersion: kyverno.io/v1Kyverno Admission Control

Key takeaways

1
Cosign provides cryptographic proof of image origin and integrity. Use keyless signing (Fulcio + Rekor) for most environments
no key management required.
2
SLSA provenance attestations (attached via cosign attest) prove how an image was built
source repo, commit, builder, and build command. Cosign + GitHub Actions achieves SLSA Level 3.
3
Kyverno admission controller enforces signature verification at deploy time. Use identity checks (cert-oidc-issuer, cert-identity) to verify WHO signed the image, not just that it's signed.
4
Cosign integrates with Azure Key Vault, AWS KMS, GCP KMS, and HashiCorp Vault for centralized key management in regulated environments. The private key never leaves the HSM.
5
Compare with Notation (Notary v2) for X.509 PKI environments. Start with Cosign keyless for cloud-native teams. Both work with Kyverno, Harbor, and all major registries.

Common mistakes to avoid

4 patterns
×

Verifying signatures without checking the signer's identity

×

Using tag-based image references instead of digest-based references

×

Not testing Kyverno signature verification in Audit mode before switching to Enforce

×

Signing images but not storing the signatures in a backup registry

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the full flow of a keyless Cosign sign operation: what happens s...
Q02SENIOR
Compare and contrast Cosign and Notation for image signing. When would y...
Q03SENIOR
How would you design a multi-team image signing strategy where each team...
Q04SENIOR
You discover that an attacker has been signing malicious images with you...
Q01 of 04SENIOR

Explain the full flow of a keyless Cosign sign operation: what happens step by step from `cosign sign` to the signature being stored in the registry?

ANSWER
(1) Cosign generates an ephemeral key pair. (2) It opens an OIDC authentication flow (interactive browser or CI token). (3) The OIDC token is sent to Fulcio. (4) Fulcio validates the token, issues an X.509 certificate binding the OIDC identity to the ephemeral public key (valid for 10 minutes). (5) Cosign signs the image manifest digest with the ephemeral private key. (6) The signature, certificate, and signed digest are bundled into an OCI artifact. (7) This artifact is pushed to the registry as <image>:<digest>.sig. (8) Cosign creates a Rekor entry containing the signature, certificate, and image digest, timestamped by the Rekor server. (9) Rekor returns a signed entry timestamp (SET) proving the entry was recorded. Verification: fetches the .sig artifact from the registry, extracts the certificate, verifies the certificate chain to Fulcio's root, and verifies the signature against the image digest.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What happens if Rekor or Fulcio is down? Can I still sign and verify images?
02
How long does a Cosign signature last? Do I need to re-sign images periodically?
03
Can Cosign sign multi-architecture images?
04
How do I revoke a compromised signing key in a keyless system?
05
Does Cosign work with all container registries or only specific ones?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 11, 2026
last updated
1,787
articles · all by Naren
🔥

That's Docker. Mark it forged?

7 min read · try the examples if you haven't

Previous
Harbor Registry Deep Dive
37 / 41 · Docker
Next
Docker Scout and Supply Chain Security