Tools · Sigstore & cosign

Sigstore & cosign

Sigstore is a set of free, public services that let you sign software without owning a signing key, and cosign is the small CLI you actually run to do it. It solves the platform problem hiding behind every deployment you have ever shipped: a container image reference is just a string, anyone who can write to your registry can put anything behind it, and by default your cluster will happily run whatever bytes arrive — with no way to answer “who built this, from which commit, on which machine?” Sigstore turns that unanswerable question into a cryptographic fact you can check in a pipeline and enforce at the cluster door.

☺ Explain it like I’m 10

Imagine your school gets lunchboxes delivered every day, and every box has a label saying “Room 4.” Anyone can print that label. So one day someone swaps a box, and Room 4 eats a sandwich nobody made for them. Now imagine instead that whoever packs a box puts a special wax seal on it — and the seal is stamped with a badge that proves who they were when they packed it. Better still: they don’t keep the stamp afterwards. They borrow one for ten minutes, use it, and give it back, so there’s no stamp lying around for a thief to steal. And every time a seal is made, it gets written into a big public notebook that nobody can erase pages from. cosign is the sealing kit, Fulcio lends out the ten-minute stamps, and Rekor is the notebook. The doorkeeper at the lunch hall checks the seal before letting a box in.

🐢Your host for this topic: Timmy the Turtle — patient, suspicious of anything that arrives without a receipt, and the only one at the Guild who reads the certificate subject instead of just noting that verification “passed.” Timmy’s rule: an unpinned verify is a decoration, not a control.

What it is and the problem it solves

☺ Like you’re 10: A wax seal for software, plus a public notebook of every seal ever made — and no key for anyone to steal.

Sigstore is a Linux Foundation project developed under the OpenSSF (Open Source Security Foundation) umbrella — not a CNCF project, though it appears in the CNCF landscape and is used by CNCF projects including Kubernetes itself. It is made of three cooperating pieces: cosign, the signing and verification client; Fulcio, a certificate authority that issues very short-lived certificates bound to an OIDC identity; and Rekor, an append-only, cryptographically verifiable transparency log of signing events. Around them sit the policy tools that use the signatures — the Sigstore Policy Controller and Kyverno’s verifyImages rules.

The problem: a tag is a promise nobody keeps

Your Deployment says image: ghcr.io/acme/checkout:1.4.3. That tag is a mutable pointer. A compromised CI token, a mistyped registry path, a typosquatted base image, or a malicious dependency inside the build can all put different bytes behind the same string, and nothing in Kubernetes will notice. Pinning to a digest fixes mutability but not provenance: @sha256:abc… tells you the bytes did not change, not that the bytes were ever supposed to be there. Supply-chain attacks live precisely in that gap between “unchanged” and “legitimate.”

Why long-lived keys were the wrong answer

The classic fix is a signing key. In practice that key ends up in a CI secret, gets shared across pipelines, never rotates, outlives the person who created it, and becomes the single most valuable object your platform owns — because whoever steals it can sign anything, forever, and every downstream verifier will believe them. Key management is the reason “we should sign our artifacts” has been on platform roadmaps for a decade without shipping. It is a secrets management problem wearing a cryptography costume.

◆ Key idea

Sigstore’s insight is to make the private key so short-lived that stealing it is pointless. In keyless mode cosign generates a keypair in memory, exchanges an OIDC token for a certificate from Fulcio that is valid for roughly ten minutes, signs, publishes the signature and certificate to Rekor, and then throws the private key away. There is no key at rest to leak, no rotation policy, no HSM procurement. The identity — “this GitHub Actions workflow, in this repo, on this branch” — becomes the thing you trust, not a blob of bytes in a vault.

What you get at the end

Three verifiable claims. Integrity: these exact bytes, addressed by digest. Identity: signed by this workload identity, vouched for by this OIDC issuer. Transparency: the signing event is in a public, tamper-evident log, so a rogue signature cannot be created quietly — it leaves a permanent trace anyone can audit. Layer attestations on top and you also get provenance: a machine-readable statement of how the artifact was built and what is inside it.

