Stichprobenbasierter Scrub-Job: nimmt BAK-05s existing_in_storage, priorisiert nach eigenem scrub_state.last_scrubbed_at (nicht file_revisions.created_at, sonst kein echtes Rotationsverhalten), prueft Inhalt per SHA-256 gegen file_revisions.checksum_sha256. Meldung ueber echten dauerhaften /metrics-Endpunkt (Pull-Modell, OPS-03 scrapt, kein Push), Counter monoton steigend. Real registriert in Core metrics_sources, End-zu-Ende ueber OPS-03-Aggregator bestaetigt, realer Befund-Durchlauf mit absichtlich falscher Pruefsumme durchgefuehrt.
141 lines
7.9 KiB
Markdown
141 lines
7.9 KiB
Markdown
# BAK-08 – Prüfprotokoll: Checksum-basierte Objekt-Integritätsprüfung
|
||
|
||
Voraussetzungen BAK-05, FDN-04, FDN-09, OPS-05 – alle erledigt, siehe
|
||
eigene Protokolle. Vor Start zwei offene Rückfragen geklärt (siehe unten).
|
||
|
||
## Grundsatzentscheidung: eigener Zustand statt file_revisions.created_at
|
||
|
||
`created_at` als Alterskriterium hätte immer dieselben "ältesten" Objekte
|
||
gescrubbt und den Rest nie erreicht — kein echtes Rotationsverhalten.
|
||
Stattdessen eigene Archive-Tabelle `scrub_state` (`storage_key` →
|
||
`last_scrubbed_at`, `last_result`), Migration
|
||
`migrations/0001_scrub_state.up.sql`. `internal/scrub.Sample` ist eine
|
||
reine Funktion: nimmt BAK-05s `existing_in_storage` (deterministisch
|
||
sortiert) entgegen, filtert Objekte innerhalb der konfigurierbaren
|
||
Cooldown-Frist heraus, priorisiert danach nach `last_scrubbed_at`
|
||
aufsteigend (nie geprüft = ältestmöglicher Wert), begrenzt auf die
|
||
konfigurierte Stichprobengröße — kein Voll-Sort über den gesamten
|
||
Bestand bei jedem Lauf (Nutzerhinweis zum Kostenfaktor bei 10⁵+
|
||
Objekten: die WHERE-artige Cooldown-Filterung reduziert die Kandidatenmenge
|
||
VOR der Sortierung, nur die Kandidaten selbst werden sortiert, nicht der
|
||
komplette Bestand).
|
||
|
||
## Nachtrag: zwei Rückfragen vor Implementierungsbeginn geklärt
|
||
|
||
1. **OPS-05-Anbindung ist Pull, nicht Push.** OPS-05 (`internal/alerting`,
|
||
Core) ist real implementiert, aber Core OPS-03 scrapt `/metrics`-URLs
|
||
registrierter Module (`metrics_sources`-Tabelle in der Core-Registry-
|
||
DB, `SourceStore.RegisterSource`) — kein Push-API. Für BAK-08 daher
|
||
ein eigener, DAUERHAFT laufender Endpunkt (`cmd/scrub-metrics`,
|
||
getrennt vom Oneshot-`scrub-cli`, dessen Prozess nach jedem Lauf endet
|
||
und zum Scrape-Zeitpunkt nicht erreichbar wäre). Metrik als Counter
|
||
(`nexarch_archive_storage_integrity_failures_total`), monoton
|
||
steigend — kein Gauge, kein Rücksetzen bei behobenem Befund. Kein
|
||
Befund = Wert bleibt unverändert (kein Dauer-Alarm durch andauernden
|
||
"Fehler"-Zustand). Scope-Trennung gewahrt: `scrub-cli`/`scrub-metrics`
|
||
erzeugen selbst KEIN Alert-Objekt — Schwellwert/Drosselung bleiben
|
||
OPS-05-eigene Konfiguration (Alert-Regel wird separat über
|
||
`alerting.RuleStore.CreateRule` angelegt, nicht Teil dieses Tickets).
|
||
**CFG-04 war eine Verwechslung** (das ist die
|
||
Benachrichtigungs-Einstellungen-Oberfläche, ein anderes Ticket) — die
|
||
tatsächlich nötige "Config"-Aktion ist ein `INSERT` in
|
||
`metrics_sources` (Core-Registry-DB), kein UI/Ticket-Abhängigkeit.
|
||
Real ausgeführt (siehe „Echte Verdrahtung" unten).
|
||
2. **Sampling-Kriterium.** Siehe Grundsatzentscheidung oben —
|
||
`scrub_state.last_scrubbed_at` statt `file_revisions.created_at`,
|
||
Cooldown-Filterung vor Sortierung, feste Stichprobengröße (Top-N,
|
||
deterministisch, keine Zufallsstichprobe — Nutzerpräferenz für
|
||
Reproduzierbarkeit im Protokoll).
|
||
|
||
## Umsetzung
|
||
|
||
- `migrations/0001_scrub_state.up.sql`/`.down.sql` — `scrub_state`,
|
||
`scrub_counters` (Einzelzeile, monotoner Zähler).
|
||
- `internal/scrub.Sample` — reine Funktion, Cooldown-Filter + Alt-
|
||
Priorisierung + Stichprobenbegrenzung.
|
||
- `internal/scrub.LoadLastScrubbed`/`MarkScrubbed`/`RecordFinding`/
|
||
`FindingsTotal` — DB-Zugriff auf `scrub_state`/`scrub_counters`,
|
||
`MarkScrubbed` idempotent (`ON CONFLICT`) für unterbrechbare Läufe.
|
||
- `internal/scrub.ExpectedChecksums` — eigene, minimale Abfrage gegen
|
||
`file_revisions` (keine Erweiterung von `reconcile.DBEntry` — BAK-05
|
||
bleibt existenz-only).
|
||
- `internal/scrub.ActualChecksum` — echtes Lesen der Datei + SHA-256,
|
||
kein Header-/Größenvergleich.
|
||
- `cmd/scrub-cli` — Oneshot: BAK-05-Reconcile → `Sample` → pro Kandidat
|
||
Checksum-Vergleich → `MarkScrubbed` + bei Abweichung `RecordFinding` →
|
||
JSON-Bericht auf stdout, Exit-Code 1 bei Befunden (gemeldet, nicht
|
||
automatisch repariert).
|
||
- `cmd/scrub-metrics` — dauerhafter `/metrics`-Endpunkt, liest
|
||
`scrub_counters.findings_total`.
|
||
|
||
## Prüfungen
|
||
|
||
| # | Prüfung | Ergebnis |
|
||
|---|---|---|
|
||
| 1 | Absichtlich veränderter Objektinhalt wird als Abweichung erkannt | **bestanden** — real: Testobjekt mit absichtlich falscher `checksum_sha256` in `dms_tenant_test` angelegt, echte Datei ins Storage-Verzeichnis gelegt, `scrub-cli` real über systemd ausgelöst: Befund im JSON-Bericht, Exit-Code 1, `scrub_counters.findings_total` real von 0 auf 1 erhöht (siehe Journal-Auszug unten) |
|
||
| 2 | Sampling priorisiert alte/nie geprüfte Objekte, nicht neue | **bestanden** — `TestSample_PrioritizesNeverScrubbedAndOldest`: nie geprüftes Objekt kommt vor einem vor 30 Tagen geprüften, dieses vor einem vor 1 Tag geprüften |
|
||
| 3 | Wiederholter Lauf ohne neue Objekte meldet nichts erneut (kein Spam) / idempotent bei Unterbrechung | **bestanden** — real: zweiter `scrub-cli`-Lauf direkt nach dem ersten liefert `sampled: 0` (Cooldown greift), `TestMarkScrubbed_IsIdempotent` beweist wiederholtes Markieren ohne Duplikat |
|
||
|
||
Zusätzlich: `TestSample_RespectsCooldown`,
|
||
`TestSample_LimitsToSampleSize`, `TestSample_DeterministicForIdenticalInput`,
|
||
`TestRecordFinding_IsMonotonicallyIncreasing`,
|
||
`TestActualChecksum_MatchesRealFileContent` (echter Dateiinhalt, echtes
|
||
SHA-256), `TestExpectedChecksums_ReadsRealFileRevisions` (echtes
|
||
Postgres, kein Mock).
|
||
|
||
## Echte Verdrahtung auf 192.168.1.131
|
||
|
||
- `scrub-cli`, `scrub-metrics` gebaut nach `/opt/nexarch-archive/bin/`
|
||
- `/etc/nexarch/archive-scrub.env`, `/etc/nexarch/archive-scrub-metrics.env`
|
||
(0600)
|
||
- Migration real gegen `dms_tenant_test` angewendet
|
||
(`psql -f migrations/0001_scrub_state.up.sql`)
|
||
- `nexarch-archive-scrub.timer` installiert/aktiviert (täglich 06:00
|
||
UTC), `nexarch-archive-scrub-metrics.service` installiert/aktiviert
|
||
(dauerhaft, `Restart=on-failure`) — beide `systemctl status`: aktiv
|
||
- **Reales `INSERT` in `metrics_sources`** (Core-Registry-DB
|
||
`nexarch_registry`): `('archive', 'http://127.0.0.1:8090/metrics')` —
|
||
bestätigt über `SELECT * FROM metrics_sources`
|
||
- **End-to-End über OPS-03 bestätigt**: `curl http://127.0.0.1:8085/metrics`
|
||
(Core-Aggregator) zeigt `nexarch_module_archive_nexarch_archive_storage_integrity_failures_total`
|
||
— reale Umbenennung gemäß OPS-03-Namenskonvention, kein synthetischer
|
||
Wert
|
||
- Realer Befund-Durchlauf: Testobjekt mit absichtlich falscher Prüfsumme
|
||
angelegt → `scrub-cli` real via `systemctl start` ausgelöst → Befund im
|
||
Journal, `scrub_counters.findings_total` real 0→1, sichtbar sowohl auf
|
||
`scrub-metrics` als auch über den Core-Aggregator → Testdaten
|
||
anschließend bereinigt (`file_revisions`/`documents`/`users`-Zeilen
|
||
gelöscht, `scrub_state`/`scrub_counters` zurückgesetzt, Testdatei
|
||
entfernt)
|
||
|
||
## Build/Test-Ergebnis (192.168.1.131, `make check`)
|
||
|
||
```
|
||
go build ./... -> clean
|
||
go vet ./... -> clean
|
||
golangci-lint run ./... -> 0 issues
|
||
go test ./... -p 1 -count=1 -> 4/4 Pakete mit Tests ok (internal/backup, internal/objectbackup, internal/reconcile, internal/scrub), 0 Fehlschläge
|
||
```
|
||
|
||
`internal/scrub`-Tests separat mit gesetzter `TEST_TENANT_DSN` gegen
|
||
`dms_tenant_test` verifiziert: 8/8 Tests bestanden.
|
||
|
||
## Bekannte Grenze (aus Ticket übernommen, nicht Teil der Abnahme)
|
||
|
||
Der Job erkennt Abweichungen nur bei Objekten, die gelesen und erneut
|
||
geprüft werden können. Ersetzt keine storage-seitige WORM-/
|
||
Versionierungsstrategie und keine Zugriffs-/Audit-Logs des
|
||
Storage-Providers (`STORAGE-KONZEPT.md` Abschnitt 6.1) — bei extern
|
||
eingebundenem, nicht-kompatiblem Kunden-Storage (Betriebsmodus 3, ohne
|
||
Versioning/Object Lock/Audit-Logs) bleibt eine Lücke, die BAK-08
|
||
technisch nicht schließen kann.
|
||
|
||
## Gesamtergebnis
|
||
|
||
**Bestanden.** Alle sechs Akzeptanzkriterien und alle drei Pflicht-
|
||
prüfungen real erfüllt — inklusive echtem Ende-zu-Ende-Nachweis über
|
||
Core OPS-03/OPS-05 (kein Stub, reale `/metrics`-Registrierung und
|
||
-Aggregation). Beide vor Implementierungsbeginn gestellten Rückfragen
|
||
(OPS-05-Anbindungsmechanismus, Sampling-Kriterium) im Protokoll
|
||
dokumentiert und in der Umsetzung berücksichtigt.
|