Sigstore and Cosign: Verifying Container Images Before They Run

Signing without managing keys, and proving an image is what it claims

A container tag is a lie you’ve agreed to believe. nginx:latest today and nginx:latest next week can be entirely different bytes, and a tag tells you nothing about who built the image or whether it’s been swapped underneath you. The whole modern supply-chain panic — compromised build pipelines, typosquatted images, dependency confusion — comes down to that one weak link: we run images we can’t actually verify. Sigstore, and its CLI cosign, is the most practical fix I’ve adopted, mostly because it finally killed the part of signing that everyone hated: key management.

Advertisement

Traditional code signing means generating a long-lived private key, guarding it forever, rotating it, and panicking if it leaks. Almost nobody does this well. Sigstore’s clever move is keyless signing: instead of a key you hold, you authenticate to an OIDC identity provider (your Google, GitHub, or corporate account), and Sigstore’s certificate authority, Fulcio, issues a short-lived certificate — valid for roughly ten minutes — bound to that identity. You sign with it, then it expires. There’s no key to steal because there’s no persistent key.

The signature, certificate, and a record of the event are published to Rekor, a public append-only transparency log. That log is the trust anchor: anyone can later prove the signature existed and hasn’t been backdated.

# sign an image keyless — a browser opens for OIDC auth
$ cosign sign registry.example.net/api@sha256:5d9f...
Generating ephemeral keys...
Retrieving signed certificate from Fulcio...
tlog entry created with index: 84213907

Note I signed the digest, not the tag. Always sign and verify by digest — signing a tag is signing a moving target.

Verification is where the value lands. You assert who you expect to have signed it and which OIDC issuer vouched for that identity:

$ cosign verify \
    --certificate-identity '[email protected]' \
    --certificate-oidc-issuer 'https://accounts.google.com' \
    registry.example.net/api@sha256:5d9f... | jq '.[0].optional'

Verification for registry.example.net/api@sha256:5d9f... --
The following checks were performed:
  - The cosign claims were validated
  - Existence of the entry in the transparency log was verified
  - The signing certificate identity matches the expected one

Those two --certificate-* flags are not optional in spirit. If you skip them, cosign will happily verify that somebody signed the image — which is nearly useless. The security comes from pinning the expected identity and issuer.

A signature says “this is the image I built”. An attestation says something about the image — most usefully an SBOM (software bill of materials) listing every package inside, so you can answer “is the new critical CVE in any image I’m running?” without guessing.

# attach an SBOM as a signed attestation
$ cosign attest --type cyclonedx \
    --predicate sbom.cdx.json \
    registry.example.net/api@sha256:5d9f...

# later, verify and pull the SBOM back out
$ cosign verify-attestation --type cyclonedx \
    --certificate-identity '[email protected]' \
    --certificate-oidc-issuer 'https://accounts.google.com' \
    registry.example.net/api@sha256:5d9f... \
    | jq -r '.payload' | base64 -d | jq '.predicate.components | length'
142

Now a signature, an SBOM, and a transparency-log entry all travel with the image. That’s a real provenance trail rather than a hopeful tag.

Verifying by hand is good discipline; enforcing it in the cluster is what actually stops a bad image running. A policy controller — the Sigstore policy controller, or Kyverno’s image-verification rules — intercepts every pod and rejects images that don’t carry a valid signature from an approved identity.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-api-images
      match:
        any:
          - resources:
              kinds: [Pod]
      verifyImages:
        - imageReferences:
            - "registry.example.net/*"
          attestors:
            - entries:
                - keyless:
                    issuer: "https://accounts.google.com"
                    subject: "[email protected]"

With Enforce, an unsigned or wrongly-signed image never schedules. That’s the line between “we sign things, mostly” and “unsigned images cannot run here”.

Worth it? If you pull images from anywhere you don’t fully control — and that’s nearly everyone — keyless signing removes the single biggest excuse for not signing, and verification by pinned identity genuinely raises the bar against supply-chain tampering. The honest caveats: keyless ties you to an OIDC provider and the public Rekor log, which some air-gapped or privacy-sensitive setups won’t accept (you can self-host both, at real operational cost). And signatures verify provenance, not safety — a signed image can still be vulnerable. This is for teams running their own registries and clusters who want to stop trusting tags on faith. For a single hobby box pulling official images, it’s overkill; verifying the official signatures by hand is plenty.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.