Where it fits in a platform

☺ Like you’re 10: It sits at the end of the build line making seals, and at the cluster door checking them.

Sigstore straddles two planes, which is what makes it easy to half-implement. Signing belongs to the delivery plane — it happens in CI, right after the image is pushed, as a normal pipeline step in Tekton, Argo Workflows or GitHub Actions. Verification belongs to the governance and control plane — it happens at admission, inside the cluster, on every pod. Do only the first half and you have signatures nobody checks, which is theatre. Do only the second and nothing deploys.

Its neighbours

Upstream sits your build system: CI/CD produces the image and the SBOM, and cosign attaches the evidence. Beside it sits Trivy, which generates the SBOM and finds the CVEs — Trivy answers “what is inside and is it vulnerable?”, cosign answers “who says so and can I trust them?” Downstream sits enforcement: Kyverno’s verifyImages rules or the Sigstore Policy Controller reject pods whose images lack a trusted signature, and those policies are themselves delivered by GitOps so nobody can quietly remove the guardrail. The resulting audit trail feeds Governance & Compliance, and the discipline around promoting only verified artifacts is the backbone of Release Engineering.

CNPE domain relevance

Sigstore and cosign are not on the official CNPE tool list — but supply-chain security, artifact signing and provenance are exam competencies inside the Security & Policy domain (15% of the exam), and they brush GitOps & Continuous Delivery (25%) wherever signing happens in the pipeline. The realistic exam surface is therefore conceptual plus the enforcement side: recognising what keyless signing gives you, and being able to write or read a Kyverno verifyImages rule that demands it. Study Security & Policy Enforcement first; this page is the depth behind it, and The Tool Landscape places it among the named projects.

How it works — architecture and components

☺ Like you’re 10: Borrow a badge, make the seal, write it in the notebook, hand back the badge.

The keyless flow is six steps and worth being able to narrate from memory, because every gotcha later on is a step in it failing.

CI workload cosign sign Fulcio (CA) ~10-minute cert Rekor (log) append-only entry Registry image + .sig + .att OIDC token cert record push sig private key discarded Pod create API server 🐢 admission policy Kyverno verifyImages or ClusterImagePolicy the check that matters signature valid AND identity == pinned subject AND issuer == pinned OIDC issuer log inclusion proof fetch signature Signing proves nothing on its own. Verification pinned to an identity is the control.

Keyed vs keyless

cosign supports both, and knowing when each is right is half the tool.

DimensionKeyed (--key)Keyless (default in cosign v2)
Private keyLong-lived file, KMS or Kubernetes SecretGenerated in memory, discarded after signing
Identity of the signerWhoever holds the key — unknowableAn OIDC subject: workflow, service account, or human email
Verification input--key cosign.pub--certificate-identity + --certificate-oidc-issuer
Network needed to verifyPublic key only — but cosign v2 still uploads to and checks Rekor by default; turn that off (--tlog-upload=false when signing, --insecure-ignore-tlog when verifying) for genuinely offline useRekor + Fulcio roots — or a bundled offline proof
Blast radius if compromisedEverything, foreverTen minutes, one identity, and it is in the log
Best forAir-gapped estates, hardware-rooted release keysCI-built artifacts — the overwhelming majority

Keys can live outside a file: cosign understands KMS references such as awskms://, gcpkms://, azurekms://, hashivault:// and k8s://namespace/secret, which is strictly better than a key on disk if you must be keyed.

Where signatures actually live

This is the detail that surprises everyone. A cosign signature is not embedded in the image — it is a separate OCI artifact stored in the same repository under a derived tag. For an image whose digest is sha256:abc123…, the signature is pushed as the tag sha256-abc123….sig, attestations as …​.att, and legacy attached SBOMs as …​.sbom. Newer registries supporting the OCI 1.1 referrers API can instead link them as referrers, but the tag scheme is still what you will meet in the wild. Two consequences follow immediately: your registry retention or garbage-collection policy can delete signatures while keeping images, and copying an image between registries with plain docker pull/push leaves the signature behind — you need cosign copy.

