4. Container & Kubernetes Scanning with Trivy
Time to Complete
Planned time: ~20 minutes
Trivy is a comprehensive security scanner that finds vulnerabilities, misconfigurations, secrets, and license issues in containers, filesystems, Git repositories, and Kubernetes clusters. In this lab, you’ll use Trivy to scan container images for CVEs, identify misconfigurations in Kubernetes manifests, and learn how to remediate security issues.
What You’ll Learn
- How to scan container images for known vulnerabilities (CVEs)
- How to scan Kubernetes manifests for security misconfigurations
- How to scan running Kubernetes namespaces for live issues
- How to interpret Trivy results and prioritize remediation
- How to fix security issues and verify improvements
- How to generate Software Bill of Materials (SBOM)
- How to scan Infrastructure as Code (Terraform, Dockerfiles)
Trainer Instructions
Tested versions:
- Trivy:
0.69.1 - Kubernetes:
1.32.x - nginx image:
nginxinc/nginx-unprivileged:1.27.3(secure),nginx:1.19(intentionally vulnerable) - alpine image:
3.21
Ensure participants have:
trivyCLI installed on their workstationkubectlaccess to a Kubernetes cluster- Cluster-admin or namespace-admin permissions
No external integrations are required.
Info
We are in the AKS cluster: kx c<x>-s1
1. Setup: Deploy an Intentionally Insecure Workload
Before scanning, we need a target workload with security issues. This deployment uses an old nginx image with known vulnerabilities and lacks security best practices.
First let’s install trivy in version 0.69.1:
curl -sL https://github.com/aquasecurity/trivy/releases/download/v0.69.1/trivy_0.69.1_Linux-32bit.tar.gz | tar -zvxf - trivy
sudo mv trivy /usr/local/bin
trivy --version
Task
- Create a namespace called
trivy-lab - Deploy the insecure workload
The insecure deployment (~/exercise/kubernetes/trivy/deployment-insecure.yaml):
# Intentionally insecure deployment for Trivy scanning lab
# Contains multiple security misconfigurations for educational purposes
apiVersion: apps/v1
kind: Deployment
metadata:
name: insecure-app
labels:
app: insecure-app
spec:
replicas: 1
selector:
matchLabels:
app: insecure-app
template:
metadata:
labels:
app: insecure-app
spec:
containers:
- name: web
# Using old image with known vulnerabilities
image: nginx:1.19
ports:
- containerPort: 80
# No securityContext = running as root
# No resource limits
# No readOnlyRootFilesystem
Solution
kubectl create namespace trivy-lab
kubectl apply -n trivy-lab -f ~/exercise/kubernetes/trivy/deployment-insecure.yaml
kubectl get pods -n trivy-lab -w
Questions
- What’s the difference between scanning an image vs scanning a manifest?
- Which issues can Trivy detect beyond CVEs?
Answers
- Image scanning finds OS and package vulnerabilities (CVEs) in the container image layers. Manifest scanning finds misconfigurations in Kubernetes YAML like missing securityContext, excessive permissions, or missing resource limits.
- Beyond CVEs, Trivy can detect: misconfigurations, exposed secrets in manifests, IaC issues, license compliance problems, and SBOM generation.
2. Scan a Container Image
Image scanning identifies known vulnerabilities (CVEs) in the operating system packages and application dependencies within a container image. This is essential for understanding your supply chain risk.
Info
Trivy maintains an offline vulnerability database that is updated regularly. The first scan may take longer as Trivy downloads the database.
Task
- Identify the image used in the deployment (hint:
nginx:1.19) - Scan the image for vulnerabilities
- Filter to show only HIGH and CRITICAL severity
Solution
Scan the image:
trivy image nginx:1.19
trivy image --severity HIGH,CRITICAL nginx:1.19
Questions
- Which severity levels do you see?
- What’s one practical next step if you see critical CVEs?
Answers
- You should see vulnerabilities across all severity levels (CRITICAL, HIGH, MEDIUM, LOW). The older the image, the more vulnerabilities.
- Practical next steps:
- Upgrade to a newer base image (
nginxinc/nginx-unprivileged:1.27.3) - Use slim or distroless variants where possible
- Patch and rebuild images regularly
- Add image scanning to your CI/CD pipeline
- Upgrade to a newer base image (
3. Scan the Kubernetes Manifest for Misconfigurations
Beyond CVEs, Trivy can analyze Kubernetes YAML files for security misconfigurations. This catches issues like running as root, missing resource limits, or overly permissive security contexts.
Task
Scan the insecure deployment manifest we deployed for misconfigurations
Hint
You can not only scan images but also files.
Solution
trivy config ~/exercise/kubernetes/trivy/deployment-insecure.yaml
Expected findings include:
- Container running as root
- Missing
runAsNonRoot: true - Missing
readOnlyRootFilesystem: true - Missing
allowPrivilegeEscalation: false - Missing resource limits
Questions
- Which security misconfigurations are reported?
- Which ones matter most in production?
Answers
- Common misconfigurations: running as root, writable root filesystem, privilege escalation allowed, missing capabilities drop, no resource limits.
- Most critical for production:
- runAsNonRoot: Prevents container escape attacks
- readOnlyRootFilesystem: Prevents malware from writing to disk
- allowPrivilegeEscalation: false: Prevents privilege escalation exploits
- Resource limits: Prevents denial of service
4. Scan the Running Namespace
Trivy can scan a live Kubernetes cluster, combining image vulnerability data with the actual deployed configuration. This provides a real-time view of your security posture.
Task
Scan the trivy-lab namespace:
Hint
Use trivy --help to see what else this tool can do for us
Solution
trivy k8s --include-namespaces trivy-lab --report summary
trivy k8s --include-namespaces trivy-lab --report all
Questions
- Do results differ from scanning the YAML file?
- Why might runtime scanning add value?
Answers
- Results may include additional findings because:
- Live scanning checks the actual image digest, not just the tag
- It includes cluster-level context (RBAC, service accounts)
- It can detect drift between desired and actual state
- Runtime scanning adds value by:
- Catching images that were updated after deployment
- Identifying configuration drift
- Providing a complete view of the security posture
- Detecting issues in sidecars or init containers
5. Fix Issues and Re-scan
Now let’s remediate the security issues by creating a hardened version of the deployment and verifying the improvements.
Task
- Review the secure deployment that addresses the misconfigurations
- Apply the secure deployment
- Re-scan to verify improvements
The secure deployment (~/exercise/kubernetes/trivy/deployment-secure.yaml):
# Hardened deployment after applying Trivy recommendations
# Uses nginxinc/nginx-unprivileged which runs as non-root on port 8080
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
labels:
app: secure-app
spec:
replicas: 1
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
containers:
- name: web
# Using unprivileged nginx that runs as non-root on port 8080
image: nginxinc/nginx-unprivileged:1.27.3
ports:
- containerPort: 8080
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
- name: run
emptyDir: {}
Solution
Apply the secure deployment:
kubectl apply -n trivy-lab -f ~/exercise/kubernetes/trivy/deployment-secure.yaml
Scan the manifest:
trivy config ~/exercise/kubernetes/trivy/deployment-secure.yaml
Scan the running namespace:
trivy k8s --include-namespaces trivy-lab --report summary
Scan the updated image:
trivy image --severity HIGH,CRITICAL nginxinc/nginx-unprivileged:1.27.3
Expected results:
- Fewer or no misconfiguration findings in the manifest
- Significantly fewer CVEs in the newer image
- The namespace scan should show improved security posture
Questions
- What changed in the output?
- Which fixes could be standardized via admission policy (e.g., Kyverno)?
Answers
- The secure deployment should eliminate most misconfigurations. The newer image has significantly fewer CVEs.
- Fixes to standardize via Kyverno:
- Enforce
runAsNonRoot: true - Block
privileged: true - Require
readOnlyRootFilesystem: true - Require resource requests and limits
- Block
:latesttag
- Enforce
Integration with Kyverno
Combine Trivy (scanning/detection) with Kyverno (enforcement) for defense in depth. Trivy identifies issues, Kyverno prevents them from being deployed.
6. Bonus: Generate Software Bill of Materials (SBOM)
Bonus Exercise
This section is optional and provides an additional challenge.
A Software Bill of Materials (SBOM) is an inventory of all components in your software. SBOMs are increasingly required for compliance and help you understand your supply chain.
Task
- Generate an SBOM for the nginx image in CycloneDX format
- Generate an SBOM in SPDX format
- Scan an existing SBOM for vulnerabilities
Solution
Generate CycloneDX SBOM:
trivy image --format cyclonedx --output nginx-sbom.cdx.json nginx:1.27.3
trivy image --format spdx-json --output nginx-sbom.spdx.json nginx:1.27.3
cat nginx-sbom.cdx.json | jq '.components | length'
trivy sbom nginx-sbom.cdx.json
Tip
SBOMs enable you to quickly identify if your software is affected when new vulnerabilities are announced. Store SBOMs alongside your release artifacts for future reference.
7. Bonus: Scan Infrastructure as Code
Bonus Exercise
This section is optional and provides an additional challenge.
Trivy can scan Infrastructure as Code (IaC) files including Terraform, CloudFormation, Dockerfiles, and more. This helps catch security issues before infrastructure is provisioned.
Task
- Scan the insecure Dockerfile
- Scan the insecure Terraform configuration
- Compare with secure versions
Insecure Dockerfile (~/exercise/kubernetes/trivy/Dockerfile.insecure):
# Insecure Dockerfile for scanning demo
FROM ubuntu:20.04
# Running as root (bad practice)
# Installing unnecessary packages
RUN apt-get update && apt-get install -y \
curl \
wget \
vim \
telnet \
netcat
# Hardcoded credentials (bad practice)
ENV DB_PASSWORD=mysecretpassword
ENV API_TOKEN=abc123secret
# Copying with overly broad permissions
COPY . /app
RUN chmod 777 /app
# Running as root
CMD ["./app/start.sh"]
Insecure Terraform (~/exercise/kubernetes/trivy/terraform-insecure.tf):
# Insecure Terraform configuration for IaC scanning demo
# Contains multiple security issues
resource "aws_s3_bucket" "data" {
bucket = "my-insecure-bucket"
acl = "public-read" # Bad: public access
# Missing encryption
# Missing versioning
# Missing logging
}
resource "aws_security_group" "allow_all" {
name = "allow_all"
description = "Allow all traffic"
# Bad: allows all inbound traffic
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
# Bad: allows all outbound traffic
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "database" {
identifier = "mydb"
engine = "mysql"
instance_class = "db.t3.micro"
# Bad: no encryption at rest
storage_encrypted = false
# Bad: publicly accessible
publicly_accessible = true
# Bad: no deletion protection
deletion_protection = false
}
Solution
Scan the Dockerfile:
trivy config ~/exercise/kubernetes/trivy/Dockerfile.insecure
Expected findings:
- Running as root user
- Hardcoded secrets in ENV
- Overly permissive chmod 777
- Using old base image
Scan the Terraform file:
trivy config ~/exercise/kubernetes/trivy/terraform-insecure.tf
Expected findings:
- S3 bucket with public access
- Security group allowing all traffic (0.0.0.0/0)
- RDS instance publicly accessible
- RDS without encryption at rest
- Missing deletion protection
Scan the secure Dockerfile:
trivy config ~/exercise/kubernetes/trivy/Dockerfile.secure
This should show significantly fewer or no findings.
Tip
Add trivy config to your CI/CD pipeline to catch IaC security issues before they reach production. Use --exit-code 1 to fail the build on findings.
8. Bonus: Scan for Secrets
Bonus Exercise
This section is optional and provides an additional challenge.
Trivy can detect hardcoded secrets in code, configuration files, and container images. This helps prevent credential leaks.
Task
- Scan the pod manifest that contains hardcoded secrets
- Understand why this is a security risk
- Review the fixed version
Pod with hardcoded secrets (~/exercise/kubernetes/trivy/pod-with-secrets.yaml):
# Pod with embedded secrets - for secret scanning demo
# DO NOT use hardcoded secrets in production!
apiVersion: v1
kind: Pod
metadata:
name: pod-with-secrets
labels:
app: secret-demo
spec:
containers:
- name: app
image: alpine:3.21
command: ["sleep", "3600"]
env:
# Bad practice: hardcoded secrets
- name: DATABASE_PASSWORD
value: "super-secret-password-123"
- name: API_KEY
value: "sk-1234567890abcdef"
- name: AWS_ACCESS_KEY_ID
value: "AKIAIOSFODNN7EXAMPLE"
Solution
Scan for secrets:
trivy config ~/exercise/kubernetes/trivy/pod-with-secrets.yaml
Or scan with explicit secret detection:
trivy fs --scanners secret ~/exercise/kubernetes/trivy/
Expected findings:
- Hardcoded database password
- Hardcoded API key
- AWS access key pattern
The fixed version uses Kubernetes Secrets properly:
trivy config ~/exercise/kubernetes/trivy/pod-with-secrets-fixed.yaml
Security Risk
Hardcoded secrets in manifests or code can be:
- Committed to version control and exposed
- Visible to anyone with read access to the manifest
- Difficult to rotate without redeploying
Always use Kubernetes Secrets (or external secret managers) for sensitive data.
9. Clean Up
Remove the resources created during this lab:
kubectl delete namespace trivy-lab
Clean up local files:
rm -f nginx-sbom.cdx.json nginx-sbom.spdx.json
Recap
You have:
- Deployed an intentionally insecure workload for scanning
- Scanned container images for CVEs using
trivy image - Scanned Kubernetes manifests for misconfigurations using
trivy config - Scanned a running namespace using
trivy k8s - Remediated security issues and verified improvements
- (Bonus) Generated SBOMs in CycloneDX and SPDX formats
- (Bonus) Scanned Infrastructure as Code (Terraform, Dockerfiles)
- (Bonus) Detected hardcoded secrets in manifests
Wrap-Up Questions
Discussion
- At which stage of the development lifecycle should you scan (build, deploy, runtime)?
- How would you integrate Trivy into a CI/CD pipeline?
- How does Trivy complement admission controllers like Kyverno?
Discussion Points
- Shift left: Scan as early as possible - in IDE, during build, in CI/CD, at admission, and continuously at runtime. Earlier detection = cheaper fixes.
- CI/CD integration: Add Trivy as a build step with
--exit-code 1 --severity CRITICAL,HIGHto fail builds with serious vulnerabilities. Store SBOMs as build artifacts. - Defense in depth: Trivy (detection) + Kyverno (prevention) + Falco (runtime detection) form layers of security. Trivy finds issues, Kyverno prevents deployment, Falco detects runtime anomalies.
Further Reading
- Trivy Documentation
- Trivy GitHub Repository
- Trivy Kubernetes Scanning
- SBOM with Trivy
- Trivy in CI/CD
- CycloneDX SBOM Standard
- SPDX SBOM Standard
End of Lab