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>
Monitoring-Stack (Operating-Host)
Prometheus + Loki + Grafana + Alloy + node-exporter fuer den Operating-Server
(CFGMON). Vollstaendig provisioniert: Datasources und Dashboards liegen als
Code in diesem Repo, ein docker compose up -d auf einem frischen Host stellt
den kompletten Stack wieder her.
Deployment
cp .env.example .env # Werte setzen (nur beim allerersten Start relevant)
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:
docker compose up -d --force-recreate prometheus alertmanager
Ob es gewirkt hat, sieht man nur im Container, nicht auf der Platte:
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 |
|---|---|
docker-compose.yml |
Stack-Definition, Images auf feste Versionen gepinnt |
prometheus/prometheus.yml |
Scrape-Konfiguration |
loki/loki-config.yaml |
Loki Single-Binary-Konfiguration, 90 Tage Retention |
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
- Remote-Write eingehend auf
:9090: k3s-Cluster und Matrix-Server pushen Metriken (flux,kube_state_metrics,synapse). - Log-Push eingehend auf
:3100(Loki) von externen Alloys. - Scrape ausgehend: k3s-Host (
10.0.0.2:9100), Pterodactyl-Host (157.90.155.206:9100+:8080). cadvisorundtraefikwerden gescraped, laufen aber in anderen Stacks (cadvisor: Portainer-Stackthread-net-git; beide haengen im externentraefik-Netz).
Dashboards aendern
Provisionierte Dashboards sind in der UI editierbar (allowUiUpdates: true),
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:
- Zustellung buendeln -- entweder
matrix-alerts.pyauf eine Sammelnachricht pro Webhook-Batch umbauen (alle fuenf Pflichtfelder je CVE als eine Zeile), oder die Regeln aufcount by (target, severity)aggregieren und die CVE-Details im Dashboard lassen. save_state()inkrementell nach jedem erfolgreichen Send, plus 429-Behandlung mitRetry-After.
Danach die room="security"-Route aus alertmanager.yml entfernen.
Kleinere offene Punkte der Pipeline
TrivyScanStalekann ein Image, das nie erfolgreich gescannt wurde, nicht melden: ohne ersten Report gibt es keine Serie, an dertime() - trivy_last_scan_timestamphaengen koennte. Ein dauerhaft fehlschlagendes Image bleibt damit still.TargetDowndeckt nur den toten Exporter ab, nicht den einzelnen blinden Fleck.coturn/coturn:latestist 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,9100sind auf der oeffentlichen IP ohne Auth erreichbar (Docker umgeht ufw). Per Hetzner Cloud Firewall auf die IPs der bekannten Absender einschraenken.- Pterodactyl-Host
157.90.155.206: node-exporter und cadvisor antworten aktuell nicht (Targets down). - Loki
table_managerist deprecated; Retention-Enforcement laeuft aktuell nicht ueber den Compactor -- bei Gelegenheit auf Compactor-Retention umstellen.