Skip to content

Registries & Supply-Chain Security

What this lesson gives you

A container registry is the distribution mechanism for images. Supply-chain security — signing images, scanning for vulnerabilities, generating SBOMs — is table stakes for any regulated or security-conscious client.

Estimated time: 18 min read · Part: Docker & Kubernetes

Learning Objectives

After this lesson you will be able to: (1) distinguish between public and private registries and explain their tradeoffs; (2) define SBOM and explain why it matters for compliance and incident response; (3) describe image signing with Sigstore/cosign; (4) explain CVE scanning and where it fits in a pipeline; (5) frame a supply-chain security conversation for a regulated enterprise client.

Container Registries

A container registry is a server that stores and distributes Docker images. When you docker push, the image layers are uploaded to the registry; when you docker pull (or a Kubernetes node pulls an image to run), the layers are downloaded. Registries are organized into repositories (one per service, typically) containing images identified by tags (version labels) and digests (immutable SHA-256 hashes of the image content).

Registry Type Key features Typical use
Docker Hub Public / private Default registry; 10M+ public images; free tier with rate limits Open-source images, base images
GitHub Container Registry (GHCR) Public / private Integrated with GitHub; free for public repos; OIDC auth with Actions Teams already on GitHub
Amazon ECR Private Deep AWS integration; IAM-based auth; vulnerability scanning included AWS workloads
Google Artifact Registry Private Successor to GCR; multi-format (containers, Maven, npm); Workload Identity GCP workloads
Azure Container Registry Private AD integration; geo-replication; tasks for cloud builds Azure/Microsoft shops
Harbor Self-hosted Open-source, CNCF project; scanning, signing, replication, compliance On-premises / regulated industries

Tags vs. Digests: Mutable vs. Immutable

A tag like myapp:latest is mutable — it can be reassigned to point to any image. A digest like myapp@sha256:a1b2c3... is immutable — it uniquely identifies exactly one image content. For production deployments, pin to the digest when security and reproducibility matter. Kubernetes image pull policy settings interact with this: IfNotPresent won't re-pull a tag that points to a different image; Always will pull the new image but will run the new code silently. Digests eliminate this ambiguity entirely.

Software Bill of Materials (SBOM)

A Software Bill of Materials (SBOM) is a machine-readable inventory of every component and dependency in an artifact — every Python package, every OS library, their versions and licenses. An SBOM serves two purposes: vulnerability response (when a new CVE is published for OpenSSL 3.0.x, you can immediately query your SBOM store to find which services are affected); license compliance (ensuring you are not shipping GPL code in a proprietary product). Executive Order 14028 (US, 2021) mandates SBOMs for software sold to the federal government, and the EU Cyber Resilience Act is moving in the same direction. Any client in a regulated industry or with government customers needs an SBOM pipeline.

.github/workflows/supply-chain.yml
name: Supply Chain Security

on:
  push:
    branches: [main]

jobs:
  build-sign-sbom:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write    # required for OIDC and Sigstore keyless signing

    steps:
      - uses: actions/checkout@v4

      - 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 image
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

      # Generate SBOM using Syft
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
          artifact-name: sbom.spdx.json
          format: spdx-json       # SPDX is the standard SBOM format (also CycloneDX)

      # Scan for CVEs using Grype
      - name: Scan image for vulnerabilities
        uses: anchore/scan-action@v3
        with:
          image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
          fail-build: true         # fail the pipeline on HIGH or CRITICAL CVEs
          severity-cutoff: high

      # Sign the image using Sigstore/cosign (keyless)
      - name: Sign image with cosign
        uses: sigstore/cosign-installer@v3
      - run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

Image Signing with Sigstore and Cosign

Image signing creates a cryptographic attestation that a specific image (identified by digest) was produced by a specific CI workflow — and has not been tampered with since. The modern tool is cosign, part of the Sigstore project. Sigstore's keyless mode uses the CI job's OIDC identity token (the same mechanism used for keyless cloud authentication) to sign the image without managing long-lived private keys. The signature is stored in the same registry alongside the image. A consumer — Kubernetes admission controller, deployment script — can verify the signature before running the image. This closes the gap where an attacker who compromises a registry could push a malicious image without CI.

terminal — verify a signed image
# Verify that an image was signed by a specific GitHub Actions workflow
cosign verify \
  --certificate-identity-regexp "https://github.com/myorg/myrepo/.github/workflows/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myrepo@sha256:a1b2c3d4...

# Output if valid:
# Verification for ghcr.io/myorg/myrepo@sha256:a1b2c3d4...
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - Existence of the claims in the transparency log was verified offline
#   - The code-signing certificate claims were validated
#
# [{"critical":{"identity":{"docker-reference":"ghcr.io/myorg/myrepo"},
#    "image":{"docker-manifest-digest":"sha256:a1b2c3..."},...}}]

# List all attestations on an image
cosign tree ghcr.io/myorg/myrepo@sha256:a1b2c3d4...

Consulting Lens: Framing Supply-Chain Security

Supply-chain security is a board-level topic after SolarWinds, Log4Shell, and similar incidents. When talking to an enterprise client: (1) SBOM is the foundation — you can't respond to a vulnerability you don't know you have. Every production container should have an associated SBOM stored and queryable. (2) CVE scanning in CI catches known vulnerabilities before they ship; but also add a daily scheduled scan on the registry, because new CVEs are published against old images constantly. (3) Image signing is the integrity guarantee — it means a compromised registry or build artifact can be detected before code runs in production. Frame these three as a progression: inventory (SBOM), detection (scanning), integrity (signing). Any client missing the first — inventory — cannot effectively do the other two.

