Safeguard
Container Security

Implementing keyless container image signing with Cosign ...

A hands-on guide to Cosign keyless signing GCP setups with Sigstore, Workload Identity Federation, and Cloud Build — sign and verify images with no key management.

Karan Patel
Cloud Security Engineer
7 min read

Container registries are full of images nobody can vouch for. A tag gets pushed, a deployment pulls it, and unless you've built a verification step into your pipeline, there's no cryptographic proof that the image running in production is the one your CI system actually built. Cosign keyless signing GCP workflows solve this by removing the weakest link in traditional signing — the long-lived private key sitting in a secrets manager waiting to be exfiltrated. Instead, Sigstore's Fulcio certificate authority issues short-lived certificates tied to a workload's OIDC identity, and Rekor logs every signature in a public, tamper-evident transparency log. In this guide, you'll configure Workload Identity Federation on Google Cloud, wire up a Cloud Build pipeline that signs images the moment they're built, and verify those signatures before deployment — all without ever generating or storing a signing key.

Step 1: Prepare Your GCP Project and Enable Workload Identity Federation

Before touching Cosign, get the underlying GCP plumbing in place. Keyless signing depends on your workload being able to present a verifiable OIDC identity token — no service account JSON keys involved.

gcloud config set project YOUR_PROJECT_ID

gcloud services enable \
  iamcredentials.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  sts.googleapis.com

Create an Artifact Registry repository to hold the images you'll sign:

gcloud artifacts repositories create app-images \
  --repository-format=docker \
  --location=us-central1 \
  --description="Signed application images"

If your builds run outside Cloud Build (say, GitHub Actions), set up a Workload Identity Federation pool so that platform's OIDC tokens can be exchanged for short-lived GCP credentials:

gcloud iam workload-identity-pools create "sigstore-pool" \
  --location="global" \
  --display-name="Sigstore Signing Pool"

gcloud iam workload-identity-pools providers create-oidc "github-provider" \
  --location="global" \
  --workload-identity-pool="sigstore-pool" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --display-name="GitHub Actions Provider"

Step 2: Install Cosign and Authenticate to Artifact Registry

Install the Cosign CLI on your build environment:

brew install cosign
# or, for Linux CI runners
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

Configure Docker to authenticate against Artifact Registry so pushes and digest lookups work:

gcloud auth configure-docker us-central1-docker.pkg.dev

Build and push a test image so you have something to sign:

docker build -t us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-images/api:latest .
docker push us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-images/api:latest

Step 3: Set Up Cosign Keyless Signing on GCP Using OIDC

This is the core of Cosign keyless signing GCP setups: instead of cosign generate-key-pair producing a cosign.key you have to protect forever, Cosign requests a short-lived code-signing certificate from Sigstore's public Fulcio CA at sign time, using your OIDC identity as proof of who's signing.

Try it locally first to see the flow:

cosign sign --yes \
  us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-images/api@sha256:<digest>

Cosign opens a browser-based OIDC prompt, authenticates against Sigstore's identity provider (Google, GitHub, or your own OIDC issuer), and binds the resulting certificate to your email or workload identity. No --key flag, no key file — the certificate is valid for minutes and never needs rotation because it's never reused.

For CI, that browser flow is replaced by ambient OIDC tokens. On Cloud Build, the build's own service identity token satisfies Fulcio directly. On GitHub Actions, the id-token: write permission lets the runner mint a token that exchanges against the Workload Identity Federation pool you created in Step 1. Either way, this is the essence of keyless signing OIDC: identity replaces key custody.

Step 4: Automate Container Image Signing in GCP Cloud Build

Wire signing into the build itself so every image that reaches your registry is signed before anything can deploy it. A minimal cloudbuild.yaml for container image signing GCP Cloud Build pipelines looks like this:

steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/api:$SHORT_SHA', '.']

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/api:$SHORT_SHA']

  - name: 'gcr.io/projectsigstore/cosign'
    entrypoint: 'sh'
    args:
      - '-c'
      - |
        DIGEST=$(gcloud artifacts docker images describe \
          us-central1-docker.pkg.dev/$PROJECT_ID/app-images/api:$SHORT_SHA \
          --format='value(image_summary.digest)')
        cosign sign --yes \
          "us-central1-docker.pkg.dev/$PROJECT_ID/app-images/api@$${DIGEST}"