Attestations, in-toto and SLSA

A signature says “I vouch for these bytes.” An attestation says something richer: “I vouch for these bytes and here is a structured statement about them.” cosign wraps that statement in a DSSE envelope containing an in-toto Statement — a subject (name plus digest), a predicateType URI, and a predicate payload. Two predicates matter most in practice: an SBOM (CycloneDX or SPDX, usually produced by Trivy or Syft) and SLSA provenance (https://slsa.dev/provenance/v1), which records the builder identity, the source repository and commit, and the build parameters.

SLSA grades that provenance in Build levels: L1 provenance exists; L2 it is signed by a hosted build service; L3 the build runs on hardened, isolated infrastructure so the provenance is resistant to forgery by the build itself. Signing your own provenance from a shared runner is roughly L2; getting to L3 is an infrastructure decision, not a cosign flag.

The resources you will actually write

☺ Like you’re 10: One pipeline step that seals the box, one policy that checks the seal, and a written-down statement of what is inside.

Signing in CI — the pipeline step

The whole point of keyless is that the pipeline needs no secret. In GitHub Actions the magic is permissions: id-token: write, which lets the job mint an OIDC token; cosign finds it automatically. Note that we sign the digest, never the tag — signing :1.4.3 signs whatever that tag resolves to at that instant, which is exactly the ambiguity we are trying to remove.

name: build-and-sign
on: { push: { branches: [main] } }

permissions:
  contents: read
  packages: write
  id-token: write            # REQUIRED — mints the OIDC token Fulcio trusts

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push, capturing the digest
        id: push
        run: |
          IMAGE=ghcr.io/acme/checkout
          docker build -t "$IMAGE:${GITHUB_SHA}" .
          docker push "$IMAGE:${GITHUB_SHA}"
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE:${GITHUB_SHA}")
          echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

      - name: Sign the image (keyless)
        run: cosign sign --yes "${{ steps.push.outputs.digest }}"

      - name: Generate and attest an SBOM
        run: |
          trivy image --format cyclonedx --output sbom.json "${{ steps.push.outputs.digest }}"
          cosign attest --yes \
            --predicate sbom.json \
            --type cyclonedx \
            "${{ steps.push.outputs.digest }}"
--yes is not cosmetic

Without --yes (short -y) cosign prompts for interactive confirmation that you understand the signing event will be published to a public transparency log — and in CI that prompt is an unexplained hang until the job times out. It is also a real warning: for a private repository, the Rekor entry publicly records the image digest, the repository path in the certificate identity, and the timestamp. That metadata leak is usually acceptable; decide deliberately, and run a private Sigstore deployment if it is not.

The in-toto statement cosign produces

You rarely hand-write this, but you must be able to read it — every verify-attestation failure is a mismatch in one of these three fields.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "ghcr.io/acme/checkout",
      "digest": { "sha256": "abc123def456..." }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://actions.github.io/buildtypes/workflow/v1",
      "externalParameters": {
        "workflow": {
          "ref": "refs/heads/main",
          "repository": "https://github.com/acme/checkout",
          "path": ".github/workflows/build-and-sign.yml"
        }
      }
    },
    "runDetails": {
      "builder": { "id": "https://github.com/acme/checkout/.github/workflows/build-and-sign.yml@refs/heads/main" },
      "metadata": { "invocationId": "https://github.com/acme/checkout/actions/runs/1234567890" }
    }
  }
}

The whole statement is base64-encoded into a DSSE envelope, signed with the ephemeral key, and stored as the …​.att artifact next to the image. cosign verify-attestation checks the envelope signature and the certificate identity, then hands you the decoded predicate so a policy can reason about it.

Enforcing at admission — two ways

