The SolarWinds attack compromised 18,000 organizations through a single poisoned software update. The Log4Shell vulnerability lurked in a dependency that most teams did not even know they shipped. The xz backdoor was inserted by a trusted maintainer over years of social engineering. Software supply chain attacks are now the primary vector for high-impact breaches — and the defense starts with knowing exactly what is in your software and cryptographically proving it has not been tampered with.
The Three Pillars of Supply Chain Security
Modern supply chain defense rests on three technologies that work together:
- SBOMs (Software Bill of Materials) — a machine-readable inventory of every component in your software. Think of it as an ingredients list for software.
- SLSA (Supply-chain Levels for Software Artifacts) — a framework that defines provenance requirements. It answers: "Where was this software built, from what source, and by whom?"
- Sigstore — a suite of tools for keyless code signing and verification. It answers: "Has this artifact been tampered with since it was produced?"
Sigstore: Keyless Code Signing
Traditional code signing requires managing long-lived private keys — generating them, securing them, rotating them, revoking them. Sigstore eliminates this entirely. Instead of a persistent key, you authenticate with your identity provider (GitHub, Google, Microsoft) and receive a short-lived certificate that binds the signature to your verified identity.
Installing Sigstore Tools
# Install cosign (container/artifact signing)
COSIGN_VERSION="v2.4.1"
curl -fsSLO "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
# Install rekor-cli (transparency log client)
REKOR_VERSION="v1.3.6"
curl -fsSLO "https://github.com/sigstore/rekor/releases/download/${REKOR_VERSION}/rekor-cli-linux-amd64"
chmod +x rekor-cli-linux-amd64
sudo mv rekor-cli-linux-amd64 /usr/local/bin/rekor-cli
# Verify installations
cosign version
rekor-cli version
Sign a Container Image (Keyless)
# Build and push an image
podman build -t registry.company.com/webapp:v1.2.0 .
podman push registry.company.com/webapp:v1.2.0
# Sign the image — opens browser for OIDC authentication
cosign sign registry.company.com/webapp:v1.2.0
# What just happened:
# 1. Cosign contacted Fulcio (Sigstore CA) with your OIDC token
# 2. Fulcio issued a short-lived certificate binding your identity to a key
# 3. Cosign signed the image digest with that key
# 4. The signature was recorded in Rekor (transparency log)
# 5. The certificate expired (typically in 10 minutes)
# No private key to manage, no key to leak
# Verify the signature
cosign verify \
--certificate-identity=engineer@company.com \
--certificate-oidc-issuer=https://accounts.google.com \
registry.company.com/webapp:v1.2.0
# Output shows the verified signature details
Sign with a Key (Offline/CI Environments)
# For CI/CD pipelines that cannot do browser-based OIDC
# Generate a key pair
cosign generate-key-pair
# Sign with the private key
cosign sign --key cosign.key registry.company.com/webapp:v1.2.0
# Verify with the public key
cosign verify --key cosign.pub registry.company.com/webapp:v1.2.0
# For GitHub Actions, use workload identity:
# cosign sign --oidc-issuer=https://token.actions.githubusercontent.com \
# registry.company.com/webapp:v1.2.0
SBOMs: Know What You Ship
An SBOM lists every component in your software — the libraries, their versions, their licenses, and their dependencies. Two standard formats dominate: SPDX (Linux Foundation) and CycloneDX (OWASP).
Generating SBOMs
# Install Syft (the best SBOM generator for Linux)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate SBOM from a container image
syft registry.company.com/webapp:v1.2.0 -o spdx-json > sbom.spdx.json
# Generate CycloneDX format
syft registry.company.com/webapp:v1.2.0 -o cyclonedx-json > sbom.cdx.json
# Generate SBOM from a directory (source code)
syft dir:./src -o spdx-json > source-sbom.spdx.json
# Generate SBOM from a running container
syft podman:webapp-container -o spdx-json
# Example output structure (simplified):
# {
# "spdxVersion": "SPDX-2.3",
# "name": "webapp:v1.2.0",
# "packages": [
# {
# "name": "openssl",
# "versionInfo": "3.0.13",
# "supplier": "Organization: OpenSSL Software Foundation",
# "licenseConcluded": "Apache-2.0"
# },
# {
# "name": "glibc",
# "versionInfo": "2.34",
# ...
# }
# ]
# }
Attach SBOMs to Container Images
# Attach the SBOM as an attestation to the image
cosign attest --predicate sbom.spdx.json \
--type spdxjson \
registry.company.com/webapp:v1.2.0
# Verify and retrieve the attestation
cosign verify-attestation \
--type spdxjson \
--certificate-identity=engineer@company.com \
--certificate-oidc-issuer=https://accounts.google.com \
registry.company.com/webapp:v1.2.0
# Now the SBOM travels WITH the image — it cannot be separated or tampered with
Vulnerability Scanning Against SBOMs
# Install Grype (vulnerability scanner that consumes SBOMs)
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Scan an SBOM for known vulnerabilities
grype sbom:sbom.spdx.json
# Scan a container image directly
grype registry.company.com/webapp:v1.2.0
# Output format options
grype sbom:sbom.spdx.json -o json > vulnerabilities.json
grype sbom:sbom.spdx.json -o table
# Filter by severity
grype sbom:sbom.spdx.json --only-fixed --fail-on critical
# Example output:
# NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
# openssl 3.0.13 3.0.14 rpm CVE-2024-XXXX High
# curl 8.5.0 8.6.0 rpm CVE-2024-YYYY Medium
SLSA: Provenance and Build Integrity
SLSA (pronounced "salsa") is a security framework with four levels:
- SLSA 1 — Build process is documented
- SLSA 2 — Build service generates provenance (who built it, from what source)
- SLSA 3 — Build service is hardened (isolated, non-forgeable provenance)
- SLSA 4 — Two-person review + hermetic builds
# Generate SLSA provenance with cosign
# The provenance attestation records: source repo, commit, builder, build time
# Using GitHub Actions (automatic SLSA 3 with official generator):
# .github/workflows/build.yaml
# uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
# For manual provenance generation:
cat > provenance.json << 'EOF'
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "registry.company.com/webapp",
"digest": {"sha256": "abc123..."}
}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://company.com/CustomBuild@v1",
"externalParameters": {
"source": "https://github.com/company/webapp",
"ref": "refs/tags/v1.2.0"
}
},
"runDetails": {
"builder": {
"id": "https://ci.company.com/build/12345"
}
}
}
}
EOF
# Attach provenance to the image
cosign attest --predicate provenance.json \
--type slsaprovenance \
registry.company.com/webapp:v1.2.0
# Verify provenance
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity=ci@company.com \
--certificate-oidc-issuer=https://accounts.google.com \
registry.company.com/webapp:v1.2.0
Enforcing Policies: Admission Controllers
# In Kubernetes: use Kyverno or Sigstore Policy Controller
# to ENFORCE that only signed images with valid SBOMs can run
# Install Sigstore Policy Controller
helm repo add sigstore https://sigstore.github.io/helm-charts
helm install policy-controller sigstore/policy-controller \
-n sigstore-system --create-namespace
# Create a policy that requires cosign signatures
cat << 'EOF' | kubectl apply -f -
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "registry.company.com/**"
authorities:
- keyless:
identities:
- issuer: "https://accounts.google.com"
subject: "*@company.com"
mode: enforce
EOF
# Now any pod using unsigned images from your registry will be REJECTED
# kubectl run test --image=registry.company.com/unsigned:latest
# Error: image policy webhook denied the request
The Rekor Transparency Log
# Rekor is an immutable, tamper-evident log of signing events
# Every cosign signature is automatically recorded in Rekor
# Search Rekor for signatures by email
rekor-cli search --email engineer@company.com
# Get a specific log entry
rekor-cli get --uuid 362f8ecba72f4326...
# Verify a signature exists in the log
rekor-cli verify --artifact myapp.tar.gz --signature myapp.tar.gz.sig
# Monitor Rekor for YOUR artifacts (detect unauthorized signing)
rekor-cli search --sha "sha256:$(sha256sum myapp.tar.gz | awk '{print $1}')"
# You can also run a private Rekor instance for internal use
# This keeps your signing metadata within your infrastructure
Complete CI/CD Pipeline Integration
#!/bin/bash
# ci-pipeline.sh — Build, scan, sign, attest, push
set -euo pipefail
IMAGE="registry.company.com/webapp"
TAG="v$(date +%Y%m%d)-${CI_COMMIT_SHORT_SHA}"
echo "=== Building ==="
buildah build -t "${IMAGE}:${TAG}" .
echo "=== Generating SBOM ==="
syft "${IMAGE}:${TAG}" -o spdx-json > sbom.spdx.json
echo "=== Scanning for vulnerabilities ==="
grype sbom:sbom.spdx.json --fail-on critical
# Pipeline FAILS if critical vulnerabilities found
echo "=== Pushing image ==="
podman push "${IMAGE}:${TAG}"
echo "=== Signing image ==="
cosign sign --key env://COSIGN_PRIVATE_KEY "${IMAGE}:${TAG}"
echo "=== Attaching SBOM attestation ==="
cosign attest --predicate sbom.spdx.json \
--type spdxjson \
--key env://COSIGN_PRIVATE_KEY \
"${IMAGE}:${TAG}"
echo "=== Attaching provenance ==="
# Generate provenance
cat > provenance.json << PROV
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "${IMAGE}", "digest": {"sha256": "$(podman inspect ${IMAGE}:${TAG} --format='{{.Digest}}' | cut -d: -f2)"}}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://company.com/ci@v1",
"externalParameters": {"source": "${CI_REPOSITORY_URL}", "ref": "${CI_COMMIT_REF}"}
},
"runDetails": {"builder": {"id": "${CI_PIPELINE_URL}"}}
}
}
PROV
cosign attest --predicate provenance.json \
--type slsaprovenance \
--key env://COSIGN_PRIVATE_KEY \
"${IMAGE}:${TAG}"
echo "=== Verification ==="
cosign verify --key cosign.pub "${IMAGE}:${TAG}"
cosign verify-attestation --type spdxjson --key cosign.pub "${IMAGE}:${TAG}"
echo "DONE: ${IMAGE}:${TAG} — signed, attested, verified"
Supply chain security is not a product you buy — it is a practice you adopt. SBOMs give you visibility into what you ship. Sigstore gives you cryptographic proof of who built it and that it has not been tampered with. SLSA gives you a framework for progressively hardening your build pipeline. Together, they transform your software delivery from "trust the binary" to "verify everything, trust nothing." The tools are free, open source, and production-ready. The only cost is the discipline to integrate them into every build.