diff --git a/STATUS.md b/STATUS.md index e55f60d..301113f 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,7 +2,7 @@ -## Issues (48 open, 51 closed) +## Issues (48 open, 52 closed) Verteilung: M1 2 · M2 16 · M3 4 · M4 11 · M5 15 @@ -12,7 +12,7 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md). | Issue | Priorität | Status | Title | |---|---|---|---| -| [0078](docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md) | medium | in-progress | CVE-Meldeweg v2: Metriken, Grafana-Dashboard, Alerts in eigenen Matrix-Raum | +| [0106](docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md) | high | open | CVE-Scan deckt 52 % — die Zielliste ist handgepflegt und läuft dem Bestand hinterher | | [0072](docs/issues/0072-gitops-34-dsgvo-datenschutz-compliance-konkretisieren.md) | low | open | DSGVO/Datenschutz-Compliance konkretisieren | ### M2 (16) diff --git a/docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md b/docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md index 20bfb88..ced88ad 100644 --- a/docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md +++ b/docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md @@ -1,13 +1,15 @@ --- type: issue id: "0078" -status: in-progress +status: done created: 2026-08-01 milestone: M1 priority: medium projekt: gitops gitlab_iid: "45" -related: [] +related: + - "docs/adr/0003-cve-meldeweg-aggregiert.md" + - "docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md" --- # CVE-Meldeweg v2: Metriken, Grafana-Dashboard, Alerts in eigenen Matrix-Raum @@ -30,4 +32,34 @@ Verweise: #31 (Scan existiert), #22 (release-watch), CFGMON-13 im Backlog (Absen --- *Migriert aus Gitea `sorb/axion1337.chat-gitops#47` — dort erstellt am 2026-08-01 von sorb.* - \ No newline at end of file + + +## Abschluss — 2026-08-21 + +**Das Zielbild ist gebaut und liefert.** Es wurde am 2026-08-01 mit ADR-0003 +entschieden und offenbar unmittelbar danach umgesetzt; das Issue beschreibt +seither einen Zustand, der bereits eingetreten war. Nachgemessen, nicht gelesen: + +| Punkt | Beleg | +|---|---| +| Eigener Matrix-Raum, `@alerts` eingeladen | Raum `security`, Bot mit `join`, **173 Nachrichten**, letzte 2026-08-20 23:03 | +| CVEs als Metriken | `trivy_vuln_info`, `trivy_last_scan_timestamp` — 29 Ziele im laufenden Prometheus | +| Grafana-Dashboard | `monitoring/grafana/dashboards/security/cve-overview.json` | +| Prometheus-Alertregeln | fünf, sämtlich mit `room: security` | +| Raum-Routing im Empfänger | `matrix-alerts.py`: Label `room=` → `MATRIX_ROOM_` | +| Scan-Ort auf dem Betriebs-Host statt Lab-CI | Compose führt `cve-scan` (Trivy) und `cve-exporter` | +| Aggregation statt eine Nachricht pro CVE | `count by (target, target_type, host)`, CRITICAL sofort / HIGH nach 24 h | + +Die im Text noch offene Checkbox „Bot einladen — sorb" war zum Zeitpunkt der +Prüfung längst erledigt. + +**Abweichung vom Vorschlag im Issue, bewusst und besser:** Vorgeschlagen war +`trivy_image_vulnerabilities{image,severity}`. Gebaut wurde `trivy_vuln_info` +mit CVE-ID, Severity, `fixed_version` und `first_seen`, aggregiert erst in der +Alarmregel. Damit liegt das Einzel-CVE-Detail im Dashboard, ohne den Raum zu +fluten — genau die Konsequenz, die ADR-0003 verlangt. + +⚠️ **Ein Fund aus der Prüfung, der nicht mehr hierher gehört:** Die Zielliste +des Scans ist handgepflegt und deckt **52 %** des Bestands — `threadnet-web:v0.6.0`, +beide Traefik-Schichten und die Registry selbst werden nicht gescannt. +Weitergeführt als **#0106**. diff --git a/docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md b/docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md new file mode 100644 index 0000000..a432ac0 --- /dev/null +++ b/docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md @@ -0,0 +1,109 @@ +--- +type: issue +id: "0106" +status: open +created: 2026-08-21 +milestone: M1 +priority: high +area: security +host: cfgmon +related: + - "docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md" + - "docs/adr/0003-cve-meldeweg-aggregiert.md" +--- + +# CVE-Scan deckt 52 % — die Zielliste ist handgepflegt und läuft dem Bestand hinterher + +Der CVE-Meldeweg funktioniert: Trivy scannt, der Exporter macht Metriken daraus, +fünf Alarmregeln routen aggregiert in den Security-Raum, das Grafana-Dashboard +steht. Alles nachgemessen beim Abschluss von #0078. + +**Er scannt nur die falsche Menge.** Die Ziele stammen aus +`monitoring/cve/images.txt` — einer von Hand gepflegten Liste, die beim +Ausrollen nicht mitgezogen wird. + +## Gemessen am 2026-08-21 + +Quellen: `kube_pod_container_info` (Cluster), `container_last_seen{job="operating_cadvisor"}` +(Betriebs-Host), `trivy_last_scan_timestamp` (Scan-Ziele) — alle drei in derselben +Prometheus-Instanz. Schreibweisen vor dem Vergleich normalisiert. + +| | Anzahl | +|---|---| +| Images im Cluster | 39 | +| Images auf dem Betriebs-Host | 12 | +| **zusammen im Betrieb** | **51** | +| davon gescannt | **27** | +| **Deckung** | **52 %** | + +**24 laufende Images werden nicht gescannt.** Darunter ausgerechnet: + +- `rohana.axion1337.de/sorb/threadnet-web:v0.6.0` — der Client, den **jeder + Nutzer im Browser lädt** +- `rancher/mirrored-library-traefik:3.6.10` **und** `traefik:v3.7.9` — beide + Ingress-Schichten, die **alles TLS terminieren** +- `gitea/gitea:1.27.0` — die **Registry selbst**, zugleich Flux' Quelle +- `ghcr.io/requarks/wiki:2.5` — das öffentlich erreichbare Wiki +- der gesamte Beobachtungs-Stack: `prom/prometheus`, `grafana/grafana`, + `grafana/loki`, `prom/alertmanager`, `grafana/alloy` +- `python:3.13-slim` — Basis von `matrix-alerts`, `release-watch` und + `cve-exporter` +- `aquasec/trivy:0.58.2` — **der Scanner scannt sich selbst nicht** +- sechs k3s-Bausteine (`klipper-lb`, `local-path-provisioner`, `coredns`, + `metrics-server`, …), `coturn:4.17.2`, `postgres:15-alpine`, `postgres:16-alpine` + +**Zwei Ziele werden gescannt, laufen aber nirgends:** `threadnet-web:v0.3.0` +und `coturn/coturn:latest`. Die Meldung vom 2026-08-20 23:03 über HIGH-CVEs in +`v0.3.0` betrifft ein Image, das niemand benutzt — Rauschen, das wie Signal +aussieht. + +## Warum das zählt + +Es ist die Fehlerklasse „meldet Erfolg, ist aber blind", nur eine Ebene höher: +Nicht die Prüfung ist kaputt, sondern ihre **Zielmenge**. Der Raum füllt sich +zuverlässig mit Meldungen, das Dashboard ist grün gepflegt — und über die drei +am stärksten exponierten Bauteile (Client, Ingress, Registry) steht dort nichts. +Ein leerer Befund und ein nie gestellter Befund sind in dieser Darstellung nicht +unterscheidbar. + +Die Drift ist außerdem nicht theoretisch: Die Liste zeigt `v0.3.0`, produktiv +läuft `v0.6.0`. Zwischen beiden liegen drei Fassungen und ein Upstream-Merge. + +## Anforderung (Entscheidung sorb, 2026-08-21) + +> „Wir können nicht jedes Mal eine Liste aktualisieren, wenn ich den Stack +> aktualisiere, das muss alles automatisch passieren. Noch besser, wenn generell +> alles aus dem Stack und der Registry gescannt wird, nicht nur eine Liste." + +Die Zielmenge wird also **abgeleitet, nicht gepflegt**. `images.txt` entfällt +ersatzlos — eine Regel, die Abweichungen einer Liste meldet, wäre nur eine +Krücke für eine Liste, die es nicht geben sollte. + +## Was dafür schon da ist + +Alle Quellen liegen bereits in **einer** Prometheus-Instanz, die der Scanner +über das vorhandene Compose-Netz erreicht — kein neuer Dienst, keine neuen +Zugangsdaten, kein neuer Netzweg: + +| Quelle | Metrik / Ort | +|---|---| +| Cluster | `kube_pod_container_info` | +| Betriebs-Host | `container_last_seen{job="operating_cadvisor", image!=""}` | +| Registry | Gitea auf **demselben Host** wie der Scanner (`10.0.0.3`) | + +⚠️ **`job="gameserver_cadvisor"` (20 Images) ist auszuschließen** — game-operating +liegt außerhalb des Auftrags. Die Trennung ist ein Beschriftungsvergleich. + +⚠️ **Die Schreibweisen unterscheiden sich** (`docker.io/library/postgres:…` +gegen `clamav/clamav:…`). Ohne Normalisierung meldet jeder Abgleich +Scheinlücken. + +⚠️ **Offene Frage für den Entwurf:** Wie weit reicht „alles aus der Registry"? +Alle Tags aller Repos kann eine Vielzahl alter Fassungen bedeuten — mit +entsprechender Scan-Dauer, Cache-Wachstum und Zugriffszahlen bei Docker Hub. +Der Umfang gehört begrenzt und begründet. + +## Abgrenzung + +Nicht Teil davon: die Behebung der gefundenen CVEs (#0051), der Meldeweg selbst +(#0078, erledigt) und `release-watch` (#0022).