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:
- vertrauenswürdigen Kontext aus der serverseitig geprüften Authentisierung bilden;
- Transaktion über den Tenant-Runtime-Client starten;
app.tenant_id,app.user_idundapp.request_idparametrisiert mitset_config(..., true)setzen;- sämtliche fachlichen Abfragen über den übergebenen Transaktionsclient ausführen;
- 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 = trueoder bleiben für Runtime-Rollen gesperrt. - Funktionen mit erhöhten Rechten benötigen festen
search_path, minimaleEXECUTE-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¶
- UC00-Zielschema und alle neuen Tabellen festlegen.
- Jede Tabelle einer Policy-Klasse und erlaubten Befehlen zuordnen.
- Datenbankrollen, Grants, Hilfsfunktionen, Indizes und Policies in die versionierte Migrationsfolge aufnehmen.
- Tenant- und Plattform-Prisma-Zugänge getrennt konfigurieren.
- verbindlichen Transaktionswrapper implementieren.
- direkte Prisma-Aufrufe inventarisieren und auf Tenant- oder Plattformpfad umstellen.
- leere Testdatenbank aus der vollständigen Migration aufbauen.
- Rollen-, Tabellen-, Tenant-, Pooling- und Negativtests ausführen.
- 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.