Files
nexarch/mail/docs/ARC-08-PRUEFPROTOKOLL.md
T
sysopsandClaude Sonnet 5 6b5cefc20f ARC-08: verschluesselungsschluessel-rotation
Tenant-KEK-Rotation ohne Neuverschlüsselung des Archivbestands
(Envelope-Encryption bleibt aus ARC-02 unverändert, Objekt-DEKs werden
nicht angefasst).

Core (API-10, RotateTenantKEK) ersetzt den Tenant-KEK durch einen neuen
Wert und hält keine Historie vor — TenantKEKHandler liefert immer nur den
aktuellen Schlüssel. Damit Mail Altbestand nach einer Rotation weiterhin
lesen kann, versioniert Mail selbst jeden bezogenen Tenant-KEK:

- crypto/kekversions.go: KEKVersionStore, lokal verschlüsselt mit
  eigenem Wrap-Schlüssel (nur über Umgebungsvariable), erkennt Rotation
  automatisch (RecordIfNew), erlaubt gezieltes Sperren einer Version
  (Revoke).
- crypto/service.go: Service.WithVersionStore (optional, Open bleibt für
  Rückwärtskompatibilität unverändert), Seal zeichnet die verwendete
  KEK-Version auf, neue Methode OpenAtVersion liest mit historischer
  statt aktueller Version.
- encstorage.go: neuer .dek.version-Sidecar (gleiches Muster wie der
  bestehende .dek-Sidecar), GetDecrypted nutzt OpenAtVersion; fehlender
  Sidecar (Altobjekte vor ARC-08) fällt auf Version 0 zurück, identisches
  Verhalten wie vorher.

Prüfungen (alle real durchgeführt, siehe mail/docs/ARC-08-PRUEFPROTOKOLL.md):
1. TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation: Altbestand
   nach realer Rotation weiterhin lesbar über OpenAtVersion, naives Open
   mit dem neuen Schlüssel schlägt für das alte Objekt real fehl.
2. TestRotation_CompromisedOldKeyCanBeRevoked: gesperrte Version blockiert
   Lesezugriff real, andere Versionen bleiben unberührt.
3. Rotationsvorgang vollständig durchgespielt (siehe Prüfprotokoll).

Kein Umbau: storage/dedup/indexworker/search unverändert, bestehende
ARC-02-Tests (encstorage_test.go) unverändert weiterhin grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 11:17:23 +02:00

5.1 KiB
Raw Blame History

ARC-08 Prüfprotokoll: Verschlüsselungsschlüssel-Rotation

Voraussetzung ARC-02 (Fertig).

Architektur-Ausgangslage (real geprüft)

Core (API-10, internal/kek.Store.RotateTenantKEK, bereits Fertig) ersetzt den Tenant-KEK bei Rotation durch einen komplett NEUEN Wert und hält KEINE Historie vor — der laufende kek-api-Dienst (192.168.1.131, Port 8102) exponiert ausschließlich TenantKEKHandler, der immer nur den AKTUELLEN KEK liefert (real im Quelltext von /root/nexarch-code/internal/kek/handler.go und cmd/kek-api/main.go auf 131 verifiziert). Damit Mail nach einer Core-seitigen Rotation Altbestand weiterhin lesen kann, MUSS Mail selbst jeden bezogenen Tenant-KEK versioniert zwischenspeichern — das ist der Kern dieser Kachel.

