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 <noreply@anthropic.com>
This commit is contained in:
committed by
Thore Cimbal
co-authored by
Claude Opus 5
parent
cdfadc0f26
commit
0bd77e28d8
@@ -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/<ordner>/*.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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user