Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 8 min read

Keyless Image Signing with Cosign and Sigstore: Proving What You Deploy

Long-lived signing keys leak. Sigstore's keyless flow ties a signature to an OIDC identity instead. Here's how to sign and verify images for real.

  • #security
  • #hardening
  • #sigstore
  • #cosign
  • #supply-chain
  • #kubernetes

For years the answer to “how do you know this container image is the one your pipeline built?” was a shrug and a tag. Tags are mutable, registries get compromised, and latest means nothing. Image signing fixes the trust question — but the old model, where you generated a long-lived keypair and guarded the private key forever, just moved the problem. Now you have a secret that, if it leaks, lets an attacker sign anything as you.

Sigstore’s keyless flow is the part that finally made signing practical for me. There’s no private key to store, rotate, or leak. The signature is bound to a short-lived OIDC identity instead. Here’s how it actually works and how to wire it into a pipeline.

What “keyless” actually means

Keyless doesn’t mean unsigned. Under the hood, cosign still uses a keypair — it just generates an ephemeral one per signing operation and throws the private key away seconds later. The chain is:

  1. Cosign requests an OIDC token (from GitHub Actions, GitLab, Google, etc.).
  2. It generates a throwaway keypair in memory.
  3. Fulcio (Sigstore’s CA) issues a short-lived (~10 minute) certificate binding the public key to your OIDC identity.
  4. Cosign signs the image digest, then publishes the signature and certificate to Rekor, a public transparency log.
  5. The private key is discarded.

The certificate’s lifetime is so short it’s useless to steal. The transparency log gives you a tamper-evident record of every signature ever made. Verification later checks: was this signed by an identity I trust, with a cert from Fulcio, recorded in Rekor?

Signing in CI

In GitHub Actions, the OIDC token comes for free — you just need the id-token: write permission. No secrets at all:

permissions:
  id-token: write   # required for keyless signing
  packages: write

steps:
  - uses: sigstore/cosign-installer@v3

  - name: Sign image
    run: |
      cosign sign --yes \
        ghcr.io/acme/api@${{ steps.build.outputs.digest }}

Note I’m signing the digest, not the tag. Always sign digests — a tag can be repointed at a different image after you sign, and your signature would still “verify” against the new content. Digests are immutable.

The --yes flag accepts the transparency-log upload prompt. In CI you want that non-interactive.

Verifying before you deploy

Signing nothing-checks is theatre. The signature only matters if something refuses to run unsigned or wrongly-signed images. The basic verification:

cosign verify \
  --certificate-identity-regexp "https://github.com/acme/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/acme/api@sha256:abc123...

The two --certificate-* flags are the whole security story. Without them, you’re verifying that somebody signed the image — which is meaningless, since anyone can sign anything keylessly. With them, you’re asserting: this was signed by a GitHub Actions workflow in the acme org, using GitHub’s OIDC issuer. Pin both. I’ve reviewed pipelines that ran cosign verify with no identity constraints and felt safe; they were verifying nothing.

Enforcing it in the cluster

The strongest place to enforce signatures is the Kubernetes admission controller, so an unsigned image can’t even be scheduled. The sigstore policy-controller does this natively:

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

Any pod pulling an acme image that lacks a matching signature gets rejected at admission time. Kyverno and Gatekeeper can enforce the same check if you’ve already standardized on one of those — no need to add a third controller.

Roll this out in warn mode first. The first time you flip enforcement on, you will discover three base images, a sidecar, and someone’s debug container that nobody ever signed. Catalog those before you start blocking, or you’ll take down a deploy at the worst time.

Signing more than images

Cosign signs any OCI artifact, which means the same flow covers your SBOMs, Helm charts, and attestations. Attaching a signed SBOM as an attestation is where this gets powerful:

cosign attest --yes \
  --predicate sbom.spdx.json \
  --type spdxjson \
  ghcr.io/acme/api@${DIGEST}

Now a downstream consumer can verify both who built the image and what’s inside it, cryptographically linked to the same digest.

The gotchas I hit

  • Airgapped environments. Public Fulcio and Rekor need outbound network access. For disconnected setups you run your own Sigstore stack or fall back to a managed key in KMS — keyless assumes you can reach the public good instances.
  • Clock skew. Short-lived certs are unforgiving of bad system clocks. Make sure your runners have NTP.
  • Verifying old images. Certs expire, but Rekor’s log entry proves the signature existed during the cert’s validity window. Verification handles this via the log timestamp — don’t panic that the cert is “expired.”
  • Identity sprawl. If every team signs with a different OIDC subject, your verification regex becomes a maintenance burden. Standardize the workflow path early.

Where to start

Pick one service. Add the signing step to its pipeline (it’s three lines), verify locally with pinned identity flags, then add a warn-mode cluster policy. Once you trust the warnings, flip to enforce. Sign digests, pin identities, and treat the transparency log as your audit trail.

For more blue-team build-and-ship hardening, see the rest of our security and hardening guides. And if you want a second set of eyes on the pipeline changes that introduce signing, our AI code review assistant is built to flag exactly the missing-identity-constraint mistake described above.

Signing and verification configs are examples. Validate identity constraints and admission policies against your own registries and clusters before enforcing in production.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.