Files
nexarch/docs/API-13-PRUEFPROTOKOLL.md
patrickandClaude Sonnet 5 81f7165bfc
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
API-13: Prüfprotokoll nachgetragen (Master-Key-Rotation & Zugriffs-Audit)
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>
2026-10-15 20:30:55 +00:00

7.6 KiB
Raw Permalink Blame History

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.