images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/app-images/api:$SHORT_SHA'

Because Cloud Build steps run under the project's default (or a custom) build service account, Cosign picks up that identity automatically when it requests a certificate from Fulcio — there's no key to inject as a build secret, which is exactly the attack surface you're trying to eliminate.

Step 5: Verify Signatures Before Deployment

Signing is only half the story; verification is what actually stops an unsigned or tampered image from running. Anyone downstream — a deploy script, an admission controller, a teammate — can check a signature without needing your key or even knowing who signed it in advance:

cosign verify \
  --certificate-identity-regexp=".*@YOUR_PROJECT_ID\.iam\.gserviceaccount\.com" \
  --certificate-oidc-issuer="https://accounts.google.com" \
  us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-images/api@sha256:<digest>

The --certificate-identity and --certificate-oidc-issuer flags matter more than people expect: they pin verification to a specific identity and issuer, so a valid Sigstore signature from an unrelated GitHub repo or unrelated GCP project won't pass. Skipping these flags and only checking "is there a signature" defeats much of the point.

Step 6: Enforce Signature Checks with Binary Authorization

Verification you run manually gets skipped under deadline pressure. Enforce it at admission time instead with Binary Authorization, which can require a valid Sigstore attestation before GKE or Cloud Run will schedule a pod:

gcloud container binauthz policy import policy.yaml

A policy referencing a Sigstore-based attestor rejects any image lacking a valid keyless signature outright, turning "we're supposed to sign images" into "unsigned images cannot run."

Troubleshooting and Verification

"no identity token available" during signing. Cosign couldn't find an OIDC token in the environment. On GitHub Actions, confirm the workflow has permissions: id-token: write. On Cloud Build, confirm the build is using a service account identity, not a bare project number with no IAM bindings.

Certificate identity mismatch on verify. If cosign verify fails with an identity error, your --certificate-identity regex is too strict or points at the wrong principal. Run cosign verify without the identity flags once (non-production only) to inspect the certificate's actual subject and issuer, then tighten the regex to match.

Requests to Fulcio or Rekor time out. Corporate proxies and locked-down VPCs frequently block fulcio.sigstore.dev and rekor.sigstore.dev. Either allowlist those endpoints or, for regulated environments, run a private Sigstore instance and point Cosign at it with --fulcio-url and --rekor-url.

Digest mismatch between build and sign steps. Always sign by digest, not tag. Tags are mutable; a tag can be repointed after signing without invalidating the old signature, which defeats the purpose. Pull the digest explicitly (as in the Cloud Build example above) rather than assuming :latest refers to what you just pushed.

Clock skew causing certificate validation failures. Fulcio certificates are valid for minutes. Build runners with drifted clocks will fail verification even though signing "succeeded." Confirm NTP sync on self-hosted runners.

Confirming end-to-end. As a final check, query the public Rekor transparency log for the entry Cosign created — if it's there, the signature is durably recorded independent of your own registry:

rekor-cli search --artifact us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-images/api@sha256:<digest>

How Safeguard Helps

Getting Cosign keyless signing GCP pipelines running is the first mile, not the finish line. In practice, teams need to know which images across dozens of repositories and Cloud Build triggers are actually signed, which signatures point at stale or overly broad identities, and whether Binary Authorization policies are being enforced consistently as services scale out.

Safeguard continuously inventories every container image, SBOM, and Sigstore attestation flowing through your GCP environment — Artifact Registry, GKE, and Cloud Run alike — and flags images that reach a cluster without a valid signature or whose certificate identity doesn't match your expected build system. Rather than relying on a single Binary Authorization policy as your only enforcement point, Safeguard gives you visibility into provenance drift across the whole software supply chain: unsigned base images pulled into a build, third-party dependencies with no attestation trail, or a Cloud Build trigger that got modified to skip the signing step entirely.

For teams standardizing on Sigstore GCP workflows, Safeguard also correlates signing and verification events with vulnerability and SBOM data, so you can answer not just "is this image signed" but "is this signed image also safe to run." That combination — cryptographic provenance plus continuous supply chain monitoring — is what turns keyless signing from a one-time setup task into an durable, auditable control.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.