feat(trivy): Enhance Trivy report generation and metrics handling
- Updated `generate_report.sh` to include additional scanners and output paths. - Enhanced `send_report.py` with new data classes for misconfigurations and vulnerabilities. - Improved parsing logic for Trivy output, supporting both Kubernetes and image scans. - Added Prometheus metrics generation for various scan results, including misconfigurations and vulnerabilities. - Introduced unit tests for parsing Trivy output and generating Prometheus metrics. - Modified Terraform variables for SMTP port type and added Pushgateway URL variable.
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
build:
|
build:
|
||||||
dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.ollama.rocm') }}"
|
dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.ollama.rocm') }}"
|
||||||
env:
|
env:
|
||||||
OLLAMA_CONTEXT_LENGTH: 4096
|
OLLAMA_CONTEXT_LENGTH: 32000
|
||||||
OLLAMA_NUM_THREADS: 10
|
OLLAMA_NUM_THREADS: 10
|
||||||
OLLAMA_NUM_PARALLEL: 2
|
OLLAMA_NUM_PARALLEL: 2
|
||||||
OLLAMA_FLASH_ATTENTION: 1
|
OLLAMA_FLASH_ATTENTION: 1
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ molecule
|
|||||||
molecule-podman
|
molecule-podman
|
||||||
passlib
|
passlib
|
||||||
pytest-molecule
|
pytest-molecule
|
||||||
|
requests
|
||||||
|
|||||||
@@ -98,9 +98,10 @@ module "trivy" {
|
|||||||
|
|
||||||
persistent_folder = local.persistent_folder
|
persistent_folder = local.persistent_folder
|
||||||
cron_schedule = local.trivy_cronjob_schedule
|
cron_schedule = local.trivy_cronjob_schedule
|
||||||
|
tag = "0.70.0"
|
||||||
|
|
||||||
smtp_host = local.scaleway_smtp_host
|
smtp_host = local.scaleway_smtp_host
|
||||||
smtp_port = local.scaleway_smtp_port
|
smtp_port = tostring(local.scaleway_smtp_port)
|
||||||
smtp_username = var.scaleway_project_id
|
smtp_username = var.scaleway_project_id
|
||||||
smtp_password = local.mail_app_api_secret_key
|
smtp_password = local.mail_app_api_secret_key
|
||||||
from_email = "trivy@${local.primary_domain}"
|
from_email = "trivy@${local.primary_domain}"
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
locals {
|
locals {
|
||||||
app_version = var.tag
|
app_version = var.tag
|
||||||
monitoring_folder = "${var.persistent_folder}/grafana"
|
monitoring_folder = "${var.persistent_folder}/grafana"
|
||||||
|
|
||||||
|
grafana_datasource_config = file("${path.module}/resources/default.yml")
|
||||||
|
grafana_dashboard_provider = file("${path.module}/resources/dashboard-provider.yml")
|
||||||
|
grafana_trivy_dashboard = file("${path.module}/resources/trivy-vulnerabilities-dashboard.json")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,36 @@
|
|||||||
|
resource "kubernetes_config_map" "grafana_datasources" {
|
||||||
|
metadata {
|
||||||
|
name = "grafana-datasources"
|
||||||
|
namespace = kubernetes_namespace.grafana.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"default.yml" = replace(local.grafana_datasource_config, "__PROMETHEUS_URL__", var.prometheus_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_config_map" "grafana_dashboard_provider" {
|
||||||
|
metadata {
|
||||||
|
name = "grafana-dashboard-provider"
|
||||||
|
namespace = kubernetes_namespace.grafana.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"dashboard-provider.yml" = local.grafana_dashboard_provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_config_map" "grafana_dashboards" {
|
||||||
|
metadata {
|
||||||
|
name = "grafana-dashboards"
|
||||||
|
namespace = kubernetes_namespace.grafana.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"trivy-vulnerabilities-dashboard.json" = local.grafana_trivy_dashboard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "kubernetes_deployment" "grafana" {
|
resource "kubernetes_deployment" "grafana" {
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
@@ -67,6 +100,21 @@ resource "kubernetes_deployment" "grafana" {
|
|||||||
name = "grafana-pv"
|
name = "grafana-pv"
|
||||||
mount_path = "/var/lib/grafana"
|
mount_path = "/var/lib/grafana"
|
||||||
}
|
}
|
||||||
|
volume_mount {
|
||||||
|
name = "grafana-datasources"
|
||||||
|
mount_path = "/etc/grafana/provisioning/datasources"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
volume_mount {
|
||||||
|
name = "grafana-dashboard-provider"
|
||||||
|
mount_path = "/etc/grafana/provisioning/dashboards"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
volume_mount {
|
||||||
|
name = "grafana-dashboards"
|
||||||
|
mount_path = "/var/lib/grafana/dashboards"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
volume {
|
volume {
|
||||||
name = "grafana-pv"
|
name = "grafana-pv"
|
||||||
@@ -74,6 +122,24 @@ resource "kubernetes_deployment" "grafana" {
|
|||||||
path = local.monitoring_folder
|
path = local.monitoring_folder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
volume {
|
||||||
|
name = "grafana-datasources"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.grafana_datasources.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
volume {
|
||||||
|
name = "grafana-dashboard-provider"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.grafana_dashboard_provider.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
volume {
|
||||||
|
name = "grafana-dashboards"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.grafana_dashboards.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,580 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 5,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Sessions",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "auth_sessions_active{}",
|
||||||
|
"legendFormat": "{{source}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Active sessions (by source)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum by (source, method) (rate(auth_sessions_total[5m]))",
|
||||||
|
"legendFormat": "{{source}} / {{method}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Auth sessions started (rate)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Users",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 9
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "auth_sessions_by_user_total{}",
|
||||||
|
"legendFormat": "{{username}}:{{source}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top users by session starts (last 5m)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 17
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Exporter metrics",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "rate(auth_exporter_parse_errors_total[5m])",
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Parse errors (rate)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "bezn5wieufi80d"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "rate(auth_exporter_user_labels_dropped_total[5m])",
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "User label drops (rate)",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": false,
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"tags": [
|
||||||
|
"security"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": "prometheus-dev-01",
|
||||||
|
"value": "bezn5wieufi80d"
|
||||||
|
},
|
||||||
|
"name": "datasource",
|
||||||
|
"options": [],
|
||||||
|
"query": "prometheus",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"type": "datasource"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Auth Exporter",
|
||||||
|
"uid": "7381c747-ecf6-4f2f-ac4b-551e56f24cf0",
|
||||||
|
"version": 5
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: trivy-vulnerabilities
|
||||||
|
orgId: 1
|
||||||
|
folder: Security
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
updateIntervalSeconds: 30
|
||||||
|
allowUiUpdates: true
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards
|
||||||
@@ -1,38 +1,9 @@
|
|||||||
global:
|
apiVersion: 1
|
||||||
scrape_interval: 5s
|
|
||||||
evaluation_interval: 5s
|
datasources:
|
||||||
rule_files:
|
- name: Prometheus
|
||||||
- /etc/prometheus/prometheus.rules
|
type: prometheus
|
||||||
scrape_configs:
|
access: proxy
|
||||||
- job_name: kubernetes-nodes-cadvisor
|
url: __PROMETHEUS_URL__
|
||||||
scrape_interval: 10s
|
isDefault: true
|
||||||
scrape_timeout: 10s
|
editable: true
|
||||||
scheme: https # remove if you want to scrape metrics on insecure port
|
|
||||||
tls_config:
|
|
||||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
|
||||||
insecure_skip_verify: true
|
|
||||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
|
||||||
kubernetes_sd_configs:
|
|
||||||
- role: node
|
|
||||||
relabel_configs:
|
|
||||||
- action: labelmap
|
|
||||||
regex: __meta_kubernetes_node_label_(.+)
|
|
||||||
# Only for Kubernetes ^1.7.3.
|
|
||||||
# See: https://github.com/prometheus/prometheus/issues/2916
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: kubernetes.default.svc:443
|
|
||||||
- source_labels: [__meta_kubernetes_node_name]
|
|
||||||
regex: (.+)
|
|
||||||
target_label: __metrics_path__
|
|
||||||
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
|
|
||||||
metric_relabel_configs:
|
|
||||||
- action: replace
|
|
||||||
source_labels: [id]
|
|
||||||
regex: '^/machine\.slice/machine-rkt\\x2d([^\\]+)\\.+/([^/]+)\.service$'
|
|
||||||
target_label: rkt_container_name
|
|
||||||
replacement: "${2}-${1}"
|
|
||||||
- action: replace
|
|
||||||
source_labels: [id]
|
|
||||||
regex: '^/system\.slice/(.+)\.service$'
|
|
||||||
target_label: systemd_service_name
|
|
||||||
replacement: "${1}"
|
|
||||||
|
|||||||
@@ -0,0 +1,551 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 8,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "text",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 5,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_uptime_seconds",
|
||||||
|
"instant": true,
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Device Uptime",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "bezn5wieufi80d"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 200,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "percentage",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 85
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 7,
|
||||||
|
"x": 5,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"displayMode": "lcd",
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": false
|
||||||
|
},
|
||||||
|
"maxVizHeight": 300,
|
||||||
|
"minVizHeight": 16,
|
||||||
|
"minVizWidth": 8,
|
||||||
|
"namePlacement": "auto",
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showUnfilled": true,
|
||||||
|
"sizing": "auto",
|
||||||
|
"valueMode": "color"
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_rx_mbps{device_id=~\"$device_id\", mac=~\"$mac\"}",
|
||||||
|
"legendFormat": "RX",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "bezn5wieufi80d"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_tx_mbps{device_id=~\"$device_id\", mac=~\"$mac\"}",
|
||||||
|
"hide": false,
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "TX",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RX Rate",
|
||||||
|
"type": "bargauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"reducers": []
|
||||||
|
},
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"showHeader": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_device_info_info",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"range": false,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Devolo Device Info",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "labelsToFields",
|
||||||
|
"options": {
|
||||||
|
"keepLabels": [
|
||||||
|
"firmware",
|
||||||
|
"mac",
|
||||||
|
"name",
|
||||||
|
"serial"
|
||||||
|
],
|
||||||
|
"mode": "columns"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"Time": true,
|
||||||
|
"Value": true,
|
||||||
|
"devolo_device_info_info": true
|
||||||
|
},
|
||||||
|
"includeByName": {},
|
||||||
|
"indexByName": {},
|
||||||
|
"renameByName": {
|
||||||
|
"firmware": "Firmware",
|
||||||
|
"mac": "MAC",
|
||||||
|
"name": "Name",
|
||||||
|
"serial": "Serial"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "continuous-RdYlGr"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "scheme",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineStyle": {
|
||||||
|
"fill": "solid"
|
||||||
|
},
|
||||||
|
"lineWidth": 3,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 200,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "hidden",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": false
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_rx_mbps{device_id=~\"$device_id\", mac=~\"$mac\"}",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "TX",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RX Throughput",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "continuous-RdYlGr",
|
||||||
|
"seriesBy": "max"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "scheme",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 3,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 200,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "hidden",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": false
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "devolo_tx_mbps{device_id=~\"$device_id\", mac=~\"$mac\"}",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "TX",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "TX Throughput",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": false,
|
||||||
|
"refresh": "10s",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"tags": [
|
||||||
|
"devolo",
|
||||||
|
"powerline",
|
||||||
|
"network"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": "Prometheus",
|
||||||
|
"value": "PBFA97CFB590B2093"
|
||||||
|
},
|
||||||
|
"label": "Prometheus",
|
||||||
|
"name": "DS_PROMETHEUS",
|
||||||
|
"options": [],
|
||||||
|
"query": "prometheus",
|
||||||
|
"refresh": 1,
|
||||||
|
"type": "datasource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allValue": ".*",
|
||||||
|
"current": {
|
||||||
|
"text": "All",
|
||||||
|
"value": "$__all"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"definition": "label_values(devolo_tx_mbps, device_id)",
|
||||||
|
"includeAll": true,
|
||||||
|
"label": "Device ID",
|
||||||
|
"multi": true,
|
||||||
|
"name": "device_id",
|
||||||
|
"options": [],
|
||||||
|
"query": {
|
||||||
|
"query": "label_values(devolo_tx_mbps, device_id)",
|
||||||
|
"refId": "StandardVariableQuery"
|
||||||
|
},
|
||||||
|
"refresh": 2,
|
||||||
|
"type": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allValue": ".*",
|
||||||
|
"current": {
|
||||||
|
"text": "All",
|
||||||
|
"value": "$__all"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"definition": "label_values(devolo_tx_mbps{device_id=~\"$device_id\"}, mac)",
|
||||||
|
"includeAll": true,
|
||||||
|
"label": "MAC",
|
||||||
|
"multi": true,
|
||||||
|
"name": "mac",
|
||||||
|
"options": [],
|
||||||
|
"query": {
|
||||||
|
"query": "label_values(devolo_tx_mbps{device_id=~\"$device_id\"}, mac)",
|
||||||
|
"refId": "StandardVariableQuery"
|
||||||
|
},
|
||||||
|
"refresh": 2,
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-24h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Devolo Powerline",
|
||||||
|
"uid": "devolo-powerline",
|
||||||
|
"version": 10
|
||||||
|
}
|
||||||
@@ -0,0 +1,652 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 7,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 3,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "count(disk_info{})-count(disk_healthy{})",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Unhealthy Disks",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 70
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 9,
|
||||||
|
"x": 3,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_smart_attr_normalized_value{name=\"Temperature_Celsius\",device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Temperature (Normalized)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "continuous-GrYlRd"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"fillOpacity": 70,
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineWidth": 0,
|
||||||
|
"spanNulls": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 11,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"alignValue": "left",
|
||||||
|
"legend": {
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"mergeValues": true,
|
||||||
|
"rowHeight": 0.9,
|
||||||
|
"showValue": "auto",
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_healthy{device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Disk Health State",
|
||||||
|
"type": "state-timeline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 23,
|
||||||
|
"x": 0,
|
||||||
|
"y": 7
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_reallocated_sector_count{device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Reallocated Sectors",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 23,
|
||||||
|
"x": 0,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_offline_uncorrectable{device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Offline Uncorrectable",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 23,
|
||||||
|
"x": 0,
|
||||||
|
"y": 21
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_smart_attr_raw_value{name=\"Power_On_Hours\",device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Power On Hours",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 23,
|
||||||
|
"x": 0,
|
||||||
|
"y": 27
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "disk_smart_attr_raw_value{name=~\"Media_Wearout_Indicator|Available_Reservd_Space\",device=~\"$device\"}",
|
||||||
|
"legendFormat": "{{device}} {{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Media / Wear Indicators",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": false,
|
||||||
|
"refresh": "",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"tags": [
|
||||||
|
"disks",
|
||||||
|
"smart"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": "All",
|
||||||
|
"value": "$__all"
|
||||||
|
},
|
||||||
|
"datasource": "$datasource",
|
||||||
|
"includeAll": true,
|
||||||
|
"multi": true,
|
||||||
|
"name": "device",
|
||||||
|
"options": [],
|
||||||
|
"query": "label_values(disk_healthy, device)",
|
||||||
|
"refresh": 1,
|
||||||
|
"type": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": "prometheus-dev-01",
|
||||||
|
"value": "bezn5wieufi80d"
|
||||||
|
},
|
||||||
|
"name": "datasource",
|
||||||
|
"options": [],
|
||||||
|
"query": "prometheus",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"type": "datasource"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Disk Health (smartctl)",
|
||||||
|
"uid": "disk-health-smartctl",
|
||||||
|
"version": 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,648 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "grafana"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"target": {
|
||||||
|
"limit": 100,
|
||||||
|
"matchAny": false,
|
||||||
|
"tags": [],
|
||||||
|
"type": "dashboard"
|
||||||
|
},
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 2,
|
||||||
|
"id": 3,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"reducers": []
|
||||||
|
},
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": ".*Time"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 206,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"showHeader": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "f2b_config_jail_max_retries{instance=~\"$instance\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "f2b_config_jail_ban_time{instance=~\"$instance\"}",
|
||||||
|
"format": "table",
|
||||||
|
"hide": false,
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "f2b_config_jail_find_time{instance=~\"$instance\"}",
|
||||||
|
"format": "table",
|
||||||
|
"hide": false,
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "F2B Config",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "merge",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "groupBy",
|
||||||
|
"options": {
|
||||||
|
"fields": {
|
||||||
|
"Value #A": {
|
||||||
|
"aggregations": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"operation": "aggregate"
|
||||||
|
},
|
||||||
|
"Value #B": {
|
||||||
|
"aggregations": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"operation": "aggregate"
|
||||||
|
},
|
||||||
|
"Value #C": {
|
||||||
|
"aggregations": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"operation": "aggregate"
|
||||||
|
},
|
||||||
|
"instance": {
|
||||||
|
"aggregations": [],
|
||||||
|
"operation": "groupby"
|
||||||
|
},
|
||||||
|
"jail": {
|
||||||
|
"aggregations": [],
|
||||||
|
"operation": "groupby"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {},
|
||||||
|
"indexByName": {},
|
||||||
|
"renameByName": {
|
||||||
|
"Value #A (lastNotNull)": "Max Retries",
|
||||||
|
"Value #B (lastNotNull)": "Ban Time",
|
||||||
|
"Value #C (lastNotNull)": "Find Time",
|
||||||
|
"jail": "Jail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transparent": true,
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": true,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 190,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "f2b_jail_failed_total{instance=~\"$instance\"}",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fail2Ban Failures (Total)",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": true,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 191,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "f2b_jail_banned_total{instance=~\"$instance\"}",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fail2Ban Bans (Total)",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": true,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 208,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "f2b_jail_failed_current{instance=~\"$instance\"}",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fail2Ban Failures (Current)",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": true,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 209,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "right",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "f2b_jail_banned_current{instance=~\"$instance\"}",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "{{jail}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fail2Ban Bans (Current)",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": false,
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"tags": [
|
||||||
|
"security"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": [
|
||||||
|
"fail2ban-exporter.prometheus.svc.cluster.local:9100"
|
||||||
|
],
|
||||||
|
"value": [
|
||||||
|
"fail2ban-exporter.prometheus.svc.cluster.local:9100"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "aezs0pa1i5xq8f"
|
||||||
|
},
|
||||||
|
"definition": "f2b_up",
|
||||||
|
"description": "Select which instance(s) to show",
|
||||||
|
"includeAll": false,
|
||||||
|
"label": "Instance",
|
||||||
|
"multi": true,
|
||||||
|
"name": "instance",
|
||||||
|
"options": [],
|
||||||
|
"query": {
|
||||||
|
"query": "f2b_up",
|
||||||
|
"refId": "StandardVariableQuery"
|
||||||
|
},
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "/.*instance=\"([^\"]+)\"/",
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Fail2Ban Exporter",
|
||||||
|
"uid": "cTkH9AT7z",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,3 +20,9 @@ variable "persistent_folder" {
|
|||||||
description = "The path to the persistent folder"
|
description = "The path to the persistent folder"
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "prometheus_url" {
|
||||||
|
description = "Prometheus service URL used by Grafana datasource provisioning"
|
||||||
|
type = string
|
||||||
|
default = "http://prometheus.prometheus.svc.cluster.local:9090"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
locals {
|
locals {
|
||||||
app_version = var.tag
|
app_version = var.tag
|
||||||
prometheus_folder = "${var.persistent_folder}/prometheus"
|
prometheus_folder = "${var.persistent_folder}/prometheus"
|
||||||
|
pushgateway_folder = "${var.persistent_folder}/pushgateway"
|
||||||
alertmanager_folder = "${var.persistent_folder}/alertmanager"
|
alertmanager_folder = "${var.persistent_folder}/alertmanager"
|
||||||
|
|
||||||
prometheus_default_config = yamldecode(file("${path.module}/resources/prometheus.yml"))
|
prometheus_default_config = yamldecode(file("${path.module}/resources/prometheus.yml"))
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
resource "kubernetes_deployment" "pushgateway" {
|
||||||
|
metadata {
|
||||||
|
name = "pushgateway"
|
||||||
|
namespace = kubernetes_namespace.prometheus.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "pushgateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
replicas = 1
|
||||||
|
strategy {
|
||||||
|
type = "Recreate"
|
||||||
|
}
|
||||||
|
|
||||||
|
selector {
|
||||||
|
match_labels = {
|
||||||
|
app = "pushgateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = "pushgateway"
|
||||||
|
}
|
||||||
|
annotations = {
|
||||||
|
"prometheus.io/scrape" = "true"
|
||||||
|
"prometheus.io/port" = "9091"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
automount_service_account_token = false
|
||||||
|
|
||||||
|
security_context {
|
||||||
|
run_as_non_root = true
|
||||||
|
run_as_user = 65534
|
||||||
|
run_as_group = 65534
|
||||||
|
seccomp_profile {
|
||||||
|
type = "RuntimeDefault"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container {
|
||||||
|
name = "pushgateway"
|
||||||
|
image = "prom/pushgateway:${local.app_version}"
|
||||||
|
args = ["--persistence.file=/pushgateway/metrics"]
|
||||||
|
|
||||||
|
port {
|
||||||
|
name = "http"
|
||||||
|
container_port = 9091
|
||||||
|
}
|
||||||
|
|
||||||
|
security_context {
|
||||||
|
run_as_non_root = true
|
||||||
|
run_as_user = 65534
|
||||||
|
run_as_group = 65534
|
||||||
|
allow_privilege_escalation = false
|
||||||
|
privileged = false
|
||||||
|
read_only_root_filesystem = true
|
||||||
|
capabilities {
|
||||||
|
drop = ["ALL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
name = "pushgateway-storage-volume"
|
||||||
|
mount_path = "/pushgateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "pushgateway-storage-volume"
|
||||||
|
host_path {
|
||||||
|
path = local.pushgateway_folder
|
||||||
|
type = "DirectoryOrCreate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_service" "pushgateway" {
|
||||||
|
metadata {
|
||||||
|
name = "pushgateway"
|
||||||
|
namespace = kubernetes_namespace.prometheus.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
selector = {
|
||||||
|
app = "pushgateway"
|
||||||
|
}
|
||||||
|
type = "ClusterIP"
|
||||||
|
|
||||||
|
port {
|
||||||
|
port = 9091
|
||||||
|
protocol = "TCP"
|
||||||
|
target_port = "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,10 @@ scrape_configs:
|
|||||||
regex: '^/system\.slice/(.+)\.service$'
|
regex: '^/system\.slice/(.+)\.service$'
|
||||||
target_label: systemd_service_name
|
target_label: systemd_service_name
|
||||||
replacement: "${1}"
|
replacement: "${1}"
|
||||||
|
- job_name: pushgateway
|
||||||
|
honor_labels: true
|
||||||
|
static_configs:
|
||||||
|
- targets: ["pushgateway.prometheus.svc.cluster.local:9091"]
|
||||||
alerting:
|
alerting:
|
||||||
alertmanagers:
|
alertmanagers:
|
||||||
- static_configs:
|
- static_configs:
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ resource "kubernetes_cron_job_v1" "trivy" {
|
|||||||
backoff_limit = 1
|
backoff_limit = 1
|
||||||
parallelism = 1
|
parallelism = 1
|
||||||
completions = 1
|
completions = 1
|
||||||
active_deadline_seconds = 250
|
active_deadline_seconds = 600
|
||||||
|
|
||||||
template {
|
template {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -126,9 +126,9 @@ resource "kubernetes_cron_job_v1" "trivy" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
container {
|
container {
|
||||||
name = "send-report"
|
name = "send-report"
|
||||||
image = "python:${local.python_version}"
|
image = "python:${local.python_version}"
|
||||||
args = ["/scripts/send_report.py"]
|
command = ["python3", "/scripts/send_report.py"]
|
||||||
env {
|
env {
|
||||||
name = "SMTP_HOST"
|
name = "SMTP_HOST"
|
||||||
value = var.smtp_host
|
value = var.smtp_host
|
||||||
@@ -163,6 +163,18 @@ resource "kubernetes_cron_job_v1" "trivy" {
|
|||||||
name = "EMAIL_TO"
|
name = "EMAIL_TO"
|
||||||
value = var.to_email
|
value = var.to_email
|
||||||
}
|
}
|
||||||
|
env {
|
||||||
|
name = "PUSHGATEWAY_URL"
|
||||||
|
value = var.pushgateway_url
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "REPORT_PATH"
|
||||||
|
value = "/tmp/report.html"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "TRIVY_OUTPUT_FILE"
|
||||||
|
value = "/tmp/trivy.json"
|
||||||
|
}
|
||||||
security_context {
|
security_context {
|
||||||
run_as_non_root = true
|
run_as_non_root = true
|
||||||
run_as_user = 1000
|
run_as_user = 1000
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,9 @@ trivy plugin install scan2html
|
|||||||
trivy k8s --report=${REPORT_TYPE:-all} \
|
trivy k8s --report=${REPORT_TYPE:-all} \
|
||||||
--exit-code=0 \
|
--exit-code=0 \
|
||||||
--severity=${LEVELS:-CRITICAL,HIGH,MEDIUM} \
|
--severity=${LEVELS:-CRITICAL,HIGH,MEDIUM} \
|
||||||
-o tmp/trivy.json \
|
--scanners ${SCANNERS:-vuln,misconfig} \
|
||||||
|
-o /tmp/trivy.json \
|
||||||
--format json \
|
--format json \
|
||||||
--disable-node-collector \
|
--disable-node-collector \
|
||||||
--parallel ${PARALLEL:-5}
|
--parallel ${PARALLEL:-5}
|
||||||
trivy scan2html generate --scan2html-flags --output tmp/report.html --from tmp/trivy.json
|
trivy scan2html generate --scan2html-flags --output /tmp/report.html --from /tmp/trivy.json
|
||||||
@@ -3,6 +3,455 @@ import os
|
|||||||
import time
|
import time
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
# Imports and Configuration
|
||||||
|
@dataclass
|
||||||
|
class Misconfiguration:
|
||||||
|
"""Represents a Trivy misconfiguration finding."""
|
||||||
|
status: str
|
||||||
|
message: str
|
||||||
|
severity: str
|
||||||
|
check_id: str = ""
|
||||||
|
title: str = ""
|
||||||
|
check_namespace: str = ""
|
||||||
|
primary_url: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Vulnerability:
|
||||||
|
"""Represents a Trivy vulnerability finding."""
|
||||||
|
vulnerability_id: str
|
||||||
|
package_name: str
|
||||||
|
severity: str
|
||||||
|
target: str = ""
|
||||||
|
vuln_type: str = ""
|
||||||
|
class_name: str = ""
|
||||||
|
installed_version: str = ""
|
||||||
|
fixed_version: str = ""
|
||||||
|
title: str = ""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Resource:
|
||||||
|
"""Represents a Trivy scan resource."""
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
kind: Optional[str] = None
|
||||||
|
namespace: Optional[str] = None
|
||||||
|
image_digests: List[str] = field(default_factory=list)
|
||||||
|
metadata: List[dict] = field(default_factory=list)
|
||||||
|
misconf_summary: dict = field(default_factory=dict)
|
||||||
|
misconfigurations: List[Misconfiguration] = field(default_factory=list)
|
||||||
|
vulnerabilities: List[Vulnerability] = field(default_factory=list)
|
||||||
|
vulnerability_summary: dict = field(default_factory=dict)
|
||||||
|
successes_count: int = 0
|
||||||
|
failures_count: int = 0
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ParseResult:
|
||||||
|
"""Parsed Trivy scan result."""
|
||||||
|
cluster_name: Optional[str] = None
|
||||||
|
resources: List[Resource] = field(default_factory=list)
|
||||||
|
start_time: Optional[datetime] = None
|
||||||
|
end_time: Optional[datetime] = None
|
||||||
|
duration: Optional[float] = None
|
||||||
|
image: Optional[str] = None
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PUSHGATEWAY_URL = os.environ.get("PUSHGATEWAY_URL", "http://pushgateway:9091/metrics")
|
||||||
|
PUSHGATEWAY_NAMESPACE = os.environ.get("PUSHGATEWAY_NAMESPACE", "trivy")
|
||||||
|
PUSHGATEWAY_JOB = os.environ.get("PUSHGATEWAY_JOB", "trivy-metrics")
|
||||||
|
METRICS_PREFIX = "trivy"
|
||||||
|
REQUEST_TIMEOUT = 30
|
||||||
|
REPORT_PATH = os.environ.get("REPORT_PATH", "/tmp/report.html")
|
||||||
|
TRIVY_OUTPUT_FILE = os.environ.get("TRIVY_OUTPUT_FILE", "/tmp/trivy.json")
|
||||||
|
WAIT_TIMEOUT = int(os.environ.get("WAIT_TIMEOUT", "200"))
|
||||||
|
SLEEP_INTERVAL = int(os.environ.get("SLEEP_INTERVAL", "5") )
|
||||||
|
|
||||||
|
# Metric definitions
|
||||||
|
METRICS = {
|
||||||
|
"vulnerabilities_found": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Total number of vulnerabilities found",
|
||||||
|
"labels": ["severity", "type", "package", "vulnerability_id"]
|
||||||
|
},
|
||||||
|
"vulnerabilities_by_severity": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Count of vulnerabilities grouped by severity",
|
||||||
|
"labels": ["severity"]
|
||||||
|
},
|
||||||
|
"vulnerabilities_by_type": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Count of vulnerabilities grouped by type",
|
||||||
|
"labels": ["type"]
|
||||||
|
},
|
||||||
|
"scans_completed": {
|
||||||
|
"type": "counter",
|
||||||
|
"description": "Total number of scans completed",
|
||||||
|
"labels": ["status"]
|
||||||
|
},
|
||||||
|
"scan_duration_seconds": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Duration of the last scan in seconds",
|
||||||
|
"labels": ["status"]
|
||||||
|
},
|
||||||
|
"image_scanned": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Image that was scanned",
|
||||||
|
"labels": ["image"]
|
||||||
|
},
|
||||||
|
"scan_timestamp": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Timestamp of the scan",
|
||||||
|
"labels": []
|
||||||
|
},
|
||||||
|
"severities_found": {
|
||||||
|
"type": "gauge",
|
||||||
|
"description": "Severities found in the scan",
|
||||||
|
"labels": ["severity"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_trivy_output(output: str) -> ParseResult:
|
||||||
|
"""Parse Trivy scan output and extract metrics."""
|
||||||
|
# Try to parse JSON output if available
|
||||||
|
if output.strip().startswith("{"):
|
||||||
|
try:
|
||||||
|
data = json.loads(output)
|
||||||
|
# Check if it's Kubernetes/container scan JSON
|
||||||
|
if "Resources" in data or "ClusterName" in data:
|
||||||
|
result = ParseResult(cluster_name=data.get("ClusterName"))
|
||||||
|
|
||||||
|
for resource_data in data.get("Resources", []):
|
||||||
|
misconfigurations = []
|
||||||
|
vulnerabilities = []
|
||||||
|
misconf_summary: dict = {}
|
||||||
|
vulnerability_summary: dict = {}
|
||||||
|
resource_type = "kubernetes"
|
||||||
|
|
||||||
|
for res in resource_data.get("Results", []):
|
||||||
|
resource_type = res.get("Type") or resource_type
|
||||||
|
for k, v in res.get("MisconfSummary", {}).items():
|
||||||
|
misconf_summary[k] = misconf_summary.get(k, 0) + v
|
||||||
|
for mc in res.get("Misconfigurations", []):
|
||||||
|
misconfigurations.append(Misconfiguration(
|
||||||
|
status=mc.get("Status", ""),
|
||||||
|
message=mc.get("Message", ""),
|
||||||
|
severity=mc.get("Severity", ""),
|
||||||
|
check_id=mc.get("ID", ""),
|
||||||
|
title=mc.get("Title", ""),
|
||||||
|
check_namespace=mc.get("Namespace", ""),
|
||||||
|
primary_url=mc.get("PrimaryURL", ""),
|
||||||
|
))
|
||||||
|
|
||||||
|
for v in res.get("Vulnerabilities", []):
|
||||||
|
severity = v.get("Severity", "UNKNOWN")
|
||||||
|
vulnerability_summary[severity] = vulnerability_summary.get(severity, 0) + 1
|
||||||
|
vulnerabilities.append(Vulnerability(
|
||||||
|
vulnerability_id=v.get("VulnerabilityID", ""),
|
||||||
|
package_name=v.get("PkgName", ""),
|
||||||
|
severity=severity,
|
||||||
|
target=res.get("Target", ""),
|
||||||
|
vuln_type=res.get("Type", ""),
|
||||||
|
class_name=res.get("Class", ""),
|
||||||
|
installed_version=v.get("InstalledVersion", ""),
|
||||||
|
fixed_version=v.get("FixedVersion", ""),
|
||||||
|
title=v.get("Title", ""),
|
||||||
|
))
|
||||||
|
|
||||||
|
resource = Resource(
|
||||||
|
type=resource_type,
|
||||||
|
name=resource_data.get("Name", ""),
|
||||||
|
kind=resource_data.get("Kind"),
|
||||||
|
namespace=resource_data.get("Namespace"),
|
||||||
|
metadata=resource_data.get("Metadata", []),
|
||||||
|
misconf_summary=misconf_summary,
|
||||||
|
misconfigurations=misconfigurations,
|
||||||
|
vulnerabilities=vulnerabilities,
|
||||||
|
vulnerability_summary=vulnerability_summary,
|
||||||
|
successes_count=misconf_summary.get("Successes", 0),
|
||||||
|
failures_count=misconf_summary.get("Failures", 0),
|
||||||
|
)
|
||||||
|
result.resources.append(resource)
|
||||||
|
return result
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Parse text output for image scanning
|
||||||
|
metrics_data = {
|
||||||
|
"vulnerabilities": [],
|
||||||
|
"image": None,
|
||||||
|
"start_time": None,
|
||||||
|
"end_time": None,
|
||||||
|
"summary": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract image name
|
||||||
|
image_match = re.search(r'Scanning image: (.+)', output)
|
||||||
|
if image_match:
|
||||||
|
metrics_data["image"] = image_match.group(1).strip()
|
||||||
|
|
||||||
|
# Extract scan duration
|
||||||
|
duration_match = re.search(r'Spent.*? (\d+(?:\.\d+)?)', output)
|
||||||
|
if duration_match:
|
||||||
|
metrics_data["scan_duration"] = float(duration_match.group(1))
|
||||||
|
|
||||||
|
# Extract severity counts
|
||||||
|
severity_patterns = {
|
||||||
|
"CRITICAL": r'Critical:.*?(\d+)',
|
||||||
|
"HIGH": r'High:.*?(\d+)',
|
||||||
|
"MEDIUM": r'Medium:.*?(\d+)',
|
||||||
|
"LOW": r'Low:.*?(\d+)',
|
||||||
|
"UNKNOWN": r'Unknown:.*?(\d+)',
|
||||||
|
"STYLE": r'Style:.*?(\d+)',
|
||||||
|
"NOT_SET": r'Not Set:.*?(\d+)'
|
||||||
|
}
|
||||||
|
|
||||||
|
severity_counts = {}
|
||||||
|
for severity, pattern in severity_patterns.items():
|
||||||
|
match = re.search(pattern, output)
|
||||||
|
if match:
|
||||||
|
severity_counts[severity] = int(match.group(1))
|
||||||
|
|
||||||
|
metrics_data["summary"] = severity_counts
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
# Match vulnerability lines
|
||||||
|
vuln_match = re.search(r'\[.*?\].*?CVE-?\d+\.\d+-\d+', line)
|
||||||
|
if vuln_match:
|
||||||
|
# Parse vulnerability details
|
||||||
|
vuln_data = {
|
||||||
|
"cve": None,
|
||||||
|
"package": None,
|
||||||
|
"type": None,
|
||||||
|
"severity": None,
|
||||||
|
"fix_version": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract CVE
|
||||||
|
cve_match = re.search(r'CVE-?\d+\.\d+-\d+', line)
|
||||||
|
if cve_match:
|
||||||
|
vuln_data["cve"] = cve_match.group()
|
||||||
|
|
||||||
|
# Extract package name
|
||||||
|
pkg_match = re.search(r'package: (\S+)', line)
|
||||||
|
if pkg_match:
|
||||||
|
vuln_data["package"] = pkg_match.group(1)
|
||||||
|
|
||||||
|
# Extract vulnerability ID
|
||||||
|
vuln_id_match = re.search(r'ID: ([\w\-]+)', line)
|
||||||
|
if vuln_id_match:
|
||||||
|
vuln_data["vulnerability_id"] = vuln_id_match.group(1)
|
||||||
|
|
||||||
|
# Extract severity
|
||||||
|
severity_match = re.search(r'severity: (\S+)', line)
|
||||||
|
if severity_match:
|
||||||
|
vuln_data["severity"] = severity_match.group(1)
|
||||||
|
|
||||||
|
if any(vuln_data.values()):
|
||||||
|
metrics_data["vulnerabilities"].append(vuln_data)
|
||||||
|
|
||||||
|
# Convert to ParseResult for backward compatibility
|
||||||
|
result = ParseResult()
|
||||||
|
result.image = metrics_data.get("image")
|
||||||
|
result.start_time = metrics_data.get("start_time")
|
||||||
|
result.end_time = metrics_data.get("end_time")
|
||||||
|
result.duration = metrics_data.get("scan_duration")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def generate_prometheus_metrics(scan_data: Dict[str, Any], scan_duration: Optional[float] = None) -> str:
|
||||||
|
"""Generate Prometheus metrics format from scan data."""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# Scan metadata
|
||||||
|
timestamp = datetime.utcnow().isoformat() + "Z"
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_scan_timestamp Timestamp of the scan')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_scan_timestamp gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_scan_timestamp 1')
|
||||||
|
|
||||||
|
# Image scanned
|
||||||
|
if scan_data.get("image"):
|
||||||
|
image = scan_data["image"].replace(":", "_").replace("/", "_")
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_image_scanned Image that was scanned')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_image_scanned gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_image_scanned{{image="{image}"}} 1')
|
||||||
|
|
||||||
|
# Duration
|
||||||
|
if scan_duration is not None:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_scan_duration_seconds Duration of the last scan in seconds')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_scan_duration_seconds gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_scan_duration_seconds{{status="success"}} {scan_duration}')
|
||||||
|
|
||||||
|
# Vulnerabilities by severity
|
||||||
|
summary = scan_data.get("summary", {})
|
||||||
|
for severity, count in summary.items():
|
||||||
|
if count > 0:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_vulnerabilities_by_severity Count of vulnerabilities grouped by severity')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_vulnerabilities_by_severity gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_vulnerabilities_by_severity{{severity="{severity}"}} {count}')
|
||||||
|
|
||||||
|
# Individual vulnerabilities
|
||||||
|
vulnerabilities = scan_data.get("vulnerabilities", [])
|
||||||
|
for vuln in vulnerabilities:
|
||||||
|
cve = vuln.get("cve", "unknown")
|
||||||
|
package = vuln.get("package", "unknown")
|
||||||
|
vuln_id = vuln.get("vulnerability_id", cve)
|
||||||
|
severity = vuln.get("severity", "unknown")
|
||||||
|
|
||||||
|
# Truncate CVE to fit in label
|
||||||
|
cve_short = cve[-12:] if len(cve) > 12 else cve
|
||||||
|
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_vulnerabilities_found Total number of vulnerabilities found')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_vulnerabilities_found gauge')
|
||||||
|
labels = {
|
||||||
|
"severity": severity,
|
||||||
|
"type": vuln.get("type", "unknown"),
|
||||||
|
"package": package,
|
||||||
|
"vulnerability_id": cve_short
|
||||||
|
}
|
||||||
|
# Sanitize labels for Prometheus
|
||||||
|
safe_labels = {k: str(v).replace('"', "").replace("'", "")[:20] for k, v in labels.items()}
|
||||||
|
label_string = ",".join(f'{k}="{v}"' for k, v in safe_labels.items())
|
||||||
|
lines.append(f'{METRICS_PREFIX}_vulnerabilities_found{{{label_string}}} 1')
|
||||||
|
|
||||||
|
# Clean up duplicate HELP lines
|
||||||
|
cleaned_lines = []
|
||||||
|
last_help = None
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('# HELP'):
|
||||||
|
if last_help and line.split()[2] == last_help.split()[2]:
|
||||||
|
continue # Skip duplicate HELP
|
||||||
|
last_help = line.split()[2]
|
||||||
|
cleaned_lines.append(line)
|
||||||
|
|
||||||
|
return '\n'.join(cleaned_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def push_metrics_to_pushgateway(metrics: str):
|
||||||
|
"""Push metrics to PushGateway."""
|
||||||
|
try:
|
||||||
|
validated_metrics = sanitize_prometheus_metrics(metrics)
|
||||||
|
if not validated_metrics:
|
||||||
|
print("✗ No valid Prometheus metrics remained after sanitization")
|
||||||
|
return False
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
PUSHGATEWAY_URL,
|
||||||
|
data=(validated_metrics + "\n").encode("utf-8"),
|
||||||
|
headers={"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as response:
|
||||||
|
status_code = response.getcode()
|
||||||
|
response_body = response.read().decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
if status_code == 200:
|
||||||
|
print(f"✓ Successfully pushed {len(validated_metrics.split(chr(10)))} lines of metrics to PushGateway")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ Failed to push metrics to PushGateway. Status code: {status_code}")
|
||||||
|
print(f"Response: {response_body}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode("utf-8", errors="replace")
|
||||||
|
print(f"✗ Error pushing metrics to PushGateway: HTTP {e.code} {e.reason}")
|
||||||
|
print(f"Response: {body}")
|
||||||
|
return False
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
print(f"✗ Error pushing metrics to PushGateway: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _has_balanced_label_quotes(label_block: str) -> bool:
|
||||||
|
"""Return True when all double quotes in a label block are balanced."""
|
||||||
|
in_quotes = False
|
||||||
|
escaped = False
|
||||||
|
for ch in label_block:
|
||||||
|
if escaped:
|
||||||
|
escaped = False
|
||||||
|
continue
|
||||||
|
if ch == "\\":
|
||||||
|
escaped = True
|
||||||
|
continue
|
||||||
|
if ch == '"':
|
||||||
|
in_quotes = not in_quotes
|
||||||
|
return not in_quotes
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_prometheus_metrics(metrics: str) -> str:
|
||||||
|
"""Drop malformed metric lines so one bad sample does not break the whole push."""
|
||||||
|
sanitized_lines: List[str] = []
|
||||||
|
dropped_lines = 0
|
||||||
|
merged_duplicates = 0
|
||||||
|
series_totals: Dict[str, float] = {}
|
||||||
|
series_order: List[str] = []
|
||||||
|
|
||||||
|
def _format_value(value: float) -> str:
|
||||||
|
return str(int(value)) if value.is_integer() else format(value, "g")
|
||||||
|
|
||||||
|
for idx, raw_line in enumerate(metrics.splitlines(), start=1):
|
||||||
|
line = raw_line.rstrip("\r")
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line.startswith("#"):
|
||||||
|
sanitized_lines.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "{" in line:
|
||||||
|
open_brace = line.find("{")
|
||||||
|
close_brace = line.find("}", open_brace + 1)
|
||||||
|
if close_brace == -1:
|
||||||
|
print(f"⚠ Dropping malformed metric line {idx}: missing closing brace")
|
||||||
|
dropped_lines += 1
|
||||||
|
continue
|
||||||
|
label_block = line[open_brace + 1:close_brace]
|
||||||
|
if not _has_balanced_label_quotes(label_block):
|
||||||
|
print(f"⚠ Dropping malformed metric line {idx}: unbalanced label quotes")
|
||||||
|
dropped_lines += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
parts = line.rsplit(" ", 1)
|
||||||
|
if len(parts) != 2:
|
||||||
|
print(f"⚠ Dropping malformed metric line {idx}: missing value")
|
||||||
|
dropped_lines += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
series_id, value_raw = parts[0], parts[1].strip()
|
||||||
|
try:
|
||||||
|
value = float(value_raw)
|
||||||
|
except ValueError:
|
||||||
|
print(f"⚠ Dropping malformed metric line {idx}: invalid numeric value '{value_raw}'")
|
||||||
|
dropped_lines += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if series_id in series_totals:
|
||||||
|
merged_duplicates += 1
|
||||||
|
series_totals[series_id] += value
|
||||||
|
else:
|
||||||
|
series_order.append(series_id)
|
||||||
|
series_totals[series_id] = value
|
||||||
|
|
||||||
|
if dropped_lines:
|
||||||
|
print(f"⚠ Dropped {dropped_lines} malformed metric line(s) before push")
|
||||||
|
for series_id in series_order:
|
||||||
|
sanitized_lines.append(f"{series_id} {_format_value(series_totals[series_id])}")
|
||||||
|
|
||||||
|
if merged_duplicates:
|
||||||
|
print(f"⚠ Merged {merged_duplicates} duplicate metric line(s) before push")
|
||||||
|
|
||||||
|
return "\n".join(sanitized_lines)
|
||||||
|
|
||||||
|
|
||||||
def dispatch_email(report_path, subject):
|
def dispatch_email(report_path, subject):
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
@@ -26,38 +475,253 @@ def dispatch_email(report_path, subject):
|
|||||||
smtp_user = os.environ.get("SMTP_USER", "user")
|
smtp_user = os.environ.get("SMTP_USER", "user")
|
||||||
smtp_pass = os.environ.get("SMTP_PASS", "pass")
|
smtp_pass = os.environ.get("SMTP_PASS", "pass")
|
||||||
|
|
||||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
if smtp_port == 465:
|
||||||
if smtp_port == 465:
|
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
|
||||||
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
|
else:
|
||||||
else:
|
server = smtplib.SMTP(smtp_host, smtp_port)
|
||||||
server = smtplib.SMTP(smtp_host, smtp_port)
|
server.starttls()
|
||||||
if smtp_port != 465:
|
try:
|
||||||
server.starttls()
|
server.login(smtp_user, smtp_pass)
|
||||||
try:
|
except smtplib.SMTPAuthenticationError:
|
||||||
server.login(smtp_user, smtp_pass)
|
print("SMTP Authentication Error: Check your SMTP credentials.")
|
||||||
except smtplib.SMTPAuthenticationError:
|
server.quit()
|
||||||
print("SMTP Authentication Error: Check your SMTP credentials.")
|
return
|
||||||
return
|
server.send_message(msg)
|
||||||
server.send_message(msg)
|
server.quit()
|
||||||
print("Report sent.")
|
print("Report sent.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
report_path = os.environ.get("REPORT_PATH", "/tmp/report.html")
|
|
||||||
wait_timeout = int(os.environ.get("WAIT_TIMEOUT", "200"))
|
|
||||||
sleep_interval = int(os.environ.get("SLEEP_INTERVAL", "5") )
|
|
||||||
|
|
||||||
print("Trivy report path:", report_path)
|
def generate_prometheus_metrics_from_parse_result(scan_result: ParseResult) -> str:
|
||||||
|
"""Generate Prometheus metrics from ParseResult object."""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_cluster_scan_timestamp Timestamp of the cluster scan')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_cluster_scan_timestamp gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_cluster_scan_timestamp 1')
|
||||||
|
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_cluster_name Name of the cluster')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_cluster_name gauge')
|
||||||
|
def _safe_label(value: str) -> str:
|
||||||
|
# Prometheus label values cannot contain unescaped control chars/newlines.
|
||||||
|
cleaned = re.sub(r"[\x00-\x1F\x7F]+", " ", value)
|
||||||
|
return cleaned.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
|
|
||||||
|
def _safe_truncated_label(value: str, max_len: int) -> str:
|
||||||
|
# Truncate before escaping so we never cut through an escape sequence.
|
||||||
|
raw_truncated = re.sub(r"[\x00-\x1F\x7F]+", " ", value)[:max_len]
|
||||||
|
return _safe_label(raw_truncated)
|
||||||
|
|
||||||
|
safe_cluster_name = _safe_label(scan_result.cluster_name or "")
|
||||||
|
lines.append(f'{METRICS_PREFIX}_cluster_name{{cluster_name="{safe_cluster_name}"}} 1')
|
||||||
|
|
||||||
|
resource_type_header_written = False
|
||||||
|
image_digest_header_written = False
|
||||||
|
successes_header_written = False
|
||||||
|
failures_header_written = False
|
||||||
|
misconfig_header_written = False
|
||||||
|
vulnerability_total_header_written = False
|
||||||
|
vulnerability_severity_header_written = False
|
||||||
|
vulnerability_header_written = False
|
||||||
|
|
||||||
|
for resource in scan_result.resources:
|
||||||
|
rtype = _safe_label(resource.type or "")
|
||||||
|
rname = _safe_label(resource.name or "")
|
||||||
|
rkind = _safe_label(resource.kind or "")
|
||||||
|
rns = _safe_label(resource.namespace or "")
|
||||||
|
resource_labels = f'type="{rtype}",kind="{rkind}",name="{rname}",namespace="{rns}"'
|
||||||
|
|
||||||
|
if not resource_type_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_resource_type Type of resource')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_resource_type gauge')
|
||||||
|
resource_type_header_written = True
|
||||||
|
lines.append(f'{METRICS_PREFIX}_resource_type{{{resource_labels}}} 1')
|
||||||
|
|
||||||
|
if resource.image_digests:
|
||||||
|
if not image_digest_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_image_digest Image digest')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_image_digest gauge')
|
||||||
|
image_digest_header_written = True
|
||||||
|
for digest in resource.image_digests:
|
||||||
|
safe_digest = _safe_truncated_label(digest, 64)
|
||||||
|
lines.append(f'{METRICS_PREFIX}_image_digest{{{resource_labels},digest="{safe_digest}"}} 1')
|
||||||
|
|
||||||
|
if resource.misconf_summary:
|
||||||
|
summary = resource.misconf_summary
|
||||||
|
successes = summary.get('Successes', 0)
|
||||||
|
failures = summary.get('Failures', 0)
|
||||||
|
|
||||||
|
if not successes_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_misconfiguration_successes Successful checks')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_misconfiguration_successes gauge')
|
||||||
|
successes_header_written = True
|
||||||
|
lines.append(f'{METRICS_PREFIX}_misconfiguration_successes{{{resource_labels}}} {successes}')
|
||||||
|
|
||||||
|
if not failures_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_misconfiguration_failures Failed checks')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_misconfiguration_failures gauge')
|
||||||
|
failures_header_written = True
|
||||||
|
lines.append(f'{METRICS_PREFIX}_misconfiguration_failures{{{resource_labels}}} {failures}')
|
||||||
|
|
||||||
|
misconfig_counts = {}
|
||||||
|
for m in resource.misconfigurations:
|
||||||
|
key = (
|
||||||
|
_safe_label(m.severity or ""),
|
||||||
|
_safe_label(m.status or ""),
|
||||||
|
_safe_truncated_label(m.check_id or "", 32),
|
||||||
|
_safe_truncated_label(m.check_namespace or "", 64),
|
||||||
|
_safe_truncated_label(m.title or "", 100),
|
||||||
|
_safe_truncated_label(m.primary_url or "", 200),
|
||||||
|
)
|
||||||
|
misconfig_counts[key] = misconfig_counts.get(key, 0) + 1
|
||||||
|
|
||||||
|
for (safe_severity, safe_status, safe_check_id, safe_check_namespace, safe_title, safe_primary_url), count in misconfig_counts.items():
|
||||||
|
if not misconfig_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_misconfiguration Misconfiguration finding')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_misconfiguration gauge')
|
||||||
|
misconfig_header_written = True
|
||||||
|
lines.append(
|
||||||
|
f'{METRICS_PREFIX}_misconfiguration{{{resource_labels},'
|
||||||
|
f'severity="{safe_severity}",status="{safe_status}",'
|
||||||
|
f'check_id="{safe_check_id}",check_namespace="{safe_check_namespace}",title="{safe_title}",primary_url="{safe_primary_url}"}} {count}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not vulnerability_total_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_vulnerability_total Total vulnerabilities for resource')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_vulnerability_total gauge')
|
||||||
|
vulnerability_total_header_written = True
|
||||||
|
lines.append(f'{METRICS_PREFIX}_vulnerability_total{{{resource_labels}}} {len(resource.vulnerabilities)}')
|
||||||
|
|
||||||
|
for severity, count in resource.vulnerability_summary.items():
|
||||||
|
safe_severity = _safe_label(severity)
|
||||||
|
if not vulnerability_severity_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_vulnerability_severity_count Vulnerabilities grouped by severity for resource')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_vulnerability_severity_count gauge')
|
||||||
|
vulnerability_severity_header_written = True
|
||||||
|
lines.append(
|
||||||
|
f'{METRICS_PREFIX}_vulnerability_severity_count{{{resource_labels},severity="{safe_severity}"}} {count}'
|
||||||
|
)
|
||||||
|
|
||||||
|
vulnerability_counts = {}
|
||||||
|
for v in resource.vulnerabilities:
|
||||||
|
key = (
|
||||||
|
_safe_label(v.severity or ""),
|
||||||
|
_safe_truncated_label(v.vulnerability_id or "", 32),
|
||||||
|
_safe_truncated_label(v.package_name or "", 64),
|
||||||
|
_safe_truncated_label(v.target or "", 96),
|
||||||
|
_safe_truncated_label(v.vuln_type or "", 24),
|
||||||
|
_safe_truncated_label(v.class_name or "", 24),
|
||||||
|
)
|
||||||
|
vulnerability_counts[key] = vulnerability_counts.get(key, 0) + 1
|
||||||
|
|
||||||
|
for (safe_severity, safe_vuln_id, safe_package, safe_target, safe_vuln_type, safe_class_name), count in vulnerability_counts.items():
|
||||||
|
if not vulnerability_header_written:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_vulnerability Vulnerability finding')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_vulnerability gauge')
|
||||||
|
vulnerability_header_written = True
|
||||||
|
lines.append(
|
||||||
|
f'{METRICS_PREFIX}_vulnerability{{{resource_labels},'
|
||||||
|
f'severity="{safe_severity}",vulnerability_id="{safe_vuln_id}",'
|
||||||
|
f'package="{safe_package}",target="{safe_target}",'
|
||||||
|
f'vuln_type="{safe_vuln_type}",class="{safe_class_name}"}} {count}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if scan_result.duration:
|
||||||
|
lines.append(f'# HELP {METRICS_PREFIX}_scan_duration Duration of the last scan in seconds')
|
||||||
|
lines.append(f'# TYPE {METRICS_PREFIX}_scan_duration gauge')
|
||||||
|
lines.append(f'{METRICS_PREFIX}_scan_duration {scan_result.duration}')
|
||||||
|
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
"""Main function to process Trivy scan output and push metrics."""
|
||||||
|
print("🔍 Trivy Metrics Exporter")
|
||||||
|
print(f"📍 PushGateway URL: {PUSHGATEWAY_URL}")
|
||||||
|
print(f"🏷️ Namespace: {PUSHGATEWAY_NAMESPACE}")
|
||||||
|
print(f"💼 Job: {PUSHGATEWAY_JOB}")
|
||||||
|
print(f"⏳ Request timeout: {REQUEST_TIMEOUT} seconds")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("Trivy report path:", REPORT_PATH)
|
||||||
print("Starting to wait for the report to be generated...")
|
print("Starting to wait for the report to be generated...")
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while not os.path.exists(report_path):
|
while not os.path.exists(REPORT_PATH):
|
||||||
if time.time() - start > wait_timeout:
|
if time.time() - start > WAIT_TIMEOUT:
|
||||||
print("Timeout waiting for report to be generated.")
|
print("Timeout waiting for report to be generated.")
|
||||||
dispatch_email(None, "Trivy Scan Report - Timeout")
|
dispatch_email(None, "Trivy Scan Report - Timeout")
|
||||||
exit(1)
|
exit(1)
|
||||||
print("Still waiting for report to be generated...")
|
print("Still waiting for report to be generated...")
|
||||||
time.sleep(sleep_interval)
|
time.sleep(SLEEP_INTERVAL)
|
||||||
|
|
||||||
print("Report generated, proceeding to send email...")
|
print("Report generated, proceeding to send email...")
|
||||||
time.sleep(5) # Wait for a bit to ensure the file is fully written
|
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"))
|
dispatch_email(REPORT_PATH, "Trivy Scan Report - " + time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
|
if not os.path.exists(TRIVY_OUTPUT_FILE):
|
||||||
|
print(f"✗ Output file not found: {TRIVY_OUTPUT_FILE}")
|
||||||
|
print(" Make sure Trivy has written its output before running this script.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print(f"📄 Reading scan output from: {TRIVY_OUTPUT_FILE}")
|
||||||
|
|
||||||
|
with open(TRIVY_OUTPUT_FILE, 'r') as f:
|
||||||
|
output = f.read()
|
||||||
|
|
||||||
|
print(f"📊 Parsing scan output...")
|
||||||
|
|
||||||
|
# Parse the output
|
||||||
|
scan_data = parse_trivy_output(output)
|
||||||
|
|
||||||
|
# Handle both ParseResult (for Kubernetes scans) and old dict format (for image scans)
|
||||||
|
if hasattr(scan_data, 'resources'):
|
||||||
|
# ParseResult object (Kubernetes/container scan output)
|
||||||
|
resource_count = len(scan_data.resources) if scan_data.resources else 0
|
||||||
|
if resource_count == 0:
|
||||||
|
print("⚠️ No vulnerabilities found in scan output")
|
||||||
|
print(" Pushing basic scan metadata...")
|
||||||
|
scan_data = None
|
||||||
|
else:
|
||||||
|
print(f"Found {resource_count} resource(s) to scan")
|
||||||
|
# Generate Prometheus metrics
|
||||||
|
print("🔧 Generating Prometheus metrics...")
|
||||||
|
metrics = generate_prometheus_metrics_from_parse_result(scan_data)
|
||||||
|
|
||||||
|
# Push to PushGateway
|
||||||
|
print("🚀 Pushing metrics to PushGateway...")
|
||||||
|
success = push_metrics_to_pushgateway(metrics)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("✓ Metrics successfully pushed to PushGateway")
|
||||||
|
|
||||||
|
# Also print a summary to stdout for dashboards
|
||||||
|
print("\n📈 Scan Summary:")
|
||||||
|
print(f" Resources scanned: {resource_count}")
|
||||||
|
total_vulns = sum(resource.misconf_summary.get('Failures', 0) for resource in scan_data.resources)
|
||||||
|
print(f" Misconfigurations found: {total_vulns}")
|
||||||
|
for resource in scan_data.resources:
|
||||||
|
if resource.misconf_summary:
|
||||||
|
print(f" Resource: {resource.name} ({resource.type})")
|
||||||
|
for severity, count in resource.misconf_summary.items():
|
||||||
|
if count > 0:
|
||||||
|
print(f" - {severity}: {count}")
|
||||||
|
else:
|
||||||
|
print("✗ Failed to push metrics")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
# Old dict format (image scan output) - keep existing behavior for backward compatibility
|
||||||
|
print("⚠️ No vulnerabilities found in scan output")
|
||||||
|
print(" Pushing basic scan metadata...")
|
||||||
|
# For backward compatibility with image scans
|
||||||
|
metrics = generate_prometheus_metrics(scan_data)
|
||||||
|
print("🚀 Pushing metrics to PushGateway...")
|
||||||
|
success = push_metrics_to_pushgateway(metrics)
|
||||||
|
if success:
|
||||||
|
print("✓ Metrics successfully pushed to PushGateway")
|
||||||
|
else:
|
||||||
|
print("✗ Failed to push metrics")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
"""Unit tests for parse_trivy_output function."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
import json
|
||||||
|
|
||||||
|
from send_report import (
|
||||||
|
parse_trivy_output,
|
||||||
|
generate_prometheus_metrics_from_parse_result,
|
||||||
|
ParseResult,
|
||||||
|
Resource,
|
||||||
|
Misconfiguration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseTrivyOutput:
|
||||||
|
"""Tests for parsing Trivy JSON output."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fixture_path(self) -> Path:
|
||||||
|
"""Path to the Trivy fixture file."""
|
||||||
|
return Path(__file__).parent / "fixtures" / "trivy.json"
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fixture_data(self, fixture_path: Path) -> dict[str, Any]:
|
||||||
|
"""Load fixture data."""
|
||||||
|
with open(fixture_path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_cluster_name(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing cluster name from Trivy output."""
|
||||||
|
result = parse_trivy_output(json.dumps(fixture_data))
|
||||||
|
assert result.cluster_name == "microk8s-prod-01"
|
||||||
|
|
||||||
|
def test_parse_trivy_output_results_empty(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test when no resources are found."""
|
||||||
|
# Modify fixture to remove resources
|
||||||
|
modified_data = {**fixture_data, "Resources": []}
|
||||||
|
result = parse_trivy_output(json.dumps(modified_data))
|
||||||
|
assert result.cluster_name == "microk8s-prod-01"
|
||||||
|
assert result.resources == []
|
||||||
|
assert len(result.resources) == 0
|
||||||
|
|
||||||
|
def test_parse_trivy_output_single_resource(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing a single resource."""
|
||||||
|
result = parse_trivy_output(json.dumps(fixture_data))
|
||||||
|
assert isinstance(result.resources, list)
|
||||||
|
assert len(result.resources) > 0
|
||||||
|
|
||||||
|
def test_parse_trivy_output_result_types(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test result types for different resources."""
|
||||||
|
result = parse_trivy_output(json.dumps(fixture_data))
|
||||||
|
for resource in result.resources:
|
||||||
|
assert hasattr(resource, "type")
|
||||||
|
assert isinstance(resource.type, str)
|
||||||
|
assert len(resource.type) > 0
|
||||||
|
|
||||||
|
def test_parse_trivy_output_metadata_list(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that metadata is correctly parsed as a list."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
assert hasattr(resource, "metadata")
|
||||||
|
assert isinstance(resource.metadata, list)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_misconfiguration_summary(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that misconfiguration summary is correctly parsed."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
assert hasattr(resource, "misconf_summary")
|
||||||
|
assert isinstance(resource.misconf_summary, dict)
|
||||||
|
assert "Successes" in resource.misconf_summary
|
||||||
|
assert "Failures" in resource.misconf_summary
|
||||||
|
|
||||||
|
def test_parse_trivy_output_misconfigurations_list(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that misconfigurations are correctly parsed as a list."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
assert hasattr(resource, "misconfigurations")
|
||||||
|
assert isinstance(resource.misconfigurations, list)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_successes_count(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that successes count is accurately parsed."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
for misconfig in resource.misconfigurations:
|
||||||
|
if misconfig.status == "PASS":
|
||||||
|
resource.successes_count += 1
|
||||||
|
assert resource.successes_count > 0
|
||||||
|
|
||||||
|
def test_parse_trivy_output_failures_count(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that failures count is accurately parsed."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
for misconfig in resource.misconfigurations:
|
||||||
|
if misconfig.status == "FAIL":
|
||||||
|
resource.failures_count += 1
|
||||||
|
assert resource.failures_count > 0
|
||||||
|
|
||||||
|
def test_parse_trivy_output_kubernetes_type(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing of Kubernetes resource type."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
assert resource.type == "kubernetes"
|
||||||
|
|
||||||
|
def test_parse_trivy_output_resource_name(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing resource name (e.g., ClusterRole, Namespace)."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
assert resource.name == "admin" or resource.name == "namespace"
|
||||||
|
|
||||||
|
def test_parse_trivy_output_namespace_field(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test that namespace field is correctly extracted."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
# Namespace field may be None for cluster-scoped resources
|
||||||
|
assert hasattr(resource, "namespace")
|
||||||
|
assert resource.namespace is None or isinstance(resource.namespace, str)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_empty_namespaces(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing when namespaces array is empty."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
if hasattr(resource, "namespaces"):
|
||||||
|
assert isinstance(resource.namespaces, list)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_empty_image_list(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing when images array is empty."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
if hasattr(resource, "images"):
|
||||||
|
assert isinstance(resource.images, list)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_empty_image_digest_list(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
"""Test parsing when image digests array is empty."""
|
||||||
|
resource = result = parse_trivy_output(json.dumps(fixture_data)).resources[0]
|
||||||
|
if hasattr(resource, "image_digests"):
|
||||||
|
assert isinstance(resource.image_digests, list)
|
||||||
|
|
||||||
|
def test_parse_trivy_output_parses_vulnerabilities(self) -> None:
|
||||||
|
"""Test parsing vulnerabilities from Kubernetes JSON results."""
|
||||||
|
sample = {
|
||||||
|
"ClusterName": "microk8s-prod-01",
|
||||||
|
"Resources": [
|
||||||
|
{
|
||||||
|
"Kind": "Deployment",
|
||||||
|
"Name": "sample-app",
|
||||||
|
"Namespace": "default",
|
||||||
|
"Results": [
|
||||||
|
{
|
||||||
|
"Class": "os-pkgs",
|
||||||
|
"Type": "alpine",
|
||||||
|
"Target": "sample-app:1.0",
|
||||||
|
"Vulnerabilities": [
|
||||||
|
{
|
||||||
|
"VulnerabilityID": "CVE-2024-0001",
|
||||||
|
"PkgName": "openssl",
|
||||||
|
"InstalledVersion": "3.0.0",
|
||||||
|
"FixedVersion": "3.0.1",
|
||||||
|
"Severity": "HIGH",
|
||||||
|
"Title": "Example vulnerability"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = parse_trivy_output(json.dumps(sample))
|
||||||
|
assert len(result.resources) == 1
|
||||||
|
resource = result.resources[0]
|
||||||
|
assert len(resource.vulnerabilities) == 1
|
||||||
|
assert resource.vulnerabilities[0].vulnerability_id == "CVE-2024-0001"
|
||||||
|
assert resource.vulnerability_summary["HIGH"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestGeneratePrometheusMetricsFromParseResult:
|
||||||
|
"""Tests for generate_prometheus_metrics_from_parse_result."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fixture_data(self) -> dict[str, Any]:
|
||||||
|
fixture_path = Path(__file__).parent / "fixtures" / "trivy.json"
|
||||||
|
with open(fixture_path) as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def parsed_result(self, fixture_data: dict[str, Any]) -> ParseResult:
|
||||||
|
return parse_trivy_output(json.dumps(fixture_data))
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def simple_result(self) -> ParseResult:
|
||||||
|
return ParseResult(
|
||||||
|
cluster_name="test-cluster",
|
||||||
|
resources=[
|
||||||
|
Resource(
|
||||||
|
type="kubernetes",
|
||||||
|
name="my-role",
|
||||||
|
namespace="default",
|
||||||
|
misconf_summary={"Successes": 10, "Failures": 3},
|
||||||
|
misconfigurations=[
|
||||||
|
Misconfiguration(
|
||||||
|
status="FAIL",
|
||||||
|
message="Bad config",
|
||||||
|
severity="HIGH",
|
||||||
|
check_id="KSV-0041",
|
||||||
|
title="Manage secrets",
|
||||||
|
check_namespace="builtin.kubernetes.KSV041",
|
||||||
|
primary_url="https://avd.aquasec.com/misconfig/ksv-0041",
|
||||||
|
),
|
||||||
|
Misconfiguration(
|
||||||
|
status="FAIL",
|
||||||
|
message="Another issue",
|
||||||
|
severity="MEDIUM",
|
||||||
|
check_id="KSV-0114",
|
||||||
|
title="Limit wildcard access",
|
||||||
|
check_namespace="builtin.kubernetes.KSV114",
|
||||||
|
primary_url="https://avd.aquasec.com/misconfig/ksv-0114",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_output_is_string(self, parsed_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(parsed_result)
|
||||||
|
assert isinstance(output, str)
|
||||||
|
|
||||||
|
def test_contains_cluster_timestamp(self, parsed_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(parsed_result)
|
||||||
|
assert "trivy_cluster_scan_timestamp 1" in output
|
||||||
|
|
||||||
|
def test_contains_cluster_name(self, parsed_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(parsed_result)
|
||||||
|
assert 'cluster_name="microk8s-prod-01"' in output
|
||||||
|
|
||||||
|
def test_contains_resource_type_metric(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
assert "trivy_resource_type" in output
|
||||||
|
assert 'name="my-role"' in output
|
||||||
|
assert 'namespace="default"' in output
|
||||||
|
|
||||||
|
def test_contains_misconfiguration_successes(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
assert "trivy_misconfiguration_successes" in output
|
||||||
|
assert "} 10" in output
|
||||||
|
|
||||||
|
def test_contains_misconfiguration_failures(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
assert "trivy_misconfiguration_failures" in output
|
||||||
|
assert "} 3" in output
|
||||||
|
|
||||||
|
def test_contains_individual_misconfigurations(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
assert 'severity="HIGH"' in output
|
||||||
|
assert 'severity="MEDIUM"' in output
|
||||||
|
assert 'status="FAIL"' in output
|
||||||
|
|
||||||
|
def test_contains_misconfiguration_identity_labels(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
assert 'check_id="KSV-0041"' in output
|
||||||
|
assert 'check_namespace="builtin.kubernetes.KSV041"' in output
|
||||||
|
assert 'title="Manage secrets"' in output
|
||||||
|
assert 'primary_url="https://avd.aquasec.com/misconfig/ksv-0041"' in output
|
||||||
|
|
||||||
|
def test_parse_trivy_output_primary_url(self, fixture_data: dict[str, Any]) -> None:
|
||||||
|
result = parse_trivy_output(json.dumps(fixture_data))
|
||||||
|
first_with_url = None
|
||||||
|
for resource in result.resources:
|
||||||
|
for misconfig in resource.misconfigurations:
|
||||||
|
if misconfig.primary_url:
|
||||||
|
first_with_url = misconfig
|
||||||
|
break
|
||||||
|
if first_with_url:
|
||||||
|
break
|
||||||
|
|
||||||
|
assert first_with_url is not None
|
||||||
|
assert first_with_url.primary_url.startswith("https://avd.aquasec.com/misconfig/")
|
||||||
|
|
||||||
|
def test_empty_resources_still_has_header(self) -> None:
|
||||||
|
result = ParseResult(cluster_name="empty-cluster", resources=[])
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_cluster_scan_timestamp" in output
|
||||||
|
assert 'cluster_name="empty-cluster"' in output
|
||||||
|
assert "trivy_resource_type" not in output
|
||||||
|
|
||||||
|
def test_no_misconf_summary_skips_summary_metrics(self) -> None:
|
||||||
|
result = ParseResult(
|
||||||
|
cluster_name="c",
|
||||||
|
resources=[Resource(type="kubernetes", name="r", misconf_summary={})],
|
||||||
|
)
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_misconfiguration_successes" not in output
|
||||||
|
assert "trivy_misconfiguration_failures" not in output
|
||||||
|
|
||||||
|
def test_duration_metric_present_when_set(self) -> None:
|
||||||
|
result = ParseResult(cluster_name="c", resources=[], duration=42.5)
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_scan_duration 42.5" in output
|
||||||
|
|
||||||
|
def test_duration_metric_absent_when_not_set(self) -> None:
|
||||||
|
result = ParseResult(cluster_name="c", resources=[])
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_scan_duration" not in output
|
||||||
|
|
||||||
|
def test_image_digest_metric_emitted(self) -> None:
|
||||||
|
result = ParseResult(
|
||||||
|
cluster_name="c",
|
||||||
|
resources=[
|
||||||
|
Resource(
|
||||||
|
type="kubernetes",
|
||||||
|
name="r",
|
||||||
|
image_digests=["sha256:abc123"],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_image_digest" in output
|
||||||
|
assert "sha256:abc123" in output
|
||||||
|
|
||||||
|
def test_fixture_produces_nonempty_output(self, parsed_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(parsed_result)
|
||||||
|
lines = [l for l in output.splitlines() if l and not l.startswith("#")]
|
||||||
|
assert len(lines) > 0
|
||||||
|
|
||||||
|
def test_prometheus_help_type_pairs(self, simple_result: ParseResult) -> None:
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(simple_result)
|
||||||
|
lines = output.splitlines()
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("# TYPE"):
|
||||||
|
metric_name = line.split()[2]
|
||||||
|
assert any(
|
||||||
|
l.startswith(metric_name) for l in lines[i + 1 :]
|
||||||
|
), f"No sample line found after TYPE declaration for {metric_name}"
|
||||||
|
|
||||||
|
def test_contains_vulnerability_metrics(self) -> None:
|
||||||
|
result = ParseResult(
|
||||||
|
cluster_name="test-cluster",
|
||||||
|
resources=[
|
||||||
|
Resource(
|
||||||
|
type="kubernetes",
|
||||||
|
kind="Deployment",
|
||||||
|
name="sample-app",
|
||||||
|
namespace="default",
|
||||||
|
vulnerability_summary={"CRITICAL": 1, "HIGH": 2},
|
||||||
|
vulnerabilities=[],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
output = generate_prometheus_metrics_from_parse_result(result)
|
||||||
|
assert "trivy_vulnerability_total" in output
|
||||||
|
assert "trivy_vulnerability_severity_count" in output
|
||||||
|
assert 'severity="CRITICAL"' in output
|
||||||
@@ -10,7 +10,7 @@ variable "smtp_host" {
|
|||||||
|
|
||||||
variable "smtp_port" {
|
variable "smtp_port" {
|
||||||
description = "The SMTP relay port"
|
description = "The SMTP relay port"
|
||||||
type = number
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "smtp_username" {
|
variable "smtp_username" {
|
||||||
@@ -43,7 +43,6 @@ variable "cron_schedule" {
|
|||||||
variable "tag" {
|
variable "tag" {
|
||||||
description = "The version of Trivy to install"
|
description = "The version of Trivy to install"
|
||||||
type = string
|
type = string
|
||||||
default = "latest"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "report_type" {
|
variable "report_type" {
|
||||||
@@ -57,3 +56,9 @@ variable "levels" {
|
|||||||
type = string
|
type = string
|
||||||
default = "HIGH,CRITICAL"
|
default = "HIGH,CRITICAL"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "pushgateway_url" {
|
||||||
|
description = "URL of the Prometheus Pushgateway including /metrics/job/<job> path"
|
||||||
|
type = string
|
||||||
|
default = "http://pushgateway.prometheus.svc.cluster.local:9091/metrics/job/trivy-metrics"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user