docs: Gate 1 for #0106 — derive the CVE targets, and one open question

Six countable criteria, the first of which is that images.txt stops existing and
is not replaced by another maintained file. Coverage of the running estate has
to reach 100 percent, measured as a set difference over normalised names, and
stay visible as a metric so the gap cannot return quietly.

The open question is how far "everything from the registry" reaches. Six repos
hold 36 tags; seven are CI artefacts and 25 of the remaining version tags run
nowhere. Scanning all of them triples the workload and puts findings about
v0.1.0 into the security room — the exact noise this work removes. Three
readings are laid out with a recommendation, not a decision.

The ledger records the rungs: nothing needed building for the sources. Both
image sets already live in the same Prometheus the scanner can reach, and the
registry answers an anonymous token. It also records two wrong turns of my own,
including reading a registry 401 as "needs credentials".
This commit is contained in:
Thore Cimbal
2026-08-21 12:00:00 +00:00
parent b3d961d92d
commit e10cd5dee3
3 changed files with 173 additions and 2 deletions
+4 -2
View File
@@ -82,9 +82,11 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md).
| [0090](docs/issues/0090-gitops-59-kein-kubernetes-audit-log-zugriffe-an-der-api.md) | low | open | Kein Kubernetes-Audit-Log — Zugriffe an der API werden nicht protokolliert |
## Active design docs (0)
## Active design docs (1)
_none active_
| Design | Gate | Title |
|---|---|---|
| [2026-08-21-cve-ziele-ableiten](docs/design/2026-08-21-cve-ziele-ableiten.md) | gate-1 | Design: Die CVE-Zielmenge ableiten statt pflegen (#0106) |
## ADRs (25)
@@ -0,0 +1,114 @@
---
type: design
status: gate-1
date: 2026-08-21
size: L
related:
- "docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md"
- "docs/adr/0003-cve-meldeweg-aggregiert.md"
---
# Design: Die CVE-Zielmenge ableiten statt pflegen (#0106)
## Gate 1 — Product
### Problem
Der CVE-Meldeweg funktioniert und meldet zuverlässig — über die Hälfte des
Bestands. Am 2026-08-21 gemessen: **27 von 51 laufenden Images** werden
gescannt, **52 % Deckung**. Nicht gescannt werden unter anderem der Client, den
jeder Nutzer im Browser lädt (`threadnet-web:v0.6.0`), **beide**
Ingress-Schichten, die alles TLS terminieren, die Registry selbst und der
gesamte Beobachtungs-Stack. Der Scanner prüft nicht einmal sein eigenes Image.
Umgekehrt meldet er CVEs für `threadnet-web:v0.3.0` — ein Image, das nirgends
läuft.
**Für wen.** Für den Betreiber, der aus dem Security-Raum ableitet, ob die
Plattform verwundbar ist. Heute kann er das nicht: Ein leerer Befund und ein nie
gestellter Befund sehen dort gleich aus.
**Ursache** ist nicht die Prüfung, sondern ihre Zielliste. `monitoring/cve/images.txt`
wird von Hand gepflegt und beim Ausrollen nicht mitgezogen. Zwischen dem
Listenstand `v0.3.0` und dem produktiven `v0.6.0` liegen drei Fassungen und ein
Upstream-Merge.
### Abnahmekriterien
1. **`monitoring/cve/images.txt` existiert nicht mehr.** Kein Ersatz durch eine
andere gepflegte Liste, auch nicht durch eine generierte Datei im Repo.
2. **Deckung des laufenden Bestands = 100 %.** Zählbar als
`|laufend \ gescannt| = 0`, wobei *laufend* die Vereinigung aus
`kube_pod_container_info` und `container_last_seen{job="operating_cadvisor"}`
ist, Schreibweisen normalisiert. Heute: 24 fehlen.
3. **Kein Ziel ohne Grund.** `|gescannt \ (laufend Registry-Auswahl)| = 0`.
Heute: 2 (`threadnet-web:v0.3.0`, `coturn/coturn:latest`).
4. **Die Deckung ist als Metrik sichtbar und alarmierbar**, nicht nur einmalig
nachgerechnet — sonst verfällt sie wieder unbemerkt, nur ohne Liste.
5. **Ein Ausrollen genügt als Nachweis:** Nach einer neuen `threadnet-web`-Fassung
erscheint sie ohne menschliches Zutun in den Scan-Zielen, die alte
verschwindet. Vorgeführt, nicht behauptet.
6. **`job="gameserver_cadvisor"` bleibt draußen** — game-operating liegt
außerhalb des Auftrags. Zählbar: kein Ziel aus dieser Menge in
`trivy_last_scan_timestamp`.
### Nicht-Ziele
- **Die gefundenen CVEs beheben.** Das ist #0051. Hier geht es allein darum,
*wonach* gesucht wird.
- **Den Meldeweg ändern.** Räume, Aggregation, Dashboard und Alarmregeln
bleiben, wie ADR-0003 sie entschieden hat und #0078 sie belegt hat.
- **Andere Scanner-Merkmale.** Keine Secret-Scans, keine Lizenzprüfung, keine
SBOM-Ablage — nur die Zielmenge.
- **game-operating und das Homelab.** Auch dann nicht, wenn ihre Images in
derselben Prometheus stehen.
- **Ein Registry-Aufräumen.** Alte Tags werden hier nicht gelöscht.
### ⚠️ Die eine Frage, die Gate 1 nicht allein entscheiden kann
Die Anforderung lautet „alles aus dem Stack **und der Registry**". Der Stack ist
eindeutig. Die Registry ist es nicht:
| | Zahl |
|---|---|
| Repos | 6 |
| Tags gesamt | 36 |
| davon `sha-*` / `latest-ci` (CI-Artefakte) | 7 |
| verbleibende Fassungs-Tags | 29 |
| davon im Betrieb | 4 |
**25 Fassungs-Tags laufen nirgends**`threadnet-web:v0.1.0` bis `v0.5.4`,
dazu sieben Bau-Artefakte. Sie alle zu scannen verdreifacht den Umfang (rund 83
Ziele statt 29) und erzeugt genau die Sorte Meldung, die dieses Vorhaben
gerade abschafft: Befunde über Images, die niemand betreibt.
Drei Lesarten, die Gate 2 gegeneinander abwägen muss:
- **A — alles.** Wörtlich die Anforderung. Vollständig, aber der Security-Raum
bekommt Befunde zu `v0.1.0`.
- **B — Bestand plus die jeweils jüngste Fassung je Repo.** Deckt „was wir
ausliefern" ab, ohne Archiv-Rauschen. 6 zusätzliche Ziele statt 32.
- **C — Bestand plus alles, was rückrollbar ist.** Die Fassungen, auf die ein
Rollback realistisch zielt (etwa die letzten drei je Repo). Zwischen A und B.
Meine Empfehlung ist **C**, weil ein Rollback-Ziel mit bekannter Lücke der
einzige Fall ist, in dem ein nicht laufendes Image betrieblich zählt. Das ist
eine Empfehlung, keine Entscheidung.
### Ankündigung
Der CVE-Scan sucht künftig selbst, wonach er suchen soll. Statt einer Liste, die
jemand beim Ausrollen mitpflegen müsste, leitet er seine Ziele aus dem ab, was
tatsächlich läuft — im Cluster und auf dem Betriebs-Host — und aus dem, was in
unserer Registry liegt. Für den Betreiber heißt das: Was neu ausgerollt wird, ist
ab dem nächsten Durchlauf mitgeprüft, ohne dass jemand daran denken muss; und was
im Security-Raum steht, bezieht sich auf Images, die es wirklich gibt. Die
Deckung wird dabei selbst zur Metrik, damit die Lücke, die es heute gab, nicht
lautlos zurückkehren kann.
### Keine Oberfläche
Kein UI-Anteil. Sichtbar wird das Ergebnis im bestehenden
Grafana-Dashboard `security/cve-overview.json` und im Security-Raum.
> **STOP — Freigabe für Gate 1.**
@@ -0,0 +1,55 @@
---
type: ledger
date: 2026-08-21
size: L
status: open
related:
- "docs/design/2026-08-21-cve-ziele-ableiten.md"
- "docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md"
---
# Ledger: Die CVE-Zielmenge ableiten statt pflegen (#0106)
## Gates
| gate | commit | approval | status | note |
|---|---|---|---|---|
| 1 | | | OPEN | Wartet auf Freigabe; die Registry-Reichweite ist die offene Frage. |
## Ladder
| searched | found | outcome | commit |
|---|---|---|---|
| ob #0078 ueberhaupt noch Arbeit ist, bevor ein Entwurf beginnt | das gesamte Zielbild war seit dem 2026-08-01 gebaut und lieferte: Raum, Metriken, Dashboard, fuenf Regeln, Raum-Routing | reused: nichts gebaut — #0078 mit Nachweis geschlossen statt umgesetzt | |
| eine Entscheidung zum Meldeweg, bevor eine eigene getroffen wird | ADR-0003 (angenommen, 2026-08-01) legt Aggregation, Security-Raum und einen Bot bereits fest | reused: der Meldeweg bleibt unberuehrt und wird Nicht-Ziel | |
| eine Quelle fuer die laufenden Images, statt eine zu bauen | `kube_pod_container_info` (39) und `container_last_seen{job="operating_cadvisor"}` (12) liegen in **derselben** Prometheus, die der Scanner ueber das vorhandene Compose-Netz erreicht | reused: kein neuer Dienst, keine Zugangsdaten, kein neuer Netzweg | |
| einen Weg, game-operating auszuschliessen | `job="gameserver_cadvisor"` (20 Images) trennt sauber von `operating_cadvisor` | reused: ein Beschriftungsvergleich statt einer Ausschlussliste | |
| ob die Registry ohne neue Zugangsdaten lesbar ist | anonymer Token-Tanz genuegt: `/v2/token` → Katalog und Tag-Listen; 6 Repos, 36 Tags | reused: die Standard-Registry-Authentifizierung, die der Scanner ohnehin schon fuer das Ziehen macht | |
| ob die rohana-Berichte ueberhaupt frisch sind | alle 29 Berichte gleichmaessig 4,1 h alt — der Scanner zieht aus der Registry erfolgreich | reused: die bestehende Scan-Schleife bleibt, nur ihre Zielbeschaffung aendert sich | |
## Notes
⚠️ **Zwei eigene Fehlschluesse in dieser Erkundung, beide durch die naechste
Messung korrigiert.**
1. Der erste Deckungsvergleich lief **ohne Normalisierung** der Schreibweisen
(`docker.io/library/postgres:…` gegen `clamav/clamav:…`). Nachgerechnet kam
dieselbe Zahl heraus — aber das war Glueck, nicht Methode. Die
Normalisierung ist deshalb Bestandteil der Abnahmekriterien.
2. Aus einem `401` am Registry-Katalog wurde vorschnell „braucht Zugangsdaten"
geschlossen. Eine Registry antwortet auf `/v2/` **immer** mit 401 und einem
Token-Verweis; anonyme Clients holen den Token und kommen durch. Aufgefallen
ist es nur, weil die Scan-Berichte der rohana-Images frisch waren — also
etwas funktionierte, das laut Schlussfolgerung nicht funktionieren konnte.
**Der erste Vorschlag war eine Kruecke und wurde von sorb verworfen.** Geplant
war eine Alarmregel, die Abweichungen der gepflegten Liste meldet. sorbs
Einwand: eine Liste, die bei jedem Ausrollen nachgezogen werden muss, erzeugt
nur Fehler und Arbeit. Damit entfaellt nicht nur die Regel, sondern die Liste —
und mit ihr die Abweichung, die zu melden waere.
**Der Umfang ist gemessen, nicht geschaetzt.** Bestand 51 Images (Cluster 39,
Betriebs-Host 12), gescannt 27, Registry 36 Tags in 6 Repos davon 7
CI-Artefakte. Die volle Lesart der Anforderung ergaebe rund 83 Ziele statt 29 —
Faktor drei. Deshalb steht die Registry-Reichweite als ausdrueckliche Frage in
Gate 1 statt als stille Annahme im Entwurf.