feat(trivy): add Trivy integration with Kubernetes, including cron job and email reporting
This commit is contained in:
@@ -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
|
||||
@@ -45,3 +45,6 @@ prod-01.alexpires.me
|
||||
|
||||
[web]
|
||||
prod-01.alexpires.me
|
||||
|
||||
[trivy]
|
||||
prod-01.alexpires.me
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user