WHY supply chain risk Your deployed application is only as secure as its weakest dependency. An attacker who compromises an OS package, a Python library, or a registry can inject malicious code that runs in production. SBOMs, scanning, and signing are the three layers of defense.

HOW the three practices Generate an SBOM on every build (Syft); scan for CVEs in CI and on a schedule (Grype, Trivy); sign images keylessly with Sigstore/cosign. Verify signatures before deploying (Kyverno policy in Kubernetes).

WHERE regulated clients EO 14028 (US federal vendors), EU CRA, SOC 2 Type II, PCI-DSS all have supply-chain provisions. For any regulated client, SBOM + signing is a compliance checkbox that also delivers real security value.

Knowledge check

InterviewA new critical CVE is published for a Python library. How does an SBOM help you respond faster than a team that lacks one?

  • An SBOM automatically patches the vulnerability without human intervention.
  • An SBOM is a machine-readable inventory of all dependencies in every deployed artifact. You query it to immediately identify which services contain the vulnerable library and what versions — turning a days-long hunt across dozens of repos into a seconds-long database query.
  • SBOMs prevent CVEs from affecting your containers by blocking malicious packages.
  • An SBOM means you never have to patch because the vulnerability is catalogued.
Answer

An SBOM is a machine-readable inventory of all dependencies in every deployed artifact. You query it to immediately identify which services contain the vulnerable library and what versions — turning a days-long hunt across dozens of repos into a seconds-long database query.

Without an SBOM, responding to a CVE requires manually checking every service's dependency list — a process that takes days and is error-prone. With an SBOM store (e.g., Grype DB, DependencyTrack), you run one query: "which deployed artifacts contain library X at version Y?" and get a list in seconds. You can then prioritize patching by severity (is the vulnerable function actually called?), exposure (is this service internet-facing?), and impact. The SBOM is the inventory that makes the rest of your security response possible.

Knowledge check

ConceptWhat does Sigstore/cosign's keyless signing model mean, and why is it advantageous over traditional image signing with a private key?

  • Keyless means the image is not actually signed, just claimed to be signed.
  • Keyless signing uses the CI job's ephemeral OIDC identity token instead of a long-lived private key. The advantage is that there is no private key to store, rotate, or accidentally expose — the credential exists only for the duration of the build.
  • Keyless signing is weaker than private-key signing — it should only be used for non-production images.
  • Keyless means anyone can sign an image without any credentials.
Answer

Keyless signing uses the CI job's ephemeral OIDC identity token instead of a long-lived private key. The advantage is that there is no private key to store, rotate, or accidentally expose — the credential exists only for the duration of the build.

Traditional code-signing requires generating an RSA or ECDSA key pair, securely storing the private key, distributing the public key to verifiers, and rotating the key periodically. Any of these steps can go wrong. Sigstore's keyless model uses the CI workflow's OIDC token — a short-lived, cryptographically verifiable identity token that attests "this job ran in GitHub Actions on workflow X at commit Y." The signing authority (Fulcio) issues a certificate bound to this identity; the signature and certificate are logged in a transparency log (Rekor). The private key is ephemeral and discarded after use. A verifier checks the Rekor log rather than distributing public keys — a fundamentally simpler trust model.

Exercise

A client's security team has been asked to implement supply-chain security for their container pipeline. They have: GitHub Actions CI, images pushed to GHCR, services deployed to Kubernetes. Design the three-layer supply-chain security implementation (SBOM, CVE scanning, signing), specifying: where each layer runs (CI/CD step, scheduled job, deployment gate), what tool to use, and how the three layers connect in the event of a new critical CVE disclosure.

Show solution
LAYER 1: SBOM GENERATION (runs in CI on every build)
Tool: Syft (anchore/sbom-action GitHub Action)
When: after docker push, before deploy
Output: SBOM in SPDX-JSON format, uploaded as CI artifact
         AND attached to the image as an OCI attestation
Storage: DependencyTrack or Grype DB for queryability across services

LAYER 2: CVE SCANNING
A) In CI (fast feedback): Grype scan on every build
   - Fail build on HIGH/CRITICAL severity
   - Allows blocking new vulnerabilities from shipping
B) Scheduled (catches newly published CVEs against old images):
   - Daily GitHub Actions scheduled workflow
   - Scan all images currently running in production
   - Alert on Slack/PagerDuty if new HIGH/CRITICAL CVEs discovered
Tool: Grype or Trivy

LAYER 3: IMAGE SIGNING
Tool: Sigstore/cosign (keyless via OIDC, no key management)
When: after CVE scan passes, before deploy step
Enforcement: Kyverno or OPA Gatekeeper policy in Kubernetes
- Kubernetes admission controller verifies cosign signature
- Unsigned or untrusted images are rejected at deploy time

EVENT FLOW: New Critical CVE Published
1. Daily scheduled scan (Layer 2B) detects vulnerability in prod image
2. Alert sent to security team with affected services and severity
3. Team queries DependencyTrack (Layer 1): which services, which versions
4. Priority triage: internet-facing services patched first
5. Dependency bumped, PR opened, CI runs (Layer 2A) — blocks if CVE persists
6. New image built, SBOM generated (Layer 1), signed (Layer 3)
7. Kubernetes admission controller verifies signature before rollout
8. DependencyTrack record updated for new clean image

Key Takeaways

  • A registry stores and distributes images by tag (mutable) and digest (immutable); pin to digest for reproducibility.
  • An SBOM is a machine-readable inventory of all components in an artifact — essential for fast CVE response.
  • CVE scanning belongs in CI (block new vulns) AND on a schedule (catch new CVEs against old images).
  • Sigstore/cosign keyless signing removes long-lived private key management from the supply chain.
  • SBOM → scanning → signing: inventory, detection, integrity — the three-layer framework for regulated clients.