Sitemap

Securing Docker Images with Sigstore Cosign

6 min readJun 17, 2025

--

Modern software supply chains are increasingly under attack. Recent high-profile incidents (SolarWinds, Codecov, supply-chain malware, etc.) show how malicious actors can inject bad code into builds or dependencies . To defend against this, teams must cryptographically tie each artifact to its source. Sigstore/Cosign is a new open-source standard for exactly this: it makes it easy to sign, store, and verify software artifacts so you can trust your containers. Google even called Sigstore “Let’s Encrypt for Code Signing” — it provides free certificates and automation for signing, backed by public transparency logs . In short, Cosign lets you attest “I built this container” and “it hasn’t been tampered with,” hardening your CI/CD pipeline against supply-chain attacks .

What Are Sigstore and Cosign?

Sigstore is a CNCF-backed project (by Linux Foundation/OpenSSF) that simplifies software signing and verification. It combines an identity system (OIDC), a free certificate authority (Fulcio), and an immutable transparency log (Rekor) to automate trust in artifacts . Cosign is the Sigstore tool for containers (OCI artifacts). With Cosign you can sign any container image or OCI artifact and automatically store its signature in the registry alongside the image. The signature is transparent: by convention it is pushed as a “.sig” tag tied to the image digest, so Cosign can later fetch and verify it without extra bookkeeping . For example, if you sign example.com/myimage@sha256:abc123, Cosign will upload a signature to example.com/myimage:sha256-abc123.sig . Cosign supports both key-based signing (using a private key or KMS) and keyless/OIDC signing (issuing ephemeral certificates via GitHub/Google etc.) . In all cases, the signature and metadata are recorded in Rekor’s public log so that anyone can audit the artifact’s provenance .

Key-Based Container Signing

A common workflow is to use a long-term key pair. For example, on your workstation or CI you can generate a key pair with Cosign and keep the private key safe (ideally in a secured secret or KMS). Then you push your container and sign it:

  • Generate a key pair:
cosign generate-key-pair

This creates cosign.key (private) and cosign.pub (public) .

  • Build, tag, and push your image:
docker build -t myapp:latest .
docker tag myapp:latest docker.io/myuser/myapp:latest
docker push docker.io/myuser/myapp:latest
  • Sign the image:
cosign sign --key cosign.key docker.io/myuser/myapp:latest

Cosign will prompt for the key password and then output something like Pushing signature to: docker.io/myuser/myapp . The .sig tag is pushed to the registry alongside your image digest.

  • Verify the signature:
cosign verify --key cosign.pub docker.io/myuser/myapp:latest

This checks that the signature matches the image and was created with the corresponding private key. A successful check will list the verified signature details . At this point you know the image came from your key and hasn’t been altered.

Keyless (OIDC) Signing

Sigstore’s recommended approach is keyless signing, which avoids handling long-lived keys. Cosign will use OpenID Connect (OIDC) to mint a short-lived Fulcio certificate tied to your identity, sign the image, then discard the private key . The typical steps in an environment (like GitHub or local with Google/Microsoft/GitHub auth) are:

  • Invoke Cosign to sign without a key:
cosign sign docker.io/myuser/myapp:latest

Cosign will open a browser or use OIDC to authenticate you (GitHub/GCP/Microsoft, etc). You’ll see a prompt asking to grant Sigstore permission to log your identity in a transparency log . This is by design — Fulcio issues you a certificate (valid for minutes) saying “this identity built the image,” and Cosign uses that to sign. The private key is ephemeral and then discarded . The signature is still pushed as a .sig tag, and the certificate+signature pair is recorded in Rekor for public audit.

  • Verify the keyless signature:
cosign verify docker.io/myuser/myapp:latest

Cosign will automatically retrieve the signature and attached certificate from the registry and validate it against Sigstore’s trust roots. By default this proves the image was signed by a valid Sigstore certificate. (You can also enforce that a specific identity signed it by adding — certificate-identity and — certificate-oidc-issuer flags .) The output will list the verified claims and digest, confirming the image’s origin.

