Zum Inhalt

DiveLogix360 – Providerneutrale RLS-Strategie

Stand: 09.08.2026 15:22 Branch: main Status: In Bearbeitung

Datum, Uhrzeit Version Änderung Autor
09.08.2026 15:22 2.1 Policy-Matrix für alle 35 Zieltabellen finalisiert und statische Umsetzung in der UC00-Migrationsfolge nachgeführt Codex
09.08.2026 14:24 2.0 Supabase-spezifische JWT-Strategie durch providerneutrale PostgreSQL-RLS gemäss ADR-012 ersetzt Codex
01.08.2026 14:55 1.0 Kopfbereich vereinheitlicht David Mittig

Dateiname: Der bestehende Dateipfad bleibt für stabile Verweise erhalten. Inhaltlich gilt die Strategie gleichermassen für Supabase-Entwicklung und generisches PostgreSQL in Test, Staging und Produktion.
Führender Entscheid: ADR-012 – Providerneutrale Row-Level-Security-Strategie

1. Ziel und Abgrenzung

Row-Level Security (RLS, zeilenbasierte Zugriffskontrolle) begrenzt Datenbankzeilen zusätzlich zur verpflichtenden NestJS-Autorisierung. Sie schützt insbesondere gegen versehentlich fehlende Tenant-Filter.

RLS ersetzt nicht:

  • Prüfung von Benutzerstatus und Zwei-Faktor-Authentifizierung;
  • Rollen- und Ressourcenautorisierung im Backend;
  • Validierung fachlicher Zustandswechsel;
  • Auditierung sicherheitsrelevanter Vorgänge;
  • minimale SQL-Grants und sichere Geheimnisverwaltung.

Direkte Browserzugriffe auf Supabase oder PostgreSQL sind in V1 nicht vorgesehen. Supabase Auth, auth.jwt(), anon und service_role sind deshalb keine Grundlage dieser Strategie.

2. Verbindliche Datenbankrollen

Rolle Verwendung Unzulässig
dlx_migrator Geprüfte Migrationen aus der Deployment-Pipeline; Objektbesitz und BYPASSRLS nur für Schema- und Datenmigrationen Anwendungsanfragen und interaktive Alltagsnutzung
dlx_app_tenant Tenantgebundene NestJS- und Prisma-Transaktionen Tabellenbesitz, Superuser, BYPASSRLS, plattformweite Abfragen
dlx_app_platform Superadmin, Voranmeldung und ausdrücklich freigegebene Systemvorgänge BYPASSRLS, allgemeiner Einsatz in Tenant-Services
Betriebs- oder Notfallrolle Backup, Wiederherstellung und freigegebener Notfall Speicherung im Anwendungscode oder Nutzung für normale Anfragen

Die Runtime-Rollen erhalten nur die je Tabelle und Befehl benötigten Rechte. PUBLIC erhält keine fachlichen Tabellenrechte. Die Anwendung verwendet weder die Objektbesitzerrolle noch eine Rolle mit BYPASSRLS.

3. Transaktionskontext mit Prisma

Jeder tenantgebundene Datenzugriff wird innerhalb einer interaktiven Prisma-Transaktion ausgeführt:

  1. vertrauenswürdigen Kontext aus der serverseitig geprüften Authentisierung bilden;
  2. Transaktion über den Tenant-Runtime-Client starten;
  3. app.tenant_id, app.user_id und app.request_id parametrisiert mit set_config(..., true) setzen;
  4. sämtliche fachlichen Abfragen über den übergebenen Transaktionsclient ausführen;
  5. Transaktion beenden; PostgreSQL verwirft den lokalen Kontext automatisch.

Konzeptionelles Muster:

return tenantPrisma.$transaction(async (tx) => {
  await tx.$queryRaw`
    SELECT
      set_config('app.tenant_id', ${tenantId}, true),
      set_config('app.user_id', ${userId}, true),
      set_config('app.request_id', ${requestId}, true)
  `;

  return operation(tx);
});

Innerhalb von operation ist nur tx zulässig. Ein sitzungsweites SET, set_config(..., false) oder ein paralleler Aufruf über den globalen Prisma-Client ist verboten.

4. Hilfsfunktionen

Policies verwenden versionierte PostgreSQL-Hilfsfunktionen. Das SQL liegt in der nachgelagerten UC00-Datenbankschutzmigration; fachlich gilt folgendes Verhalten:

Funktion Ergebnis bei gültigem Kontext Ergebnis bei fehlendem Kontext
app.current_tenant_id() Tenant-UUID NULL
app.current_user_id() Benutzer-UUID NULL
app.current_request_id() Request-ID NULL

