skip to content

Explain cosign's Sigstore 'keyless' signing flow: how a CI job signs a container image without holding any long-lived private key, where the resulting signature is stored, and what a verifier must check.

level: seniorimportance: should knowfreq 38%

answer

  1. OIDC token → Fulcio → 10-minute cert → sign digest → Rekor
  2. ephemeral key discarded, nothing to rotate
  3. signature stored as sha256-<hex>.sig / OCI referrer
  4. verify MUST pin --certificate-identity + --certificate-oidc-issuer
  5. cert expiry OK: Rekor timestamp proves validity at signing

basics

~20 s

CI presents its OIDC identity token; Fulcio returns a short-lived certificate binding that identity to an ephemeral key; cosign signs the image digest, logs the signature in the Rekor transparency log, and discards the private key. Verifiers check the certificate chain, the logged inclusion time, and the expected signer identity and issuer.

solid answer

~50 s

Keyless removes the long-lived key, not the cryptography. 1. The CI job obtains an **OIDC token** proving its workflow identity (for example a GitHub Actions job with `id-token: write`). 2. cosign generates an **ephemeral key pair** and sends the public key plus the token to **Fulcio**, Sigstore's CA, which issues a **short-lived X.509 certificate** (roughly ten minutes) whose SAN is the workflow identity and which records the OIDC issuer. 3. cosign signs the **image digest**, uploads the signature and certificate to the registry, and records an entry in **Rekor**, the append-only transparency log. 4. The private key is **discarded**. Nothing long-lived can be stolen. Signatures live in the same repository as the image — historically a tag derived from the digest, `sha256-<hex>.sig`; with OCI 1.1 as a **referrer** attached to the image. Verification must **constrain identity**: `--certificate-identity(-regexp)` and `--certificate-oidc-issuer`. Cosign checks the chain to Fulcio's root, that the cert was valid at the Rekor-recorded signing time, and the log inclusion proof. Verifying without identity constraints accepts anyone.

code

yaml · 10 lines
yaml
permissions:
  contents: read
  packages: write
  id-token: write

steps:
  - run: |
      DIGEST=$(docker buildx build --push -t ghcr.io/acme/api:1.4 . \
        --metadata-file meta.json >/dev/null; jq -r '."containerimage.digest"' meta.json)
      cosign sign --yes "ghcr.io/acme/api@${DIGEST}"

go deeper

for a junior

Know the shape: CI proves its identity, gets a short-lived certificate, signs the image, and the signature is recorded in a public log.

for a middle

Name the components — OIDC, Fulcio, Rekor, ephemeral key — and know signatures are made over the digest and stored beside the image.

for a senior

Explain why the transparency log makes short-lived certificates verifiable later, pin identity and issuer in verification, and handle mirroring and GC pitfalls.

for a principal

Decide the trust model: which workflow identities may sign for which repositories, public-good versus self-hosted Sigstore, offline/air-gapped verification material, and incident response using the log.

