Erreichen robuster Multi-Tenant-Datenisolation mit PostgreSQL Row-Level Security
Daniel Hayes
Full-Stack Engineer · Leapcell

Einleitung
In der Welt der modernen Softwareentwicklung, insbesondere bei SaaS und Cloud-basierten Systemen, ist Multi-Tenancy zu einem Eckpfeiler des Architekturmusters geworden. Sie ermöglicht einer einzigen Instanz einer Anwendung, mehrere Kunden (Mieter) zu bedienen, was zu erheblichen Kosteneinsparungen und vereinfachter Verwaltung führt. Diese Effizienz bringt jedoch eine entscheidende Herausforderung mit sich: die Gewährleistung absoluter Datenisolation zwischen den Mietern. Ein Bruch dieser Isolation kann zu Datenschutzverletzungen, Sicherheitsvorfällen und schwerwiegenden Reputationsschäden führen. Traditionell haben sich Entwickler stark auf die Logik auf Anwendungsebene verlassen, um Daten basierend auf dem authentifizierten Mieter zu filtern. Obwohl dies bis zu einem gewissen Grad wirksam ist, liegt die Last vollständig bei der Anwendung, was die Komplexität und die Fehleranfälligkeit erhöht und es zu einem potenziellen Single Point of Failure macht. Dieser Artikel befasst sich damit, wie die Row-Level Security (RLS) von PostgreSQL einen leistungsstarken, datenbanknativen Mechanismus darstellt, um die Multi-Tenant-Datenisolation grundlegend zu lösen und eine robustere und sicherere Lösung zu bieten.
Das Fundament verstehen
Bevor wir uns mit RLS befassen, wollen wir einige Kernkonzepte klar verstehen:
- Multi-Tenancy: Eine Architektur, bei der eine einzige Instanz einer Softwareanwendung mehrere Mieter (Kunden oder Gruppen) bedient. Die Daten jedes Mieters sind von den Daten anderer Mieter isoliert, aber sie teilen sich alle dieselbe Anwendungsinstanz und dasselbe Datenbankschema.
 - Datenisolation: Das Prinzip, sicherzustellen, dass Daten, die zu einem Mieter gehören, für andere Mieter unzugänglich und unsichtbar sind. Dies ist für Sicherheit und Datenschutz von größter Bedeutung.
 - Row-Level Security (RLS): Eine Datenbankfunktion, die den Zugriff auf einzelne Datenzeilen basierend auf den Merkmalen des Benutzers, der eine Abfrage ausführt, einschränkt, und nicht auf der gesamten Tabelle. Diese granulare Kontrolle wird direkt vom Datenbanksystem durchgesetzt.
 - Richtlinie (Policy): Im Kontext von RLS ist eine Richtlinie eine Reihe von Regeln, die für eine Tabelle definiert werden und bestimmt, auf welche Zeilen ein Benutzer zugreifen oder diese ändern kann. Richtlinien können für 
