Appearance
Trivy Cheat Sheet
Overview
Trivy is a comprehensive security scanner designed to find vulnerabilities, misconfigurations, secrets, SBOM (Software Bill of Materials), and license issues in containers, Kubernetes, code repositories, clouds, and more. Developed by Aqua Security, Trivy is fast, accurate, and easy to use, making it an essential tool for DevSecOps pipelines.
Key Features
- Multi-Target Scanning: Container images, filesystems, repositories, Kubernetes clusters
- Vulnerability Detection: OS packages, language-specific packages, known vulnerabilities
- Misconfiguration Detection: Infrastructure as Code (IaC) security issues
- Secret Detection: API keys, passwords, tokens in code and images
- SBOM Generation: Software Bill of Materials in multiple formats
- License Compliance: License detection and compliance checking
- CI/CD Integration: Native integration with popular CI/CD platforms
- Multiple Output Formats: JSON, SARIF, CycloneDX, SPDX, and more
Installation
Binary Installation (Recommended)
bash
# Linux (x86_64)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.48.0
# Linux (ARM64)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.48.0
# macOS (Homebrew)
brew install trivy
# Windows (Chocolatey)
choco install trivy
# Windows (Scoop)
scoop install trivy
# Verify installation
trivy version
Docker Installation
bash
# Pull Trivy image
docker pull aquasec/trivy:latest
# Create alias for easy usage
alias trivy="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy:latest"
# Scan with Docker
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v $HOME/Library/Caches:/root/.cache/ \
aquasec/trivy:latest image python:3.4-alpine
Package Manager Installation
bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
# CentOS/RHEL/Fedora
sudo vim /etc/yum.repos.d/trivy.repo
# Add:
# [trivy]
# name=Trivy repository
# baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$releasever/$basearch/
# gpgcheck=0
# enabled=1
sudo yum -y update
sudo yum -y install trivy
# Arch Linux
yay -S trivy-bin
# Alpine Linux
apk add trivy
Kubernetes Installation
bash
# Install as a Kubernetes job
kubectl create job trivy --image=aquasec/trivy:latest -- trivy image --exit-code 1 nginx:latest
# Install Trivy Operator for continuous scanning
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/main/deploy/static/trivy-operator.yaml
# Helm installation
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
helm install trivy-operator aqua/trivy-operator \
--namespace trivy-system \
--create-namespace \
--version 0.16.4
Basic Usage
Container Image Scanning
bash
# Basic image scan
trivy image nginx:latest
# Scan specific image with tag
trivy image ubuntu:20.04
# Scan image with specific severity levels
trivy image --severity HIGH,CRITICAL nginx:latest
# Scan image and ignore unfixed vulnerabilities
trivy image --ignore-unfixed nginx:latest
# Scan image with custom format
trivy image --format json nginx:latest
# Scan image and save results to file
trivy image --output result.json --format json nginx:latest
# Scan image with specific vulnerability types
trivy image --vuln-type os,library nginx:latest
# Scan private registry image
trivy image --username myuser --password mypass myregistry.com/myimage:tag
# Scan image with custom timeout
trivy image --timeout 10m nginx:latest
# Scan image and exit with code on vulnerabilities
trivy image --exit-code 1 nginx:latest
Filesystem Scanning
bash
# Scan current directory
trivy fs .
# Scan specific directory
trivy fs /path/to/project
# Scan filesystem with specific file patterns
trivy fs --skip-files "*.test.js,*.spec.js" .
# Scan filesystem for secrets only
trivy fs --scanners secret .
# Scan filesystem for misconfigurations
trivy fs --scanners config .
# Scan filesystem with custom config
trivy fs --config-file trivy.yaml .
# Scan filesystem and ignore specific paths
trivy fs --skip-dirs vendor,node_modules .
# Scan filesystem with license detection
trivy fs --scanners license .
Repository Scanning
bash
# Scan remote Git repository
trivy repo https://github.com/aquasecurity/trivy
# Scan specific branch
trivy repo --branch main https://github.com/aquasecurity/trivy
# Scan repository with authentication
trivy repo --username user --password token https://github.com/private/repo
# Scan repository for specific scanners
trivy repo --scanners vuln,secret https://github.com/aquasecurity/trivy
# Scan repository with custom output
trivy repo --format sarif --output results.sarif https://github.com/aquasecurity/trivy
Kubernetes Scanning
bash
# Scan Kubernetes cluster
trivy k8s cluster
# Scan specific namespace
trivy k8s --namespace kube-system cluster
# Scan specific Kubernetes resource
trivy k8s deployment/nginx
# Scan all resources in namespace
trivy k8s --namespace default all
# Scan with specific report format
trivy k8s --format json --output k8s-report.json cluster
# Scan Kubernetes manifests
trivy k8s --scanners config deployment.yaml
# Scan Helm charts
trivy k8s --scanners config helm-chart/
# Scan with compliance report
trivy k8s --compliance k8s-cis cluster
Advanced Usage
Configuration Management
bash
# Create Trivy configuration file
cat > trivy.yaml << 'EOF'
# Trivy configuration file
# Cache settings
cache:
dir: /tmp/trivy-cache
# Database settings
db:
skip-update: false
# Vulnerability settings
vulnerability:
type: os,library
# Secret settings
secret:
config: secret-config.yaml
# Misconfiguration settings
misconfiguration:
trace: false
config-file: misconfig.yaml
# Output settings
format: json
output: trivy-results.json
# Severity settings
severity: HIGH,CRITICAL
# Skip settings
skip-files:
- "*.test.js"
- "*.spec.js"
- "test/**"
skip-dirs:
- vendor
- node_modules
- .git
# Ignore settings
ignorefile: .trivyignore
# Exit code settings
exit-code: 1
# Timeout settings
timeout: 5m
# Registry settings
registry:
username: ""
password: ""
# Compliance settings
compliance: docker-cis
EOF
# Use configuration file
trivy image --config trivy.yaml nginx:latest
Custom Policies and Rules
bash
# Create custom policy for misconfigurations
mkdir -p policies/kubernetes
cat > policies/kubernetes/custom-policy.rego << 'EOF'
# METADATA
# title: "Custom Kubernetes Security Policy"
# description: "Custom security checks for Kubernetes resources"
# scope: package
# schemas:
# - input: schema["kubernetes"]
# custom:
# id: CUSTOM001
# avd_id: AVD-CUSTOM-001
# severity: HIGH
# short_code: no-root-user
# recommended_action: "Ensure containers do not run as root user"
package builtin.kubernetes.KSV001
import rego.v1
deny contains res if {
input.kind == "Pod"
input.spec.securityContext.runAsUser == 0
res := result.new("Container should not run as root user", {})
}
deny contains res if {
input.kind == "Deployment"
input.spec.template.spec.securityContext.runAsUser == 0
res := result.new("Container should not run as root user", {})
}
EOF
# Scan with custom policy
trivy k8s --policy policies/ deployment.yaml
Secret Detection Configuration
bash
# Create secret detection configuration
cat > secret-config.yaml << 'EOF'
# Secret detection configuration
# Custom secret patterns
rules:
- id: custom-api-key
category: general
title: Custom API Key
severity: HIGH
regex: 'custom_api_[a-zA-Z0-9]{32}'
keywords:
- custom_api
- id: internal-token
category: general
title: Internal Token
severity: MEDIUM
regex: 'internal_[a-zA-Z0-9]{16}'
keywords:
- internal_
# Allowlist patterns
allowlist:
description: Allowlist for false positives
rules:
- description: Test API keys
regex: 'test_api_[a-zA-Z0-9]+'
- description: Example tokens
regex: 'example_[a-zA-Z0-9]+'
# Global allowlist
global-allowlist:
- rule-id: generic-api-key
paths:
- "test/**"
- "**/*test*"
EOF
# Scan with custom secret configuration
trivy fs --scanners secret --secret-config secret-config.yaml .
SBOM Generation
bash
# Generate SBOM in CycloneDX format
trivy image --format cyclonedx --output sbom.json nginx:latest
# Generate SBOM in SPDX format
trivy image --format spdx --output sbom.spdx nginx:latest
# Generate SBOM in SPDX-JSON format
trivy image --format spdx-json --output sbom.spdx.json nginx:latest
# Generate SBOM for filesystem
trivy fs --format cyclonedx --output fs-sbom.json .
# Generate SBOM with license information
trivy image --scanners vuln,license --format cyclonedx nginx:latest
# Generate comprehensive SBOM
trivy image --format cyclonedx --output comprehensive-sbom.json \
--scanners vuln,license,secret nginx:latest
Automation and Scripting
Comprehensive Scanning Script
bash
#!/bin/bash
# trivy_scan.sh - Comprehensive Trivy scanning automation
set -euo pipefail
# Configuration
SCAN_TARGET="${1:-}"
SCAN_TYPE="${2:-image}"
OUTPUT_DIR="/tmp/trivy-results"
REPORT_FORMAT="json"
SEVERITY_LEVELS="HIGH,CRITICAL"
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
EMAIL_RECIPIENTS="${EMAIL_RECIPIENTS:-}"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$OUTPUT_DIR/scan.log"
}
# Create output directory
mkdir -p "$OUTPUT_DIR"
# Validate input
if [ -z "$SCAN_TARGET" ]; then
echo "Usage: $0 <target> [scan_type]"
echo "Scan types: image, fs, repo, k8s"
exit 1
fi
# Update Trivy database
update_database() {
log "Updating Trivy vulnerability database..."
trivy image --download-db-only
log "Database update completed"
}
# Scan container image
scan_image() {
local image="$1"
local output_file="$OUTPUT_DIR/image-$(echo "$image" | tr '/:' '_')-$(date +%Y%m%d_%H%M%S)"
log "Scanning container image: $image"
# Comprehensive scan
trivy image \
--format "$REPORT_FORMAT" \
--output "${output_file}.json" \
--severity "$SEVERITY_LEVELS" \
--vuln-type os,library \
--scanners vuln,secret,config \
--exit-code 0 \
"$image"
# Generate SBOM
trivy image \
--format cyclonedx \
--output "${output_file}-sbom.json" \
--scanners vuln,license \
"$image"
# Generate human-readable report
trivy image \
--format table \
--output "${output_file}.txt" \
--severity "$SEVERITY_LEVELS" \
"$image"
log "Image scan completed: $output_file"
echo "$output_file"
}
# Scan filesystem
scan_filesystem() {
local path="$1"
local output_file="$OUTPUT_DIR/fs-$(basename "$path")-$(date +%Y%m%d_%H%M%S)"
log "Scanning filesystem: $path"
# Comprehensive filesystem scan
trivy fs \
--format "$REPORT_FORMAT" \
--output "${output_file}.json" \
--severity "$SEVERITY_LEVELS" \
--scanners vuln,secret,config,license \
--skip-dirs vendor,node_modules,.git \
--exit-code 0 \
"$path"
# Generate SBOM
trivy fs \
--format cyclonedx \
--output "${output_file}-sbom.json" \
--scanners vuln,license \
"$path"
log "Filesystem scan completed: $output_file"
echo "$output_file"
}
# Scan repository
scan_repository() {
local repo="$1"
local output_file="$OUTPUT_DIR/repo-$(echo "$repo" | tr '/:' '_')-$(date +%Y%m%d_%H%M%S)"
log "Scanning repository: $repo"
# Repository scan
trivy repo \
--format "$REPORT_FORMAT" \
--output "${output_file}.json" \
--severity "$SEVERITY_LEVELS" \
--scanners vuln,secret,config \
--exit-code 0 \
"$repo"
log "Repository scan completed: $output_file"
echo "$output_file"
}
# Scan Kubernetes cluster
scan_kubernetes() {
local target="$1"
local output_file="$OUTPUT_DIR/k8s-$(echo "$target" | tr '/:' '_')-$(date +%Y%m%d_%H%M%S)"
log "Scanning Kubernetes: $target"
# Kubernetes scan
trivy k8s \
--format "$REPORT_FORMAT" \
--output "${output_file}.json" \
--severity "$SEVERITY_LEVELS" \
--scanners vuln,config \
--exit-code 0 \
"$target"
# Compliance scan
trivy k8s \
--compliance k8s-cis \
--format json \
--output "${output_file}-compliance.json" \
"$target"
log "Kubernetes scan completed: $output_file"
echo "$output_file"
}
# Analyze scan results
analyze_results() {
local result_file="$1"
if [ ! -f "$result_file.json" ]; then
log "Result file not found: $result_file.json"
return 1
fi
log "Analyzing scan results..."
# Count vulnerabilities by severity
local critical_count high_count medium_count low_count
critical_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$result_file.json" 2>/dev/null || echo "0")
high_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$result_file.json" 2>/dev/null || echo "0")
medium_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' "$result_file.json" 2>/dev/null || echo "0")
low_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW")] | length' "$result_file.json" 2>/dev/null || echo "0")
# Count secrets
local secret_count
secret_count=$(jq '[.Results[]?.Secrets[]?] | length' "$result_file.json" 2>/dev/null || echo "0")
# Count misconfigurations
local misconfig_count
misconfig_count=$(jq '[.Results[]?.Misconfigurations[]?] | length' "$result_file.json" 2>/dev/null || echo "0")
# Generate summary
cat > "$result_file-summary.txt" << EOF
Trivy Scan Summary
==================
Scan Target: $SCAN_TARGET
Scan Type: $SCAN_TYPE
Scan Date: $(date)
Vulnerability Summary:
- Critical: $critical_count
- High: $high_count
- Medium: $medium_count
- Low: $low_count
Security Issues:
- Secrets Found: $secret_count
- Misconfigurations: $misconfig_count
Total Issues: $((critical_count + high_count + medium_count + low_count + secret_count + misconfig_count))
EOF
log "Analysis completed. Summary saved to: $result_file-summary.txt"
# Return exit code based on critical/high vulnerabilities
if [ "$((critical_count + high_count))" -gt 0 ]; then
return 1
else
return 0
fi
}
# Send notifications
send_notifications() {
local result_file="$1"
local summary_file="$result_file-summary.txt"
if [ ! -f "$summary_file" ]; then
log "Summary file not found: $summary_file"
return 1
fi
local summary_content
summary_content=$(cat "$summary_file")
# Send Slack notification
if [ -n "$SLACK_WEBHOOK" ]; then
log "Sending Slack notification..."
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🔍 Trivy Scan Results\n\`\`\`\n$summary_content\n\`\`\`\"}" \
"$SLACK_WEBHOOK" 2>/dev/null || log "Failed to send Slack notification"
fi
# Send email notification
if [ -n "$EMAIL_RECIPIENTS" ] && command -v mail >/dev/null 2>&1; then
log "Sending email notification..."
echo "$summary_content" | mail -s "Trivy Scan Results - $SCAN_TARGET" "$EMAIL_RECIPIENTS" || log "Failed to send email notification"
fi
}
# Generate HTML report
generate_html_report() {
local result_file="$1"
local html_file="$result_file.html"
log "Generating HTML report..."
# Convert JSON to HTML (basic implementation)
cat > "$html_file" << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Trivy Scan Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
.critical { color: #d32f2f; font-weight: bold; }
.high { color: #f57c00; font-weight: bold; }
.medium { color: #fbc02d; font-weight: bold; }
.low { color: #388e3c; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Trivy Security Scan Report</h1>
<p><strong>Target:</strong> SCAN_TARGET_PLACEHOLDER</p>
<p><strong>Date:</strong> DATE_PLACEHOLDER</p>
</div>
<div id="summary">
<h2>Summary</h2>
<div id="summary-content">
<!-- Summary will be inserted here -->
</div>
</div>
<div id="details">
<h2>Detailed Results</h2>
<div id="results-content">
<!-- Detailed results will be inserted here -->
</div>
</div>
<script>
// Load and display JSON results
// This would be populated with actual data processing
</script>
</body>
</html>
EOF
# Replace placeholders
sed -i "s/SCAN_TARGET_PLACEHOLDER/$SCAN_TARGET/g" "$html_file"
sed -i "s/DATE_PLACEHOLDER/$(date)/g" "$html_file"
log "HTML report generated: $html_file"
}
# Main execution
main() {
log "Starting Trivy scan automation..."
log "Target: $SCAN_TARGET, Type: $SCAN_TYPE"
# Update database
update_database
# Perform scan based on type
local result_file
case "$SCAN_TYPE" in
"image")
result_file=$(scan_image "$SCAN_TARGET")
;;
"fs")
result_file=$(scan_filesystem "$SCAN_TARGET")
;;
"repo")
result_file=$(scan_repository "$SCAN_TARGET")
;;
"k8s")
result_file=$(scan_kubernetes "$SCAN_TARGET")
;;
*)
log "Unknown scan type: $SCAN_TYPE"
exit 1
;;
esac
# Analyze results
if analyze_results "$result_file"; then
log "✅ Scan completed successfully - No critical/high vulnerabilities found"
exit_code=0
else
log "⚠️ Scan completed with critical/high vulnerabilities found"
exit_code=1
fi
# Generate additional reports
generate_html_report "$result_file"
# Send notifications
send_notifications "$result_file"
log "Trivy scan automation completed"
log "Results available in: $OUTPUT_DIR"
exit $exit_code
}
# Execute main function
main "$@"
CI/CD Integration Scripts
bash
# Jenkins Pipeline
cat > Jenkinsfile << 'EOF'
pipeline {
agent any
environment {
TRIVY_CACHE_DIR = '/tmp/trivy-cache'
TRIVY_NO_PROGRESS = 'true'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build Image') {
steps {
script {
def image = docker.build("${env.JOB_NAME}:${env.BUILD_NUMBER}")
env.IMAGE_NAME = image.id
}
}
}
stage('Trivy Scan') {
parallel {
stage('Vulnerability Scan') {
steps {
script {
sh '''
trivy image \
--format json \
--output trivy-vuln-report.json \
--severity HIGH,CRITICAL \
--exit-code 1 \
${IMAGE_NAME}
'''
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-vuln-report.json', fingerprint: true
}
}
}
stage('Secret Scan') {
steps {
script {
sh '''
trivy fs \
--format json \
--output trivy-secret-report.json \
--scanners secret \
--exit-code 0 \
.
'''
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-secret-report.json', fingerprint: true
}
}
}
stage('Config Scan') {
steps {
script {
sh '''
trivy fs \
--format json \
--output trivy-config-report.json \
--scanners config \
--exit-code 0 \
.
'''
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-config-report.json', fingerprint: true
}
}
}
}
}
stage('Generate SBOM') {
steps {
script {
sh '''
trivy image \
--format cyclonedx \
--output sbom.json \
${IMAGE_NAME}
'''
}
}
post {
always {
archiveArtifacts artifacts: 'sbom.json', fingerprint: true
}
}
}
stage('Security Gate') {
steps {
script {
def vulnReport = readJSON file: 'trivy-vuln-report.json'
def criticalCount = 0
def highCount = 0
vulnReport.Results.each { result ->
if (result.Vulnerabilities) {
result.Vulnerabilities.each { vuln ->
if (vuln.Severity == 'CRITICAL') criticalCount++
if (vuln.Severity == 'HIGH') highCount++
}
}
}
echo "Critical vulnerabilities: ${criticalCount}"
echo "High vulnerabilities: ${highCount}"
if (criticalCount > 0) {
error("Build failed due to ${criticalCount} critical vulnerabilities")
}
if (highCount > 5) {
unstable("Build marked unstable due to ${highCount} high vulnerabilities")
}
}
}
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-*.json',
reportName: 'Trivy Security Report'
])
}
failure {
emailext (
subject: "Security Scan Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Security vulnerabilities found. Check console output for details.",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
}
}
EOF
# GitHub Actions Workflow
cat > .github/workflows/trivy.yml << 'EOF'
name: Trivy Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t test-image:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'test-image:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'json'
output: 'trivy-fs-results.json'
severity: 'CRITICAL,HIGH'
- name: Generate SBOM
uses: aquasecurity/trivy-action@master
with:
image-ref: 'test-image:${{ github.sha }}'
format: 'cyclonedx'
output: 'sbom.json'
- name: Upload scan results
uses: actions/upload-artifact@v3
if: always()
with:
name: trivy-results
path: |
trivy-results.sarif
trivy-fs-results.json
sbom.json
- name: Security Gate
run: |
# Check for critical vulnerabilities
CRITICAL_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' trivy-fs-results.json)
HIGH_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' trivy-fs-results.json)
echo "Critical vulnerabilities: $CRITICAL_COUNT"
echo "High vulnerabilities: $HIGH_COUNT"
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "::error::Build failed due to $CRITICAL_COUNT critical vulnerabilities"
exit 1
fi
if [ "$HIGH_COUNT" -gt 10 ]; then
echo "::warning::Build has $HIGH_COUNT high vulnerabilities"
fi
trivy-config-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run Trivy config scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: '.'
format: 'sarif'
output: 'trivy-config-results.sarif'
- name: Upload config scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-config-results.sarif'
EOF
# GitLab CI Configuration
cat > .gitlab-ci.yml << 'EOF'
stages:
- build
- security-scan
- deploy
variables:
DOCKER_DRIVER: overlay2
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
cache:
paths:
- .trivycache/
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
- develop
trivy-vulnerability-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy image --format json --output trivy-vuln-report.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- trivy image --format template --template "@contrib/html.tpl" --output trivy-vuln-report.html $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
reports:
container_scanning: trivy-vuln-report.json
paths:
- trivy-vuln-report.html
expire_in: 1 week
only:
- main
- develop
trivy-config-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy fs --format json --output trivy-config-report.json .
- trivy fs --format template --template "@contrib/html.tpl" --output trivy-config-report.html .
artifacts:
paths:
- trivy-config-report.json
- trivy-config-report.html
expire_in: 1 week
only:
- main
- develop
trivy-sbom-generation:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy image --format cyclonedx --output sbom.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- sbom.json
expire_in: 1 week
only:
- main
- develop
EOF
Kubernetes Monitoring Script
bash
#!/bin/bash
# k8s_trivy_monitor.sh - Continuous Kubernetes security monitoring with Trivy
set -euo pipefail
# Configuration
NAMESPACE="${NAMESPACE:-default}"
OUTPUT_DIR="/tmp/k8s-trivy-results"
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
SCAN_INTERVAL="${SCAN_INTERVAL:-3600}" # 1 hour
# Create output directory
mkdir -p "$OUTPUT_DIR"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$OUTPUT_DIR/monitor.log"
}
# Get all container images in namespace
get_container_images() {
local namespace="$1"
kubectl get pods -n "$namespace" -o jsonpath='{.items[*].spec.containers[*].image}' | \
tr ' ' '\n' | sort -u | grep -v '^$'
}
# Scan container image
scan_image() {
local image="$1"
local output_file="$OUTPUT_DIR/$(echo "$image" | tr '/:' '_')-$(date +%Y%m%d_%H%M%S).json"
log "Scanning image: $image"
trivy image \
--format json \
--output "$output_file" \
--severity HIGH,CRITICAL \
--vuln-type os,library \
--exit-code 0 \
"$image" 2>/dev/null || {
log "Failed to scan image: $image"
return 1
}
echo "$output_file"
}
# Scan Kubernetes cluster
scan_cluster() {
local namespace="$1"
local output_file="$OUTPUT_DIR/cluster-$namespace-$(date +%Y%m%d_%H%M%S).json"
log "Scanning Kubernetes cluster namespace: $namespace"
trivy k8s \
--namespace "$namespace" \
--format json \
--output "$output_file" \
--severity HIGH,CRITICAL \
cluster 2>/dev/null || {
log "Failed to scan cluster namespace: $namespace"
return 1
}
echo "$output_file"
}
# Analyze scan results
analyze_results() {
local result_file="$1"
if [ ! -f "$result_file" ]; then
log "Result file not found: $result_file"
return 1
fi
local critical_count high_count
critical_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$result_file" 2>/dev/null || echo "0")
high_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$result_file" 2>/dev/null || echo "0")
echo "$critical_count,$high_count"
}
# Send alert
send_alert() {
local message="$1"
if [ -n "$SLACK_WEBHOOK" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 Kubernetes Security Alert: $message\"}" \
"$SLACK_WEBHOOK" 2>/dev/null || log "Failed to send alert"
fi
log "ALERT: $message"
}
# Generate summary report
generate_summary() {
local namespace="$1"
log "Generating summary report for namespace: $namespace"
local summary_file="$OUTPUT_DIR/summary-$namespace-$(date +%Y%m%d_%H%M%S).txt"
local total_critical=0
local total_high=0
local scanned_images=0
cat > "$summary_file" << EOF
Kubernetes Security Summary Report
==================================
Namespace: $namespace
Generated: $(date)
Image Scan Results:
EOF
# Scan all images in namespace
while IFS= read -r image; do
if [ -n "$image" ]; then
local result_file
result_file=$(scan_image "$image")
if [ -n "$result_file" ] && [ -f "$result_file" ]; then
local counts
counts=$(analyze_results "$result_file")
local critical_count="${counts%,*}"
local high_count="${counts#*,}"
total_critical=$((total_critical + critical_count))
total_high=$((total_high + high_count))
scanned_images=$((scanned_images + 1))
echo " $image: Critical=$critical_count, High=$high_count" >> "$summary_file"
# Alert on critical vulnerabilities
if [ "$critical_count" -gt 0 ]; then
send_alert "Critical vulnerabilities found in $image: $critical_count critical, $high_count high"
fi
fi
fi
done < <(get_container_images "$namespace")
# Add cluster scan results
local cluster_result
cluster_result=$(scan_cluster "$namespace")
if [ -n "$cluster_result" ] && [ -f "$cluster_result" ]; then
local cluster_counts
cluster_counts=$(analyze_results "$cluster_result")
local cluster_critical="${cluster_counts%,*}"
local cluster_high="${cluster_counts#*,}"
echo "" >> "$summary_file"
echo "Cluster Configuration Scan:" >> "$summary_file"
echo " Critical: $cluster_critical" >> "$summary_file"
echo " High: $cluster_high" >> "$summary_file"
fi
# Add totals
cat >> "$summary_file" << EOF
Summary:
========
Total Images Scanned: $scanned_images
Total Critical Vulnerabilities: $total_critical
Total High Vulnerabilities: $total_high
Recommendations:
- Update images with critical vulnerabilities immediately
- Review and remediate high-severity vulnerabilities
- Implement automated vulnerability scanning in CI/CD pipeline
- Consider using admission controllers to prevent vulnerable images
EOF
log "Summary report generated: $summary_file"
# Send summary alert if significant issues found
if [ "$total_critical" -gt 0 ] || [ "$total_high" -gt 10 ]; then
send_alert "Namespace $namespace scan completed: $total_critical critical, $total_high high vulnerabilities found across $scanned_images images"
fi
}
# Continuous monitoring loop
monitor_loop() {
log "Starting continuous Kubernetes monitoring..."
while true; do
log "Starting scan cycle..."
# Get all namespaces or scan specific namespace
if [ "$NAMESPACE" = "all" ]; then
while IFS= read -r ns; do
if [ -n "$ns" ] && [ "$ns" != "kube-system" ] && [ "$ns" != "kube-public" ]; then
generate_summary "$ns"
fi
done < <(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n')
else
generate_summary "$NAMESPACE"
fi
log "Scan cycle completed. Sleeping for $SCAN_INTERVAL seconds..."
sleep "$SCAN_INTERVAL"
done
}
# Cleanup old results
cleanup_old_results() {
local retention_days="${1:-7}"
log "Cleaning up results older than $retention_days days..."
find "$OUTPUT_DIR" -name "*.json" -mtime +$retention_days -delete
find "$OUTPUT_DIR" -name "*.txt" -mtime +$retention_days -delete
}
# Main execution
main() {
case "${1:-monitor}" in
"monitor")
monitor_loop
;;
"scan")
generate_summary "$NAMESPACE"
;;
"cleanup")
cleanup_old_results "${2:-7}"
;;
"images")
get_container_images "$NAMESPACE"
;;
*)
echo "Usage: $0 {monitor|scan|cleanup [days]|images}"
echo "Environment variables:"
echo " NAMESPACE: Kubernetes namespace to scan (default: default, use 'all' for all namespaces)"
echo " SLACK_WEBHOOK: Slack webhook URL for alerts"
echo " SCAN_INTERVAL: Scan interval in seconds (default: 3600)"
exit 1
;;
esac
}
# Execute main function
main "$@"
Integration Examples
SIEM Integration
bash
# Splunk integration
cat > /etc/filebeat/conf.d/trivy.yml << 'EOF'
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/trivy/*.json
json.keys_under_root: true
json.add_error_key: true
fields:
logtype: trivy
source: security_scan
fields_under_root: true
output.elasticsearch:
hosts: ["localhost:9200"]
index: "trivy-%{+yyyy.MM.dd}"
template.name: "trivy"
template.pattern: "trivy-*"
processors:
- add_host_metadata:
when.not.contains.tags: forwarded
- add_docker_metadata: ~
- add_kubernetes_metadata: ~
EOF
# ELK Stack dashboard configuration
cat > trivy-dashboard.json << 'EOF'
{
"version": "7.15.0",
"objects": [
{
"id": "trivy-vulnerability-overview",
"type": "dashboard",
"attributes": {
"title": "Trivy Vulnerability Overview",
"hits": 0,
"description": "Overview of vulnerabilities detected by Trivy",
"panelsJSON": "[{\"version\":\"7.15.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{},\"panelRefName\":\"panel_1\"}]",
"optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"hidePanelTitles\":false}",
"version": 1,
"timeRestore": false,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
}
}
}
]
}
EOF
Vulnerability Management Integration
bash
# DefectDojo integration
cat > defectdojo_import.py << 'EOF'
#!/usr/bin/env python3
"""
DefectDojo Trivy Integration
Import Trivy scan results into DefectDojo
"""
import json
import requests
import argparse
import sys
from datetime import datetime
class DefectDojoClient:
def __init__(self, url, api_key):
self.url = url.rstrip('/')
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Token {api_key}',
'Content-Type': 'application/json'
})
def create_engagement(self, product_id, name, description):
"""Create a new engagement"""
data = {
'name': name,
'description': description,
'product': product_id,
'target_start': datetime.now().isoformat(),
'target_end': datetime.now().isoformat(),
'status': 'In Progress'
}
response = self.session.post(f'{self.url}/api/v2/engagements/', json=data)
response.raise_for_status()
return response.json()['id']
def import_scan(self, engagement_id, scan_file, scan_type='Trivy Scan'):
"""Import scan results"""
with open(scan_file, 'rb') as f:
files = {'file': f}
data = {
'engagement': engagement_id,
'scan_type': scan_type,
'verified': True,
'active': True,
'minimum_severity': 'Info'
}
response = self.session.post(
f'{self.url}/api/v2/import-scan/',
files=files,
data=data
)
response.raise_for_status()
return response.json()
def main():
parser = argparse.ArgumentParser(description='Import Trivy results to DefectDojo')
parser.add_argument('--url', required=True, help='DefectDojo URL')
parser.add_argument('--api-key', required=True, help='DefectDojo API key')
parser.add_argument('--product-id', required=True, type=int, help='Product ID')
parser.add_argument('--scan-file', required=True, help='Trivy scan result file')
parser.add_argument('--engagement-name', required=True, help='Engagement name')
args = parser.parse_args()
try:
client = DefectDojoClient(args.url, args.api_key)
# Create engagement
engagement_id = client.create_engagement(
args.product_id,
args.engagement_name,
f'Trivy security scan - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
)
print(f"Created engagement: {engagement_id}")
# Import scan
result = client.import_scan(engagement_id, args.scan_file)
print(f"Imported scan: {result}")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
EOF
chmod +x defectdojo_import.py
# JIRA integration for vulnerability tracking
cat > jira_integration.py << 'EOF'
#!/usr/bin/env python3
"""
JIRA Trivy Integration
Create JIRA tickets for critical vulnerabilities
"""
import json
import requests
import argparse
from jira import JIRA
def create_vulnerability_ticket(jira_client, project_key, vulnerability):
"""Create JIRA ticket for vulnerability"""
summary = f"[SECURITY] {vulnerability['Title']} - {vulnerability['Severity']}"
description = f"""
*Vulnerability Details:*
- *Package:* {vulnerability.get('PkgName', 'N/A')}
- *Installed Version:* {vulnerability.get('InstalledVersion', 'N/A')}
- *Fixed Version:* {vulnerability.get('FixedVersion', 'N/A')}
- *Severity:* {vulnerability['Severity']}
- *CVE:* {vulnerability.get('VulnerabilityID', 'N/A')}
*Description:*
{vulnerability.get('Description', 'No description available')}
*References:*
{chr(10).join(vulnerability.get('References', []))}
*Remediation:*
Update package to fixed version: {vulnerability.get('FixedVersion', 'No fix available')}
"""
issue_dict = {
'project': {'key': project_key},
'summary': summary,
'description': description,
'issuetype': {'name': 'Bug'},
'priority': {'name': 'High' if vulnerability['Severity'] == 'CRITICAL' else 'Medium'},
'labels': ['security', 'vulnerability', vulnerability['Severity'].lower()]
}
return jira_client.create_issue(fields=issue_dict)
def main():
parser = argparse.ArgumentParser(description='Create JIRA tickets for Trivy vulnerabilities')
parser.add_argument('--jira-url', required=True, help='JIRA URL')
parser.add_argument('--username', required=True, help='JIRA username')
parser.add_argument('--api-token', required=True, help='JIRA API token')
parser.add_argument('--project-key', required=True, help='JIRA project key')
parser.add_argument('--scan-file', required=True, help='Trivy scan result file')
parser.add_argument('--severity', default='CRITICAL,HIGH', help='Severity levels to create tickets for')
args = parser.parse_args()
# Connect to JIRA
jira = JIRA(server=args.jira_url, basic_auth=(args.username, args.api_token))
# Load scan results
with open(args.scan_file, 'r') as f:
scan_data = json.load(f)
severity_filter = args.severity.split(',')
created_tickets = []
# Process vulnerabilities
for result in scan_data.get('Results', []):
for vulnerability in result.get('Vulnerabilities', []):
if vulnerability['Severity'] in severity_filter:
try:
ticket = create_vulnerability_ticket(jira, args.project_key, vulnerability)
created_tickets.append(ticket.key)
print(f"Created ticket: {ticket.key} for {vulnerability['VulnerabilityID']}")
except Exception as e:
print(f"Failed to create ticket for {vulnerability.get('VulnerabilityID', 'unknown')}: {e}")
print(f"Created {len(created_tickets)} tickets: {', '.join(created_tickets)}")
if __name__ == '__main__':
main()
EOF
chmod +x jira_integration.py
Troubleshooting
Common Issues and Solutions
bash
# Database update issues
trivy image --reset # Reset database
trivy image --download-db-only # Download database only
# Permission issues
sudo chown -R $(whoami) ~/.cache/trivy/
# Network connectivity issues
trivy image --insecure nginx:latest # Skip TLS verification
trivy image --timeout 10m nginx:latest # Increase timeout
# Memory issues
trivy image --no-progress nginx:latest # Disable progress bar
export TRIVY_CACHE_DIR=/tmp/trivy-cache # Use different cache directory
# Registry authentication
trivy image --username user --password pass registry.com/image:tag
docker login registry.com && trivy image registry.com/image:tag
# Debug mode
trivy image --debug nginx:latest
# Clear cache
trivy image --clear-cache
# Check version and update
trivy version
trivy image --download-db-only --db-repository ghcr.io/aquasecurity/trivy-db
Performance Optimization
bash
# Optimize cache settings
export TRIVY_CACHE_DIR=/fast/storage/trivy-cache
export TRIVY_TEMP_DIR=/fast/storage/trivy-temp
# Parallel scanning
cat > parallel_scan.sh << 'EOF'
#!/bin/bash
# Parallel image scanning
images=(
"nginx:latest"
"ubuntu:20.04"
"python:3.9"
"node:16"
)
# Function to scan image
scan_image() {
local image="$1"
echo "Scanning $image..."
trivy image --format json --output "${image//[:\/]/_}.json" "$image"
echo "Completed $image"
}
# Export function for parallel execution
export -f scan_image
# Run scans in parallel
printf '%s\n' "${images[@]}" | xargs -n 1 -P 4 -I {} bash -c 'scan_image "$@"' _ {}
echo "All scans completed"
EOF
chmod +x parallel_scan.sh
Security Considerations
Best Practices
Database Security:
- Regularly update vulnerability database
- Use trusted database sources
- Verify database integrity
Credential Management:
- Use secure credential storage
- Rotate registry credentials regularly
- Implement least privilege access
Result Handling:
- Secure scan result storage
- Implement proper access controls
- Encrypt sensitive scan data
CI/CD Integration:
- Implement security gates
- Use fail-fast approaches
- Monitor scan performance
Compliance:
- Regular compliance scanning
- Document remediation efforts
- Track vulnerability metrics
Conclusion
Trivy provides comprehensive security scanning capabilities for modern DevSecOps environments. This cheatsheet covers installation, configuration, advanced usage, automation, CI/CD integration, and troubleshooting. Regular scanning and proper integration ensure robust security posture across container and cloud-native environments.