Security
Trivy
Send Trivy vulnerability scan results to Parseable. Follow the setup steps and practical guidance for using Trivy in your Parseable observability workflow.
Collect and analyze Trivy vulnerability scan results in Parseable for comprehensive security monitoring.
Overview
Integrate Trivy with Parseable to:
- Vulnerability Tracking - Track vulnerabilities across your infrastructure
- Container Security - Scan container images for known vulnerabilities
- IaC Security - Detect misconfigurations in infrastructure code
- Compliance - Monitor security compliance over time
Prerequisites
- Trivy installed (CLI or Operator)
- Parseable instance accessible
- CI/CD pipeline or scheduled scanning
What is Trivy?
Trivy is a comprehensive security scanner that detects:
- Vulnerabilities - CVEs in OS packages and application dependencies
- Misconfigurations - Security issues in IaC (Terraform, Kubernetes, etc.)
- Secrets - Hardcoded secrets and credentials
- Licenses - License compliance issues
Method 1: CI/CD Integration
Send scan results from your CI/CD pipeline.
GitHub Actions
name: Security Scan
on:
push:
branches: [main]
schedule:
- cron: '0 0 * * *' # Daily scan
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'json'
output: 'trivy-results.json'
- name: Send results to Parseable
run: |
# Transform Trivy output to Parseable format
jq -c '[.Results[] | .Vulnerabilities[]? | {
timestamp: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
scan_type: "filesystem",
repository: "${{ github.repository }}",
ref: "${{ github.ref }}",
sha: "${{ github.sha }}",
vulnerability_id: .VulnerabilityID,
pkg_name: .PkgName,
installed_version: .InstalledVersion,
fixed_version: .FixedVersion,
severity: .Severity,
title: .Title,
description: .Description,
cvss_score: .CVSS.nvd.V3Score
}]' trivy-results.json > parseable-payload.json
curl -X POST "${{ secrets.PARSEABLE_URL }}/api/v1/ingest" \
-H "Authorization: Basic ${{ secrets.PARSEABLE_AUTH }}" \
-H "X-P-Stream: trivy-vulnerabilities" \
-H "Content-Type: application/json" \
-d @parseable-payload.jsonContainer Image Scanning
name: Container Security Scan
on:
push:
branches: [main]
jobs:
scan-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scanner
run: |
trivy image --format json --output trivy-results.json myapp:${{ github.sha }}
- name: Send to Parseable
run: |
jq -c '[.Results[] | .Vulnerabilities[]? | {
timestamp: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
scan_type: "container",
image: "myapp:${{ github.sha }}",
repository: "${{ github.repository }}",
target: .Target,
vulnerability_id: .VulnerabilityID,
pkg_name: .PkgName,
installed_version: .InstalledVersion,
fixed_version: .FixedVersion,
severity: .Severity,
title: .Title
}]' trivy-results.json > payload.json
curl -X POST "${{ secrets.PARSEABLE_URL }}/api/v1/ingest" \
-H "Authorization: Basic ${{ secrets.PARSEABLE_AUTH }}" \
-H "X-P-Stream: trivy-vulnerabilities" \
-H "Content-Type: application/json" \
-d @payload.jsonGitLab CI
trivy-scan:
stage: security
image: aquasec/trivy:latest
script:
- trivy image --format json --output trivy-results.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- |
apk add --no-cache jq curl
jq -c '[.Results[] | .Vulnerabilities[]? | {
timestamp: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
scan_type: "container",
image: "'$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA'",
project: "'$CI_PROJECT_PATH'",
vulnerability_id: .VulnerabilityID,
pkg_name: .PkgName,
severity: .Severity,
title: .Title
}]' trivy-results.json > payload.json
curl -X POST "${PARSEABLE_URL}/api/v1/ingest" \
-H "Authorization: Basic ${PARSEABLE_AUTH}" \
-H "X-P-Stream: trivy-vulnerabilities" \
-H "Content-Type: application/json" \
-d @payload.jsonMethod 2: Trivy Operator
Use the Trivy Operator for continuous Kubernetes scanning.
Install Trivy Operator
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
helm install trivy-operator aqua/trivy-operator \
--namespace trivy-system \
--create-namespaceCollect Reports with Fluent Bit
# fluent-bit-config.yaml
service:
flush: 30
log_level: info
pipeline:
inputs:
- name: kubernetes_events
tag: k8s_events
kube_url: https://kubernetes.default.svc
filters:
- name: grep
match: k8s_events
regex: involvedObject.kind VulnerabilityReport
outputs:
- name: http
match: k8s_events
host: parseable
port: 8000
uri: /api/v1/ingest
format: json
header: Authorization Basic YWRtaW46YWRtaW4=
header: X-P-Stream trivy-operatorExport Reports Script
Create a script to export Trivy Operator reports:
#!/bin/bash
# export-trivy-reports.sh
PARSEABLE_URL="${PARSEABLE_URL:-http://parseable:8000}"
PARSEABLE_AUTH="${PARSEABLE_AUTH:-YWRtaW46YWRtaW4=}"
# Get all VulnerabilityReports
kubectl get vulnerabilityreports -A -o json | jq -c '
[.items[] | {
timestamp: .metadata.creationTimestamp,
namespace: .metadata.namespace,
name: .metadata.name,
image: .report.artifact.repository,
tag: .report.artifact.tag,
critical: .report.summary.criticalCount,
high: .report.summary.highCount,
medium: .report.summary.mediumCount,
low: .report.summary.lowCount,
vulnerabilities: [.report.vulnerabilities[] | {
id: .vulnerabilityID,
severity: .severity,
pkg: .resource,
version: .installedVersion,
fixed: .fixedVersion,
title: .title
}]
}]
' > /tmp/vuln-reports.json
curl -X POST "${PARSEABLE_URL}/api/v1/ingest" \
-H "Authorization: Basic ${PARSEABLE_AUTH}" \
-H "X-P-Stream: trivy-operator" \
-H "Content-Type: application/json" \
-d @/tmp/vuln-reports.jsonKubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: trivy-report-exporter
spec:
schedule: "0 * * * *" # Every hour
jobTemplate:
spec:
template:
spec:
serviceAccountName: trivy-exporter
containers:
- name: exporter
image: bitnami/kubectl:latest
command: ["/bin/bash", "/scripts/export-trivy-reports.sh"]
env:
- name: PARSEABLE_URL
value: "http://parseable:8000"
- name: PARSEABLE_AUTH
valueFrom:
secretKeyRef:
name: parseable-auth
key: auth
volumeMounts:
- name: scripts
mountPath: /scripts
volumes:
- name: scripts
configMap:
name: trivy-exporter-scripts
restartPolicy: OnFailureMethod 3: Direct CLI Integration
Send scan results directly from Trivy CLI.
Wrapper Script
#!/bin/bash
# trivy-to-parseable.sh
PARSEABLE_URL="${PARSEABLE_URL:-http://localhost:8000}"
PARSEABLE_AUTH="${PARSEABLE_AUTH:-YWRtaW46YWRtaW4=}"
STREAM="${STREAM:-trivy-scans}"
# Run Trivy scan
trivy "$@" --format json --output /tmp/trivy-results.json
# Transform and send to Parseable
jq -c '[{
timestamp: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
scan_target: .ArtifactName,
scan_type: .ArtifactType,
results: [.Results[] | {
target: .Target,
class: .Class,
type: .Type,
vulnerabilities: (.Vulnerabilities // []) | length,
critical: [(.Vulnerabilities // [])[] | select(.Severity == "CRITICAL")] | length,
high: [(.Vulnerabilities // [])[] | select(.Severity == "HIGH")] | length,
medium: [(.Vulnerabilities // [])[] | select(.Severity == "MEDIUM")] | length,
low: [(.Vulnerabilities // [])[] | select(.Severity == "LOW")] | length
}]
}]' /tmp/trivy-results.json > /tmp/parseable-payload.json
curl -s -X POST "${PARSEABLE_URL}/api/v1/ingest" \
-H "Authorization: Basic ${PARSEABLE_AUTH}" \
-H "X-P-Stream: ${STREAM}" \
-H "Content-Type: application/json" \
-d @/tmp/parseable-payload.json
echo "Scan results sent to Parseable"Usage:
./trivy-to-parseable.sh image nginx:latest
./trivy-to-parseable.sh fs /path/to/project
./trivy-to-parseable.sh config /path/to/terraformQuerying Trivy Results
Query your vulnerability data in Parseable:
-- Get recent vulnerability scans
SELECT timestamp, scan_target, scan_type,
results[0].critical as critical,
results[0].high as high
FROM "trivy-scans"
ORDER BY timestamp DESC
LIMIT 100
-- Find critical vulnerabilities
SELECT timestamp, image, vulnerability_id, pkg_name, title
FROM "trivy-vulnerabilities"
WHERE severity = 'CRITICAL'
ORDER BY timestamp DESC
-- Vulnerability count by severity
SELECT
severity,
COUNT(*) as count
FROM "trivy-vulnerabilities"
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY severity
ORDER BY
CASE severity
WHEN 'CRITICAL' THEN 1
WHEN 'HIGH' THEN 2
WHEN 'MEDIUM' THEN 3
WHEN 'LOW' THEN 4
END
-- Most vulnerable images
SELECT
image,
COUNT(*) as total_vulns,
SUM(CASE WHEN severity = 'CRITICAL' THEN 1 ELSE 0 END) as critical,
SUM(CASE WHEN severity = 'HIGH' THEN 1 ELSE 0 END) as high
FROM "trivy-vulnerabilities"
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY image
ORDER BY critical DESC, high DESC
LIMIT 20
-- Vulnerabilities with available fixes
SELECT vulnerability_id, pkg_name, installed_version, fixed_version, severity
FROM "trivy-vulnerabilities"
WHERE fixed_version IS NOT NULL AND fixed_version != ''
ORDER BY
CASE severity
WHEN 'CRITICAL' THEN 1
WHEN 'HIGH' THEN 2
END
LIMIT 50Setting Up Alerts
Create alerts for critical vulnerabilities:
{
"name": "Critical Vulnerability Detected",
"dataset": "trivy-vulnerabilities",
"alertType": "threshold",
"condition": {
"field": "severity",
"operator": "equals",
"value": "CRITICAL"
},
"threshold": 1,
"duration": "5m",
"webhook": {
"url": "https://your-slack-webhook",
"method": "POST"
}
}Best Practices
- Scan Regularly - Run scans on every build and on a schedule
- Track Trends - Monitor vulnerability counts over time
- Prioritize Fixes - Focus on critical and high severity first
- Include Context - Add repository, image, and commit information
- Set SLAs - Define remediation timelines by severity
Troubleshooting
Empty Results
- Verify Trivy is finding vulnerabilities (check raw output)
- Check jq transformation is correct
- Verify Parseable dataset accepts the payload format
Large Payloads
- Split large scans into smaller batches
- Send summary data instead of full vulnerability details
- Use compression if supported
Next Steps
- Set up alerts for vulnerability thresholds
- Create dashboards for security posture
- Configure Falco for runtime security
Was this page helpful?