Signaturschluessel der Modul-JWTs liegen verschluesselt in der Mandanten-Datenbank
(module_signing_keys), werden von mehreren Instanzen geteilt und rotieren mit
Ueberlappung; nicht lesbarer Speicher ergibt 503 statt Token/JWKS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
POST /authorize nimmt optional den Mandanten entgegen und wertet modulgebundene Rechte ueber AuthorizeForTenant aus (fail closed). Mandant ist Pflicht fuer Rechte mit Modul-Bindung, das Service-Token laesst sich auf Mandanten einschraenken. Migration 0012 bindet die dms.*-Rechte an das Flag dms. Der DMS-policyclient sendet den Mandanten mit und trennt den Cache je Mandant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Core exponierte bislang nur GET /audit/export (AUD-03) - physisch
getrennte Module (Archive, DMS, Mail) hatten keinen Weg, eigene
sicherheitsrelevante Vorgaenge im zentralen Audit-Log zu vermerken.
IngestHandler (POST /audit/ingest, X-Service-Token) ruft denselben
alleinigen Schreibpfad wie core-interne Aufrufer auf (audit.Log.Record) -
kein zweiter, laxerer Validierungspfad ueber REST.
Notwendige Grundlage fuer Archive CMP-04 (Audit-Trail-Kopplung an
Core-Audit-Log), das per REST an dieses Modul anbindet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Haertet die Control-Plane-Registry als eigene Schutzzone:
- Dedizierte Postgres-Rolle nexarch_registry (ohne CREATEDB) fuer
NEXARCH_REGISTRY_DSN, getrennt von der Provisioning-Rolle nexarch_core
(deploy/install.sh). Neu angelegte Tenant-Datenbanken entziehen PUBLIC
explizit das CONNECT-Recht (internal/tenant/provisioner.go), damit die
Registry-Rolle unter keinen Umstaenden Zugriff auf eine Tenant-Datenbank
erhaelt (Akzeptanzkriterium 1).
- Auditierte Zugriffsmethoden (*AsActor) fuer Superadmin-Konten
(internal/user.SuperadminStore) und Verbindungsinformationen
(internal/tenant.Registry) — jeder lesende/schreibende Zugriff erzeugt
einen Audit-Event mit Akteur und Zeitstempel, verdrahtet in
internal/tenantadmin (TEN-05-Konsole) und cmd/core (Akzeptanzkriterium 2).
- Betriebsanleitung docs/TEN-09-REGISTRY-BETRIEB.md dokumentiert
Rollentrennung und den auf den Core-Dienst beschraenkten Netzwerkzugriff
(pg_hba.conf, listen_addresses, Firewall-Default-Policy), abgesichert
durch internal/opsdocs/registry_hardening_test.go (Akzeptanzkriterium 3).
Automatisierte Tests je Akzeptanzkriterium: internal/tenant/isolation_test.go
(negativer Zugriffstest), internal/audit/wiring_test.go
(TestWiring_SuperadminReadIsAudited, TestWiring_RegistryConnectionInfoReadIsAudited),
internal/opsdocs/registry_hardening_test.go.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
- cmd/kek-api: startet internal/kek.Handler.TenantKEKHandler (API-10),
tenantResolverAdapter bildet tenant.Registry.GetBySlug auf
kek.TenantResolver ab (nur Signatur-Anpassung)
- reines Wiring, kein Diff an internal/kek|moduleregistry|tenant
(verifiziert)
- real deployed auf 131, end-zu-ende bewiesen: echtes Modul
registriert+provisioniert, echter Tenant-KEK erzeugt, curl gegen
laufenden Dienst liefert exakt denselben KEK zurueck (byte-fuer-byte
verglichen), falsches Credential -> 403
- reale Grant-Luecke gefunden und behoben (nexarch_core auf
tenant_keks), verifiziert
Pruefungen siehe docs/API-12-PRUEFPROTOKOLL.md
- cmd/notifyprefs-api: startet internal/notifyprefs.Handler (CFG-04),
hinter auth.RequireAuth mit demselben JWT-Secret wie IAM-16
- reines Wiring, kein Diff an internal/notifyprefs/ (verifiziert)
- real deployed auf 131, cross-service-Session real bewiesen: Login
gegen account-api, dasselbe Cookie gegen notifyprefs-api verwendet ->
echtes Setzen und Lesen einer Praeferenz, ohne Cookie -> 401
- nutzt dieselbe nexarch_registry-DB wie CFG-05, Grants bereits
vorhanden
Pruefungen siehe docs/CFG-06-PRUEFPROTOKOLL.md
- cmd/rbac-admin-api: startet internal/rbac.Handler (RBAC-05), hinter
auth.RequireAuth mit demselben JWT-Secret wie IAM-16 (account-api)
- reines Wiring, kein Diff an internal/rbac/ (verifiziert)
- real deployed auf 131, cross-service-Session real bewiesen: Login
gegen account-api (Port 8099), dasselbe Session-Cookie gegen
rbac-admin-api (Port 8100) verwendet -> echte Rollenliste, ohne
Cookie -> 401
- Migrationen (role_assignments/groups) real auf tenant_acme
angewendet, reale Grant-Luecke behoben und verifiziert
- Bootstrap-Erkenntnis dokumentiert: ListRoles selbst verlangt bereits
eine Rolle mit PermManageUsers, Testnutzer daher mit direkter
DB-Bootstrap-Rolle angelegt (wie Ersteinrichtung)
Pruefungen siehe docs/RBAC-07-PRUEFPROTOKOLL.md
- internal/notifyapi.Mount (POST /notify/enqueue), reiner Wrapper um
notifyprefs.EnqueueIfAllowed (CFG-04)
- Reuse von internal/policyapi.RequireServiceToken (RBAC-06), kein
neues Provisorium, eigener Service-Token pro Endpunkt
- Tests: 401 ohne Token, Aequivalenz HTTP vs. direkter Aufruf
(aktiviert/deaktiviert), realer Fremd-Modul-Client mit echter
notification_jobs-Zeile
- real deployed auf 131 (notify-api, Port 8094), Grant-Nachverfolgung
fuer nexarch_core auf notification_preferences/notification_jobs,
end-zu-ende per curl nachgewiesen (401, job_id+skipped:false)
Pruefungen siehe docs/CFG-05-PRUEFPROTOKOLL.md
internal/policyapi: POST /authorize wrapt policy.Enforcer.Authorize
(RBAC-02) fuer physisch getrennte Module (DMS, Mail, Archive) - reiner
Wrapper, keine zweite Autorisierungslogik, 100 Stichproben beweisen
Uebereinstimmung mit dem direkten Enforcer-Aufruf. Service-Auth ueber
schlanken, timing-safe verglichenen Token statt moduleregistrys
schwererem Credential-System (unnoetige internal/flag-Abhaengigkeit,
nie mit internal/policy gemergt). Ersetzt spaeter das dokumentierte
RET-06-API-Provisorium (Archive, eigenes Folgeticket). Reale
Rechtevergabe-Luecke auf policy_rules gefunden und behoben (Tabelle
von frueherem Testlauf unter anderem Owner). Real auf 131 deployed,
beide Pfade (Allow/Deny) per curl end-to-end verifiziert.
OnboardingService.Onboard: ein API-Aufruf legt Tenant (via bestehendem
Provisioner) UND ersten Administrator-Account (via IAM-01 TenantUserStore)
an. CREATE DATABASE erlaubt keine echte cross-database Transaktion, daher
Saga-Kompensation: schlaegt die Admin-Anlage nach erfolgreichem Provisioning
fehl, wird der Tenant per neuem Provisioner.Deprovision wieder vollstaendig
entfernt (Registry.Delete + DB-Drop).
Provisioner.Provision mappt Duplikat-Faelle (42P04 duplicate_database und
den bei echt parallelen CREATE DATABASE moeglichen 23505-Unique-Konflikt auf
pg_database) jetzt auf ErrTenantExists statt einer rohen Postgres-Fehlermeldung.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Onboarding-Vorgang zweimal parallel mit gleichem Slug ausgeloest —
TestOnboarding_RejectsDuplicateSlugConcurrently: genau ein Erfolg, kein
Doppel-Tenant, zweiter Aufruf bekommt ErrTenantExists. PASS.
2. Fehleingaben (leere Pflichtfelder, ungueltige E-Mail, ungueltiger Slug) —
TestOnboarding_ValidationErrors deckt alle vier Faelle mit klaren Fehlern
ab, bevor irgendein DB-Zugriff stattfindet. PASS.
3. Erfolgreicher Durchlauf von zweiter Person end-to-end nachvollzogen —
NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar).
Offen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Benutzer-Datenmodell + CRUD fuer Tenant-User (tenant-scoped, keine
tenant_id-Spalte noetig, Tenant ergibt sich aus der DB-Verbindung, Modell C)
und getrennt dafuer SuperadminStore fuer mandantenuebergreifende Konten in
der Registry-DB — First-Class-Typ statt tenant_id-NULL-Sonderfall im
Tenant-User-Code (bekannter archivdms-Fehler vermieden).
E-Mail-Eindeutigkeit: tenant-scoped fuer normale Benutzer (UNIQUE-Constraint
gilt nur innerhalb der jeweiligen Tenant-DB), global fuer Superadmins
(eine Registry-DB, ein UNIQUE-Constraint).
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. CRUD automatisiert getestet inkl. Negativfaellen — TestTenantUserStore_CRUD
deckt doppelte E-Mail (ErrEmailTaken) und unbekannte ID ab. PASS.
2. Superadmin-Anlage ohne Tenant-Kontext — TestSuperadminStore_CreateWithoutTenantContext:
SuperadminStore.Create hat syntaktisch keinen Tenant-Parameter, kein
if-Zweig fuer "kein Tenant" im Code. PASS.
3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT
durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen.
Tenant-User-Handler ist im Code vorhanden, aber in cmd/core/main.go noch
nicht geroutet — braucht Connection-Routing pro Mandant (TEN-06), das nicht
Teil dieser Kachel ist. Nur der Superadmin-Endpunkt ist verdrahtet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registry-DB (nur Tenant-Metadaten), Provisioning-Routine legt pro Mandant
eine physisch isolierte Postgres-DB an und registriert sie transaktional
(Rollback der DB bei fehlgeschlagener Registrierung). Schlanker HTTP-Handler
als Schnittstellen-Vorbereitung fuer API-01/TEN-02, kein eigenes REST-Grundgerüst.
Pruefungen:
1. Migration up/down geschrieben (0001_tenant_registry.{up,down}.sql) — nicht
gegen echte DB ausgefuehrt, da auf dieser Maschine kein Go/Postgres-Test-
Setup verfuegbar ist. Offen zur Ausfuehrung.
2. Integrationstest TestProvision_CreatesIsolatedDatabases geschrieben (zwei
Mandanten, prueft unterschiedliche db_name und current_database()) —
ebenfalls nicht ausgefuehrt, guarded per TEST_ADMIN_DSN env var. Offen.
3. Slug-Validierung (unit test TestValidateSlug) deckt SQL-Injection-Versuch
im Datenbanknamen ab — ebenfalls nicht lokal ausgefuehrt, da kein Go
Compiler auf dieser Maschine vorhanden ist. Offen.
Alle drei Pruefungen sind vorbereitet, aber NICHT durchgefuehrt worden —
zaehlen laut Vorgabe als offen bis auf einer Maschine mit Go+Postgres verifiziert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>