## The problem keyless solves Traditional signing needs a private key that lives somewhere for years: on a build agent, in a secrets manager, in a KMS. It must be protected, rotated, revoked on compromise, and audited. Keyless replaces the long-lived secret with a **short-lived certificate bound to a verifiable workload identity**, so the answer to 'where is your signing key?' becomes 'it existed for a few seconds inside the job that built the artifact'. ## The flow, step by step **1. Identity.** The CI job requests an OIDC token from its platform. On GitHub Actions this needs `permissions: id-token: write`; equivalents exist on GitLab, Buildkite, and for humans via an interactive browser login. The token's subject encodes exactly which workflow, repository and ref is running — that specificity is the whole security value. **2. Certificate.** cosign generates an ephemeral key pair in memory and sends the public key plus the OIDC token to **Fulcio**. Fulcio validates the token against the issuer, then issues an X.509 certificate whose Subject Alternative Name is the workload identity (e.g. `https://github.com/acme/api/.github/workflows/release.yml@refs/tags/v1.4`) with the issuer recorded in an extension. The certificate is deliberately **short-lived** (about ten minutes) — long enough to sign, too short to be worth stealing. **3. Sign.** cosign signs the **image digest**, not the tag, so the signature survives retagging and copying. The signature plus the certificate are pushed into the registry beside the image. **4. Transparency.** The signature, certificate and metadata are appended to **Rekor**, a tamper-evident public log, which returns an inclusion proof and a signed entry timestamp. This is what makes short-lived certificates verifiable *after* expiry: the verifier does not need the certificate to be valid now, only to have been valid **at the time Rekor recorded the signature**. It also gives detection — a compromised identity's signatures are permanently visible. **5. Discard.** The ephemeral private key is thrown away. There is nothing to rotate or leak. ## Where the signature lives Originally cosign stores signatures as an OCI object in the **same repository**, under a tag derived from the subject digest: `sha256-<hex>.sig` (with `.att` for attestations and `.sbom` variants). With **OCI 1.1** and the referrers API, cosign can instead attach them as **referrers** with a `subject` field, discoverable by API rather than by tag convention. Two practical consequences: - **Copying an image does not copy its signature.** Mirroring tools that move only the manifest and layers leave signatures behind; use `cosign copy`, or a copy that follows referrers, when populating mirrors or air-gapped registries. - **Registry garbage collection can delete signatures.** Retention rules like 'delete untagged manifests' or 'keep the last 10 tags' can silently remove the `.sig` objects, and verification then fails on images that were correctly signed. ## What verification must actually check `cosign verify` with no identity flags is close to meaningless — it proves *someone* whom Sigstore's public CA served signed the image. Real verification pins: - `--certificate-identity` or `--certificate-identity-regexp` — the exact workflow/subject allowed to sign; - `--certificate-oidc-issuer` — which OIDC provider issued the identity, so an attacker cannot mint an equivalent-looking subject at another issuer; - optionally the annotations/attestation predicate, when policy demands provenance rather than a bare signature. Under the hood cosign validates the chain to Fulcio's root (distributed via a TUF-managed trust root), checks the Rekor inclusion proof and the signed entry timestamp against the certificate's validity window, and then checks the signature over the digest. ## Operating it Sign in the same job that built the image, immediately after push, using the digest returned by the push. Keep the transparency requirement in mind for restricted networks: verification normally consults Rekor and the TUF trust root, so air-gapped verifiers need bundled material (cosign supports bundles and offline verification) or a self-hosted Sigstore stack. Decide that before mandating enforcement, or the exceptions list will become the policy.

  • If the signing certificate expires after ten minutes, how can anyone verify the signature a year later?
    Because validity is evaluated at signing time, not verification time. Rekor records the signature with a signed entry timestamp and an inclusion proof, so the verifier checks that the certificate was within its validity window when the log entry was made. That is the specific reason a transparency log is required for the keyless flow rather than optional.
  • We mirrored our images into another registry and verification now fails. What happened?
    The signature is a separate object in the source repository — a `sha256-<digest>.sig` tag or an OCI referrer attached to the image — and a plain image copy does not bring it along. Re-run the copy with `cosign copy` or a tool that follows referrers. The same class of failure comes from registry retention rules that prune untagged or unrecognised manifests.
  • What does `cosign verify` prove if you omit the identity flags?
    Almost nothing useful: only that some certificate chaining to the Sigstore root signed that digest and was logged. Since Fulcio issues certificates to any authenticated OIDC identity, an attacker with any account at a supported issuer can produce such a signature. Policy must pin both the expected subject identity and the OIDC issuer.

It is a day pass rather than a permanent badge: the door checks who you are today, prints a pass valid for ten minutes, and the visitor log records the exact moment you used it — so an expired pass still proves the visit was legitimate.

saying these in an interview costs you the question

  • Thinking 'keyless' means no cryptography or no certificate, rather than an ephemeral key plus short-lived certificate.
  • Running `cosign verify` without `--certificate-identity` and `--certificate-oidc-issuer` and calling the image trusted.
  • Assuming the signature travels automatically when an image is copied or mirrored.
  • Believing certificate expiry invalidates old signatures, missing the role of the Rekor timestamp.
  • Signing the tag instead of the digest returned by the push.

context