Cosign Container Image Signing

Authentication & Authorization advanced Building with AI ~35 min read

Overview

Software supply chain security has become a critical concern as organizations increasingly rely on container images as their primary deployment artifact. A container image pulled from a registry could have been tampered with in transit, built from compromised source code, or replaced entirely by an attacker who gained registry access. Without cryptographic verification, you’re deploying code on trust alone. Container image signing solves this by attaching a digital signature to each image, allowing consumers to verify that the image was produced by a trusted builder and hasn’t been modified since.

Cosign, part of the Sigstore project, is the leading tool for container image signing and verification. Its breakthrough innovation is “keyless” signing: instead of managing long-lived private keys (which themselves become targets), Cosign uses ephemeral keys backed by OIDC identity providers. A developer or CI system authenticates with GitHub, Google, or another OIDC provider, receives a short-lived certificate from Sigstore’s Fulcio CA, signs the image, and the signing event is recorded in Sigstore’s Rekor transparency log. The private key exists only in memory for the duration of the signing operation and is then discarded.

In a SaaS platform like the platform, Cosign is integrated into the CI/CD pipeline to sign every container image built from the main branch. GitHub Actions authenticates via OIDC to obtain a Fulcio certificate, signs the image digest (never the mutable tag), and verification is enforced at deployment time using admission controllers. This creates an unbroken chain of trust from source code commit to running container.

Core Concepts

Sigstore Ecosystem

Sigstore is an umbrella project providing three key services: Cosign for signing and verifying container images and other artifacts, Fulcio as a free certificate authority that issues short-lived certificates based on OIDC identity, and Rekor as an immutable transparency log that records signing events. Together, they eliminate the need for key management while providing non-repudiation through the transparency log.

Key Concept: Keyless Signing

Keyless signing doesn’t mean “no keys.” It means the signing key is ephemeral—generated on the fly, used once, and discarded. Trust is anchored in the OIDC identity (e.g., a GitHub Actions workflow identity) and recorded in a tamper-proof transparency log, not in a long-lived private key that could be stolen.

Image Digests vs. Tags

Container image tags (like :latest or :v1.2.3) are mutable pointers—they can be moved to reference a different image at any time. Image digests (sha256:abc123...) are content-addressable and immutable. Cosign always signs the digest, never the tag. This ensures that the signature is bound to the exact image content. If the tag is moved to a different image, verification fails because the digest doesn’t match.

OCI Registry as Signature Storage

Cosign stores signatures as OCI artifacts in the same container registry as the image itself. The signature is pushed as a separate tag (derived from the image digest) in the same repository. This means no additional infrastructure is needed to store or distribute signatures—they travel with the image through any standard OCI registry.

Key Concept: Transparency Logs

Rekor records every signing event in an append-only Merkle tree. Even if an attacker compromises a signing identity, the transparency log provides a public audit trail. Any signature not present in the log can be rejected, and anomalous signing activity (unexpected signers, unusual times) can be detected by monitoring the log.

How It Works

Signing Flow (Keyless with GitHub Actions)

  1. GitHub Actions workflow requests an OIDC token from GitHub’s identity provider
  2. Cosign presents this token to Fulcio, which verifies the OIDC claims
  3. Fulcio issues a short-lived X.509 certificate binding the ephemeral public key to the GitHub identity
  4. Cosign generates an ephemeral keypair, signs the image digest with the private key
  5. The signature, certificate, and image digest are recorded in Rekor’s transparency log
  6. The signature is pushed to the OCI registry alongside the image
  7. The ephemeral private key is discarded from memory

Verification Flow

  1. Consumer pulls the image and its associated signature from the registry
  2. Cosign verifies the signature against the image digest using the certificate in the signature bundle
  3. The certificate chain is validated against Fulcio’s root CA
  4. The signing event is verified to exist in Rekor’s transparency log
  5. Identity claims are checked (e.g., the signer must be a specific GitHub Actions workflow)
  6. If all checks pass, the image is considered trusted

Implementation Patterns

GitHub Actions Signing Workflow

# .github/workflows/build-sign.yml
name: Build and Sign Container Image
on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  id-token: write  # Required for OIDC keyless signing

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

      - uses: sigstore/cosign-installer@v3

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

      - name: Build and Push
        id: build
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Sign the image
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        env:
          COSIGN_EXPERIMENTAL: "true"

Verification in Go (Admission Controller)

package verify

import (
    "context"
    "fmt"

    "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
    "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
    "github.com/sigstore/cosign/v2/pkg/cosign"
    ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
    "github.com/google/go-containerregistry/pkg/name"
)

// VerifyImage checks that a container image has a valid Cosign signature
// from the expected OIDC issuer and subject.
func VerifyImage(ctx context.Context, imageRef string, expectedIssuer string, expectedSubject string) error {
    ref, err := name.ParseReference(imageRef)
    if err != nil {
        return fmt.Errorf("parse image ref: %w", err)
    }

    co := &cosign.CheckOpts{
        RekorClient:       rekor.GetRekorClient(rekor.DefaultRekorURL),
        RootCerts:         fulcio.GetRoots(),
        CertOidcIssuer:    expectedIssuer,
        CertIdentity:      expectedSubject,
        ClaimVerifier:     cosign.SimpleClaimVerifier,
    }

    signatures, _, err := cosign.VerifyImageSignatures(ctx, ref, co)
    if err != nil {
        return fmt.Errorf("signature verification failed: %w", err)
    }

    if len(signatures) == 0 {
        return fmt.Errorf("no valid signatures found for %s", imageRef)
    }

    return nil
}
Key Concept: Identity-Based Verification

When verifying keyless signatures, you specify who should have signed (e.g., the GitHub Actions identity for your repo) rather than what key signed. This is more meaningful—you trust the CI pipeline identity, not a specific key that could be rotated or compromised.

Practical Applications

The platform’s deployment pipeline enforces image signature verification at multiple stages. In the CI pipeline, after building and signing the image, a verification step immediately confirms the signature is valid and recorded in Rekor. At deployment time, a Kubernetes admission webhook (using the Connaisseur or Kyverno policy engine) intercepts pod creation requests and verifies that every image has a valid Cosign signature from the expected GitHub Actions workflow identity.

This prevents several attack vectors: a compromised registry cannot serve unsigned or re-signed images; a compromised CI pipeline on a non-main branch cannot produce images that pass verification (the OIDC subject includes the branch name); and any manual image push that bypasses CI will lack a valid signature and be rejected at deployment.

Software Bill of Materials (SBOM) Attestation

Beyond simple signatures, Cosign supports attaching attestations to images. The platform generates an SBOM (Software Bill of Materials) during the build process and attaches it as an in-toto attestation signed with the same keyless flow. This allows consumers to inspect the exact dependencies included in the image, supporting vulnerability scanning and compliance requirements.

Key Concept: Defense in Depth

Image signing is one layer of supply chain security. Combine it with pinned base images, reproducible builds, SBOM generation, vulnerability scanning, and admission controllers for comprehensive protection. Each layer catches what others miss.

Common Pitfalls

Further Reading