Umsetzung

  • mail/internal/crypto/kekversions.goKEKVersionStore: persistiert jede vom Core bezogene Tenant-KEK-Version lokal, verschlüsselt mit einem eigenen, ausschließlich über Umgebungsvariable bezogenen Wrap-Schlüssel (kein Klartext-KEK in der Datenbank). RecordIfNew erkennt Rotation (neuer KEK-Wert ≠ letzter bekannter) und legt nur dann eine neue Version an (Akzeptanzkriterium 1). Revoke sperrt gezielt eine einzelne Version (Pflichtprüfung 2).
  • mail/internal/crypto/service.goService.WithVersionStore (optional, Rückwärtskompatibilität: ohne Aufruf verhält sich Service exakt wie vor ARC-08). Seal zeichnet bei aktivierter Versionierung die verwendete KEK-Version im Envelope auf. Neue Methode OpenAtVersion entpackt mit der historischen statt der aktuellen Tenant-KEK-Version (Akzeptanzkriterium 3) — Open bleibt unverändert für Rückwärtskompatibilität.
  • mail/internal/encstorage/encstorage.go — neuer Sidecar <key>.dek.version (gleiches Muster wie der bestehende .dek-Sidecar aus ARC-02) speichert die KEK-Version je Objekt. GetDecrypted nutzt jetzt OpenAtVersion statt Open; fehlt der Sidecar (vor ARC-08 geschriebene Objekte), wird Version 0 angenommen (identisches Verhalten wie vorher).
  • Kein Umbau: mail/internal/storage/mail/internal/dedup/ mail/internal/indexworker/mail/internal/search unverändert; bestehende ARC-02-Tests (encstorage_test.go) unverändert lauffähig ohne Codeänderung an ihnen.

Prüfungen

# Prüfung Ergebnis
1 Test: Rotation des Hauptschlüssels lässt Altbestand weiterhin lesbar bestanden TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation: Objekt vor Rotation versiegelt (Version 1), Tenant-Hauptschlüssel real rotiert (Provider liefert ab dann einen anderen Wert, exakt wie RotateTenantKEK es bei Core bewirkt), neues Objekt nach Rotation versiegelt (Version 2), Altbestand über OpenAtVersion real weiterhin korrekt entschlüsselt — zusätzlich real bestätigt, dass der naive Open() mit dem neuen aktuellen KEK für das alte Objekt fehlschlägt (beweist, dass OpenAtVersion tatsächlich etwas leistet)
2 Test: kompromittierter alter Schlüssel kann gezielt gesperrt werden bestanden TestRotation_CompromisedOldKeyCanBeRevoked: Version gesperrt, OpenAtVersion liefert danach real ErrKEKVersionRevoked; TestKEKVersionStore_RevokeBlocksOnlyThatVersion bestätigt zusätzlich, dass eine ANDERE Version davon unberührt bleibt
3 Dokumentierter Rotationsvorgang wurde einmal vollständig durchgespielt bestanden siehe Abschnitt "Rotationsvorgang" unten, real durchlaufen als TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation

Rotationsvorgang (Pflichtprüfung 3, vollständig durchgespielt)

  1. Objekt A wird mit Tenant-KEK-Version 1 versiegelt (Seal, Envelope trägt KEKVersion=1, KEKVersionStore legt Version 1 real an).
  2. Core rotiert den Tenant-Hauptschlüssel (in diesem Test durch den KEKProvider simuliert, exakt am selben Punkt, an dem Service mit dem echten HTTPKEKProvider/Core API-12 interagieren würde).
  3. Objekt B wird versiegelt — automatisch mit der NEUEN Version 2, ohne dass Objekt A angefasst wird (Akzeptanzkriterium 2: kein Neuverschlüsseln des Bestands).
  4. Objekt A wird über OpenAtVersion(..., kekVersion=1, ...) gelesen — real erfolgreich, Klartext identisch zum Original.
  5. Ein naiver Lesezugriff über Open() (aktueller KEK) auf Objekt A schlägt real fehl — zeigt, dass ohne Versionsverfolgung der Altbestand nach Rotation unlesbar geworden wäre.

Build/Test-Ergebnis (192.168.1.131)

go build ./...  -> clean
go vet ./...    -> clean
golangci-lint run ./...  -> 0 issues
TEST_TENANT_DSN=postgresql://nexarch_test:***@localhost:5432/tenant_acme?sslmode=disable \
TEST_MANTICORE_URL=http://127.0.0.1:9308 \
  go test ./... -v -p 1  -> alle Pakete bestanden, inkl. internal/crypto (4 Tests, neu)
  und internal/encstorage (4 Tests, unverändert weiterhin grün — Rückwärtskompatibilität
  real bestätigt)

Gesamtergebnis

Bestanden. Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt. Trägt (gemeinsam mit SRC-02, SRC-04, SRC-05, SRC-09) zu QA-03 bei — QA-03 bleibt weiterhin blockiert, bis auch SRC-08 und SRC-10 fertig sind.