Supply Chain Security
End-to-end software supply chain security for Kubernetes workloads: SBOM generation, in-toto attestations, Sigstore Policy Controller, CI/CD pipeline hardening, dependency management, third-party operator security, and Helm chart verification.
- Supply Chain Attack Landscape
- The Full Trust Chain
- SBOM: Software Bill of Materials
- in-toto Attestations
- Sigstore Policy Controller
- CI/CD Pipeline Hardening
- Dependency Management
- Third-Party Operators & Helm Charts
- Software Composition Analysis
- Runtime Integrity
- Metrics, Alerts & Runbooks
- Best Practices
Coverage Checklist
- Supply chain attack taxonomy: 9 attack types
- Full trust chain from source to running pod
- SBOM formats: CycloneDX vs SPDX comparison
- SBOM generation: Syft, Trivy, Docker buildx
- SBOM storage and distribution via OCI registry
- in-toto attestation framework
- in-toto link metadata and layouts
- Cosign attest / verify-attestation
- Sigstore Policy Controller: ClusterImagePolicy
- Policy Controller: keyless + attestation policies
- GitHub Actions: pin actions to SHA, OIDC permissions
- GitHub Actions: secret scanning, dependency review
- Lockfile enforcement: go.sum, package-lock.json
- Renovate / Dependabot for automated updates
- Private module proxy / artifact repository
- Third-party operator security vetting
- Helm chart verification: provenance files, OCI signing
- SCA tools: OWASP Dependency-Check, Snyk, Grype
- VEX (Vulnerability Exploitability eXchange)
- Runtime integrity: dm-verity, kata containers
- 5 metrics, 4 alerts, 5 runbooks, 8 best practices
Supply Chain Attack Landscape
A software supply chain attack compromises software or its delivery infrastructure before it reaches the consumer. Kubernetes clusters are high-value targets because a single compromised component can affect all workloads.
Source Code Compromise
Attacker gains write access to source repository and injects malicious code. Bypasses all binary-level controls — the attack is in source.
Build System Compromise
Attacker controls CI/CD pipeline and injects malicious code during compilation or packaging without touching source. Classic SolarWinds pattern.
Dependency Confusion
Attacker publishes a package to a public registry with the same name as a private internal package at a higher version number. Build tools prefer the public version.
Typosquatting
Malicious packages with names similar to popular packages: reqeusts instead of requests, colourama instead of colorama.
Compromised Base Image
Malicious code injected into a popular Docker Hub base image. All downstream images built from it inherit the backdoor. See Image Security.
Registry Credential Theft
Attacker steals registry push credentials from CI/CD environment and replaces legitimate images with malicious ones using the same tag.
Malicious Third-Party Operator
Kubernetes operator installed from an untrusted source requests excessive RBAC permissions and acts as persistent backdoor with cluster-wide access.
Compromised Helm Chart
Helm chart from a public repository contains malicious container images or injects privileged workloads. Chart maintainer account compromise.
CI/CD Secret Exfiltration
Pull request from a fork triggers a CI workflow with access to organization secrets. Attacker exfiltrates registry credentials, signing keys, or cloud credentials.
The Full Trust Chain
Securing the supply chain means establishing and verifying trust at every step from source code to running container.
1. Source Code
├── Branch protection, required reviews, signed commits
├── Secret scanning (detect-secrets, Gitleaks, GitHub secret scanning)
└── Dependency lockfiles committed to repo
│
▼
2. CI/CD Build
├── Pinned action SHAs (GitHub Actions)
├── Minimal OIDC permissions, no long-lived secrets
├── Reproducible builds (hermetic where possible)
├── Generate SBOM (Syft / Trivy)
└── Generate SLSA provenance attestation
│
▼
3. Container Image
├── Multi-stage build, distroless base
├── Vulnerability scan (Trivy — block on CRITICAL/HIGH)
├── Cosign sign (keyless via OIDC)
└── Cosign attest (SBOM + provenance)
│
▼
4. Registry
├── Private registry, immutable tags
├── Registry scanning gate
└── Push credentials short-lived (OIDC, not static credentials)
│
▼
5. Admission
├── Sigstore Policy Controller: verify signature + attestations
├── Registry allow-list: only approved registries
└── Digest pinning enforcement
│
▼
6. Running Pod
├── Falco: runtime behavior monitoring
├── Trivy Operator: continuous CVE scanning
└── Network policies restrict blast radius
SBOM: Software Bill of Materials
An SBOM is a machine-readable inventory of every component in a software artifact — OS packages, language libraries, build tools, and their versions and licenses. It is the foundation of supply chain visibility.
SBOM Formats
| Format | Maintained By | Primary Use | Interchange |
|---|---|---|---|
| CycloneDX | OWASP | Security analysis, vulnerability correlation | JSON, XML, Protobuf |
| SPDX | Linux Foundation | License compliance, legal review | JSON, YAML, RDF, tag-value |
| SWID | ISO/IEC 19770-2 | Enterprise asset management | XML |
CycloneDX is optimized for security use cases — it has native fields for vulnerabilities, CVSS scores, and patch status. SPDX is the NTIA minimum elements standard and better suited for license compliance. Most tools support both. Prefer CycloneDX for Kubernetes supply chain pipelines.
Generating SBOMs with Syft
# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate CycloneDX SBOM from container image
syft registry.example.com/myapp@sha256:abc123 \
-o cyclonedx-json=sbom.cyclonedx.json
# Generate SPDX SBOM
syft registry.example.com/myapp@sha256:abc123 \
-o spdx-json=sbom.spdx.json
# Generate SBOM from source directory (during build)
syft dir:. -o cyclonedx-json=sbom.cyclonedx.json
# Generate from Docker image tarball
syft docker-archive:myapp.tar -o cyclonedx-json=sbom.cyclonedx.json
# Include licenses in output
syft registry.example.com/myapp@sha256:abc123 \
-o cyclonedx-json=sbom.cyclonedx.json \
--config syft.yaml # syft.yaml: catalogers, exclude patterns
Generating SBOMs with Trivy
# Generate CycloneDX SBOM with Trivy
trivy image \
--format cyclonedx \
--output sbom.cyclonedx.json \
registry.example.com/myapp@sha256:abc123
# Generate SPDX SBOM
trivy image \
--format spdx-json \
--output sbom.spdx.json \
registry.example.com/myapp@sha256:abc123
# Docker buildx: generate SBOM during build (BuildKit 0.11+)
docker buildx build \
--sbom=true \
--output type=registry \
--tag registry.example.com/myapp:1.0.0 \
.
Distributing SBOMs via OCI Registry
# Attach SBOM as OCI artifact referrer (using cosign)
cosign attach sbom \
--sbom sbom.cyclonedx.json \
--type cyclonedx \
registry.example.com/myapp@sha256:abc123
# Verify and retrieve attached SBOM
cosign download sbom registry.example.com/myapp@sha256:abc123
# Attach as attestation (preferred — signed and verifiable)
cosign attest \
--key cosign.key \
--type cyclonedx \
--predicate sbom.cyclonedx.json \
registry.example.com/myapp@sha256:abc123
# Download and verify SBOM attestation
cosign verify-attestation \
--key cosign.pub \
--type cyclonedx \
registry.example.com/myapp@sha256:abc123 | \
jq '.payload | @base64d | fromjson | .predicate'
in-toto Attestations
in-toto is a framework for cryptographically securing software supply chain operations. It defines a format for recording facts about software artifacts (attestations) that can be verified independently of the artifact itself.
Attestation Envelope (DSSE — Dead Simple Signing Envelope)
├── Statement
│ ├── _type: "https://in-toto.io/Statement/v0.1"
│ ├── subject: [{name, digest}] ← what artifact this is about
│ └── predicateType: "..." ← what kind of fact
│
└── Predicate (varies by predicateType)
├── slsaprovenance/v1: build metadata (builder, source, inputs)
├── cyclonedx: SBOM component list
├── spdx: SBOM (SPDX format)
├── cosign.sigstore.dev/attestation/vuln/v1: vulnerability scan results
└── custom: any JSON predicate you define
SLSA Provenance Attestation
# SLSA provenance predicate (v1) — generated by build platform
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [{
"name": "registry.example.com/myapp",
"digest": { "sha256": "abc123..." }
}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"ref": "refs/tags/v1.2.3",
"repository": "https://github.com/myorg/myrepo",
"path": ".github/workflows/release.yaml"
}
}
},
"runDetails": {
"builder": {
"id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0"
},
"metadata": {
"invocationId": "https://github.com/myorg/myrepo/actions/runs/1234567890",
"startedOn": "2024-01-15T10:00:00Z",
"finishedOn": "2024-01-15T10:05:00Z"
}
}
}
}
Vulnerability Attestation
# Generate vulnerability scan attestation with Trivy + Cosign
# 1. Run Trivy scan and save results
trivy image \
--format cosign-vuln \
--output vuln-results.json \
registry.example.com/myapp@sha256:abc123
# 2. Attest vulnerability scan results
cosign attest \
--key cosign.key \
--type vuln \
--predicate vuln-results.json \
registry.example.com/myapp@sha256:abc123
# 3. Verify vulnerability attestation at admission (Policy Controller)
# (Policy Controller can reject images whose latest scan exceeds CVE threshold)
Sigstore Policy Controller
The Sigstore Policy Controller is a validating admission webhook that enforces image signature and attestation policies. It is more expressive than Kyverno's verifyImages and is the reference implementation for Sigstore-native policies.
Both enforce Cosign signatures at admission. Policy Controller is purpose-built for Sigstore and supports richer attestation policies (e.g., require SBOM with no Critical CVEs). Kyverno's verifyImages is integrated with Kyverno's broader policy framework. If you already run Kyverno, use its verifyImages. If you want a dedicated supply chain tool, use Policy Controller. See Image Security for Kyverno verifyImages details.
# Install Sigstore Policy Controller
helm repo add sigstore https://sigstore.github.io/helm-charts
helm install policy-controller sigstore/policy-controller \
--namespace cosign-system \
--create-namespace
# Opt namespace into Policy Controller enforcement
kubectl label namespace production \
policy.sigstore.dev/include=true
ClusterImagePolicy: Keyless Signature
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "registry.example.com/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subject: https://github.com/myorg/myrepo/.github/workflows/release.yaml@refs/heads/main
ClusterImagePolicy: Require SBOM Attestation
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-sbom-attestation
spec:
images:
- glob: "registry.example.com/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "https://github.com/myorg/.*"
attestations:
- name: must-have-sbom
predicateType: https://cyclonedx.org/bom
policy:
type: cue
data: |
# CUE policy: SBOM must have at least one component
predicate: components: [_, ...]
ClusterImagePolicy: Require Vulnerability Scan with No Critical CVEs
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-vuln-attestation
spec:
images:
- glob: "registry.example.com/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "https://github.com/myorg/.*"
attestations:
- name: no-critical-cves
predicateType: cosign.sigstore.dev/attestation/vuln/v1
policy:
type: cue
data: |
import "time"
# Scan must be less than 24 hours old
predicate: scanner: result: Summary: Critical: 0
predicate: metadata: scanFinishedOn: >time.Now().Add(-24*time.Hour)
CI/CD Pipeline Hardening
The CI/CD pipeline is the highest-privilege environment in the supply chain. Compromising the build pipeline allows injecting malicious code into all downstream artifacts without modifying source.
GitHub Actions Hardening
# .github/workflows/release.yaml — hardened workflow
name: Release
on:
push:
tags: ['v*']
permissions: # Minimal top-level permissions
contents: read
jobs:
build-and-sign:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for OIDC token (Cosign keyless)
packages: write # Required for pushing to GHCR
attestations: write # Required for GitHub Attestations API
steps:
# Pin actions to immutable SHA — never use @main or @v1 (mutable tags)
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- name: Log in to GHCR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # Short-lived, auto-rotated
- name: Build and push
id: build
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
sbom: true # BuildKit SBOM generation
provenance: mode=max # SLSA provenance
- name: Sign image
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
env:
COSIGN_EXPERIMENTAL: "1"
Using actions/checkout@v4 is dangerous — the v4 tag can be moved to point to a different commit. A compromised action maintainer or tag overwrite runs arbitrary code in your pipeline. Pin every action to an immutable commit SHA. Use tools like pin-github-action or Dependabot to manage SHA updates.
Fork PR Security
# Prevent fork PRs from accessing secrets
on:
pull_request_target: # DANGER: runs in context of base repo, has secrets
# Use pull_request instead — runs in fork context, no secrets
on:
pull_request: # Safe: no access to base repo secrets
# Require approval for first-time contributors
# Settings → Actions → Fork pull request workflows → Require approval
Securing CI Secrets
| Approach | Description | Security Level |
|---|---|---|
| Static secrets in CI vars | Long-lived credentials stored in CI settings | Low |
| OIDC federation | CI gets short-lived token via OIDC trust with cloud provider | High |
| Vault AppRole from CI | CI authenticates to Vault, retrieves short-lived creds | High |
| GitHub Actions OIDC → AWS AssumeRoleWithWebIdentity | No static AWS credentials; role assumed per workflow run | Highest |
# GitHub Actions OIDC → AWS ECR (no static credentials)
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr-push
aws-region: us-east-1
# AWS IAM role trust policy (allows GitHub Actions OIDC)
{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
}
}
}
Dependency Management
Lockfile Integrity
Lockfiles record the exact versions and checksums of every dependency. Committing them to the repository ensures reproducibility and enables detection of unexpected changes.
| Language | Lockfile | Integrity Mechanism | Enforce in CI |
|---|---|---|---|
| Go | go.sum | SHA-256 hash of each module version | go mod verify |
| Node.js | package-lock.json / yarn.lock | SHA-512 per package (npm); SHA-1 (yarn) | npm ci (fails if lock mismatch) |
| Python | requirements.txt + hashes / poetry.lock | --hash=sha256: per package | pip install --require-hashes |
| Rust | Cargo.lock | Checksum per crate version | cargo fetch --locked |
| Java (Maven) | None built-in | Use checksum plugin or Gradle verification | Gradle: verification-metadata.xml |
# Python: install with hash verification
# requirements.txt with hashes
requests==2.31.0 \
--hash=sha256:58cd2187423839f822e6c5d5ded81a... \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc...
# Install enforcing hash verification
pip install --require-hashes -r requirements.txt
# Go: verify all module checksums
go mod verify
# Fails if any module hash doesn't match go.sum
# Node: use npm ci (not npm install) in CI
# npm ci: installs exactly what's in package-lock.json, fails on mismatch
npm ci --only=production
Private Module Proxy / Artifact Repository
Routing all dependency downloads through a private proxy prevents dependency confusion attacks — the proxy enforces that internal package names never resolve to public registries.
# Go: use private module proxy (Athens, Artifactory, Nexus)
GONOSUMCHECK=*.internal.example.com \
GONOSUMDB=*.internal.example.com \
GOFLAGS=-mod=mod \
GOPROXY=https://goproxy.internal.example.com,direct \
go build ./...
# npm: private registry via .npmrc
# .npmrc
registry=https://registry.example.com
//registry.example.com/:_authToken=${NPM_TOKEN}
# Block public registry fallback:
@myorg:registry=https://registry.example.com
# pip: private PyPI mirror
pip install --index-url https://pypi.internal.example.com/simple/ \
--no-deps \
requests==2.31.0
If your build tool falls back to a public registry when a package isn't found in your private registry, an attacker can publish a package with the same name as your internal package at a higher version. The build tool will download and execute the attacker's package. Prevent fallback by configuring your proxy to be authoritative for your namespace — or block all external registry access from build nodes.
Automated Dependency Updates
# .github/renovate.json — automated dependency updates with PR per change
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true // Auto-merge patch updates if CI passes
},
{
"matchDepTypes": ["action"],
"pinDigests": true // Pin GitHub Action versions to SHA
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
}
}
Third-Party Operators & Helm Charts
Kubernetes operators and Helm charts from external sources are high-risk: they often request cluster-admin or broad RBAC permissions and run privileged workloads.
Operator Security Vetting Checklist
| Check | Command / Tool | Red Flag |
|---|---|---|
| RBAC permissions requested | kubectl auth can-i --list --as=system:serviceaccount:operator-ns:operator-sa | cluster-admin, wildcard resources, secret reads cluster-wide |
| Pod security | Review operator Deployment spec | privileged:true, hostPID, hostNetwork, hostPath mounts |
| Image provenance | cosign verify operator images | No signature, unknown registry, no provenance |
| CVE status | trivy image on operator images | CRITICAL CVEs in running operator image |
| Source code | Review GitHub repo | Inactive maintainers, no release process, no security policy |
| Network egress | Apply NetworkPolicy deny-all first | Operator dials back to vendor infrastructure unexpectedly |
Helm Chart Verification
# Helm provenance: verify chart integrity (classic Helm signing)
# Chart packager creates .prov file with PGP signature
# Import chart maintainer's PGP key
gpg --import maintainer-pubkey.gpg
# Install with provenance verification
helm install myrelease myrepo/mychart --verify
# Download chart and verify manually
helm pull myrepo/mychart --verify --provenance
# Helm OCI: charts stored as OCI artifacts can be signed with Cosign
cosign sign oci://registry.example.com/charts/mychart:1.2.3
cosign verify \
--key cosign.pub \
oci://registry.example.com/charts/mychart:1.2.3
# Helm chart security review: render and inspect before installing
# Render all templates to stdout
helm template myrelease myrepo/mychart --values my-values.yaml
# Check for dangerous RBAC
helm template myrelease myrepo/mychart | \
grep -A5 "ClusterRoleBinding\|cluster-admin\|\"*\""
# Check for privileged pods
helm template myrelease myrepo/mychart | \
grep -i "privileged\|hostPID\|hostNetwork\|hostPath"
# Use Polaris to audit rendered templates
helm template myrelease myrepo/mychart | polaris audit --audit-path -
Helm charts can have sub-chart dependencies. A parent chart with reasonable permissions might pull in a sub-chart with cluster-admin. Always render and inspect the full template output, not just the parent chart. Run helm dependency update and review charts/ directory contents.
Software Composition Analysis
Software Composition Analysis (SCA) identifies open-source components and their known vulnerabilities (CVEs) and license obligations in your codebase and container images.
SCA in the Pipeline
# Grype: vulnerability scan from SBOM
grype sbom:sbom.cyclonedx.json \
--fail-on critical \
--output json > vuln-report.json
# OWASP Dependency-Check for Java/Node/Python
dependency-check \
--project myapp \
--scan ./app \
--format JSON \
--out ./reports \
--nvdApiKey $NVD_API_KEY
# OSV-Scanner: checks against OSV database (comprehensive, fast)
osv-scanner --lockfile package-lock.json
osv-scanner --sbom sbom.cyclonedx.json
VEX: Vulnerability Exploitability eXchange
VEX documents allow you to assert that a CVE in a dependency is not exploitable in your specific artifact — reducing false positives in vulnerability reports.
# VEX document: assert CVE not exploitable in our context
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://example.com/vex/myapp-1.0.0",
"author": "security@example.com",
"timestamp": "2024-01-15T10:00:00Z",
"statements": [
{
"vulnerability": { "name": "CVE-2023-44487" },
"products": [{ "@id": "pkg:oci/myapp@sha256:abc123" }],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "HTTP/2 is not used by this component; TLS terminates at ingress"
}
]
}
# Generate VEX with vexctl
vexctl create \
--product "pkg:oci/myapp@sha256:abc123" \
--vuln CVE-2023-44487 \
--status not_affected \
--justification vulnerable_code_not_in_execute_path
# Trivy respects VEX: filter results using VEX document
trivy image \
--vex vex-document.openvex.json \
registry.example.com/myapp@sha256:abc123
Runtime Integrity
dm-verity for Container Image Verification
dm-verity is a Linux kernel mechanism that cryptographically verifies block device data on every read. When used with container storage, it ensures the on-disk image layers match their expected hash — preventing tampering after pull.
containerd and CRI-O support read-only overlay filesystems backed by dm-verity via the stargz-snapshotter or overlayfs-verity. This is primarily useful in environments where you cannot fully trust the node storage, such as edge deployments or multi-tenant bare-metal clusters.
Kata Containers for Workload Isolation
Kata Containers run each pod in a lightweight VM instead of a container. This provides hardware-level isolation between workloads and between workloads and the host kernel — the strongest available workload isolation.
# Use Kata Containers runtime class for high-security workloads
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata-containers
handler: kata
scheduling:
nodeSelector:
kata-containers: "true"
---
spec:
runtimeClassName: kata-containers
containers:
- name: sensitive-workload
image: registry.example.com/payment-service@sha256:abc123
Metrics, Alerts & Runbooks
Key Metrics
| Metric | Source | Description |
|---|---|---|
cosign_verification_failures_total | Policy Controller | Images rejected due to missing/invalid signature |
policy_controller_admissions_total | Policy Controller | Total admissions evaluated by Policy Controller |
trivy_vulnerability_id | Trivy Operator | CVEs in running workloads (see Image Security) |
sbom_age_hours | Custom / SBOM pipeline | Age of most recent SBOM per image |
unsigned_image_deployments_total | Policy Controller audit | Deployments that would be blocked in enforce mode |
Alerts
# Alert: Unsigned image deployment blocked
- alert: UnsignedImageBlocked
expr: increase(cosign_verification_failures_total[5m]) > 0
for: 0m
severity: critical
annotations:
summary: "Image signature verification failed — possible supply chain attack or CI misconfiguration"
# Alert: SBOM attestation missing for new deployment
- alert: SBOMAttestationMissing
expr: increase(policy_controller_admissions_total{policy="require-sbom-attestation",result="denied"}[5m]) > 0
annotations:
summary: "Deployment rejected: missing SBOM attestation"
# Alert: Critical CVE in SBOM attestation
- alert: CriticalCVEInAttestaton
expr: increase(policy_controller_admissions_total{policy="require-vuln-attestation",result="denied"}[5m]) > 0
annotations:
summary: "Deployment rejected: Critical CVE in vulnerability attestation"
# Alert: Policy Controller webhook unhealthy
- alert: PolicyControllerWebhookDown
expr: up{job="policy-controller"} == 0
for: 2m
severity: critical
annotations:
summary: "Policy Controller webhook down — supply chain enforcement not active"
Runbooks
Unsigned Image Deployment Blocked
1. Check which image is failing: Policy Controller logs
2. Determine if CI signing step ran: check workflow logs
3. If CI failure: re-run signing step, push signature
4. If attack suspected: check registry push audit logs for unauthorized pushes
Dependency Confusion Detected
1. Identify the suspicious package version pulled
2. Check if it came from public registry vs internal proxy
3. Block package at proxy level immediately
4. Audit all CI runs that may have used the package
5. Rotate any credentials that ran in affected CI jobs
Compromised Third-Party Operator
1. Delete operator deployment immediately
2. Audit RBAC actions by operator SA in last 24h via audit logs
3. Rotate any secrets the operator had access to
4. Check for resources created by the operator (check ownerReferences)
Policy Controller Admission Failures Spike
1. Check Policy Controller pod health and logs
2. Verify Fulcio/Rekor (Sigstore) are reachable (for keyless)
3. Check if signing keys/certificates have expired
4. Review ClusterImagePolicy for syntax errors after recent changes
Build Pipeline Secret Exposure
1. Immediately rotate all secrets that may have been exposed
2. Identify which runs/forks had access to the workflow
3. Revoke and re-issue registry push credentials, signing keys
4. Audit all images pushed during the exposure window
Best Practices
Sign every image — enforce verification at admission
Keyless Cosign signing in CI costs nothing and takes seconds. Without admission enforcement, signing is security theater. Deploy Policy Controller or Kyverno verifyImages with Enforce mode in production. See Image Security for Cosign details.
Generate and attest SBOMs for every build
SBOMs provide the component inventory needed for rapid CVE triage when new vulnerabilities are disclosed. Without an SBOM, determining whether your workload is affected requires manual analysis of every image.
Pin all CI actions to immutable SHA digests
Mutable action references (@main, @v1) execute whatever code the tag points to at run time — including code pushed after a maintainer account compromise. Use Renovate to automate SHA updates.
Use OIDC federation instead of static CI credentials
Static registry push credentials, cloud API keys, and signing keys stored in CI secrets are a high-value target. Replace them with OIDC federation: GitHub Actions OIDC → AWS AssumeRoleWithWebIdentity, GCP Workload Identity, or Azure Federated Credentials.
Route all dependencies through a private proxy
A private artifact proxy (Artifactory, Nexus, Athens) centralizes dependency fetching, prevents dependency confusion attacks, and provides a single place to block compromised packages. Configure build tools to use the proxy with no public fallback for internal package namespaces.
Vet third-party operators before installation
Review RBAC requests, pod security settings, and image provenance of every operator before installing it in a production cluster. Prefer operators from vendors with active security programs, signed images, and published SBOMs.
Achieve SLSA Level 3 for critical workloads
SLSA Level 3 provides non-forgeable provenance from a hosted build platform. Use the slsa-github-generator reusable workflow to generate SLSA 3 attestations automatically for every release. Require SLSA provenance verification in Policy Controller.
Use VEX to reduce CVE triage noise
Not every CVE in a dependency is exploitable in your deployment context. Publish VEX documents alongside your SBOMs to assert non-exploitability for known false-positive CVEs. This reduces alert fatigue and lets teams focus on real risks.