Kubernetes Supply Chain Security: From Image Scanning to SLSA
Secure your Kubernetes supply chain with image scanning, Cosign signing, admission control, SBOM generation, and SLSA framework compliance.
Kubernetes Supply Chain Security: From Image Scanning to SLSA
Supply chain attacks are among the most devastating threats to Kubernetes environments. When an attacker compromises a container image, a base layer, or a CI/CD pipeline, they gain code execution inside your cluster without ever exploiting a vulnerability in your application. The SolarWinds attack, the Codecov breach, and the event-stream npm compromise all demonstrated that attackers increasingly target the supply chain rather than the application itself.
In Kubernetes, the supply chain spans everything from the base images you pull from public registries to the Helm charts you deploy from third-party repositories. Securing it requires controls at every stage: build, store, deploy, and runtime.
Introduction: Supply Chain Attacks in Kubernetes
A supply chain attack in Kubernetes typically follows one of these patterns:
- Compromised base image — An attacker injects malware into a popular base image on Docker Hub. Every image built on top of it inherits the compromise.
- Typosquatting — An attacker publishes
ngingx(note the typo) on a public registry. Developers who mistype the image name pull the malicious version. - Tag mutability — An attacker overwrites the
v1.0tag on a registry with a compromised image. Pods pullingimage:v1.0get the malicious version without the tag changing. - CI/CD pipeline compromise — An attacker gains access to the build pipeline and injects malicious code during the build process.
- Dependency confusion — An attacker publishes a public package with the same name as an internal one, and the build system pulls the public (malicious) version.
The fundamental problem: Kubernetes trusts whatever image you tell it to pull. There is no built-in verification that the image was built by you, hasn’t been tampered with, or is free of known vulnerabilities.
Image Scanning with Trivy
Trivy (by Aqua Security) is the most widely used open-source vulnerability scanner for container images. It scans for known CVEs in OS packages and application dependencies.
CLI Scanning
# Scan an image for vulnerabilities
trivy image nginx:1.25
# Fail on critical vulnerabilities (for CI/CD)
trivy image --exit-code 1 --severity CRITICAL my-registry/my-app:v1.2.3
# Scan with both vulnerability and misconfiguration checks
trivy image --scanners vuln,misconfig my-registry/my-app:v1.2.3
# Output as JSON for processing
trivy image --format json --output scan-results.json my-registry/my-app:v1.2.3
CI/CD Integration
Add Trivy to your pipeline to block images with critical vulnerabilities:
GitHub Actions:
- name: Scan container image
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
GitLab CI:
container_scanning:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity CRITICAL ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
Runtime Scanning with Trivy Operator
For continuous scanning of running workloads (catching newly disclosed CVEs):
helm install trivy-operator aquasecurity/trivy-operator \
--namespace trivy-system --create-namespace \
--set operator.scanJobsConcurrentLimit=3
# Check results
kubectl get vulnerabilityreports -A
kubectl get vulnerabilityreports -n production -o json | jq '.items[] | {name: .metadata.name, critical: .report.summary.criticalCount, high: .report.summary.highCount}'
Image Signing with Cosign and Sigstore
Scanning tells you if an image has known vulnerabilities. Signing tells you if the image was built by you and hasn’t been tampered with. Both are essential.
Cosign (part of the Sigstore project) provides keyless and key-based container image signing.
Signing Images
# Generate a key pair
cosign generate-key-pair
# Sign an image
cosign sign --key cosign.key my-registry/my-app:v1.2.3
# Sign with keyless mode (uses OIDC identity, no keys to manage)
cosign sign my-registry/my-app:v1.2.3
Verifying Signatures
# Verify with a public key
cosign verify --key cosign.pub my-registry/my-app:v1.2.3
# Verify keyless signature (checks against Sigstore transparency log)
cosign verify my-registry/my-app:v1.2.3
CI/CD Integration
Sign images as part of your build pipeline:
# GitHub Actions
- name: Sign container image
run: cosign sign --key env://COSIGN_PRIVATE_KEY ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
Admission Control: Blocking Untrusted Images
Scanning and signing are only useful if you enforce them. Admission controllers act as the final gate, rejecting any image that doesn’t meet your standards.
Disallow Latest Tag
The :latest tag is mutable — it can point to different images at different times. This breaks reproducibility and makes it impossible to audit which image is actually running.
Using Kyverno:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: enforce
rules:
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using ':latest' tag is not allowed. Specify a version."
pattern:
spec:
containers:
- image: "!*:latest"
- name: validate-init-container-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using ':latest' tag is not allowed on initContainers."
pattern:
spec:
=(initContainers):
- image: "!*:latest"
This checks regular containers, init containers, and ephemeral containers. Any pod using :latest is rejected at admission time.
Require Image Digest
For even stronger guarantees, require SHA256 digests instead of tags:
# Instead of: nginx:1.25
# Use: nginx@sha256:abc123...
# Get the digest for a tag
crane digest nginx:1.25
Verify Image Signatures at Admission
Kyverno can verify Cosign signatures before allowing an image to run:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "my-registry/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQY...
-----END PUBLIC KEY-----
Any image from my-registry/ that isn’t signed with the corresponding private key is blocked.
OPA/Gatekeeper Policies for Supply Chain
OPA/Gatekeeper provides equivalent controls using Rego policies.
Require Resource Limits
A compromised container without resource limits can consume all CPU for crypto mining or fill all memory to cause OOM cascades:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequireresourcelimits
spec:
crd:
spec:
names:
kind: K8sRequireResourceLimits
validation:
openAPIV3Schema:
type: object
properties:
resources:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequireresourcelimits
violation[{"msg": msg}] {
container := input_containers[_]
required := input.parameters.resources[_]
not has_resource_limit(container, required)
msg := sprintf(
"Container '%v' does not have a resource limit for '%v'.",
[container.name, required]
)
}
has_resource_limit(container, resource) {
container.resources.limits[resource]
}
input_containers[c] { c := input.review.object.spec.containers[_] }
input_containers[c] { c := input.review.object.spec.initContainers[_] }
Disallow Privileged Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowprivilegedcontainers
spec:
crd:
spec:
names:
kind: K8sDisallowPrivilegedContainers
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowprivilegedcontainers
violation[{"msg": msg}] {
container := input_containers[_]
container.securityContext.privileged == true
msg := sprintf(
"Container '%v' has privileged: true. Remove it.",
[container.name]
)
}
input_containers[c] { c := input.review.object.spec.containers[_] }
input_containers[c] { c := input.review.object.spec.initContainers[_] }
Kyverno Policy Bundle for Supply Chain
Kyverno provides a more accessible alternative to OPA/Gatekeeper for teams that prefer YAML over Rego. A comprehensive policy bundle covers:
- require-resource-limits — Block pods without CPU/memory limits
- require-run-as-nonroot — Block pods running as root
- require-read-only-root-filesystem — Audit pods with writable root FS
- disallow-host-namespaces — Block pods sharing host PID/IPC/network
- require-labels — Audit pods without standard labels
- disallow-privilege-escalation — Block privileged containers
- add-default-security-context — Mutate pods to add security defaults
The mutate policy is particularly valuable for supply chain security — it acts as a safety net by automatically injecting runAsNonRoot: true and seccompProfile: RuntimeDefault on pods that don’t already define a security context.
SBOM Generation and Management
A Software Bill of Materials (SBOM) lists every component in your container image — OS packages, application dependencies, and their versions. SBOMs are increasingly required by regulations (US Executive Order 14028) and enterprise customers.
Generate SBOMs with Syft
# Generate SBOM for a container image
syft my-registry/my-app:v1.2.3 -o spdx-json > sbom.spdx.json
# Generate in CycloneDX format
syft my-registry/my-app:v1.2.3 -o cyclonedx-json > sbom.cdx.json
# Attach SBOM to image as an OCI artifact (using Cosign)
cosign attach sbom --sbom sbom.spdx.json my-registry/my-app:v1.2.3
Scan SBOMs for Vulnerabilities
# Scan an existing SBOM with Grype
grype sbom:sbom.spdx.json
# Or scan with Trivy
trivy sbom sbom.spdx.json
SBOMs enable rapid response when a new CVE is disclosed — instead of scanning every image, you search your SBOM database for the affected package.
CI/CD Integration: Shift-Left Security
The earlier you catch supply chain issues, the cheaper they are to fix. Integrate security at every stage:
Pre-Commit
# Scan Kubernetes manifests for misconfigurations
kubescape scan *.yaml
# Validate Kyverno policies locally
kyverno apply policies/ --resource manifests/
Build Stage
# 1. Build image
# 2. Scan for vulnerabilities (fail on CRITICAL)
# 3. Sign the image
# 4. Generate SBOM
# 5. Attach SBOM to image
# 6. Push to registry
Deploy Stage
# Admission controllers verify:
# 1. Image is signed
# 2. Image has no critical CVEs
# 3. Image uses a specific tag (not :latest)
# 4. Pod has security context
# 5. Pod has resource limits
Runtime
# Trivy Operator: continuous vulnerability scanning
# Falco: runtime behavior monitoring
# kube-bench: periodic CIS Benchmark checks
SLSA Framework and Supply Chain Levels
SLSA (Supply Chain Levels for Software Artifacts, pronounced “salsa”) is a framework by Google for progressively securing the software supply chain. It defines four levels:
| Level | Name | Requirements | Protects Against |
|---|---|---|---|
| SLSA 1 | Build exists | Automated build process, provenance generated | Ad-hoc builds, no audit trail |
| SLSA 2 | Hosted build | Build runs on a hosted service, signed provenance | Compromised developer machines |
| SLSA 3 | Hardened build | Isolated build environment, non-falsifiable provenance | Compromised build infrastructure |
| SLSA 4 | Two-party review | All code changes reviewed by two independent parties, hermetic build | Insider threats |
Implementing SLSA in Kubernetes
SLSA 1: Use GitHub Actions, GitLab CI, or similar to build images automatically. Generate provenance metadata:
# Using SLSA GitHub Generator
# Generates a signed SLSA provenance attestation
- uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1
SLSA 2: Run builds on hosted runners (GitHub-hosted, GitLab SaaS). Sign provenance with Sigstore:
cosign attest --predicate provenance.json --key cosign.key my-registry/my-app:v1.2.3
SLSA 3: Use isolated build environments (ephemeral runners, no persistent state). Verify build logs are tamper-proof.
SLSA 4: Require two approvals for all code changes. Use hermetic builds (no network access during build, all dependencies vendored).
Verifying Provenance at Admission
# Verify SLSA provenance with Cosign
cosign verify-attestation --type slsaprovenance --key cosign.pub my-registry/my-app:v1.2.3
Kyverno can verify attestations at admission time, ensuring only images with valid build provenance are deployed to your cluster.
Practical Recommendations
Here’s a prioritized approach to securing your Kubernetes supply chain:
Start here (week 1):
- Add Trivy scanning to your CI/CD pipeline, fail on CRITICAL
- Deploy the “disallow latest tag” Kyverno policy
- Pin image versions in all manifests (use tags, move to digests later)
Add next (week 2-3): 4. Sign images with Cosign in your CI/CD pipeline 5. Deploy Trivy Operator for runtime scanning 6. Add resource limits enforcement via Kyverno/OPA
Mature (month 2+): 7. Verify image signatures at admission 8. Generate and store SBOMs for all production images 9. Implement SLSA Level 2+ provenance 10. Use private registries for all production images
Each step builds on the previous one. Scanning without enforcement catches issues but doesn’t prevent them. Signing without verification at admission is security theater. Build incrementally and enforce at each layer.
Conclusion
Supply chain security in Kubernetes requires defense at every stage — build, store, deploy, and runtime. No single tool or policy is sufficient. The combination of image scanning (Trivy), image signing (Cosign), admission control (Kyverno/OPA), and runtime monitoring (Falco, Trivy Operator) creates a layered defense that catches threats regardless of where they originate.
The templates and policies referenced in this guide — Kyverno disallow-latest-tag, Kyverno policy bundle, OPA/Gatekeeper ConstraintTemplates, and Falco runtime rules — are all included in the K8s Security Pro template pack. Get started with the free K8s Security Quick-Start Kit which includes the checklist and essential security templates.
Related Templates
Implement what you’ve learned with these production-ready YAML templates:
- Template 11: Kyverno Disallow Latest Tag — Block mutable :latest tags and enforce explicit versioned images.
- Template 12: Falco Runtime Security Rules — 7 detection rules for shells, crypto miners, credential theft, and privilege escalation.
- Template 18: External Secrets Operator — Eliminate hardcoded credentials with AWS Secrets Manager integration.
Related Articles
- Kubernetes CIS Benchmark and SOC2 Compliance: A Practical Guide — Map your supply chain controls to CIS and SOC2 compliance frameworks.
- Kubernetes Pod Security Standards: From PSP to PSS Migration Guide — Harden pod security alongside your supply chain defenses.
Get the Free K8s Security Quick-Start Kit
Join 500+ engineers. Get 5 essential templates + audit checklist highlights delivered to your inbox.
No spam. Unsubscribe anytime.
Secure Your Kubernetes Clusters
Get the complete 50-point audit checklist and 20+ production-ready YAML templates.
View Pricing Plans