diff --git a/FRAMEWORK-BEFUNDE.md b/FRAMEWORK-BEFUNDE.md index b40ea3f..4d03978 100644 --- a/FRAMEWORK-BEFUNDE.md +++ b/FRAMEWORK-BEFUNDE.md @@ -7,7 +7,7 @@ als Wiki-Seiten unter [docs/wiki/stolpersteine/](docs/wiki/index.md) — eine Seite je Muster, mit Stand und, wo zutreffend, der Version, die es abdeckt (ADR-0009 des Rahmenwerks). -**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 6 offen, 3 teilweise, 4 geerntet, 2 nicht abgedeckt. +**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 7 offen, 3 teilweise, 4 geerntet, 2 nicht abgedeckt. Diese Datei hält keinen eigenen Bestand mehr. Sie bleibt als Wegweiser bestehen, weil angenommene Entscheidungen auf sie diff --git a/STATUS.md b/STATUS.md index 83c33ac..19b5f56 100644 --- a/STATUS.md +++ b/STATUS.md @@ -128,15 +128,16 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md). - [AAR — Game-Host-Anbindung: Deploy über zwei Übergaben, Ursache war nie die Firewall](docs/aar/2026-08-20-game-host-anbindung.md) - [AAR — rohana lief voll, der ThreadNet-Web-Spiegel riss](docs/aar/2026-08-20-rohana-platte-voll-spiegel-gerissen.md) -## Fehlerklassen (17) +## Fehlerklassen (18) -### offen (6) +### offen (7) - [Die AAR-Pflicht setzt sich ohne Werkzeug nicht durch](docs/wiki/stolpersteine/aar-pflicht-ohne-werkzeug.md) - [Prüfungen erzeugen Befunde, die niemand beheben kann](docs/wiki/stolpersteine/befunde-die-niemand-beheben-kann.md) - [Ein gleichzeitiger, sachfremder Defekt wird der eigenen Änderung angelastet](docs/wiki/stolpersteine/fremder-defekt-der-aenderung-angelastet.md) - [Issues behaupten Zustände, die längst überholt sind](docs/wiki/stolpersteine/issues-behaupten-ueberholte-zustaende.md) - [Eine Datei kann nicht maßgeblich und zugleich unveränderlich sein](docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md) +- [Ein nachgebauter Test prüft den Nachbau](docs/wiki/stolpersteine/nachgebauter-test-prueft-den-nachbau.md) - [Eine Quittung bezeugt Aufmerksamkeit, nicht Vollständigkeit](docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md) ### teilweise gedeckt (3) diff --git a/docs/design/2026-08-21-cve-ziele-ableiten.md b/docs/design/2026-08-21-cve-ziele-ableiten.md index 117f46a..fd9f5b1 100644 --- a/docs/design/2026-08-21-cve-ziele-ableiten.md +++ b/docs/design/2026-08-21-cve-ziele-ableiten.md @@ -599,3 +599,115 @@ Zusicherungen grün · **13** Shell-Zusicherungen grün · `sh -n` beide · **Weicht 1–3 ab, ist die Herleitung falsch — nicht die Handmessung überholt.** Dann nicht weiterfahren, sondern den Unterschied ausmessen. + +### Slice 5 — durchgeführt, 2026-08-21 · Status: **DONE** + +**5.1 — die wackeligste Annahme aus Gate 3, gemessen.** Befürchtet war: cAdvisor +sieht nur, was *läuft*; ein gestoppter Dienst fehlt in der Soll-Menge, und die +Deckung meldet trotzdem 100 %. + +| | Zahl | +|---|---| +| im Compose deklarierte Images | 8 | +| davon von cAdvisor gesehen | **8** — Lücke **0** | +| zusätzlich gesehen, in **keinem** unserer Compose-Dateien | **4** | + +Die vier sind `gitea/gitea:1.27.0` (**die Registry selbst**), `traefik:v3.7.9`, +`portainer/agent:2.27.5` und `cadvisor`. **Eine aus dem Compose abgeleitete +Liste hätte genau diese vier übersehen** — cAdvisor ist also nicht der +schwächere, sondern der stärkere Weg. Das Restrisiko (ein gerade gestoppter +Dienst) bleibt und ist heute mit **0** beziffert statt vermutet. + +**5.2 — Kriterium 5** zeigt sich beim nächsten `threadnet-web`-Release von +selbst und ist bis dahin offen. + +## Gate 5 — Closeout (AAR) + +**Planned vs. actual.** Geplant war ein Vorhaben, das eine Alarmregel gegen eine +handgepflegte Liste baut. Nach sorbs Einwand wurde daraus der Wegfall der Liste +— und damit ein anderes, kleineres und besseres Vorhaben. Fünf Slices statt +einer Regel. + +| Abnahmekriterium (Gate 1) | Ergebnis | +|---|---| +| 1 `images.txt` existiert nicht mehr | erfüllt, ersatzlos | +| 2 Deckung 100 % | erfüllt: `cve_targets_missing` **0** bei 56 Zielen | +| 3 kein Ziel ohne Grund | erfüllt: `orphaned` **0**; `v0.3.0` und `coturn:latest` sind fort | +| 4 Deckung als Metrik, alarmierbar | erfüllt: fünf Serien, vier Regeln, drei Panels | +| 5 ein Ausrollen genügt als Nachweis | **offen** bis zum nächsten Release | +| 6 `gameserver_cadvisor` bleibt draußen | erfüllt | + +**Der Ertrag, gemessen statt behauptet.** + +| | vorher (29) | nachher (56) | +|---|---|---| +| Deckung | 52 % | **100 %** | +| CRITICAL | 126 | **253** | +| HIGH | 1222 | **3674** | + +⚠️ **Die zuvor unsichtbare Hälfte trägt 127 zusätzliche CRITICAL-Befunde.** Zum +ersten Mal überhaupt gemeldet: das öffentlich erreichbare Wiki, **beide** +Traefik-Schichten, Prometheus, Grafana, Alertmanager, die k3s-Bausteine — und +`aquasec/trivy` selbst. Nicht die Pipeline war kaputt; die Hälfte des Bestands +war nie befragt worden. + +**Was diese Arbeit falsch gemacht hat.** + +1. ⚠️ **Mein erster Vorschlag war eine Krücke.** Eine Regel, die Abweichungen + einer gepflegten Liste meldet, hält die Liste am Leben und fügt ihr eine + Pflicht hinzu. sorbs Einwand („das muss automatisch passieren") hat das + Vorhaben halbiert und verbessert. Ich hatte die Leiter nur bis „was fehlt in + der Liste" bestiegen, nicht bis „braucht es die Liste". +2. **Die Mengenschätzung war falsch.** „~65 Ziele" beruhte auf drei Tags je + Repo; vier der sechs Repos haben nur **einen**. Tatsächlich 56 — erklärbar + (51 laufend + 9 Registry − 4 Überschneidung), aber eben nachträglich. + Ebenso „≤ 65 Meldungen": es waren **39**. +3. ⚠️ **Ein Nebeneffekt, den ich hätte vorhersehen können.** Die Normalisierung + ändert die `target`-Beschriftung (`docker.io/postgres:17-alpine` → + `postgres:17-alpine`) und damit **jeden Alarm-Fingerabdruck**. Alle + bekannten Befunde galten einmalig als neu und wurden erneut gemeldet. Das + steht in keinem der Gates, obwohl Gate 2 die Normalisierung ausdrücklich als + Pflicht führt — ich habe ihre Wirkung auf die Alarmschicht nicht zu Ende + gedacht. +4. **Drei Einrückungs- und Gruppierungsfehler** (Compose 4/6 statt 6/8, Alarme 6 + statt 8, `runde || sleep A && sleep B`). Alle von Kontrollen gefangen, keiner + ausgeliefert. + +**Why the difference.** Die drei Korrekturen, die etwas geändert haben, kamen +von außen oder aus einer Messung: sorbs Einwand, der fehlgeschlagene Testlauf, +der Vergleich Compose gegen cAdvisor. Keine kam aus dem Nachdenken über den +eigenen Plan. + +**Learnings.** + +- ⚠️ **Ein nachgebauter Test prüft den Nachbau.** Der erste `test_scan_loop.sh` + baute die Schleifenlogik nach und wäre grün geblieben, während die + ausgelieferte Datei kaputt ist. Nach dem Umbau auf *Laden* der echten + `runde()` fiel binnen **eines Laufs** auf, dass `scan-loop.sh` seine Pfade + fest verdrahtete. Das ist die billigste Lehre des Tages und die + übertragbarste. +- **Eine Prüfung schuldet den Nachweis, dass sie rot werden kann** — hier + viermal geführt (Normalisierung sabotiert: 11 von 21 rot; Alarmschwelle + unerreichbar: rot; Karenz entfernt: rot; Wache entfernt: „Berichte + unberührt" fällt von 2 auf 0). +- **Wer Ziele ableitet, muss das Ausbleiben der Herleitung alarmieren.** Eine + leere Soll-Menge sieht aus wie vollständige Deckung. Das ist der Kern von + ADR-0026 und der Grund für `cve_target_source_stale`. +- **Die laufende Anlage ist die bessere Quelle als ihre Beschreibung.** cAdvisor + sah vier Images, die in **keiner** unserer Compose-Dateien stehen — darunter + die Registry selbst. + +**Harvested.** ADR-0026. Für die nächste Ernte vorgemerkt: die Lehre über +nachgebaute Tests, als eigener Stolperstein. + +**Open uncertainties.** + +1. **Kriterium 5** ist bis zum nächsten Release unbewiesen. +2. ⚠️ **127 neu sichtbare CRITICAL-Befunde sind unbearbeitet.** Sie gehören zu + #0051, nicht hierher — aber sie sind ab heute sichtbar und laufen auf. +3. **Ein gestoppter Dienst fehlt weiterhin still in der Soll-Menge.** Heute mit + 0 beziffert, morgen nicht zwingend. +4. **Die Auswahl „letzte drei je Repo" ist grob.** Bei `axion-backup` sind das + beide Tags, auch der ungenutzte `v1` — der prompt CRITICAL meldet. + +> **STOP — Freigabe für Gate 5.** diff --git a/docs/issues/0051-cve-remediation-pass.md b/docs/issues/0051-cve-remediation-pass.md index 95dbf20..243a6a1 100644 --- a/docs/issues/0051-cve-remediation-pass.md +++ b/docs/issues/0051-cve-remediation-pass.md @@ -6,7 +6,7 @@ created: 2026-08-14 milestone: M5 priority: high area: security -related: [docs/issues/0025-deploy-uebergabe-cve-alarme-aggregiert-receiver.md, docs/aar/2026-08-01-cve-pipeline-gitops47.md] +related: [docs/issues/0025-deploy-uebergabe-cve-alarme-aggregiert-receiver.md, docs/aar/2026-08-01-cve-pipeline-gitops47.md, docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md] gitlab_iid: "37" --- # CVE-Remediation-Pass: Schwachstellen-Report abarbeiten @@ -32,3 +32,32 @@ nur die Alarm-Zustellung ab — die eigentliche **Behebung** fehlte als eigenes Hängt eng an der Update-Kadenz (#0052) — Updaten ist der Haupthebel gegen die CVEs. Methode/Runbook: `/betrieb/sicherheit`; Update-Prozess: `/betrieb/upgrades`. + +## ⚠️ Die Grundmenge hat sich am 2026-08-21 verdoppelt (#0106) + +Bis dahin beruhte jede Zahl in diesem Issue auf **29** gescannten Images. Das +waren 52 % des Bestands — die Zielliste war handgepflegt und lief dem Ausrollen +hinterher. Seit #0106 wird sie abgeleitet, es sind **56** Ziele, und die Deckung +liegt bei 100 %. + +| | vorher (29 Images) | jetzt (56 Images) | +|---|---|---| +| CRITICAL | 126 | **253** | +| HIGH | 1222 | **3674** | + +**Der Zuwachs ist kein neuer Schaden, sondern vorher unsichtbarer Bestand.** +Erstmals überhaupt erfasst und sofort mit CRITICAL-Befunden dabei: das +öffentlich erreichbare Wiki, **beide** Traefik-Schichten (Cluster und +Betriebs-Host), Prometheus, Grafana, Alertmanager, die k3s-Bausteine, mehrere +Postgres-Fassungen — und `aquasec/trivy` selbst. + +Für diesen Remediation-Pass heißt das: Der Umfang ist rund doppelt so groß wie +bei der Erstellung dieses Issues angenommen, und die neu hinzugekommenen +Kandidaten sind die exponierteren (Ingress, öffentliches Wiki), nicht die +harmloseren. + +⚠️ **Ein Kandidat ist vermutlich gar nicht zu beheben, sondern zu entfernen:** +`rohana.axion1337.de/sorb/axion-backup:v1` meldet CRITICAL, wird aber nirgends +betrieben — er steht nur in der Zielmenge, weil die Registry-Auswahl „die +letzten drei Fassungen je Repo" nimmt und das Repo nur zwei hat. Ein +Registry-Aufräumen wäre hier die billigere Antwort als ein Update. diff --git a/docs/ledger/2026-08-21-cve-ziele-ableiten.md b/docs/ledger/2026-08-21-cve-ziele-ableiten.md index 28f349c..d20fe00 100644 --- a/docs/ledger/2026-08-21-cve-ziele-ableiten.md +++ b/docs/ledger/2026-08-21-cve-ziele-ableiten.md @@ -23,6 +23,8 @@ related: | slice | commit | status | note | |---|---|---|---| +| 5 | | | OPEN | Wartet auf Freigabe; Ausrollen durch sorb, Deckung 100 % bei 56 Zielen gemessen. | +| 5 | (slice) | DONE | cAdvisor gegen docker compose config: Luecke 0, und vier Images gesehen, die in keinem Compose stehen. | | 4 | 07875ba | DONE | Berichte ausgefallener Ziele entfernt; Test laedt die echte runde() statt sie nachzubauen. | | 3 | 386b65a | DONE | Scanner liest die abgeleitete Menge, images.txt geloescht (Kriterium 1). | | 2 | b989987 | DONE | Vier Regeln statt drei; erste Regeltests im Stack, zwei Sabotagen belegen sie. | diff --git a/docs/wiki/stolpersteine/nachgebauter-test-prueft-den-nachbau.md b/docs/wiki/stolpersteine/nachgebauter-test-prueft-den-nachbau.md new file mode 100644 index 0000000..a8aa45b --- /dev/null +++ b/docs/wiki/stolpersteine/nachgebauter-test-prueft-den-nachbau.md @@ -0,0 +1,88 @@ +--- +type: wiki-page +area: stolpersteine +status: open +sources: + - "docs/design/2026-08-21-cve-ziele-ableiten.md" +related: + - "docs/wiki/stolpersteine/meldet-erfolg-ist-aber-blind.md" + - "docs/wiki/stolpersteine/fremder-defekt-der-aenderung-angelastet.md" +--- + +# Ein nachgebauter Test prüft den Nachbau + +> **Stand: offen.** Noch nicht an das Rahmenwerk übergeben. + +Ein Test, der die zu prüfende Logik **nachbildet**, statt sie zu **laden**, +prüft die Nachbildung. Er kann dauerhaft grün bleiben, während die ausgelieferte +Datei kaputt ist — und er sieht dabei genauso aus wie ein richtiger Test: +gleiche Zusicherungen, gleiche Ausgabe, gleiche Zeile im Abschlussbericht. + +Das ist der Sonderfall von [[meldet-erfolg-ist-aber-blind]], der die Prüfung +selbst trifft statt das Geprüfte. + +## Das belegte Vorkommen + +Am **2026-08-21** entstand ein Shell-Test für eine Scan-Schleife. Die Schleife +lebt in einer Datei, die als Endlosschleife startet und deshalb nicht ohne +Weiteres geladen werden kann. Der bequeme Ausweg: den Rumpf im Test noch einmal +hinschreiben, „wortgleich zur echten Datei". + +**Dreizehn Zusicherungen liefen grün.** Darunter der wichtigste Fall überhaupt — +*„ohne Zielliste darf nichts gelöscht werden"*, dessen Verletzung den gesamten +Berichtsbestand vernichtet hätte. + +Beim Umbau auf *Laden* der echten Funktion (Ladewache über eine +Umgebungsvariable, die den Treiber am Anlaufen hindert) fielen **binnen eines +einzigen Laufs** fünf Zusicherungen um: Die ausgelieferte Datei setzte ihre +Pfade **fest** und ignorierte die Umgebung vollständig. Der Nachbau tat es +nicht, weil im Nachbau die Pfade aus dem Test kamen. + +Der Unterschied war also nicht theoretisch. Er war schon da, und der grüne Test +hat ihn zugedeckt. + +## Warum es so verführerisch ist + +Der Nachbau ist fast immer der **kürzere** Weg, und er sieht aus wie +Sorgfalt — schließlich steht die Logik zweimal da, einmal geprüft. Drei +Umstände machen ihn attraktiv: + +1. **Die Datei lässt sich nicht laden**, weil sie beim Laden etwas tut + (Endlosschleife, Serverstart, Seiteneffekt beim Import). +2. **Der Nachbau ist beim Schreiben korrekt.** Er entsteht ja aus derselben + Vorlage; die Abweichung entsteht erst später, oder — wie hier — sie war von + Anfang an da und fiel nicht auf, weil beide Fassungen nebeneinander gelesen + und für gleich gehalten wurden. +3. **Der Test wird nie wieder angefasst.** Die geprüfte Datei ändert sich, der + Nachbau nicht. Ab da prüft er eine Vergangenheit. + +## Was stattdessen zu tun ist + +**Die zu prüfende Einheit muss ladbar sein, und der Test muss sie laden.** + +Ist die Datei nicht ladbar, ist *das* der zu behebende Mangel — nicht ein +Grund für einen Nachbau. Die übliche Lösung kostet drei Zeilen: + +- Den Rumpf in eine **Funktion** heben und den Treiber ans Ende stellen. +- Eine **Ladewache** davor, die den Treiber überspringt, wenn eine + Umgebungsvariable gesetzt ist (`[ "${X_NUR_LADEN:-0}" = "1" ] && return 0`). + In Python ist das `if __name__ == "__main__":` — dieselbe Sache, nur + eingebaut. +- Die Berührpunkte nach außen (Netzaufruf, Unterprozess) als **eigene + Funktionen**, die der Test überschreibt. Nicht die Logik nachbauen, nur die + Ränder ersetzen. + +**Die Gegenprobe, die den Unterschied sichtbar macht:** Die geprüfte Datei +absichtlich kaputt machen und schauen, ob der Test rot wird. Ein Test, der eine +Nachbildung prüft, bleibt dabei **grün** — und genau daran erkennt man ihn. + +## Warum das nicht plattformspezifisch ist + +Die Umstände sind allgemein: eine Datei, die beim Laden etwas tut, und ein Test, +der es bequem haben will. Das trifft Shell-Skripte mit Endlosschleife, +Dienste, die beim Import lauschen, Konfigurationen, die im Test „nachgebildet" +statt eingelesen werden, und jede Prüfung, die eine Regel im Testcode noch +einmal formuliert, statt die ausgelieferte Regel zu laden. + +Erntewürdig ist die Regel dahinter: **Wenn ein Test die zu prüfende Einheit +nicht laden kann, ist die Einheit zu ändern — nicht der Test zu erfinden.**