From 0bd77e28d849cfdf485b1a5e58ec44eaebad47bd Mon Sep 17 00:00:00 2001 From: sorb Date: Sat, 1 Aug 2026 14:01:14 +0200 Subject: [PATCH] monitoring: CVE-Alarme vorerst stumm, Deploy-Fallstricke dokumentiert (gitops#47) Die CVE-Pipeline aus #47 ist deployt und sammelt Daten, die Alarm-Zustellung ist aber bewusst abgeklemmt: room="security" routet auf einen Null-Receiver. Grund: die Regeln erzeugen eine Alarm-Instanz pro CVE pro Image (bei 14 von 29 gescannten Images bereits 59 CRITICAL / 445 HIGH). group_by legt alle in eine Gruppe, matrix-alerts.py schickt eine Nachricht pro Alarm -> Schwall. Dazu steht save_state() hinter der Sende-Schleife: bricht ein Send ab (Synapse rate-limitet nach ~10 mit 429), wird kein State gespeichert, der Receiver antwortet 502 und Alertmanager wiederholt die ganze Gruppe -- mit leerer Deduplizierung. Details und Weg zum Scharfschalten im README. Ausserdem dokumentiert: Config-Aenderungen an prometheus.yml/alerts.yml/ alertmanager.yml werden von "docker compose up -d" NICHT aktiv. Die Dateien sind einzeln gemountet, Docker haengt den Mount am Inode, git pull erzeugt beim Umbenennen einen neuen -- der Container sieht weiter die alte Datei. Es braucht --force-recreate; ein SIGHUP laedt nur den alten Inhalt erneut. Co-Authored-By: Claude Opus 5 --- monitoring/README.md | 87 ++++++++++++++++++++++++ monitoring/alertmanager/alertmanager.yml | 16 +++++ 2 files changed, 103 insertions(+) diff --git a/monitoring/README.md b/monitoring/README.md index 542262b..a4ee1bd 100644 --- a/monitoring/README.md +++ b/monitoring/README.md @@ -13,6 +13,35 @@ docker network create traefik # falls noch nicht vorhanden docker compose up -d ``` +### ACHTUNG: Config-Aenderungen brauchen ein `--force-recreate` + +`docker compose up -d` allein aktiviert **keine** geaenderte Config-Datei. +Grund: `prometheus.yml`, `alerts.yml` und `alertmanager.yml` sind als +**einzelne Dateien** gemountet, und Docker haengt so einen Mount am Inode auf. +`git pull` schreibt eine neue Datei und benennt sie um -- neuer Inode. Der +Container zeigt danach weiter auf die alte Datei und sieht die Aenderung nie. +`up -d` merkt davon nichts (Service-Definition unveraendert -> kein Neustart), +und ein `SIGHUP`-Reload laedt bloss den alten Inhalt erneut. + +Nach jedem `git pull`, der eine dieser Dateien anfasst: + +```bash +docker compose up -d --force-recreate prometheus alertmanager +``` + +Ob es gewirkt hat, sieht man nur **im Container**, nicht auf der Platte: + +```bash +docker exec prometheus grep -c axion-cve /etc/prometheus/alerts.yml # 0 = alter Inode +``` + +Nicht betroffen sind Verzeichnis-Mounts (`grafana/provisioning/`, +`grafana/dashboards/`) -- die loesen bei jedem Zugriff ueber den Pfad auf. +Grafana liest **Provider-Definitionen** allerdings nur beim Start: ein neuer +Ordner in `provisioning/dashboards/dashboards.yml` braucht einen +`docker compose restart grafana`. Neue oder geaenderte Dashboard-JSONs +innerhalb eines bestehenden Providers werden dagegen laufend nachgezogen. + ## Struktur | Pfad | Inhalt | @@ -23,6 +52,9 @@ docker compose up -d | `alloy/config.alloy` | Docker-Log-Collection -> Loki, node-exporter -> Prometheus | | `grafana/provisioning/` | Datasources (feste UIDs!) und Dashboard-Provider | | `grafana/dashboards//*.json` | Dashboards, je Unterordner ein Grafana-Ordner | +| `cve/images.txt` | Liste der real deployten Images, Inventur aus dem Cluster | +| `cve/scan-loop.sh` | Trivy-Scan-Schleife (24h), schreibt JSON-Reports | +| `cve/cve-exporter.py` | Reports -> Prometheus-Metriken, mit First-Seen-State | ## Externe Abhaengigkeiten @@ -42,6 +74,61 @@ aber die JSON-Datei im Repo ist die Quelle der Wahrheit: UI-Aenderungen muessen exportiert und committet werden, sonst gehen sie beim naechsten Datei-Update verloren. +## CVE-Pipeline (gitops#47) + +`cve-scan` scannt alle 24h die Images aus `cve/images.txt` mit Trivy und legt +JSON-Reports in ein Volume; `cve-exporter` serviert sie als Metriken auf +`:9101`, Prometheus scraped sie als Job `cve_exporter`. Dashboard: +**Security / CVE-Uebersicht (Trivy)**. + +Der Exporter merkt sich je `(CVE, Target)` den Erstfund in einem persistenten +State (`cve_exporter_state`-Volume) -- daher kommt die Spalte +"erstmals gesehen". Verschwindet ein Finding, faellt der Eintrag raus +(= "geschlossen"). + +Bei Stack-Aenderungen `cve/images.txt` nachziehen, sonst scannt die Pipeline +an der Realitaet vorbei. + +### Bekanntes Problem: Alarm-Zustellung ist stummgeschaltet + +**Die CVE-Regeln laufen, die Alarme werden aber bewusst nicht zugestellt.** +In `alertmanager.yml` routet `room="security"` derzeit auf den Null-Receiver. + +Grund: `TrivyCriticalVuln` und `TrivyHighVuln` erzeugen eine Alarm-Instanz +**pro CVE pro Image**. Beim ersten Scan-Durchlauf waren das bei 14 von 29 +Images bereits 59 CRITICAL und 445 HIGH. Da `group_by: [alertname, instance]` +alle in *eine* Gruppe legt und `matrix-alerts.py` **eine Matrix-Nachricht pro +Alarm** schickt, wird daraus ein Nachrichten-Schwall. Verschaerfend: +`save_state()` steht in `do_POST` hinter der Sende-Schleife -- bricht ein Send +ab (Synapse rate-limitet nach ~10 Nachrichten mit 429), wird der State nicht +gespeichert, der Receiver antwortet 502 und Alertmanager wiederholt die +komplette Gruppe. Die Fingerprint-Deduplizierung ist beim Retry noch leer, +also wiederholt sich das. + +Zum Scharfschalten sind **beide** Punkte noetig: + +1. Zustellung buendeln -- entweder `matrix-alerts.py` auf eine Sammelnachricht + pro Webhook-Batch umbauen (alle fuenf Pflichtfelder je CVE als eine Zeile), + oder die Regeln auf `count by (target, severity)` aggregieren und die + CVE-Details im Dashboard lassen. +2. `save_state()` inkrementell nach jedem erfolgreichen Send, plus + 429-Behandlung mit `Retry-After`. + +Danach die `room="security"`-Route aus `alertmanager.yml` entfernen. + +### Kleinere offene Punkte der Pipeline + +- `TrivyScanStale` kann ein Image, das **nie** erfolgreich gescannt wurde, + nicht melden: ohne ersten Report gibt es keine Serie, an der + `time() - trivy_last_scan_timestamp` haengen koennte. Ein dauerhaft + fehlschlagendes Image bleibt damit still. `TargetDown` deckt nur den toten + Exporter ab, nicht den einzelnen blinden Fleck. +- `coturn/coturn:latest` ist als einziges Image ungepinnt -- Scan-Ergebnisse + sind dadurch nicht reproduzierbar. +- Der Exporter prunt den First-Seen-State bei **jedem** Scrape anhand der + gerade gelesenen Reports. Ein transienter Lesefehler (`except: continue`) + loescht die Erstfund-Zeitstempel des betroffenen Targets dauerhaft. + ## Offene Punkte / Sicherheit - `9090`, `3100`, `9100` sind auf der oeffentlichen IP ohne Auth erreichbar diff --git a/monitoring/alertmanager/alertmanager.yml b/monitoring/alertmanager/alertmanager.yml index 29b9510..841250b 100644 --- a/monitoring/alertmanager/alertmanager.yml +++ b/monitoring/alertmanager/alertmanager.yml @@ -5,9 +5,25 @@ route: group_wait: 1m group_interval: 5m repeat_interval: 4h + routes: + # VORUEBERGEHEND STUMM (gitops#47): Die CVE-Regeln feuern eine Alarm- + # Instanz pro CVE pro Image. Messung an 3 der 29 Images ergab 27 CRITICAL + # und 126 HIGH -> hochgerechnet ~260 CRITICAL / ~1200 HIGH. matrix-alerts.py + # schickt eine Nachricht pro Alarm; Synapse rate-limitet nach ~10, die + # Exception verhindert save_state(), der Receiver antwortet 502 und + # Alertmanager wiederholt die komplette Gruppe -> Dauerschleife. + # Regeln laufen bewusst weiter (Zahlen sichtbar in Prometheus/Grafana), + # nur die Zustellung haengt hier. Wieder scharf schalten: diese route raus, + # sobald der Receiver Sammelnachrichten kann bzw. die Regeln aggregieren. + - matchers: + - room="security" + receiver: "null" receivers: - name: matrix webhook_configs: - url: http://matrix-alerts:8080/alert send_resolved: true + + # Schwarzes Loch fuer stummgeschaltete Routen - bewusst ohne Konfiguration. + - name: "null"