Signing without enforcement is a compliance checkbox. Here are both enforcement paths; pick one, not both. First Kyverno, which is the right choice if you already run it — the signature check becomes one more rule alongside your other guardrails:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  background: false                      # image verification needs registry access; not a background scan
  webhookTimeoutSeconds: 30              # registry + Rekor round-trips are not fast
  rules:
    - name: verify-keyless-signature
      match:
        any:
          - resources: {kinds: [Pod]}
      verifyImages:
        - imageReferences:
            - "ghcr.io/acme/*"           # our images only; public bases can't be pinned
          failureAction: Enforce
          required: true
          mutateDigest: true             # rewrite tag -> digest once verified
          verifyDigest: true
          attestors:
            - count: 1
              entries:
                - keyless:
                    # BOTH of these are load-bearing. Omit either and the rule is theatre.
                    subject: "https://github.com/acme/*/.github/workflows/build-and-sign.yml@refs/heads/main"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

One version caveat worth carrying into a task: the per-rule failureAction is the newer spelling. Older Kyverno releases carried the blocking decision at policy level as spec.validationFailureAction: Enforce (still accepted, but deprecated). Whichever cluster you are handed, confirm with kubectl explain clusterpolicy.spec --recursive rather than guessing — the installed CRD is the authority.

Second, the Sigstore Policy Controller, a purpose-built admission webhook whose only job is image policy. It introduces the ClusterImagePolicy CRD and — importantly — is opt-in per namespace via a label, which is both a safety feature and the most common reason people think it is broken.

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: acme-images-must-be-signed
spec:
  mode: enforce                          # 'warn' first, then flip — with a date on it
  images:
    - glob: "ghcr.io/acme/**"
  authorities:
    - name: github-actions-keyless
      keyless:
        url: https://fulcio.sigstore.dev
        identities:
          - issuer: "https://token.actions.githubusercontent.com"
            subjectRegExp: "^https://github\\.com/acme/.+/\\.github/workflows/.+@refs/heads/main$"
      ctlog:
        url: https://rekor.sigstore.dev
      attestations:
        - name: must-have-sbom
          predicateType: cyclonedx
---
# Nothing is checked until the namespace opts in:
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    policy.sigstore.dev/include: "true"
🦆 Dot’s-eye view

“Honestly? I did not notice this shipping, which is the highest compliment I can pay a platform feature. My pipeline grew two lines, I have no key to look after, and nothing about my day changed. The one time it did matter, someone pushed a hand-built image straight from a laptop to the shared registry to ‘just test something,’ and the cluster refused it with a message naming the exact policy. Ten seconds of confusion instead of a week of forensics.”

Day-to-day commands

☺ Like you’re 10: Four verbs: sign, verify, attest, and check-the-attestation. Everything else is plumbing.

Signing — keyed and keyless

# --- keyed: only when you genuinely need offline verification ---
cosign generate-key-pair                      # writes cosign.key (encrypted) + cosign.pub
cosign generate-key-pair k8s://platform/cosign-keys   # or straight into a Secret
cosign sign --key cosign.key ghcr.io/acme/checkout@sha256:abc123...
cosign public-key --key cosign.key            # print the public half to publish

# --- keyless: the default in cosign v2 ---
cosign sign --yes ghcr.io/acme/checkout@sha256:abc123...
cosign sign --yes --identity-token "$OIDC_TOKEN" ghcr.io/acme/checkout@sha256:abc...

# sign a plain file / any blob, not an image
cosign sign-blob --yes \
  --output-signature release.tgz.sig \
  --output-certificate release.tgz.pem \
  release.tgz
cosign sign-blob --yes --bundle release.tgz.bundle release.tgz   # one self-contained file

# sign any OCI artifact — Helm charts, WASM modules, policy bundles all work
cosign sign --yes ghcr.io/acme/charts/checkout@sha256:def456...

Always sign a digest. If you hand cosign a tag it will resolve it for you and warn, but the habit of typing @sha256: is what keeps a tag-mutation race out of your build.