Die Funktionen lesen current_setting(<name>, true), behandeln leere Werte als NULL und laufen als STABLE. Sie besitzen keinen unnötigen SECURITY DEFINER-Kontext. Ein ungültiges UUID-Format muss den Vorgang abbrechen und darf nicht zu einer Freigabe führen.

5. Policy-Matrix des aktuellen Schemas

Die Matrix beschreibt den statisch umgesetzten Zielschutz für alle 35 Tabellen. Die Plattform-Runtime erhält je Tabelle getrennte SELECT-, INSERT- und UPDATE-Policies. Harte Löschungen sind nur mit transaktionslokaler Retention- und Request-ID zulässig. Append-only-Tabellen besitzen keine Runtime-UPDATE-Policy.

Tabelle oder Gruppe Tenant-Runtime Plattform-Runtime Zielregel
tenants Nur eigener Datensatz lesbar Tenant-Anlage, Vertragsstatus, Lifecycle und Administration Tenant-Profiländerungen laufen über tenant_profiles; keine normale Hard-Delete-Policy
users Eigener Tenant für SELECT, INSERT, UPDATE Globale Identität, Superadmin und Voranmeldung Datenbank-Check schliesst Superadmin mit Tenant und Tenant-Benutzer ohne Tenant aus
tenant_profiles, tenant_onboardings, onboarding_steps Eigener Tenant für SELECT, INSERT, UPDATE Vertrags- und Lifecycle-Orchestrierung Direkter Tenantvergleich mit USING und WITH CHECK
tenant_contracts Eigener Tenant lesend Vollständiger Vertrags-Lifecycle Höchstens ein akzeptierter Vertrag je Tenant; Änderungen nur Plattformpfad
contract_acceptance_tokens Kein Tenant-Direktzugriff Erzeugung, Einlösung, Widerruf und Bereinigung Nur Hash; sieben Tage durch Datenbank-Check begrenzt
contract_documents Eigene, malwaregeprüfte Dokumentmetadaten mit Status clean lesend Upload, Scan, Prüfung, Freigabe und Bereinigung PDF-, 10-MB-, SHA-256- und Scan-Checks
tenant_offboardings, tenant_exports Eigener Tenant lesend Offboarding, Export und Löschorchestrierung 30- und 90-Tage-Grenzen als Datenbank-Checks
support_access_grants Eigener Tenant lesend; nur Freigabe-, Widerruf- und Zeitstempelspalten änderbar Anlage, Notfall und Administration Acht-Stunden- beziehungsweise Ein-Stunden-Grenze
legal_holds, audit_seals, retention_runs Kein Tenant-Direktzugriff Compliance-, Audit- und Retention-Prozess Plattformpfad; Sammelnachweise und Fristen technisch begrenzt
customers, equipment, cylinder_details, regulator_details Eigener Tenant für SELECT, INSERT, UPDATE Freigegebene Support- oder Lifecycle-Vorgänge Tenantkonsistente zusammengesetzte Fremdschlüssel; operative Löschung zunächst soft
usecase_workflows Eigener Tenant für SELECT, INSERT, UPDATE Freigegebene Administration Tenantkonsistente Ressourcen- und Korrekturbeziehungen
workflow_history Eigener Tenant für SELECT, INSERT Plattformweite Auswertung und Retention Append-only für Runtime-Rollen
audit_log, deletion_log, access_log, customer_activity_log Eigener Tenant für SELECT, INSERT über autorisierte Backendfunktionen Plattformweite Audit-, Auskunfts- und Löschprozesse Append-only; Scope- und Tenant-Konsistenz geprüft
auth_log, system_log Kein Tenant-Direktzugriff Authentisierung, Sicherheit und Plattformbetrieb Optionale Tenant-ID erzeugt keine implizite Freigabe; append-only
sync_queue Eigener Tenant für SELECT, INSERT, UPDATE Plattformweite Worker Direkter Tenantvergleich; Workerzugriff separat auditieren
notification_rules Eigener Tenant für SELECT, INSERT, UPDATE Plattformbetrieb Deaktivierung statt normaler Hard-Delete
notification_log Eigener Tenant für SELECT, INSERT Zustellstatus und Providerverarbeitung Tenantseitig append-only; Plattform darf Zustellstatus ändern
invitation_tokens, refresh_tokens, recovery_codes, two_factor_sessions Kein Tenant-Direktzugriff Voranmeldung, Rotation, Widerruf und Sicherheitsadministration Plattformpfad; Hash-, Frist- und Benutzer-Tenant-Konsistenzchecks
notification_templates Globale und eigene Vorlagen lesend; eigene Vorlagen schreibbar Globale und tenantbezogene Vorlagenverwaltung Getrennte partielle Eindeutigkeit für globale und Tenant-Vorlagen
plan_features Lesend Plattformweite Pflege Befehlsbezogene Policy und minimale Grants

