Signing artifacts in CI
Cosign keyless from the pipeline.
You scanned the image and it passed — but what proves the image running in production is the same one your pipeline built and scanned, and not one an attacker swapped in the registry? Signing answers that. The pipeline cryptographically signs the exact image digest it produced, and downstream (at deploy or admission) that signature is verified. Without the verify step signing is theatre, but together they make a tampered or unauthorized image detectable.
sign-image:stage: signimage: gcr.io/projectsigstore/cosignid_tokens:SIGSTORE_ID_TOKEN: { aud: sigstore } # GitLab OIDC identity for keylessscript:- cosign sign --yes "$IMAGE@$DIGEST" # sign the digest, not a mutable tagrules:- if: '$CI_COMMIT_BRANCH == "main"'
Keyless signing: no key to leak
Traditional signing means a private key you must store and protect — and in CI, a stored signing key is just another leakable secret. Cosign keyless signing removes it entirely: it uses the pipeline’s OIDC identity to get a short-lived certificate from Fulcio, signs with that, and records the signature in the public Rekor transparency log. There is no long-lived key anywhere — the signature is bound to "GitLab CI, this project, this pipeline," which is exactly the identity you want to verify later.
Sign the digest, verify the identity
Two details make signing meaningful. Sign the immutable digest (image@sha256:...), never a tag — a tag can be repointed, so signing it proves nothing about the bytes. And when you verify, assert the identity you expect: not merely "is it signed" but "is it signed by our GitLab project’s CI identity." An attacker can always sign their own image with their own identity; the verification must pin who signed it.
$ cosign verify \--certificate-identity-regexp "https://gitlab.acme.internal/acme/.*" \--certificate-oidc-issuer https://gitlab.acme.internal \"$IMAGE@$DIGEST" # non-zero exit = reject; identity must match, not just "signed"