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>
7.6 KiB
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 StoreErrAuditNotConfiguredstatt 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 durchRotateMasterKeyauf ~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 realeinternal/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.