Example: Docker Hub and GHCR

Whether you use Docker Hub or GitHub Container Registry (GHCR), the pattern is the same: push the image, then sign the registry reference. For example, with GitHub Packages:

docker pull nginx:latest
docker tag nginx:latest ghcr.io/myorg/my-repo:latest
docker push ghcr.io/myorg/my-repo:latest
cosign sign --key cosign.key ghcr.io/myorg/my-repo:latest # key-based

In this example, after docker push the image, cosign sign uploads ghcr.io/myorg/my-repo:sha256-*.sig. You can verify it with cosign verify — key cosign.pub ghcr.io/myorg/my-repo:latest . Note that Cosign requires access to the registry: by Sigstore’s docs you must push the image first because Cosign needs to read it from the registry . Once signed, any client can do cosign verify on that same reference to ensure integrity.

GitHub Actions CI/CD Workflow

To integrate signing into CI/CD, you can use GitHub Actions. Set up a workflow that builds your image, pushes it to a registry, signs it, and then verifies before deployment. Key points:

  • Permissions: Grant id-token: write (for OIDC), plus whatever registry write permissions you need (e.g. packages: write for GHCR) .
  • Install Cosign: Use the official sigstore/cosign-installer Action in a step. For example:
- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.2

This ensures cosign is available in later steps .

  • Build & Push: Use actions/checkout, login to your registry (e.g. docker/login-action for GHCR), and then docker/build-push-action to build and push your image tag.
  • Sign: After pushing, run cosign sign (with — keyless if using OIDC) on the newly pushed reference.
  • Verify: In a downstream job (or step) run cosign verify on that same reference. If verification fails, the workflow can halt and prevent deployment.

A full example workflow might look like this:

name: Build, Sign, and Verify

on:
push:
branches: [ main ]

permissions:
contents: read
packages: write # grant write to registry (GHCR/Docker Hub)
id-token: write # needed for GitHub OIDC (keyless signing)

jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}

- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.2

- name: Sign image with Cosign
run: |
cosign sign --keyless ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}

verify-and-deploy:
runs-on: ubuntu-latest
needs: build-and-sign
steps:
- uses: actions/checkout@v3

- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.2

- name: Verify image signature
run: |
cosign verify ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}

- name: Deploy (placeholder)
run: echo "Deploying verified image..."

This workflow checks out the code, builds and pushes the Docker image tagged with the commit SHA, then signs it with Cosign using GitHub’s OIDC (note the id-token: write permission) . The verify-and-deploy job then ensures the image signature is valid before proceeding. By failing this step if verification fails, you guarantee only trusted images get deployed.

Best Practices

  • Use Transparency Logs: Sigstore automatically records every signature in the Rekor log . This makes signatures auditable and tamper-evident. When verifying, Cosign checks Rekor to ensure the signature is genuine and hasn’t been revoked or spoofed.
  • Avoid Long-lived Secrets: Prefer keyless (OIDC) signing or short-lived keys. The Fulcio-issued certificates expire in minutes, so even if an attacker stole them, they’d quickly become useless . If you do use keys, store them in a secure KMS or hardware module and rotate them often.
  • Least-Privilege CI: Grant only necessary permissions in CI. For example, give Actions only id-token: write (not broad repo access) so OIDC can work without exposing secrets . Do not bake private keys into the repo — use secrets or KMS references.
  • Verify in Deployment: Always run cosign verify (or equivalent policy checks) before deploying a container. This closes the loop: your deployment pipeline will refuse to run images that aren’t properly signed.
  • Annotate & Audit: You can attach annotations (e.g. git commit, build ID) to signatures with cosign sign -a key=value. These show up in the certificate claims and can enforce policies downstream.

By integrating Cosign signing and verification into your build process (as shown above), you greatly strengthen the trustworthiness of your Docker images. Any user or system pulling your images can cosign verify them to confirm they came from you and haven’t been tampered with, defending your supply chain against many modern threats .

--

--

No responses yet