SELECT-,INSERT-,UPDATE- undDELETE-Operationen angewendet werden. 
Die Macht der PostgreSQL Row-Level Security
Die RLS von PostgreSQL funktioniert, indem Richtlinien direkt an Tabellen angehängt werden. Diese Richtlinien werten für jede Zeile, auf die zugegriffen oder die geändert werden soll, eine Bedingung aus. Wenn die Bedingung zu true ausgewertet wird, ist die Operation zulässig; andernfalls wird sie verweigert. Diese Durchsetzung geschieht, bevor Ergebnisse an die Anwendung zurückgegeben werden, und bietet eine unumgängliche Sicherheitsebene.
Die Kernidee für die Multi-Tenant-Isolation ist das Filtern von Zeilen basierend auf einer tenant_id-Spalte, die in relevanten Tabellen vorhanden ist. Durch die Integration der ID des aktuellen Mieters in eine RLS-Richtlinie stellt die Datenbank selbst sicher, dass nur Zeilen, die zu diesem bestimmten Mieter gehören, jemals sichtbar oder modifizierbar sind.
Funktionsweise: Schritt-für-Schritt-Implementierung
Lassen Sie uns dies anhand eines praktischen Beispiels veranschaulichen. Stellen Sie sich eine Multi-Tenant-Tabelle products vor, bei der jedes Produkt zu einem bestimmten Mieter gehört.
Zuerst benötigen wir eine Möglichkeit, damit die Datenbank weiß, welcher Mieter gerade aktiv ist. PostgreSQLs SET SESSION AUTHORIZATION oder, häufiger für Multi-Tenancy, SET LOCAL-Variablen sind dafür perfekt geeignet. Wir werden eine benutzerdefinierte Sitzungsvariable namens app.current_tenant_id verwenden.
-- 1. Erstellen Sie die `products`-Tabelle mit einer tenant_id CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10, 2) NOT NULL, tenant_id INT NOT NULL ); -- Fügen Sie Beispieldaten für verschiedene Mieter hinzu INSERT INTO products (name, price, tenant_id) VALUES ('Laptop A', 1200.00, 1), ('Mouse B', 25.00, 1), ('Keyboard C', 75.00, 2), ('Monitor D', 300.00, 1), ('Webcam E', 50.00, 2); -- 2. Aktivieren Sie Row-Level Security in der Tabelle ALTER TABLE products ENABLE ROW LEVEL SECURITY; -- 3. Erstellen Sie eine Richtlinie, um den Zugriff basierend auf tenant_id zu beschränken -- Diese Richtlinie stellt sicher, dass Benutzer nur Produkte ihres aktuellen tenant_id sehen/ändern können. CREATE POLICY tenant_isolation_policy ON products FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::int); -- Möglicherweise möchten wir auch eine Richtlinie für Einfügungen, um sicherzustellen, dass neue Produkte korrekt gekennzeichnet werden -- Alternativ würde die obige 'USING'-Klausel auch für Einfügungen gelten, wenn 'FOR ALL' verwendet wird. -- Für eine strengere Kontrolle oder andere Logik für INSERT, UPDATE, DELETE können separate Richtlinien erstellt werden: -- CREATE POLICY insert_tenant_policy ON products FOR INSERT WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::int); -- CREATE POLICY update_tenant_policy ON products FOR UPDATE USING (tenant_id = current_setting('app.current_tenant_id')::int) WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::int);
Nun sehen wir dies in Aktion. Wenn Ihre Anwendung eine Verbindung zur Datenbank herstellt und einen Benutzer als Teil von Mieter 1 authentifiziert, würde sie vor jeder Datenabfrage den folgenden Befehl ausgeben:
SET app.current_tenant_id = '1';
Dann wird jede nachfolgende Abfrage der products-Tabelle durch die Anwendung automatisch per RLS gefiltert:
-- Anwendungssuche für Mieter 1 SELECT * FROM products;
Erwartete Ausgabe für app.current_tenant_id = '1':
| id | name | price | tenant_id | 
|---|---|---|---|
| 1 | Laptop A | 1200.00 | 1 | 
| 2 | Mouse B | 25.00 | 1 | 
| 4 | Monitor D | 300.00 | 1 | 
Wenn die Anwendung dann den Kontext zu Mieter 2 wechselt (nach Authentifizierung eines Mieter 2-Benutzers):
SET app.current_tenant_id = '2'; -- Anwendungssuche für Mieter 2 SELECT * FROM products;
Erwartete Ausgabe für app.current_tenant_id = '2':
| id | name | price | tenant_id | 
|---|---|---|---|
| 3 | Keyboard C | 75.00 | 2 | 
| 5 | Webcam E | 50.00 | 2 | 
Beachten Sie, dass die SELECT-Abfrage selbst generisch ist. Die Filterung wird vollständig an die Datenbank delegiert und von ihr durchgesetzt, wodurch Klauseln wie WHERE tenant_id = <current_tenant_id> in jeder einzelnen Anwendungssuche überflüssig werden.
Robustheit und Vorteile
- Absolute Datenisolation: RLS fungiert als endgültiger Gatekeeper. Selbst wenn es einen Fehler im Anwendungscode gibt, der 
WHERE tenant_id = Xvergisst, wird die Datenbank die Richtlinie dennoch durchsetzen und Datenlecks verhindern. - Reduzierte Anwendungskomplexität: Entwickler schreiben weniger Boilerplate-Code für die Mieterfilterung, sodass sie sich auf die Geschäftslogik konzentrieren können.
 - Erhöhte Sicherheit: Durch die Verlagerung von Sicherheitsproblemen in die Datenbankebene wird es für Angreifer schwieriger, die Mieterisolation durch SQL-Injection oder andere Schwachstellen zu umgehen.
 - Zentralisierte Steuerung: Sicherheitsrichtlinien werden auf Datenbankebene definiert und verwaltet, was Konsistenz über alle Anwendungsteile und Microservices gewährleistet, die mit den Daten interagieren.
 - Leistung: Der PostgreSQL-Abfrageplaner kennt RLS-Richtlinien und kann die Abfrageausführung optimieren, was oft zu einer effizienten Indexnutzung auf 
tenant_idführt. 
Fortgeschrittene Überlegungen
- RLS umgehen (für Superuser/Admins): PostgreSQL-Superuser oder Rollen mit 
BYPASS-Privileg können Richtlinien umgehen. Dies ist entscheidend für Wartung, Backups und administrative Aufgaben, sollte aber mit äußerster Vorsicht angewendet werden. - Richtlinien pro Befehl: RLS ermöglicht die Definition separater Richtlinien für 
SELECT-,INSERT-,UPDATE- undDELETE-Operationen und bietet so eine feingranulare Kontrolle über die Datenmanipulation. Die KlauselWITH CHECKist besonders nützlich fürINSERT- undUPDATE-Richtlinien, um sicherzustellen, dass neue oder geänderte Zeilen immer noch der Richtlinie entsprechen (z. B. kann ein Benutzer keine Zeile für einen anderen Mieter einfügen). - Komplexe Mieterhierarchien: RLS kann komplexere Szenarien handhaben, bei denen Mieter Eltern-Kind-Beziehungen oder gemeinsam genutzte Daten haben könnten. Richtlinien können Funktionen oder Unterabfragen enthalten, um komplizierte Zugriffsregeln zu implementieren.
 
Fazit
Die Row-Level Security von PostgreSQL bietet eine elegante und leistungsstarke Lösung für die Multi-Tenant-Datenisolation. Indem die Last der Mieter-Datenfilterung von der Anwendungsebene in den Datenbankkern verlagert wird, verbessert RLS die Sicherheit drastisch, reduziert die Anwendungskomplexität und erzwingt Datengrenzen mit unerschütterlicher Hand. Für jede Multi-Tenant-Anwendung, die auf PostgreSQL basiert, ist die Einführung von RLS nicht nur eine Best Practice, sondern ein grundlegender Schritt hin zu einer wirklich sicheren und wartbaren Architektur. Sie befähigt die Datenbank, der ultimative Hüter der Mieterdatentrennung zu sein.