Zurück zu allen Beiträgen
·5 Min. Lesezeit

Datenisolation in mandantenfähigen Anwendungen: Tenant Context als Grenze behandeln

In einer mandantenfähigen Anwendung ist der gefährlichste Fehler oft kein raffinierter Angriff. Ein fehlender tenant_id-Filter in einer Query, ein ausgelassener Tenant-Teil in einem Cache-Key oder ein nicht in einen Background Job übertragener Kontext kann Daten einer Organisation für eine andere sichtbar machen. Solche Fehler sind selten nur Datenbankprobleme. Sie sind Grenzprobleme über Authentifizierung, Autorisierung, Datenzugriff, Dateispeicher, Suche und Observability hinweg.

Das Kernprinzip ist einfach: Ein Tenant ist kein Filter, der einer Anfrage spät hinzugefügt wird; er ist obligatorischer Sicherheitskontext für jeden Workflow. Wenn sich ein Nutzer anmeldet, wird der Tenant, in dessen Namen er handelt, aus einer vertrauenswürdigen Quelle bestimmt und begleitet jede Zugriffsentscheidung im System.

Tenant Context aus Identität ableiten

Einen vom Client gesendeten X-Tenant-Id-Header als ausreichenden Nachweis zu behandeln, ist unsicher. Ein Nutzer kann eine andere Tenant-Kennung probieren oder ein alter Client den falschen Wert senden. Der Server muss die Tenant-Auswahl über verifizierte Identität, Mitgliedschaft, aktive Organisationsauswahl und Autorisierungsregeln auflösen.

Bei Personen in mehreren Organisationen muss der Server Mitgliedschaft und Rolle für den gewählten aktiven Tenant prüfen.

Ist der Kontext bestimmt, darf er nicht nur eine Variable im Controller sein. Request-gebundene Services, Datenschicht, Audit Records und aufgerufene Services müssen denselben Kontext lesen. Ihn in einem veränderbaren globalen Zustand zu speichern, kann parallele Anfragen vermischen. Ein request-gebundenes, explizit weitergegebenes Modell ist sicherer.

Eigentümerschaft im Datenmodell sichtbar machen

Bei gemeinsamer Datenbank und gemeinsamem Schema sollte jede mandanteneigene Zeile ein explizites tenant_id tragen. Es darf keine sekundäre Spalte sein, die nur für Performance ergänzt wurde; sie ist Teil der geschäftlichen Bedeutung in Foreign Keys, Unique Constraints und Indizes.

Ist etwa eine E-Mail-Adresse innerhalb einer Organisation eindeutig, sollte die Eindeutigkeit für (tenant_id, email) gelten und nicht für email allein. Je nach Query-Muster und Integritätsbedarf kann es sinnvoll sein, Tenant-Identität direkt auf einem Child Record zu tragen, statt sich nur auf das Parent zu verlassen. Das macht Filterung und falsche Beziehungen sichtbarer.

Auch die Trennung von Tenant-Daten und globalen Daten ist wichtig. Systemvorlagen, Länderlisten oder Produktdefinitionen gehören möglicherweise keinem Tenant. Diese Datensätze ausdrücklich als global zu modellieren, verhindert, dass unklare Ausnahmen wie ein null Tenant im System wachsen.

Query-Filter zum Standard und Constraints zur letzten Verteidigung machen

Alle Repositories oder Queries sollten den Tenant-Filter standardmäßig hinzufügen. Eine Sicherheitsregel, an die Entwickler bei jeder Query denken müssen, wird irgendwann verletzt. Query Helpers, Repository-Interfaces oder globale ORM-Filter können dieses Risiko verringern.

Anwendungsfilter sollten jedoch nicht die einzige Verteidigung sein. Unterstützt die Datenbank es, können Policies wie Row-Level Security sicherstellen, dass selbst eine unvollständige Query nur Zeilen des aktiven Tenants zurückgibt. Solche Policies brauchen bewusstes Design: Wie Verbindungen Tenant Context erhalten, wie Migration und Administration funktionieren, welche Performancewirkung entsteht und wie Notfallzugriff behandelt wird.

Ziel ist nicht, eine Regel über Schichten zu kopieren. Es geht darum, unabhängige Schutzmechanismen zu schaffen, die den Schaden begrenzen, wenn eine Schicht einen Fehler macht. Schreibt ein Service eine falsche Query, verhindert aber eine Datenbank-Policy das Leck, wird die Grenze stärker.

