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]
|
[web]
|
||||||
prod-01.alexpires.me
|
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
|
access_key = scaleway_iam_api_key.sftpgo_app_api.access_key
|
||||||
secret_key = scaleway_iam_api_key.sftpgo_app_api.secret_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}"
|
rustdesk_fqdn = "rustdesk.${local.primary_domain}"
|
||||||
|
|
||||||
|
# trivy
|
||||||
|
trivy_cronjob_schedule = "0 1 1/15 * *"
|
||||||
|
|
||||||
# backup
|
# backup
|
||||||
local_backup_retention_days = 2
|
local_backup_retention_days = 2
|
||||||
cloud_backup_retention_days = 3
|
cloud_backup_retention_days = 3
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
variable "persistent_folder" {
|
variable "persistent_folder" {
|
||||||
description = "The path to the persistent folder"
|
description = "The path to the persistent folder"
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
variable "backup_folder" {
|
variable "backup_folder" {
|
||||||
description = "The path to the backup folder"
|
description = "The path to the backup folder"
|
||||||
type = string
|
type = string
|
||||||
@@ -54,7 +52,6 @@ variable "from_email" {
|
|||||||
description = "The email address to use as the sender"
|
description = "The email address to use as the sender"
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "access_key" {
|
variable "access_key" {
|
||||||
description = "The access key for the S3 compatible storage"
|
description = "The access key for the S3 compatible storage"
|
||||||
type = string
|
type = string
|
||||||
@@ -83,4 +80,4 @@ variable "tls_enabled" {
|
|||||||
type = bool
|
type = bool
|
||||||
default = true
|
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