Zum Inhalt

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:

  1. Der Browser greift in V1 nicht direkt auf Supabase oder PostgreSQL zu. Fachliche Datenzugriffe laufen ausschliesslich über das NestJS-Backend.
  2. 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.
  3. Tenant- und Plattformautorisierung bleiben im Backend verpflichtend. RLS schützt zusätzlich gegen fehlende oder fehlerhafte Tenant-Filter.
  4. Die Datenbankrollen für Migration, Tenant-Laufzeit und Plattform-Laufzeit werden getrennt. Runtime-Rollen sind weder Tabellenbesitzer noch Superuser und besitzen kein BYPASSRLS.
  5. 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.
  6. Auf jeder geschützten Tabelle werden ENABLE ROW LEVEL SECURITY und FORCE ROW LEVEL SECURITY gesetzt. Fehlende Policies oder fehlender Kontext führen zur Standardverweigerung.
  7. Tenant-Policies verwenden bei Lese-, Änderungs- und Löschzugriffen USING; für neue oder geänderte Zeilen wird zusätzlich WITH CHECK ausdrücklich definiert.
  8. Tenant-Tabellen mit direktem tenant_id vergleichen 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.
  9. Tabellen mit Plattform- und Tenant-Zeilen benötigen getrennte, explizite Policies. Ein optionales tenant_id bedeutet niemals automatisch plattformweiten Zugriff.
  10. 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.
  11. 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.
  12. Views müssen als security_invoker ausgeführt werden oder für Runtime-Rollen unzugänglich sein. Funktionen mit SECURITY DEFINER sind zu vermeiden; erforderliche Ausnahmen erhalten festen search_path, minimale Berechtigungen und einen eigenen Sicherheitstest.
  13. RLS-Policies, Rollen, Grants, Hilfsfunktionen und zugehörige Indizes werden gemäss ADR-010 in der Prisma-Migrationsfolge versioniert.
  14. 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 CHECK verhindert 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

Verknüpfte Dokumente