Verifying — the flags that carry all the meaning

# keyed
cosign verify --key cosign.pub ghcr.io/acme/checkout@sha256:abc123...

# keyless — BOTH pins are mandatory in cosign v2; it errors without them
cosign verify \
  --certificate-identity "https://github.com/acme/checkout/.github/workflows/build-and-sign.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/acme/checkout@sha256:abc123... | jq .

# regex variants, for "any workflow in this org on main"
cosign verify \
  --certificate-identity-regexp '^https://github\.com/acme/.+@refs/heads/main$' \
  --certificate-oidc-issuer-regexp '^https://token\.actions\.githubusercontent\.com$' \
  ghcr.io/acme/checkout@sha256:abc123...

# verify a blob from its bundle
cosign verify-blob --bundle release.tgz.bundle \
  --certificate-identity "..." --certificate-oidc-issuer "..." release.tgz

# where does the signature actually live, and what is attached?
cosign triangulate ghcr.io/acme/checkout@sha256:abc123...   # prints the .sig tag
cosign tree ghcr.io/acme/checkout@sha256:abc123...          # sigs + attestations + SBOMs
cosign copy ghcr.io/acme/checkout:1.4.3 registry.internal/acme/checkout:1.4.3  # brings sigs along

Attestations

# attach a predicate (SBOM, provenance, custom JSON)
cosign attest --yes --predicate sbom.json --type cyclonedx  IMAGE@sha256:...
cosign attest --yes --predicate prov.json --type slsaprovenance IMAGE@sha256:...
# an explicit URI is unambiguous about the version; a bare alias may not be
cosign attest --yes --predicate prov.json --type https://slsa.dev/provenance/v1 IMAGE@sha256:...
cosign attest --yes --predicate audit.json --type https://acme.io/reviewed/v1 IMAGE@sha256:...

# verify one, then read the predicate back out
cosign verify-attestation --type cyclonedx \
  --certificate-identity-regexp '^https://github\.com/acme/' \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  IMAGE@sha256:... \
  | jq -r '.payload' | base64 -d | jq '.predicate'

# trust-root and private-deployment plumbing
cosign initialize                                        # fetch/refresh the TUF trust root
cosign initialize --mirror "$TUF_MIRROR" --root root.json  # private Sigstore
cosign sign --yes --fulcio-url https://fulcio.internal --rekor-url https://rekor.internal IMAGE@sha256:...

The short --type aliases (cyclonedx, spdx, slsaprovenance, vuln, custom) each expand to a fixed predicate-type URI, and the SLSA alias has tracked older provenance versions than the one your generator emits. Pass the full URI whenever the version matters, and make sure the predicateType your policy demands is exactly the one that ends up in the statement — a mismatch there is silent: the attestation exists, and the policy simply never finds it. More commands live on the command reference.

🐢 Timmy’s workshop · 25 min

Push any tiny image to a registry you control. Sign it keyless with cosign sign --yes — you will be sent to a browser to authenticate, which is the OIDC step. Now run cosign triangulate and then crane ls (or your registry UI) and physically look at the sha256-….sig tag sitting beside your image; that single moment removes most of the confusion about this tool. Verify it correctly with --certificate-identity set to your own email and --certificate-oidc-issuer set to your provider. Then do the dangerous thing: verify with an identity regexp of .* and note that it passes — anyone on earth could have signed that image and it would still pass. Finally, install Kyverno, apply the require-signed-images policy above pointed at your registry, and try to run an unsigned image. The 403 is the whole point of the exercise.

Gotchas and failure modes

☺ Like you’re 10: Checking that a seal exists is not the same as checking whose seal it is.

Verifying without pinning the identity proves almost nothing

