Keyless signing does not mean "no keys" — it means no key management: cosign generates an ephemeral keypair per signing operation, Fulcio issues a certificate binding the public key to your OIDC identity for roughly ten minutes, and the Rekor transparency log preserves proof that the signature happened while the certificate was valid. The private key is discarded after use. There is nothing to rotate, nothing to leak from a laptop, and no signing-key.pem sitting in a CI secret since 2019 that four departed employees could still hold.
That is the pitch. Here is how the machinery actually works and where teams trip on it.
The three moving parts
| Component | Role | Trust it provides |
|---|---|---|
| OIDC issuer | Authenticates the signer (GitHub Actions, Google, GitLab, email via browser) | "This identity requested a certificate" |
| Fulcio | Certificate authority; issues short-lived certs binding key to identity | "This key belonged to that identity at this time" |
| Rekor | Append-only transparency log of signatures and certificates | "This signature existed during the cert's validity window" |
The subtlety that confuses everyone: signatures made with an expired certificate remain verifiable forever. Verification does not ask "is this certificate currently valid?" — it asks "was the signature logged in Rekor inside the certificate's ten-minute window?" The transparency log's signed timestamp answers that. This is why keyless works at all: validity is anchored to an immutable log entry, not to certificate lifetime.
Signing from CI, the right way
Human-identity signing (the browser OAuth dance) is fine for experiments and bad for production, because verification policy would then pin to a person's email. Sign from CI with the workload's identity instead. On GitHub Actions:
permissions:
id-token: write # allows the job to mint an OIDC token
packages: write
steps:
- uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
ghcr.io/myorg/api@sha256:4f2a...c81b
Three rules worth enforcing in review. Sign the digest, never the tag — tags move, digests do not, and cosign will warn you for good reason. The id-token: write permission goes on the job, not the whole workflow. And --yes acknowledges that the signing event lands in a public log (more on that below).
The certificate Fulcio issues for this job encodes the repository, the workflow path, the ref, and the issuer (https://token.actions.githubusercontent.com) as certificate extensions. Those fields are what your verification policy pins against.
Verification is where the real bugs live
The most common production mistake is verifying only the issuer:
# WRONG: any GitHub Actions workflow on the planet passes this
cosign verify ghcr.io/myorg/api@sha256:4f2a... \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp='.*'
Every public repo on GitHub can produce a signature matching that policy. Pin the identity to your release workflow, with an anchored regex when refs vary:
cosign verify ghcr.io/myorg/api@sha256:4f2a... \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp='^https://github.com/myorg/api/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$'
At the cluster boundary, the Sigstore policy controller or Kyverno's verifyImages applies the same check at admission time, which is where enforcement belongs — CI verification alone doesn't stop someone deploying an unsigned image by hand. Attestations (SBOMs, SLSA provenance) ride the same rails via cosign attest and cosign verify-attestation --type, so once image signing works, attaching an SBOM from SBOM Studio or provenance from your builder is an incremental change, not a new system.
The awkward questions, answered honestly
"Our signing events are in a public log?" Yes. Rekor is public and searchable; entries include the certificate, so a CI-based signature exposes your org, repo, and workflow names. For most teams that is acceptable metadata leakage — your GitHub org is hardly secret. If you sign with human identities, personal emails land in the log, which is a real reason to avoid human signing beyond experiments.
"What if we can't tolerate that, or we're air-gapped?" Run private Sigstore infrastructure — the sigstore/scaffolding project deploys Fulcio, Rekor, and the TUF trust root on your own cluster. Or skip keyless and use cosign with a KMS-backed key (cosign sign --key awskms:///alias/signing), which trades key-management burden for log privacy. Keyless and keyed cosign coexist fine; verification policy just differs per source.
"What happens when Fulcio or Rekor is down?" Signing fails; verification of existing signatures keeps working if you have the log proofs bundled (cosign bundles them by default since v2). Treat public-good-instance outages like any external dependency: they are rare, and release pipelines can retry.
Rolling it out without breaking Friday deploys
The sequence that has worked for us, and for most teams I have compared notes with:
- Sign everything, verify nothing (week 1–2). Add the signing step to build workflows. Zero deployment risk.
- Verify in audit mode (week 3–6). Policy controller in
warnmode; dashboards show what would fail. You will find images nobody remembered — third-party sidecars, that one cron job built on a laptop in 2023. - Enforce namespace by namespace, starting with new services. Grandfather legacy images behind an explicit, expiring exception list rather than a wildcard.
- Delete the old GPG key ceremony from the calendar. This step has the best morale-per-effort ratio in all of application security.
Budget the audit-mode phase honestly — surfacing unsigned third-party images and deciding policy for them takes longer than any technical step. Platforms like Safeguard can inventory which images in your registries carry signatures and attestations, which turns step 2 from spelunking into a report. For the verification-policy patterns beyond the basics — reusable workflows, multi-org fleets — see our deeper piece on cosign verification policies in production.
Frequently asked questions
Is keyless signing less secure than using our own keys?
It removes the biggest practical failure mode of keyed signing: long-lived private keys that leak, get copied, or outlive their owners. The trade is trusting Sigstore's CA and log infrastructure plus your OIDC issuer. For most organizations, "compromise GitHub's OIDC issuer" is a strictly harder attack than "find a signing key in a CI secret."
Do expired certificates break old signatures?
No. Verification checks that the signature was recorded in Rekor during the certificate's short validity window, using the log's signed timestamp. A signature made in 2023 with a ten-minute cert verifies identically today.
Can we use cosign keyless for internal-only images?
Yes — the images never leave your registry; only the signing metadata (identity, repo, workflow) appears in the public Rekor log. If that metadata is itself sensitive, deploy a private Sigstore stack with sigstore/scaffolding or use a KMS-backed key instead.
What should verification pin, exactly?
At minimum: the OIDC issuer and a fully anchored certificate identity matching your specific repo and workflow path. For reusable workflows, additionally pin the caller repository via the workflow claims. Never ship an unanchored .* identity regex; it converts your policy into "signed by anyone."