feat(trivy): add Trivy integration with Kubernetes, including cron job and email reporting

This commit is contained in:
2025-05-10 18:16:08 +02:00
parent 5d2f50feb9
commit bbc4fef0e8
9 changed files with 363 additions and 4 deletions
+28
View File
@@ -0,0 +1,28 @@
---
- name: Trivy Playbook (K8s)
hosts: trivy
gather_facts: true
# Specific tasks for this playbook
tasks:
- name: Set required facts
ansible.builtin.set_fact:
trivy_home: "{{ persistent_data | default('/var/lib') }}/trivy"
trivy_user_id: 1000
trivy_group_id: 1000
tags: always
- name: Create required trivy data folder
become: true
ansible.builtin.file:
state: directory
path: "{{ item }}"
mode: "0750"
owner: "{{ trivy_user_id }}"
group: "{{ trivy_group_id }}"
with_items:
- "{{ trivy_home }}"
- "{{ trivy_home }}/cache"
tags:
- tasks
- tasks::folders
+3
View File
@@ -45,3 +45,6 @@ prod-01.alexpires.me
[web]
prod-01.alexpires.me
[trivy]
prod-01.alexpires.me
+14
View File
@@ -114,3 +114,17 @@ module "sftpgo" {
access_key = scaleway_iam_api_key.sftpgo_app_api.access_key
secret_key = scaleway_iam_api_key.sftpgo_app_api.secret_key
}
module "trivy" {
source = "../modules/utils/trivy"
persistent_folder = local.persistent_folder
cron_schedule = local.trivy_cronjob_schedule
smtp_host = local.scaleway_smtp_host
smtp_port = local.scaleway_smtp_port
smtp_username = var.scaleway_project_id
smtp_password = scaleway_iam_api_key.mail_app_api.secret_key
from_email = "trivy@${local.primary_domain}"
to_email = "c.alexandre.pires@${local.primary_domain}"
}
+3
View File
@@ -39,6 +39,9 @@ locals {
rustdesk_fqdn = "rustdesk.${local.primary_domain}"
# trivy
trivy_cronjob_schedule = "0 1 1/15 * *"
# backup
local_backup_retention_days = 2
cloud_backup_retention_days = 3
+1 -4
View File
@@ -1,10 +1,8 @@
variable "persistent_folder" {
description = "The path to the persistent folder"
type = string
}
variable "backup_folder" {
description = "The path to the backup folder"
type = string
@@ -54,7 +52,6 @@ variable "from_email" {
description = "The email address to use as the sender"
type = string
}
variable "access_key" {
description = "The access key for the S3 compatible storage"
type = string
@@ -83,4 +80,4 @@ variable "tls_enabled" {
type = bool
default = true
}
+7
View File
@@ -0,0 +1,7 @@
locals {
cache_path = "${var.persistent_folder}/trivy/cache"
cronjob_schedule = var.cron_schedule
python_version = "3.12-alpine3.21"
send_report_script = file("${path.module}/resources/send_report.py")
}
+186
View File
@@ -0,0 +1,186 @@
resource "kubernetes_namespace" "trivy" {
metadata {
name = "trivy"
}
}
resource "kubernetes_service_account" "trivy" {
metadata {
name = "trivy"
namespace = kubernetes_namespace.trivy.metadata[0].name
}
automount_service_account_token = false
}
resource "kubernetes_cluster_role" "trivy" {
metadata {
name = "trivy"
}
rule {
api_groups = [""]
resources = ["*"]
verbs = ["list"]
}
rule {
api_groups = ["apps", "batch", "networking.k8s.io", "rbac.authorization.k8s.io"]
resources = ["*"]
verbs = ["list"]
}
}
resource "kubernetes_cluster_role_binding" "trivy" {
metadata {
name = "trivy"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role.trivy.metadata[0].name
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account.trivy.metadata[0].name
namespace = kubernetes_namespace.trivy.metadata[0].name
}
}
resource "kubernetes_secret" "email_credentials" {
metadata {
name = "trivy-email-credentials"
namespace = kubernetes_namespace.trivy.metadata[0].name
}
data = {
smtp_username = var.smtp_username
smtp_password = var.smtp_password
}
}
resource "kubernetes_config_map" "send_report" {
metadata {
name = "trivy-send-report"
namespace = kubernetes_namespace.trivy.metadata[0].name
}
data = {
"send_report.py" = local.send_report_script
}
}
resource "kubernetes_cron_job_v1" "trivy" {
metadata {
name = "trivy-scan"
namespace = kubernetes_namespace.trivy.metadata[0].name
}
spec {
schedule = var.cron_schedule
concurrency_policy = "Forbid"
successful_jobs_history_limit = 1
failed_jobs_history_limit = 1
job_template {
metadata {
name = "trivy-scan"
}
spec {
backoff_limit = 1
parallelism = 1
completions = 1
active_deadline_seconds = 250
template {
metadata {
labels = {
app = "trivy-scan"
}
}
spec {
restart_policy = "Never"
service_account_name = kubernetes_service_account.trivy.metadata[0].name
automount_service_account_token = true
container {
image = "aquasec/trivy:${var.tag}"
name = "trivy"
args = ["k8s", "--report=${var.report_type}", "--exit-code=0", "--severity=${var.levels}", "--cache-dir=/cache", "-o", "/reports/trivy.txt", "--disable-node-collector"]
volume_mount {
name = "trivy-reports"
mount_path = "/reports"
}
volume_mount {
name = "trivy-cache"
mount_path = "/cache"
}
}
container {
name = "send-report"
image = "python:${local.python_version}"
args = ["/scripts/send_report.py"]
env {
name = "REPORT_PATH"
value = "/reports/trivy.txt"
}
env {
name = "SMTP_HOST"
value = var.smtp_host
}
env {
name = "SMTP_PORT"
value = var.smtp_port
}
env {
name = "SMTP_USER"
value_from {
secret_key_ref {
key = "smtp_username"
name = kubernetes_secret.email_credentials.metadata[0].name
}
}
}
env {
name = "SMTP_PASS"
value_from {
secret_key_ref {
key = "smtp_password"
name = kubernetes_secret.email_credentials.metadata[0].name
}
}
}
env {
name = "EMAIL_FROM"
value = var.from_email
}
env {
name = "EMAIL_TO"
value = var.to_email
}
volume_mount {
name = "trivy-reports"
mount_path = "/reports"
}
volume_mount {
name = "trivy-scripts"
mount_path = "/scripts"
}
}
volume {
name = "trivy-reports"
empty_dir {}
}
volume {
name = "trivy-cache"
host_path {
path = local.cache_path
}
}
volume {
name = "trivy-scripts"
config_map {
name = kubernetes_config_map.send_report.metadata[0].name
default_mode = "0755"
}
}
}
}
}
}
}
}
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
import os
import time
import smtplib
from email.message import EmailMessage
REPORT_PATH = os.environ.get("REPORT_PATH", "/tmp/trivy_report.txt")
WAIT_TIMEOUT = int(os.environ.get("WAIT_TIMEOUT", "200"))
SLEEP_INTERVAL = int(os.environ.get("SLEEP_INTERVAL", "5") )
def dispatch_email(report_path, subject):
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = os.environ.get("EMAIL_FROM", "noreply@example.com")
msg["To"] = os.environ.get("EMAIL_TO", "admin@example.com")
if report_path:
msg.set_content("Trivy scan report attached.")
# Attach the report file
with open(report_path, "rb") as f:
file_data = f.read()
file_name = os.path.basename(report_path)
msg.add_attachment(file_data, maintype="text", subtype="plain", filename=file_name)
else:
msg.set_content("Trivy scan report not found. Check logs for details.")
smtp_host = os.environ.get("SMTP_HOST", "smtp.example.com")
smtp_port = int(os.environ.get("SMTP_PORT", "587"))
smtp_user = os.environ.get("SMTP_USER", "user")
smtp_pass = os.environ.get("SMTP_PASS", "pass")
with smtplib.SMTP(smtp_host, smtp_port) as server:
if smtp_port == 465:
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
else:
server = smtplib.SMTP(smtp_host, smtp_port)
if smtp_port != 465:
server.starttls()
try:
server.login(smtp_user, smtp_pass)
except smtplib.SMTPAuthenticationError:
print("SMTP Authentication Error: Check your SMTP credentials.")
return
server.send_message(msg)
print("Report sent.")
print("Trivy report path:", REPORT_PATH)
print("Starting to wait for the report to be generated...")
start = time.time()
while not os.path.exists(REPORT_PATH):
if time.time() - start > WAIT_TIMEOUT:
print("Timeout waiting for report to be generated.")
dispatch_email(None, "Trivy Scan Report - Timeout")
exit(1)
print("Still waiting for report to be generated...")
time.sleep(SLEEP_INTERVAL)
print("Report generated, proceeding to send email...")
time.sleep(5) # Wait for a bit to ensure the file is fully written
dispatch_email(REPORT_PATH, "Trivy Scan Report - " + time.strftime("%Y-%m-%d %H:%M:%S"))
@@ -0,0 +1,59 @@
variable "persistent_folder" {
description = "The path to the persistent folder"
type = string
}
variable "smtp_host" {
description = "The SMTP relay host"
type = string
}
variable "smtp_port" {
description = "The SMTP relay port"
type = number
}
variable "smtp_username" {
description = "The SMTP relay username"
type = string
sensitive = true
}
variable "smtp_password" {
description = "The SMTP relay password"
type = string
sensitive = true
}
variable "from_email" {
description = "The email address to use as the sender"
type = string
}
variable "to_email" {
description = "The email address to send the report to"
type = string
}
variable "cron_schedule" {
description = "Cronjob schedule for data backups"
type = string
}
variable "tag" {
description = "The version of Trivy to install"
type = string
default = "latest"
}
variable "report_type" {
description = "The type of report to generate"
type = string
default = "all"
}
variable "levels" {
description = "The severity levels to include in the report"
type = string
default = "HIGH,CRITICAL"
}