Files
nexarch/docs/API-16-PRUEFPROTOKOLL.md
patrickandClaude Sonnet 5 9fb2b25cbb
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) 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 / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (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
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 / 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
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
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
API-16: Pruefprotokoll
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 23:54:50 +02:00

7.8 KiB
Raw Permalink Blame History

API-16 – Prüfprotokoll: Persistente Modul-Signaturschlüssel und Rotation

Ticket: core-kanban/tickets/API-16.md (Planungs-Repo, Commit bfea8f8). Branch feature/api-16-persistente-modul-signaturschluessel, Basis integration/gesamtprojekt (6b9350b). Geprüft auf dem Entwicklungshost 204 (Wegwerf-Cluster 17/coredev, seriell über die gemeinsame Sperre; der archivdms-Cluster blieb unberührt).

Umsetzung

  • Ablage: Tabelle module_signing_keys in der Mandanten-Datenbank (migrations/tenant/0008_module_signing_keys, up und down). Abweichung vom Auftragstext, der die Registry nannte: Der Kontodienst kennt nur die Mandanten-DSN und stellt Tokens je Mandant aus (Modell C, wie IAM-19). Alle Instanzen desselben Mandanten teilen damit dieselbe Tabelle, jeder Mandant hat eigene Schlüssel.
  • Verschlüsselung: Der private Schlüssel (Seed) wird mit kek.EncryptWithTenantKEK (AES-256-GCM, Muster aus API-10) verpackt, der Schlüssel kommt aus NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY (base64, 32 Byte). Beim Laden wird der öffentliche Schlüssel aus dem entpackten Seed neu berechnet und mit der Spalte verglichen (vertauschte Zeilen fallen auf).
  • Instanzen: Änderungen laufen in einer Transaktion unter pg_advisory_xact_lock. Beim gleichzeitigen Start entsteht genau ein Schlüssel. Jede Instanz lädt den Satz alle REFRESH_SECONDS neu.
  • Rotation: Ein neuer Schlüssel signiert erst ab active_from = jetzt + Aktivierungsverzögerung (Verifier-Caches kennen ihn dann; JWKS-Cache-Control ist 60 s). Der bisherige Schlüssel bekommt retire_after = active_from + Token-TTL + 30 s Toleranz, steht bis dahin in der JWKS und wird danach aus JWKS und Tabelle entfernt. Auslöser: automatisch nach ROTATION_DAYS (unter der Sperre, nur eine Instanz rotiert) oder per account-api -rotate-module-key.
  • Fail closed: Ist der Speicher nicht lesbar oder ein Schlüssel nicht entschlüsselbar, gibt es weder Token (503) noch JWKS (503, no-store). Ein veralteter Satz wird nach Ablauf des Aktualisierungsintervalls nicht weiterverwendet.
  • Code: internal/moduletrust/{persist,pgstore,memstore}.go, Anpassungen in keys.go/issuer.go, internal/moduletoken/token.go (503), cmd/account-api/{main,modulekeys}.go. Signaturen und Verifier unverändert, NewKeyManager() bleibt der Prozessspeicher-Variante vorbehalten.

Umgebungsvariablen (Kontodienst)

Variable Standard Zulässig Bedeutung
NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY – (Pflicht) base64, 32 Byte Verschlüsselung der privaten Schlüssel
NEXARCH_ACCOUNT_MODULE_KEYS_EPHEMERAL false true/false Nur Prozessspeicher (Entwicklung), Tokens sind nach jedem Neustart ungültig
NEXARCH_ACCOUNT_MODULE_KEY_ROTATION_DAYS 30 0–3650 (0 = aus) Automatische Rotation nach Alter
NEXARCH_ACCOUNT_MODULE_KEY_ACTIVATION_DELAY_SECONDS 120 60–3600, ≥ Aktualisierungsintervall Verzögerung bis der neue Schlüssel signiert
NEXARCH_ACCOUNT_MODULE_KEY_REFRESH_SECONDS 30 1–300 Neuladeintervall je Instanz

Betriebshinweis (Bruch): Ein bestehender Kontodienst startet nicht mehr ohne NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY (oder ausdrücklich ..._EPHEMERAL=true). Die Tenant-Migration 0008 muss vorher angewendet sein. Der Verschlüsselungsschlüssel gehört wie die anderen Betriebsgeheimnisse in /etc/nexarch/account-api.env (nicht ins Repo).

Prüfungen und Ergebnis

Prüfung (Ticket) Ergebnis
AK1/Prüfung 1: Neustart am echten Kontodienst, Token bleibt gültig, JWKS liefert dieselbe KID bestanden (scripts/e2e-module-keys.sh, alter Prozess beendet und Port frei, neuer Prozess mit anderer PID)
AK2/Prüfung 3: zwei Instanzen gegen dieselbe Datenbank bestanden: gleiche KID, Token der einen Instanz wird mit der JWKS der anderen verifiziert, genau eine Zeile nach Start beider Instanzen; Unit- und Postgres-Test: 8 bzw. 6 parallele Starts ergeben genau einen Schlüssel
AK3/Prüfung 2: Rotation mit Überlappung bestanden am Prozess: nach der Rotation listen beide Instanzen 2 Schlüssel, signiert wird bis zur Aktivierung mit dem alten, danach mit dem neuen; der alte bleibt bis TTL + Toleranz in der JWKS und ist danach aus JWKS und Datenbank entfernt
AK4/Prüfung 3: Ausfall des Speichers bestanden: Tabelle nicht lesbar ergibt 503 für JWKS und Token, nach Wiederherstellung 200; falscher oder fehlender Verschlüsselungsschlüssel: Dienst startet nicht
Klartext-Test Chiffretext enthält weder Seed noch privaten Schlüssel (Unit- und Postgres-Test)
Manipulation vertauschte Chiffretexte zweier Zeilen und falscher Schlüssel werden beim Laden abgewiesen (fail closed)
Automatische Rotation drei Instanzen nach Ablauf des Intervalls ergeben genau eine Rotation
Migration 0008 up, down und erneutes up grün; scripts/migrations-fresh-check.sh: Core Mandant 18 (vorher 17), gemeinsame Mandanten-DB 88 Tabellen ohne Kollision
Ende-zu-Ende Core→DMS scripts/e2e-core-dms.sh grün (TestCoreE2E_TokenJWKSAndPolicy, 42,8 s)
Regression moduletrust, moduletoken, oidc, contracttest, loadtest, resync, mtls, tenant, user, rbac, auth, kek je einzeln nach Cluster-Reset grün; go build ./..., go vet Core, check-port-defaults.sh, check-route-prefixes.sh sauber

Neue Tests: internal/moduletrust/persist_test.go (9), pgstore_test.go (2, gegen echte Datenbank), internal/moduletoken/persistent_test.go (1), cmd/account-api/modulekeys_test.go (1).

Iterationen

  • Produktivcode: 0 Fix-Runden (alle neuen Tests im ersten Lauf grün).
  • Testaufbau: 1 Fix-Runde. Der erste Lauf von e2e-module-keys.sh meldete den Neustart als bestanden, obwohl er keiner war: Das Skript startete den Kontodienst über eine Shell-Funktion im Hintergrund, kill traf nur den Zwischenprozess, der alte Kontodienst lief weiter (der zweite Start scheiterte am belegten Port), und der Test lief gegen den alten Prozess. Erkannt wurde das, als e2e-core-dms.sh an „address already in use" scheiterte. Behoben: Start ohne Zwischenprozess, das Skript prüft jetzt, dass der alte Prozess beendet, der Port frei und die neue PID eine andere ist. Die Reste der ersten Läufe (zwei Prozesse auf 204) habe ich beendet, danach lief alles grün und ohne Reste.

Grenzen und offene Punkte

  1. Verschlüsselungsschlüssel: Eine Neuverpackung bei Wechsel von ..._KEY_ENCRYPTION_KEY fehlt (Nicht-Bestandteil laut Ticket). Ein Wechsel ohne Neuverpackung macht die Schlüssel unlesbar, der Dienst startet dann nicht.
  2. Token-TTL: Die Aufbewahrungsfrist des alten Schlüssels berechnet sich aus der TTL der laufenden Konfiguration. Wird die TTL verkürzt und sofort rotiert, können früher mit der längeren TTL ausgestellte Tokens den alten Schlüssel überleben (sie sind dann nicht mehr verifizierbar). Nach einer TTL-Verkürzung deshalb erst nach Ablauf der alten TTL rotieren.
  3. Uhren: Aktivierung und Ablauf hängen an der Zeit der Instanzen. Bei Abweichungen über die 30 s Toleranz hinaus sind Randfehler möglich (Zeitsynchronisation der Hosts vorausgesetzt).
  4. Widerruf ausgestellter Modul-Tokens bleibt offen (Ticket API-15/IAM-17), ebenso eine Oberfläche zur Rotation (hier nur der Aufruf account-api -rotate-module-key und die Rotation nach Alter).
  5. Speicherort: Die Schlüssel liegen je Mandanten-Datenbank; eine zentrale Registry-Ablage (Auftragstext) wäre eine eigene Architekturentscheidung, wenn ein Kontodienst mandantenübergreifend arbeiten soll.
  6. Nicht geprüft: Betrieb hinter dem Reverse Proxy und mit dem laufenden dms-app-Prozess (nur In-Process-Kette und Kontodienst-Prozesse); der Gesamtsystem-Smoke im Token-Modus mit persistenten Schlüsseln steht aus.
  7. gofmt: internal/moduletrust/moduletrust_test.go war schon vorher unformatiert und ist nicht Teil dieser Änderung.