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.
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# 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.
- 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.
- 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 criticalapiVersion: 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.devOperational 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.
