secure the build pipeline from dependency to signed deploy

Software supply chain security in DevOps: from bill of materials to image signing

14 min read

Production software is assembled from hundreds of dependencies, base images, and build tools. Without SBOMs, signatures, and admission policies, teams cannot prove what shipped or block tampered artifacts before they reach the cluster.

Why supply chain attacks outpace application-level defenses

Modern services are assembled, not authored from scratch. A single container image can pull in hundreds of transitive libraries, a mutable base image tag, third-party build actions, and deployment manifests from Git. Incidents such as SolarWinds, Log4Shell, and the xz Utils backdoor show adversaries targeting build systems and dependencies rather than application logic alone. Platform teams routinely cannot enumerate every package inside an image, cannot prove the binary in production matches CI output, and cannot trace provenance when auditors ask who built a component. EO 14028 and the EU Cyber Resilience Act push SBOMs and vulnerability handling into procurement and compliance reviews even for teams outside government. The fix is not one scanner—it is visibility through SBOMs, integrity through signing, detection through CI gates, and enforcement through Kubernetes admission.

Four pillars: SBOM, signing, scanning, and policy enforcement

A mature posture combines four layers that reinforce each other. An SBOM is a machine-readable inventory—SPDX or CycloneDX—of every component in a build artifact. Signing attaches a cryptographic identity to the image or binary so registries and clusters can verify it was produced by an expected pipeline and was not modified in transit. Vulnerability scanning at build time maps SBOM packages to CVE databases before push, when remediation is cheapest. Policy enforcement at the cluster rejects unsigned images, images above your severity threshold, or images missing required attestations. Typical flow: developer pushes to Git, CI builds and tests, parallel jobs generate an SBOM, scan with Grype or Trivy, sign with Cosign using OIDC from the CI provider, push to the registry with SBOM and provenance attached, then Kyverno or OPA Gatekeeper verifies signatures and vulnerability policies at admission.

Generate and publish SBOMs with Syft on every build

Manual or quarterly SBOM exports are useless during an incident. Generate an SBOM for every image build in CI, store it as a build artifact, and attach it to the registry record alongside the image. Syft from Anchore inventories OS packages and application dependencies and emits SPDX or CycloneDX JSON. Pin the sbom-action version, scan the built image digest—not a mutable tag—and upload the SBOM to artifact storage for auditors. OCI-compatible registries accept SBOM attachments through Cosign referrers so scanners can correlate image digest to component list continuously.

YAML · GitHub Actions permissions and SBOM generation
permissions:
  contents: read
  packages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: |
          docker build -t "${REGISTRY}/${IMAGE}:${GITHUB_SHA}" .
          docker push "${REGISTRY}/${IMAGE}:${GITHUB_SHA}"
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: "${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}"
          format: cyclonedx-json
          output-file: sbom.cdx.json
      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cdx.json
Dockerfile · pin base image by digest
# Mutable tags change under you; pin digest for reproducible builds
FROM python:3.12-slim@sha256:abc123def456...
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER 10001
CMD ["python", "-m", "app.main"]

Sign images with Cosign keyless OIDC and attach the SBOM

Managing long-lived signing keys is operationally risky. Sigstore keyless signing uses short-lived certificates bound to your CI OIDC identity—GitHub Actions, GitLab CI, or cloud OIDC—and records events in the Rekor transparency log for audit. Grant id-token write permission, install Cosign, sign the image digest after push, then attach the SBOM or publish a CycloneDX attestation. Verify locally with cosign verify before enabling cluster enforcement. For air-gapped or regulated environments, use KMS-backed key pairs instead of keyless mode.

YAML · Cosign keyless sign and SBOM attach
- uses: sigstore/cosign-installer@v3

- name: Sign container image
  run: |
    cosign sign --yes \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

- name: Attach SBOM to image
  run: |
    cosign attach sbom --sbom sbom.cdx.json \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

- name: Verify signature in CI
  run: |
    cosign verify \
      --certificate-identity-regexp "https://github.com/${GITHUB_REPOSITORY}/.*" \
      --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

Gate builds with Grype and enforce signatures in Kubernetes

Scan the built image digest in CI before promotion. Grype and Trivy map SBOM contents to CVE data; fail the pipeline on critical severity or on a custom allow list for accepted risks. At deploy time, do not rely on developers to check signatures manually. Kyverno verifyImages validates Cosign signatures and can require SLSA or vulnerability attestations. Scope policies to your private registry patterns first, then expand. Test policies in audit mode before enforce to avoid blocking system namespaces.

YAML · Grype CVE gate in CI
- name: Scan image for vulnerabilities
  uses: anchore/scan-action@v3
  with:
    image: "${REGISTRY}/${IMAGE}:${GITHUB_SHA}"
    fail-build: true
    severity-cutoff: critical

# Alternative: Grype CLI
- name: Grype scan
  run: |
    grype "${REGISTRY}/${IMAGE}@${DIGEST}" \
      --fail-on critical
YAML · Kyverno ClusterPolicy for Cosign keyless verification
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: verify-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          mutateDigest: true
          required: true
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/your-org/your-repo/.github/workflows/build.yml@refs/heads/main"
                    rekor:
                      url: https://rekor.sigstore.dev

Operational practices: SLSA, provenance, CI hardening, and dependency hygiene

Adopt SLSA levels incrementally: start with build provenance attestation, then hardened isolated builders, then non-falsifiable provenance for release artifacts. Use GitHub attest-build-provenance or cosign attest with in-toto predicates where your platform supports it. Pin base images and GitHub Actions by digest or immutable version tags. Automate dependency updates with Renovate or Dependabot and run tests on every bump. Treat CI runners as production infrastructure: least-privilege cloud IAM, network egress controls, branch protection, and audit logs on workflow changes. Periodically review Rekor entries for unexpected signing identities. Store SBOMs and scan reports with the same retention as release artifacts so incident response can answer what shipped on a given date. Supply chain security is a lifecycle practice—visibility without enforcement drifts, and enforcement without SBOMs leaves blind spots during CVE disclosure.

Admission policies extend cluster hardening from our Kubernetes security hardening guide for production clusters.

Signing identities and registry credentials still depend on how secrets flow through CI, covered in secrets management for DevOps and CI/CD.