ADR-012: Providerneutrale Row-Level-Security-Strategie¶
Stand: 09.08.2026 16:40 Branch: main Status: Akzeptiert
| Datum, Uhrzeit | Version | Änderung | Autor |
|---|---|---|---|
| 09.08.2026 16:40 | 1.2 | Getrennte Prisma-Runtime-Clients und transaktionsgebundenen Kontextwrapper mit Unit-Tests umgesetzt | Codex |
| 09.08.2026 15:22 | 1.1 | Tabellen- und Befehlsmatrix finalisiert sowie Rollen, Kontextfunktionen, Grants und Policies in der Migrationsfolge umgesetzt | Codex |
| 09.08.2026 14:24 | 1.0 | Providerneutrale RLS-Strategie mit getrennten Datenbankrollen und transaktionsgebundenem Tenant-Kontext festgelegt | Codex |
Kontext¶
DiveLogix360 verwendet in V1 ein gemeinsames PostgreSQL-Schema für mehrere Tenants. Die Fachregel G82 verlangt eine serverseitige Standardverweigerung nach Rolle, Tenant und Ressource. Row-Level Security (RLS, zeilenbasierte Zugriffskontrolle) bildet eine zusätzliche Schutzschicht und ersetzt die Autorisierung im NestJS-Backend nicht.
Die bisherige Supabase-Strategie ging von direkten Browserzugriffen, Supabase Auth, Supabase-JWT-Claims und einem service_role-Zugang aus. Die aktuelle Anwendung verwendet dagegen eine eigene NestJS-Authentisierung und Prisma. Produktion ist als generisches PostgreSQL bei Hetzner vorgesehen; Supabase dient nur als Entwicklungsdatenbank. Eine Supabase-spezifische Policy wäre deshalb nicht produktionsgleich.
Prisma verwendet einen Verbindungspool. Ein sitzungsweit gesetzter Tenant-Kontext könnte auf einer wiederverwendeten Verbindung in eine andere Anfrage gelangen. Der Kontext muss deshalb auf die konkrete Datenbanktransaktion begrenzt werden.
Geprüfte Varianten¶
Variante A: Supabase Auth und auth.jwt()¶
Browser oder Backend übergeben Supabase-JWTs; Policies lesen Tenant und Rolle aus auth.jwt(). Dies ist für direkte Supabase-Clients geeignet, bindet die Autorisierung aber an Supabase Auth und weicht von der NestJS- und Hetzner-Zielarchitektur ab.
Variante B: Ausschliessliche Tenant-Filter im Backend¶
Jede Prisma-Abfrage enthält einen expliziten tenantId-Filter. Das ist weiterhin erforderlich, schützt aber nicht gegen einen versehentlich ausgelassenen oder falsch gebildeten Filter.
Variante C: Providerneutrale PostgreSQL-RLS mit Transaktionskontext¶
Das Backend setzt den geprüften Tenant- und Akteurskontext innerhalb derselben Prisma-Transaktion. PostgreSQL-Policies erzwingen die Tenant-Grenze zusätzlich. Getrennte Datenbankrollen begrenzen Tenant-, Plattform- und Migrationszugriffe.
Variante D: Eigenes Schema oder eigene Datenbank pro Tenant¶
Jeder Tenant erhält eine eigene technische Datenablage. Die Isolation ist stark, erhöht für V1 aber Migrationen, Betrieb, Backup, Monitoring und Verbindungsverwaltung erheblich und widerspricht dem aktuellen gemeinsamen Datenmodell.
Entscheidung¶
Variante C wird akzeptiert: RLS wird providerneutral mit nativen PostgreSQL-Policies, getrennten Datenbankrollen und transaktionsgebundenem Kontext umgesetzt.
Es gelten folgende Regeln:
- Der Browser greift in V1 nicht direkt auf Supabase oder PostgreSQL zu. Fachliche Datenzugriffe laufen ausschliesslich über das NestJS-Backend.
- Supabase ist in der Entwicklung ein PostgreSQL-Provider, keine abweichende Authentisierungs- oder Autorisierungsarchitektur. Es werden weder
auth.jwt()noch ein Supabase-Auth-Hook als Grundlage der V1-Policies verwendet. - Tenant- und Plattformautorisierung bleiben im Backend verpflichtend. RLS schützt zusätzlich gegen fehlende oder fehlerhafte Tenant-Filter.
- Die Datenbankrollen für Migration, Tenant-Laufzeit und Plattform-Laufzeit werden getrennt. Runtime-Rollen sind weder Tabellenbesitzer noch Superuser und besitzen kein
BYPASSRLS. - Eine Notfall- oder Betriebsrolle mit erweiterten Rechten darf nicht von der Anwendung verwendet werden. Ihr Zugriff folgt ADR-023, ist zeitbegrenzt, persönlich zuordenbar und auditiert.
- Auf jeder geschützten Tabelle werden
ENABLE ROW LEVEL SECURITYundFORCE ROW LEVEL SECURITYgesetzt. Fehlende Policies oder fehlender Kontext führen zur Standardverweigerung. - Tenant-Policies verwenden bei Lese-, Änderungs- und Löschzugriffen
USING; für neue oder geänderte Zeilen wird zusätzlichWITH CHECKausdrücklich definiert. - Tenant-Tabellen mit direktem
tenant_idvergleichen diesen Wert mit dem transaktionsgebundenen Kontext. Tabellen ohne direkte Tenant-ID verwenden eine geprüfte Beziehung oder erhalten im UC00-Zielmodell eine direkte Tenant-Zuordnung, wenn dies Sicherheit und Prüfbarkeit verbessert. - Tabellen mit Plattform- und Tenant-Zeilen benötigen getrennte, explizite Policies. Ein optionales
tenant_idbedeutet niemals automatisch plattformweiten Zugriff. - Superadmin-Vorgänge verwenden die getrennte Plattform-Runtime-Rolle. Ein Superadmin erhält weiterhin keinen Tenant. Plattformzugriff wird nur in ausdrücklich dafür vorgesehenen Backend-Komponenten verwendet und bleibt an Rollen-, Ressourcen- und Auditprüfungen gebunden.
- Globale Voranmelde-, Authentisierungs- und Hintergrundvorgänge laufen über den Plattformpfad mit tabellen- und befehlsbezogen minimalen Grants. Wächst dieser Umfang, werden Auth- oder Worker-Rollen zusätzlich getrennt.
- Views müssen als
security_invokerausgeführt werden oder für Runtime-Rollen unzugänglich sein. Funktionen mitSECURITY DEFINERsind zu vermeiden; erforderliche Ausnahmen erhalten festensearch_path, minimale Berechtigungen und einen eigenen Sicherheitstest. - RLS-Policies, Rollen, Grants, Hilfsfunktionen und zugehörige Indizes werden gemäss ADR-010 in der Prisma-Migrationsfolge versioniert.
- Supabase-Entwicklung, automatisierte Tests und Hetzner-Produktion verwenden dasselbe Policy-SQL. Kann eine Umgebung die Rollen- und Policystruktur nicht gleichwertig abbilden, darf sie nicht als RLS-Abnahmenachweis dienen.
Datenbankrollen¶
| Rolle | Zweck | RLS-Verhalten |
|---|---|---|
dlx_migrator |
Ausschliesslich kontrollierte Migrationen aus der Deployment-Pipeline | Objektbesitzer; BYPASSRLS nur für kontrollierte Schema- und Datenmigrationen; keine Runtime-Nutzung |
dlx_app_tenant |
Normale authentisierte Tenant-Anfragen | RLS erzwungen; Zugriff nur auf den gesetzten Tenant und erlaubte Befehle |
dlx_app_platform |
Superadmin, Voranmeldung, ausdrücklich freigegebene Systemvorgänge | RLS erzwungen; nur explizite Plattform-Policies und minimale Grants |
| Betriebs- oder Notfallrolle | Backup, Wiederherstellung oder zeitlich begrenzter Notfall | Nicht im Anwendungscode; erweiterte Rechte nur soweit betrieblich erforderlich und vollständig auditiert |
Produktivzugänge werden getrennt gespeichert und rotiert. DATABASE_URL darf nicht mit der Migrations- oder Notfallrolle verbunden sein. Die Migrationsrolle ist wegen Objektbesitz und RLS-Umgehung besonders privilegiert und nur für die automatisierte Pipeline freigegeben.
Die Backend-Konfiguration verwendet DATABASE_URL für den Plattformpfad, DATABASE_TENANT_URL für den Tenant-Pfad und DIRECT_URL ausschliesslich für kontrollierte Migrationen. Das Backend verweigert den Start, wenn einer der beiden Runtime-Zugänge fehlt oder beide URLs identisch sind. Die zugehörigen Login-Rollen und Geheimnisse werden weiterhin ausserhalb der Migration provisioniert.
Transaktionsgebundener Kontext¶
Für jeden Tenant-Datenzugriff startet das Backend eine interaktive Prisma-Transaktion. Innerhalb dieser Transaktion werden mindestens folgende Werte mit parametrisiertem set_config(..., true) gesetzt:
app.tenant_id;app.user_id;app.request_id.
Der Parameter true begrenzt den Wert auf die aktuelle Transaktion. Alle nachfolgenden Abfragen müssen denselben Transaktionsclient verwenden. Sitzungsweites SET, set_config(..., false) und eine Weiterverwendung des globalen Prisma-Clients innerhalb des Vorgangs sind unzulässig.
Eine Policy liest den Tenant fehlertolerant, beispielsweise über eine versionierte Hilfsfunktion auf Basis von current_setting('app.tenant_id', true). Ein fehlender oder leerer Wert ergibt NULL und damit keine Tenant-Freigabe. Ungeprüfte Freitextwerte werden nicht als SQL zusammengesetzt.
Policy-Grundklassen¶
| Klasse | Tabellenbeispiele | Grundregel |
|---|---|---|
| Direkte Tenant-Tabellen | customers, equipment, Detailtabellen, Workflows, Tenant-Protokolle, Benachrichtigungen, Einladungen |
tenant_id muss dem Transaktionskontext entsprechen; USING und WITH CHECK |
| Tenant-Stammsatz | tenants, geplantes tenant_profiles |
Tenant-Rolle nur für die eigene ID; Plattform-Rolle nur für freigegebene Verwaltungsvorgänge |
| Gemischte Plattform-/Tenant-Tabellen | users, auth_log, system_log, notification_templates, geplante Audit- und Vertragsmodelle |
Getrennte Policies nach Kontext; tenant_id IS NULL gewährt keiner Tenant-Rolle Zugriff |
| Abgeleitete Sicherheitsobjekte | refresh_tokens, recovery_codes |
Zugriff nur über den zugehörigen Benutzer und die ausdrücklich freigegebenen Authentisierungsvorgänge; Zielmodell auf direkte Tenant-Zuordnung prüfen |
| Plattformtabellen | plan_features und rein technische Konfiguration |
Tenant-Rolle höchstens lesend, wenn fachlich erforderlich; Änderungen ausschliesslich über Plattformpfad |
Die vollständige, tabellen- und befehlsbezogene Policy-Matrix steht in der verknüpften technischen RLS-Strategie. Sie ist gegen alle 35 Tabellen des UC00-Prisma-v4-Zielschemas finalisiert und in der versionierten Datenbankschutzmigration umgesetzt.
Verbindliche Prüfgates¶
Vor einer RLS-Abnahme sind mindestens folgende Nachweise erforderlich:
- Tenant A kann keine Zeile von Tenant B lesen, anlegen, ändern oder löschen;
WITH CHECKverhindert das Einschleusen einer fremden Tenant-ID;- fehlender, leerer oder ungültiger Kontext führt zur Verweigerung;
- parallele Anfragen über den Prisma-Pool übernehmen keinen Kontext voneinander;
- Plattformzugriff funktioniert nur über den Plattformpfad und bleibt auditiert;
- Runtime-Rollen sind weder Objektbesitzer noch
BYPASSRLS-Rollen; - jede schutzbedürftige Tabelle besitzt aktivierte und erzwungene RLS sowie die erwarteten Policies und Grants;
- Views, Funktionen, Fremdschlüssel- und Eindeutigkeitsfehler erzeugen keinen unzulässigen tenantübergreifenden Informationsabfluss;
- Policy-Spalten und geprüfte Beziehungswege besitzen geeignete Indizes;
- dieselben Migrationen und Isolationstests laufen gegen Entwicklungs- und produktionsnahes PostgreSQL.
Konsequenzen¶
- Die frühere Supabase-JWT-Strategie wird ersetzt; ihr Dateipfad bleibt nur für stabile Verweise bestehen.
- Das Backend benötigt getrennte Prisma-Zugänge für Tenant- und Plattformvorgänge sowie einen verbindlichen Transaktionswrapper.
- Direkte Prisma-Aufrufe ohne passenden Zugriffskontext müssen inventarisiert und auf die vorgesehenen Datenzugriffspfade umgestellt werden.
- Das UC00-Zielschema ordnet jede der 35 Tabellen einer Policy-Grundklasse zu.
- RLS bleibt eine zusätzliche Schutzschicht; feinere Rollen- und Ressourcenregeln werden weiterhin im Backend geprüft.
- Rollen, Kontextfunktionen, Grants, Policies, RLS-Indizes und zentrale Datenbank-Constraints sind gegen die Entwicklungsdatenbank ausgerollt und grundlegend geprüft.
- Getrennte Prisma-Clients und der Transaktionswrapper sind umgesetzt und unit-getestet. Die Runtime-Login-Provisionierung, die Umstellung aller Dienste auf den passenden Zugriffspfad sowie vollständige Pooling- und Sicherheitstests bleiben offen.
Umsetzungsstand¶
| Teil | Status |
|---|---|
| Providerneutrale RLS-Grundsätze | Akzeptiert |
| Datenbankrollen und Kontextgrenzen | Akzeptiert |
| Tabellen- und Befehlsmatrix | Finalisiert für 35 Tabellen |
| Migration mit Rollen, Grants, Hilfsfunktionen und Policies | In Entwicklung ausgerollt und grundlegend zur Laufzeit geprüft |
| Tenantkonsistente Fremdschlüssel und RLS-Indizes | Umgesetzt, statisch geprüft und in Entwicklung ausgerollt |
| Prisma-Transaktionswrapper und getrennte Runtime-Zugänge | Clients, Konfigurationssperren und Wrapper umgesetzt sowie mit fünf Unit-Tests geprüft; Login-Provisionierung und Dienstumstellung offen |
| Automatisierte RLS- und Poolingtests | Sieben grundlegende RLS-Laufzeitprüfungen erfolgreich; vollständige Ressourcen- und Poolingmatrix offen |
| Produktiver Rollen- und Berechtigungsnachweis | Offen |
Quellen¶
- PostgreSQL: Row Security Policies
- PostgreSQL: Configuration Settings Functions
- Prisma: Transactions and batch queries
- Prisma: Raw queries
- Supabase: Row Level Security