Skip to content

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:

  • trivy CLI installed on their workstation
  • kubectl access 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

  1. Create a namespace called trivy-lab
  2. 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
Wait for the pod to be running before continuing.

Questions

  • What’s the difference between scanning an image vs scanning a manifest?
  • Which issues can Trivy detect beyond CVEs?
Answers
  1. 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.
  2. 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

  1. Identify the image used in the deployment (hint: nginx:1.19)
  2. Scan the image for vulnerabilities
  3. Filter to show only HIGH and CRITICAL severity
Solution

Scan the image:

trivy image nginx:1.19
Filter to high and critical only:
trivy image --severity HIGH,CRITICAL nginx:1.19
Expected output shows numerous vulnerabilities because nginx:1.19 is from 2020.

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

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
For detailed output:
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

  1. Review the secure deployment that addresses the misconfigurations
  2. Apply the secure deployment
  3. 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 :latest tag

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

  1. Generate an SBOM for the nginx image in CycloneDX format
  2. Generate an SBOM in SPDX format
  3. Scan an existing SBOM for vulnerabilities
Solution

Generate CycloneDX SBOM:

trivy image --format cyclonedx --output nginx-sbom.cdx.json nginx:1.27.3
Generate SPDX SBOM:
trivy image --format spdx-json --output nginx-sbom.spdx.json nginx:1.27.3
View the SBOM:
cat nginx-sbom.cdx.json | jq '.components | length'
Scan an existing SBOM:
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

  1. Scan the insecure Dockerfile
  2. Scan the insecure Terraform configuration
  3. 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

  1. Scan the pod manifest that contains hardcoded secrets
  2. Understand why this is a security risk
  3. 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,HIGH to 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


End of Lab