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
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:
1 parent
dac7fff504
commit
81f7165bfc
1 file changed
+138
@@ -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.
|
||||
Reference in new issue
Block a user