Server

Signing Container Images with cosign

By Prabath Thalangama· October 24, 2026· 3 min read
#containers#supply-chain#security

Introduction

A registry tag is mutable and says nothing about where an image came from. cosign (part of Sigstore) signs images by digest so you can verify provenance — and block anything unsigned from running in your cluster.

Keyless signing

The modern default. No long-lived key to manage or leak:

  1. cosign gets a short-lived OIDC token (from GitHub Actions, GitLab, or an interactive browser login).
  2. Fulcio issues a short-lived (~10 min) X.509 cert bound to that identity.
  3. cosign signs the image digest with the ephemeral key.
  4. The signature + cert are pushed to the registry (as an OCI artifact) and logged in Rekor, the public transparency log.
  5. Verification checks the signature, that the cert chains to Fulcio, that the identity matches your expected issuer/subject, and that it's in Rekor.
# In CI (GitHub Actions), with id-token: write permission
cosign sign --yes \
  ghcr.io/org/app@sha256:abc123...

# Verify
cosign verify \
  --certificate-identity-regexp 'https://github.com/org/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/org/app@sha256:abc123...

Always sign and verify by digest, not tag — a tag can be repointed after signing.

Key-based (if you must)

cosign generate-key-pair                 # or --kms awskms://... / cosign.key in a secret
cosign sign --key cosign.key ghcr.io/org/app@sha256:...
cosign verify --key cosign.pub ghcr.io/org/app@sha256:...

Now you own key rotation and distribution. Keyless avoids all of that.

Attestations and SBOMs

Beyond "who signed it", attach signed statements about the image:

# Attach an SPDX SBOM as a signed attestation
syft ghcr.io/org/app@sha256:... -o spdx-json > sbom.spdx.json
cosign attest --yes --predicate sbom.spdx.json --type spdxjson \
  ghcr.io/org/app@sha256:...

# SLSA provenance from the build
cosign attest --yes --predicate provenance.json --type slsaprovenance ...

cosign verify-attestation --type spdxjson \
  --certificate-identity-regexp '...' --certificate-oidc-issuer '...' \
  ghcr.io/org/app@sha256:...

Enforce at admission

Signing is pointless if unsigned images still run. Use an admission controller:

Sigstore policy-controller

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata: { name: require-signed }
spec:
  images:
    - glob: "ghcr.io/org/**"
  authorities:
    - keyless:
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: 'https://github.com/org/.*'

Kyverno

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: verify-images }
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-signature
      match:
        any: [{ resources: { kinds: [Pod] } }]
      verifyImages:
        - imageReferences: ["ghcr.io/org/*"]
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/org/*"
                    issuer: "https://token.actions.githubusercontent.com"

Both mutate the pod spec to pin the digest on admission — a nice side effect.

Verification and troubleshooting

cosign tree ghcr.io/org/app@sha256:...     # what signatures/attestations exist
cosign verify ... --output json | jq
  • no matching signatures — verifying by tag while signed by digest (or vice versa), wrong identity/issuer regex, or the signature is in a different registry/repo (COSIGN_REPOSITORY).
  • CI signing fails "getting key from Fulcio" — missing id-token: write permission on the job, or not running in a supported OIDC environment.
  • Admission blocks your own images — the identity regex doesn't match the actual workflow path (reusable workflows change the subject), or you're pulling a mutating base tag. kubectl get events shows the policy denial reason.
  • Third-party / base images unsigned — scope the policy to your own repos and either mirror+sign upstream images or allow specific known digests.
  • Rekor unreachable in an airgapped cluster — run a private Rekor + Fulcio, or use key-based signing with --offline verification against a pinned transparency-log entry.
  • Signature verified but the image is still malicious — signing proves origin, not safety. Pair with vulnerability scanning (Trivy/Grype) and SBOM policy.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.