This is the number one mistake and it is worth being blunt. Fulcio will issue a certificate to anyone who can authenticate with any supported OIDC provider — that is by design, it is a public good, not a gate. So a signature that merely “verifies” only proves that somebody with a Google account signed those bytes. An attacker who compromises your registry can push a malicious image, sign it with their own throwaway identity, and sail through an unpinned check. The security boundary is --certificate-identity plus --certificate-oidc-issuer, and both must be present. Be equally careful with regexes: an unanchored subjectRegExp matching github.com/acme also matches github.com/acme-evil-corp. Anchor with ^ and $ and escape the dots.

⚠ The flags that quietly disable the control

--insecure-ignore-tlog skips the transparency-log check, --insecure-ignore-sct skips the certificate-transparency check, and --allow-insecure-registry drops TLS verification to the registry. Each one is legitimate in a specific air-gapped or private-deployment scenario and each one appears in Stack Overflow answers as a fix for a network problem. If one of these is in your production pipeline as a workaround, your signing story has a hole in it. Treat them like --insecure anywhere else: never permanent, always with a comment explaining why. The related anti-patterns are catalogued on Anti-Patterns.

Reachability, latency and the admission path

Keyless verification is a network operation: the verifier needs the registry (for the .sig artifact), the Sigstore trust root that contains Fulcio’s and Rekor’s public keys (distributed over TUF and then cached locally by cosign initialize), and Rekor itself for the inclusion proof. Put that inside an admission webhook and a slow or unreachable Rekor becomes a cluster-wide pod-creation failure. The symptoms show up as admission timeouts rather than as signature errors, which sends people debugging the wrong system entirely — see Triage: Workloads when pods will not schedule and Triage: Delivery when a sync stalls. Mitigations: raise webhookTimeoutSeconds, scope imageReferences narrowly so you are not verifying public base images you cannot pin anyway, rely on the engine’s verification cache, and for genuinely disconnected estates use offline verification with a bundled inclusion proof or run a private Sigstore.

Signatures are separate artifacts — and they get lost

Three practical bites follow from the tag scheme. Registry garbage collection or retention rules that key on “untagged manifests” or “tags not matching v*” will happily delete every signature while leaving the images, and you discover it when admission starts rejecting things that worked yesterday. Promotion between registries with a plain pull-and-push silently drops signatures — use cosign copy or a promotion tool that understands referrers. And rebuilding rather than promoting defeats the whole model: if staging and production build separately, the digest differs, the signature does not apply, and you have verified nothing about what actually runs. Build once, sign once, promote the digest — the same discipline described in Release Engineering.

Identity churn and other quiet traps

The certificate subject includes the workflow path and the ref. Rename the workflow file, move the repo, or switch from refs/heads/main to a tag trigger, and every pinned policy stops matching — verification fails for a perfectly legitimate image, usually on a Friday. Anticipate it with a deliberately scoped regexp and treat identity strings as an interface with a change process. Two more: a signature only covers the artifact, so signed does not mean safe — a faithfully signed image full of critical CVEs verifies perfectly, which is why Trivy and cosign are complements, not substitutes. And cosign attest without a matching predicateType in your policy is a file nobody reads; an unverified attestation is documentation, not a control.

Alternatives and when to choose it

☺ Like you’re 10: Other people also invented seals. Sigstore’s is the one that does not need you to guard a stamp.

The comparison

DimensionSigstore / cosignNotation (CNCF Notary Project)GPG detached signaturesRegistry content trust (Notary v1)
Key modelEphemeral, identity-based (or keyed)X.509 certs you manageLong-lived personal keysLong-lived per-repo keys (TUF)
Transparency logYes — RekorNo (optional external)NoNo
Attestations / SBOMYes — in-toto DSSEYes, via referrersNot nativelyNo
Kubernetes admission supportKyverno + Policy ControllerRatify pluginNoneDocker client only
StorageDerived .sig tag or referrersOCI 1.1 referrersAnywhere you put the fileRegistry-side TUF metadata
Works offlineKeyed yes (with the tlog turned off); keyless with bundlesYesYesPartially
Ecosystem momentumVery high — npm, PyPI, Kubernetes releasesModerate, enterprise/CNCF-alignedLegacyEffectively deprecated

