API-13: Prüfprotokoll nachgetragen (Master-Key-Rotation & Zugriffs-Audit)
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Archive-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s

Bereits auf main implementierte Rotations-/Audit-Logik real gegen eine
Postgres-Testdatenbank verifiziert (kein Mocking). Alle 3 Akzeptanzkriterien
und alle 3 geforderten Prüfungen bestanden, 13/13 Tests in internal/kek grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-10-15 20:30:55 +00:00
1 parent dac7fff504
commit 81f7165bfc
1 file changed
+138
+138
View File
@@ -0,0 +1,138 @@
# API-13 – Prüfprotokoll: Master-Key-Rotation & Zugriffs-Audit
**Datum:** 2026-10-15
**Prüfer:** Claude Sonnet 5 (Subagent, im Auftrag von bundyxl@gmx.de)
**Umgebung:** Host 192.168.1.204, frisches Wegwerf-Arbeitsverzeichnis `/root/api-13-work-1792096067`, Postgres 17 (Rolle `nexarch_test`, Wartungs-DB `postgres`, Port 5433)
**Branch:** `feature/api-13-master-key-rotation-zugriffs-audit`
## Befund vor Prüfung
API-13 war im Core-Repo bereits vollständig implementiert (Commits `bc2126f` API-10, `99001a5` API-13,
`d2e5904` API-13-Testfix, alle bereits auf `main` gemergt). Es gab keinen offenen Feature-Branch und keine
Pull-Request-Spur für API-13 — der Code existiert, wurde aber nie formal durch dieses Prüfprotokoll
abgenommen. Diese Session hat daher keinen neuen Code geschrieben, sondern den bestehenden Code gegen alle
drei Akzeptanzkriterien real verifiziert (echte Postgres-Testdatenbank, keine Mocks) und dieses Protokoll
nachgetragen.
Betroffene Dateien: `internal/kek/master.go`, `internal/kek/store.go`, `internal/kek/handler.go`,
`internal/kek/rotation_monitoring.go`, `internal/kek/kek_test.go`, `migrations/0008_kek_master_key_rotation_state.up.sql`.
## Prüfung 1 — Master-Key-Rotationstest (Akzeptanzkriterium 1)
**Ziel:** Nach Rotation des Master-Keys bleiben alle Tenant-KEKs mit demselben Klartext entschlüsselbar;
Objekt-DEKs werden nicht angefasst.
**Vorgehen:** `Store.RotateMasterKey(ctx, oldKey, newKey)` entpackt jeden `tenant_keks.wrapped_kek`-Datensatz
mit dem alten Master-Key und verpackt denselben Klartext-KEK mit dem neuen Master-Key neu — der
Tenant-KEK-Klartext selbst ändert sich nie. Objekt-DEKs liegen strukturell außerhalb dieser Tabelle/dieses
Moduls (DMS/Mail-eigene Schemas) und werden von `RotateMasterKey` gar nicht berührt (kein Zugriff auf andere
Tabellen/Module im Code).
**Test:** `TestRotateMasterKey_AllTenantKEKsRemainDecryptableWithSamePlaintext`
(`internal/kek/kek_test.go`) — legt mehrere Tenant-KEKs mit altem Master-Key an, rotiert, prüft dass
`GetDecrypted` mit dem neuen Master-Key denselben Klartext wie vor der Rotation liefert.
**Ergebnis:** PASS (echte DB, 0.18s). Zielwert erreicht.
```
=== RUN TestRotateMasterKey_AllTenantKEKsRemainDecryptableWithSamePlaintext
--- PASS: TestRotateMasterKey_AllTenantKEKsRemainDecryptableWithSamePlaintext (0.18s)
```
## Prüfung 2 — Audit-Event bei Tenant-KEK-Zugriff (Akzeptanzkriterium 2)
**Ziel:** Jede Entschlüsselung eines Tenant-KEK durch den Master-Key erzeugt einen eigenen,
hochprivilegierten Audit-Event-Typ (`kek.master_key_access`), getrennt von normalen Audit-Events
abrufbar, mit Akteur, Zeitpunkt und betroffenem Tenant.
**Vorgehen:** `Store.GetDecrypted(ctx, tenantID, masterKey, actor)` schreibt bei konfiguriertem
`WithAudit(log)` einen `audit.Event` mit `Action = MasterKeyAccessAction`, `Actor = <Service-Credential>`,
`Target = tenantID` (Zeitpunkt wird vom zentralen Audit-Log selbst gesetzt, AUD-01). `Store.
StreamMasterKeyAccessEvents` erzwingt `filter.Action = MasterKeyAccessAction` und liest ausschließlich diese
Events — kein Vermischen mit normalen Audit-Events möglich.
**Tests:**
- `TestTenantKEKHandler_RecordsPrivilegedMasterKeyAccessAuditEvent` — ruft den HTTP-Handler mit einem
authentifizierten, für den Tenant aktivierten Modul auf, prüft danach den geschriebenen Audit-Event
(Akteur = Modulname, Target = Tenant, Action = `kek.master_key_access`).
- `TestStreamMasterKeyAccessEvents_OnlyReturnsPrivilegedEvents` — schreibt sowohl privilegierte als auch
normale Audit-Events, prüft dass der Stream ausschließlich die privilegierten liefert.
- `TestStreamMasterKeyAccessEvents_RequiresAudit` — ohne konfiguriertes Audit-Log liefert der Store
`ErrAuditNotConfigured` statt stillschweigend leer zu bleiben.
**Ergebnis:** PASS (alle drei Tests, echte DB). Zielwert erreicht.
```
=== RUN TestTenantKEKHandler_RecordsPrivilegedMasterKeyAccessAuditEvent
--- PASS: TestTenantKEKHandler_RecordsPrivilegedMasterKeyAccessAuditEvent (0.14s)
=== RUN TestStreamMasterKeyAccessEvents_OnlyReturnsPrivilegedEvents
--- PASS: TestStreamMasterKeyAccessEvents_OnlyReturnsPrivilegedEvents (0.13s)
=== RUN TestStreamMasterKeyAccessEvents_RequiresAudit
--- PASS: TestStreamMasterKeyAccessEvents_RequiresAudit (0.06s)
```
Zusätzlich geprüft (Zugriffskontrolle vor dem eigentlichen Zugriff, Voraussetzung für einen aussagekräftigen
Akteur im Audit-Event):
```
=== RUN TestTenantKEKHandler_RejectsMissingCredential
--- PASS: TestTenantKEKHandler_RejectsMissingCredential (0.16s)
=== RUN TestTenantKEKHandler_RejectsModuleNotActiveForTenant
--- PASS: TestTenantKEKHandler_RejectsModuleNotActiveForTenant (0.12s)
=== RUN TestTenantKEKHandler_AllowsActiveModuleForTenant
--- PASS: TestTenantKEKHandler_AllowsActiveModuleForTenant (0.12s)
```
## Prüfung 3 — Konfigurierbares Rotationsintervall & OPS-Monitoring-Warnung (Akzeptanzkriterium 3)
**Ziel:** Rotationsintervall ist über Umgebungsvariable konfigurierbar; bei Überschreitung erscheint eine
Warnung im bestehenden OPS-Monitoring statt einer parallelen Alarmierungslösung.
**Vorgehen:** `LoadRotationIntervalFromEnv` liest das Intervall (Tage) aus einer Umgebungsvariable, Default
90 Tage. `Store.MasterKeyAgeSeconds` liefert die Sekunden seit letzter Rotation (Singleton-Zeile aus
Migration `0008_kek_master_key_rotation_state`). `NewMasterKeyAgeGauge` exportiert diese Kennzahl als
Prometheus-Collector (`nexarch_core_kek_master_key_age_seconds`, OPS-03-Muster). `EnsureRotationAlertRule`
registriert — idempotent — eine Schwellwertregel im bestehenden `internal/alerting`-Regelspeicher (OPS-05),
die warnt, sobald die Kennzahl das konfigurierte Intervall überschreitet. Kein eigener, paralleler
Alarmierungsweg.
**Tests:**
- `TestLoadRotationIntervalFromEnv` — Default-Wert und explizite Umgebungsvariable, inkl. Fehlerfälle
(keine Zahl, negativer/0-Wert).
- `TestMasterKeyAgeSeconds_ResetsAfterRotation` — Alter wächst mit der Zeit und wird durch
`RotateMasterKey` auf ~0 zurückgesetzt.
- `TestEnsureRotationAlertRule_FiresWhenIntervalExceeded` — legt die Alert-Regel an, setzt die
Rotationszeit künstlich in die Vergangenheit (älter als Intervall), prüft dass die reale
`internal/alerting`-Infrastruktur die Regel als überschritten/ausgelöst meldet.
- `TestEnsureRotationAlertRule_IdempotentForSameInterval` — zweiter Aufruf mit gleichem Intervall legt
keine doppelte Regel an; geändertes Intervall aktualisiert die bestehende Regel statt eine zweite
anzulegen.
**Ergebnis:** PASS (alle vier Tests, echte DB). Zielwert erreicht.
```
=== RUN TestLoadRotationIntervalFromEnv
--- PASS: TestLoadRotationIntervalFromEnv (0.00s)
=== RUN TestMasterKeyAgeSeconds_ResetsAfterRotation
--- PASS: TestMasterKeyAgeSeconds_ResetsAfterRotation (0.14s)
=== RUN TestEnsureRotationAlertRule_FiresWhenIntervalExceeded
--- PASS: TestEnsureRotationAlertRule_FiresWhenIntervalExceeded (0.12s)
=== RUN TestEnsureRotationAlertRule_IdempotentForSameInterval
--- PASS: TestEnsureRotationAlertRule_IdempotentForSameInterval (0.08s)
```
## Gesamt-Testlauf
```
go build ./... → OK
go vet ./internal/kek/... → OK
go test ./internal/kek/... -v -p 1 -count=1 → 13/13 PASS, 1.670s
```
Zusätzlich: `TestLoadMasterKeyFromEnv` und `TestCreateForTenant_EachTenantGetsDistinctKEK` (Grundlagen aus
API-10, als Regressionsschutz mitgeprüft) ebenfalls PASS.
## Fazit
Alle drei Akzeptanzkriterien und alle drei geforderten Prüfungen sind real gegen eine echte
Postgres-Testdatenbank (keine Mocks) nachgewiesen. Kein offener Punkt. Keine Code-Änderung in dieser
Session erforderlich — bestehende Implementierung erfüllt das Ticket vollständig.