Kontext mit Background Work weitergeben

Endet eine HTTP-Anfrage, verschwindet ihr Request Scope. Soll E-Mail-, Reporting-, Indexierungs- oder Benachrichtigungsarbeit später laufen, muss Tenant-Identität ein expliziter Teil des Job Payloads sein. Der Worker validiert diese Identität und baut seinen eigenen Zugriffskontext auf; er verwendet nie Kontext wieder, der von einem vorherigen Job übrig blieb.

Messages sollten nur die für die Arbeit nötigen Informationen enthalten. Statt große oder sensible Objekte in einen Message Body zu legen, ist es besser, eine sichere Record-ID zusammen mit der Tenant-Grenze zu übergeben. Beim Laden wendet der Worker Tenant-Filter und Zugriffspolicy erneut an.

Das gilt auch für Retries. Wird ein Job erneut zugestellt, muss er unter demselben Tenant Context idempotent laufen, ohne dass er einen Record eines anderen Tenants zuordnen kann.

Cache, Dateien und Suche nicht vergessen

Datenlecks können aus dem Cache kommen, obwohl Datenbankqueries korrekt gefiltert sind. Ein Cache-Key wie user:123 kann bei gleicher lokaler Identität in verschiedenen Tenants kollidieren. Keys müssen einen Tenant-Teil enthalten; auch Invalidation und TTL-Strategien müssen Tenant-Auswirkungen berücksichtigen.

In Object Storage müssen Dateipfade und die Erzeugung signierter URLs von der Tenant-Identität abhängen. Die Kenntnis einer Dateikennung darf nicht genügen, um ein Objekt eines anderen Tenants herunterzuladen. Jedes Suchdokument sollte eine Tenant-Kennung tragen und Queries müssen einen Pflichtfilter anwenden. Indexierungsverzögerung oder Löschereignisse dürfen Zugriffskontrolle nicht vorübergehend ausschalten.

Für jede Abhängigkeit hilft eine gemeinsame Checkliste: Welcher Tenant besitzt diese Daten? Tragen Key, Query, Berechtigung und Löschablauf diese Information?

Administration, Logs und Analytics getrennt behandeln

Plattformadministratoren müssen möglicherweise mehrere Tenants sehen. Dieser Bedarf rechtfertigt nicht, normalen Nutzerkontext zu umgehen. Administrativer Zugriff verdient eigene Rolle, expliziten Umfang, Begründung und Audit Record. Wo möglich, sollte breiter Zugriff zeitlich begrenzt, genehmigt und sichtbar sein.

Tenant-Identität kann Logs, Traces und Metriken bei der Diagnose helfen, kann aber auch sensibel oder hochkardinal sein. Ein Pseudonym, begrenzter Hash oder nur in Fehlerdatensätzen verfügbarer Kontext kann sicherer sein. Logs dürfen keinen Kundeninhalt, Access Tokens oder private Dokumenttexte enthalten.

Auch Analytics-Queries müssen Tenant-Trennung bewahren. Ist ein aggregierter Bericht wirklich plattformweit, sollte dieser Zugriff durch Produktpolitik definiert sein—nicht durch eine versteckte Lösung, die einen Filter „für Reporting“ entfernt.

Isolation zum Testszenario machen

Tenant-Isolation ist nicht nur ein Prinzip für Code Reviews. Automatisierte Tests sollten mindestens zwei Tenants mit ähnlich benannten oder identifizierten Records erzeugen. Kann ein Nutzer von Tenant A Liste, Detail, Datei, Cache-Ergebnis, Suchrecord oder Background-Job-Ergebnis von Tenant B sehen? Die Antwort muss über alle Kanäle hinweg systematisch nein sein.

Tests sollten auch verweigerte Autorisierung, direktes Raten von Kennungen, Pagination, Exporte und Administratorrollen abdecken. In Produktion sollten tenantübergreifende Zugriffsversuche und unerwartet fehlende Filter beobachtbar sein und Alarme erzeugen.

Mandantenfähige Isolation ist kein einzelnes Middleware-Element oder SQL-Condition. Systeme, die Tenant Context aus Identität ableiten und ihn über Daten, Jobs, Speicher und Observability hinweg bewahren, machen Sicherheit zu einem natürlichen Teil der Produktarchitektur statt zu einer nachträglichen Korrektur nach dem Wachstum.