A practical rule

Choose Sigstore keyless for anything built by CI, which is nearly everything — it removes the key-management problem that killed every previous attempt, and its ecosystem support means your verifier already exists. Choose cosign keyed with a KMS when you need verification in a disconnected environment, or when a regulator wants a hardware-rooted release key with a documented custody chain. Look at notation when your organisation has an existing PKI it must reuse and the certificate hierarchy is non-negotiable. And do not let the tool choice delay you: pinned verification of one critical image in production is worth more than a perfect design of nothing. See Best Practices for how this fits the wider platform discipline, and The Tool Landscape for its neighbours.

🎬 At the Platform Guild
🦊

Foxy: Good news — we’re signing every image now. The pipeline’s green, cosign says Verified OK. Supply chain: solved.

🐢

Timmy: Show me the verify command. …Right there. No --certificate-identity. That check passes for a signature made by anyone with an email address.

🦊

Foxy: But it says Verified.

🐢

Timmy: It verified the maths. It did not verify the signer. Pin the identity and the issuer, or we’ve installed a lock with no key hole.

👺

Gizmo: Just add --insecure-ignore-tlog while you’re in there — Rekor was slow this morning and it made my build take nine extra seconds. 🤑

🐢

Timmy: That flag deletes the transparency log from the trust model, Gizmo. Nine seconds is not a price. It’s the receipt.

🦫

Benny: And nothing is real until admission enforces it. Kyverno rule, failureAction: Enforce, in Git, delivered by Argo. Otherwise we’re just decorating images.

🦆

Dot: Fine by me — as long as when it rejects my pod, the message tells me which policy and which identity it wanted. Don’t make me guess.

Exam relevance and going further

☺ Like you’re 10: You cannot open Sigstore’s website on exam day — so the ideas and the Kyverno YAML have to be in your head.

Sigstore and cosign are not on the CNPE tool list, so do not expect a task that asks you to run cosign sign on the exam machine. What is examinable is the competency around it: supply-chain security, artifact signing and provenance, and — most concretely — enforcement. The realistic task shape is a policy one: “ensure images from registry.example.com cannot run unless they carry a valid signature from <identity>,” which you answer with a Kyverno verifyImages rule. Practise that shape on Practice: Security.

The documentation allowlist — read this twice

⚠ Sigstore’s own docs are not available during the exam

During the CNPE the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, task-specific documentation explicitly linked in the exam’s Quick Reference box, and local man pages and /usr/share docs on the exam machine. sigstore.dev, docs.sigstore.dev and slsa.dev are all off-limits, and so is kyverno.io — which means the verifyImages block comes out of your memory or out of kubectl explain clusterpolicy.spec.rules.verifyImages --recursive against the cluster in front of you. Installed CRDs register their full schema, so kubectl explain is your version-correct offline reference; cosign --help and any local man pages are fair game too. Drill the manifests on Know Cold and read the allowlist rules in full on The Docs Map. Confirm the current allowlist on the Linux Foundation’s own exam pages in the days before you sit.

⚖ CNPA vs CNPE — That allowlist mechanic is CNPE-specific: CNPE is hands-on and lets you reference a narrow set of docs while you work. CNPA is fully closed-book — a multiple-choice exam with zero external lookups of any kind, no allowlist to fall back on at all. That makes concepts like keyless signing, Fulcio/Rekor, and signature-vs-attestation worth knowing cold in your own words, since this concept-level knowledge still matters for CNPA's closed-book recall.

What to be able to do cold

Explain keyless signing in four sentences: ephemeral keypair, OIDC token exchanged at Fulcio for a ~10-minute certificate, signature and certificate recorded in Rekor, private key discarded. Name the three components and what each one is for. State the two flags that make a keyless verification meaningful — --certificate-identity and --certificate-oidc-issuer — and explain why omitting them proves almost nothing. Know that signatures are separate OCI artifacts under a sha256-….sig tag, and what that implies for registry GC and image promotion. Distinguish a signature from an attestation, and know that an attestation carries an in-toto predicate such as an SBOM or SLSA provenance. And write a Kyverno verifyImages rule from a blank file with imageReferences, attestors[].entries[].keyless carrying subject and issuer, and failureAction: Enforce.