6. Policy-Muster

Für eine direkte Tenant-Tabelle gilt konzeptionell:

ALTER TABLE example ENABLE ROW LEVEL SECURITY;
ALTER TABLE example FORCE ROW LEVEL SECURITY;

CREATE POLICY example_tenant_select
  ON example
  FOR SELECT
  TO dlx_app_tenant
  USING (tenant_id = app.current_tenant_id());

CREATE POLICY example_tenant_insert
  ON example
  FOR INSERT
  TO dlx_app_tenant
  WITH CHECK (tenant_id = app.current_tenant_id());

CREATE POLICY example_tenant_update
  ON example
  FOR UPDATE
  TO dlx_app_tenant
  USING (tenant_id = app.current_tenant_id())
  WITH CHECK (tenant_id = app.current_tenant_id());

DELETE wird nur angelegt, wenn die Fachregel eine operative Löschung zulässt. Plattform-Policies werden separat und befehlsbezogen definiert. Eine pauschale Policy USING (true) für alle Runtime-Befehle ist unzulässig.

7. Views, Funktionen und Integritätsfehler

  • Views verwenden auf unterstützten PostgreSQL-Versionen security_invoker = true oder bleiben für Runtime-Rollen gesperrt.
  • Funktionen mit erhöhten Rechten benötigen festen search_path, minimale EXECUTE-Grants und eigene Tests.
  • Fremdschlüssel- und Eindeutigkeitsprüfungen können RLS umgehen und dadurch indirekte Existenzinformationen offenlegen. API-Fehler werden deshalb normalisiert und nicht ungefiltert weitergegeben.
  • Policy-Spalten und häufig verwendete Beziehungswege erhalten geeignete Indizes.

8. Einführung

  1. UC00-Zielschema und alle neuen Tabellen festlegen.
  2. Jede Tabelle einer Policy-Klasse und erlaubten Befehlen zuordnen.
  3. Datenbankrollen, Grants, Hilfsfunktionen, Indizes und Policies in die versionierte Migrationsfolge aufnehmen.
  4. Tenant- und Plattform-Prisma-Zugänge getrennt konfigurieren.
  5. verbindlichen Transaktionswrapper implementieren.
  6. direkte Prisma-Aufrufe inventarisieren und auf Tenant- oder Plattformpfad umstellen.
  7. leere Testdatenbank aus der vollständigen Migration aufbauen.
  8. Rollen-, Tabellen-, Tenant-, Pooling- und Negativtests ausführen.
  9. denselben Nachweis gegen Supabase-Entwicklung und produktionsnahes PostgreSQL ausführen.

9. Abnahmetests

ID Test
RLS-01 Tenant A kann keine Zeile von Tenant B lesen.
RLS-02 Tenant A kann keine Zeile mit Tenant B anlegen oder auf Tenant B umhängen.
RLS-03 Tenant A kann keine Zeile von Tenant B ändern oder löschen.
RLS-04 Fehlender oder leerer Tenant-Kontext verweigert jeden Tenant-Zugriff.
RLS-05 Ungültiger Tenant-Kontext bricht sicher ab.
RLS-06 Parallele Transaktionen über den Pool vermischen keinen Kontext.
RLS-07 Superadmin funktioniert nur über den Plattformpfad und ohne Tenant-Zuordnung.
RLS-08 Tenant-Runtime kann Plattformzeilen mit tenant_id IS NULL nicht ändern.
RLS-09 Runtime-Rollen besitzen weder Tabellen noch BYPASSRLS.
RLS-10 Alle schutzbedürftigen Tabellen besitzen ENABLE und FORCE RLS, Policies und erwartete Grants.
RLS-11 Views und Funktionen umgehen die Policy nicht.
RLS-12 Normalisierte API-Fehler verraten keine fremden Datensätze über Constraints.

10. Offene Umsetzung

  • [x] Policy-Matrix gegen das freigegebene UC00-Zielschema finalisieren.
  • [x] Rollen, Grants, Hilfsfunktionen und Policies in Prisma-Migrationen statisch umsetzen.
  • [x] Statische Prüfung für Tabellenvollständigkeit, Runtime-Rollen, Fristen und Tenant-Fremdschlüssel automatisieren.
  • [ ] Getrennte Runtime-Zugänge und Geheimnisse konfigurieren.
  • [ ] Prisma-Transaktionswrapper implementieren.
  • [ ] Direkte Datenzugriffe auf Tenant- oder Plattformpfad umstellen.
  • [ ] RLS-01 bis RLS-12 automatisieren und produktionsnah nachweisen.
  • [ ] Rollen- und Policykatalog in den Betriebsnachweis übernehmen.

Quellen