Official resources for after the exam

Outside the exam, the canonical sources are docs.sigstore.dev (start with the cosign signing and verifying guides), the CLI reference and source at github.com/sigstore/cosign, the Policy Controller at docs.sigstore.dev/policy-controller, the attestation format specification at github.com/in-toto/attestation, the framework and level definitions at slsa.dev, and the project home at sigstore.dev. Pair this page with Security & Policy Enforcement for the domain, Kyverno for the enforcement half, Trivy for what goes into an SBOM attestation, and the glossary whenever a term stops making sense.

🐢 Timmy’s checkpoint

1. Narrate the keyless signing flow, naming Fulcio and Rekor and saying what happens to the private key. 2. A colleague runs cosign verify IMAGE, it prints Verified OK, and they declare the image trusted. What have they actually proven, and what is missing? 3. Where is a cosign signature stored, and name two operational consequences of that. 4. What is the difference between a signature and an attestation, and give two predicate types you would attach in a real pipeline. 5. Your admission policy starts timing out and pods stop being created, but the images are correctly signed. What is the likely cause? 6. Why should you sign the digest rather than the tag? 7. During the exam, where can you look up the schema for a Kyverno verifyImages rule?

Check your answers
  1. cosign generates an ephemeral keypair in memory; it presents an OIDC identity token (from the CI workload or a human login) to Fulcio, which returns a short-lived (~10 minute) X.509 certificate binding that public key to the identity; cosign signs the image digest, uploads the signature and certificate to the Rekor transparency log — whose entry also acts as a timestamp so verification still works after the certificate expires — pushes the signature to the registry, and discards the private key. Nothing signable is left at rest.
  2. They have proven only that someone holding a valid Fulcio certificate signed those exact bytes — and Fulcio issues certificates to anyone who can authenticate with any supported OIDC provider. Missing are --certificate-identity (or --certificate-identity-regexp) and --certificate-oidc-issuer, which pin who signed it. Without both, an attacker who can push to the registry can sign with their own identity and pass.
  3. As a separate OCI artifact in the same repository, under a derived tag sha256-<digest>.sig (attestations use .att), or as a referrer on OCI 1.1 registries. Consequences: registry garbage collection or retention rules can delete signatures while keeping images; and copying an image between registries with a plain pull/push drops the signature — use cosign copy. A third: rebuilding instead of promoting changes the digest and invalidates the signature.
  4. A signature asserts only “I vouch for these bytes.” An attestation is a signed, structured statement about the artifact — an in-toto Statement in a DSSE envelope with a subject, a predicateType and a predicate. Common predicates: a CycloneDX or SPDX SBOM and SLSA provenance (https://slsa.dev/provenance/v1).
  5. Keyless verification needs network reachability to the registry and to Rekor (plus a cached Sigstore trust root); if Rekor or the registry is slow or unreachable, the webhook exceeds its timeout and admission fails. Raise webhookTimeoutSeconds, narrow imageReferences so you are not verifying unpinnable public base images, lean on the verification cache, or run a private Sigstore / use offline bundles.
  6. Because a tag is a mutable pointer — signing :1.4.3 signs whatever it resolves to at that instant, and the tag can be moved afterwards to different bytes that your signature appears to cover. A digest is the content itself, so the signature is unambiguous.
  7. Not on sigstore.dev or kyverno.io — neither is on the CNPE allowlist. Use kubectl explain clusterpolicy.spec.rules.verifyImages --recursive against the cluster in front of you, look at any existing policy with kubectl get cpol -o yaml, check the Quick Reference box for a task-specific link, and otherwise write it from memory — drill it on Know Cold.