Skip to Content

Fine-Grained Authorization (Zanzibar-Modell)

Role-Based Access Control (RBAC) ist für viele Anwendungen ausreichend: Benutzer haben Rollen, Rollen haben Berechtigungen, du prüfst, ob ein Benutzer die richtige Berechtigung hat. Aber RBAC stößt an seine Grenzen, wenn du ressourcenebenen- Zugriffskontrolle benötigst — „Alice kann Dokument 42 bearbeiten, aber nicht Dokument 43,” oder „Bob kann alle Dokumente seines Teams einsehen.”

Auris implementiert eine Zanzibar-kompatible Fine-Grained Authorization (FGA)-Engine für diese Szenarien.

Was ist Zanzibar?

Zanzibar ist Googles globales Autorisierungssystem, das 2019 in einem Forschungspapier veröffentlicht wurde. Es steuert die Zugriffskontrolle für Gmail, Drive, Docs, Maps und Dutzende andere Google-Produkte — mit Billionen von Autorisierungsprüfungen pro Sekunde bei Millisekunden-Latenz.

Die zentrale Erkenntnis von Zanzibar ist die Modellierung von Autorisierung als Beziehungen zwischen Objekten und Subjekten, die als Tupel gespeichert werden. Autorisierungsfragen werden zu Graph-Traversal-Problemen: „Gibt es einen Pfad von Subjekt user:alice zu Objekt document:readme über die viewer-Relation?”

Auris implementiert eine Zanzibar-kompatible FGA-Engine mit:

  • Einer OpenFGA-kompatiblen DSL zur Definition von Autorisierungsmodellen
  • Einem Tupel-Speicher, der durch PostgreSQL (via Prisma) gesichert ist
  • Einem rekursiven Check-Algorithmus mit Zykluserkennung
  • Expand- und List-Objects-Abfrageoperationen
  • Einem visuellen Debugger in der Auris-Konsole

Kernkonzepte

Objekt

Ein Objekt ist jede Ressource, die du schützen möchtest. Objekte haben einen Typ und eine ID:

document:readme folder:engineering organization:acme-corp report:q4-2024

Der Typ wird in deinem Autorisierungsmodell definiert. Die ID ist ein beliebiger String, der die spezifische Instanz identifiziert.

Relation

Eine Relation ist eine benannte Kante im Autorisierungsgraphen — eine Art Beziehung, die ein Objekt mit Subjekten haben kann. Relationen werden pro Objekttyp im Modell definiert:

  • document kann Relationen haben: owner, editor, viewer
  • folder kann Relationen haben: owner, viewer
  • organization kann Relationen haben: admin, member

Subjekt

Ein Subjekt ist, wer (oder was) eine Relation zu einem Objekt hat. Subjekte können sein:

  • Ein Benutzer: user:alice
  • Ein Benutzerset (die Relation eines anderen Objekts): group:engineering#member (alle Mitglieder der Engineering-Gruppe)

Tupel

Ein Tupel ist eine gespeicherte Tatsache: „Dieses Subjekt hat diese Relation zu diesem Objekt.”

Format: objekt#relation@subjekt

Beispiele:

document:readme#viewer@user:alice document:readme#editor@user:bob document:readme#viewer@group:engineering#member folder:engineering#owner@user:charlie

Das vorletzte Beispiel bedeutet: „Alle Mitglieder der Engineering-Gruppe sind Viewer des Dokuments readme.” Dies wird als Userset bezeichnet — eine Gruppe von Subjekten, die durch eine andere Relation definiert ist.

Die Auris-DSL

Auris verwendet eine OpenFGA-kompatible Domain-Specific Language zur Definition von Autorisierungsmodellen. Die DSL spezifiziert Objekttypen und ihre Relationen, einschließlich wie Relationen aus anderen Relationen abgeleitet werden.

Grundlegende Struktur

type user type group relations define member: [user] type folder relations define owner: [user] define viewer: [user, group#member] or owner type document relations define parent: [folder] define owner: [user] or owner from parent define editor: [user, group#member] or owner define viewer: [user, group#member] or editor

Jeder type-Block definiert einen Objekttyp. relations listet die benannten Relationen für diesen Typ, mit Regeln, wie sie abgeleitet werden.

Relationsdefinitionen lesen

define viewer: [user, group#member] or editor

Das bedeutet: „Ein Subjekt ist ein viewer eines Dokuments, wenn:

  • Das Subjekt ein user-Typ ist UND ein direktes Tupel document:X#viewer@user:Y existiert, ODER
  • Das Subjekt ein group#member (Mitglied einer Gruppe) ist UND ein direktes Tupel document:X#viewer@group:Z existiert, ODER
  • Das Subjekt ein editor des Dokuments ist (rekursiv berechnet).”

Sechs Rewrite-Typen

Die Autorisierungs-Engine unterstützt sechs Arten von Relation-Rewrite-Regeln. Sie können kombiniert werden, um praktisch jedes Zugriffsmuster zu modellieren.

1. this — Direkte Zuweisung

Die einfachste Regel: Die Relation gilt, wenn ein Tupel direkt im Speicher existiert.

define owner: [user]

„Benutzer Alice ist ein Owner, wenn das Tupel document:X#owner@user:alice existiert.”

2. computedUserset — Von einer anderen Relation erben

Die Relation gilt, wenn das Subjekt eine andere Relation zum gleichen Objekt hat.

define editor: [user] or owner

„Benutzer Alice ist ein Editor, wenn ein direktes Editor-Tupel für sie existiert ODER wenn sie ein Owner ist (berechnet aus der owner-Relation des gleichen Dokuments).“

3. tupleToUserset — Einer Relation zu einem anderen Objekt folgen

Dies ist die mächtigste Regel. Sie folgt einer Relation vom aktuellen Objekt zu einem anderen Objekt und prüft dann eine Relation auf diesem anderen Objekt.

define owner: [user] or owner from parent

„Benutzer Alice ist Owner von document:X, wenn ein direktes Owner-Tupel für sie existiert ODER wenn ein Tupel document:X#parent@folder:Y existiert UND Alice Owner von folder:Y ist.”

So funktioniert Vererbung in Google Drive: Dokumente erben Berechtigungen von ihrem übergeordneten Ordner.

4. union — ODER-Kombination

Eine Vereinigung von zwei oder mehr Rewrites. Die Relation gilt, wenn eine der Unterregeln zutrifft.

define viewer: [user] or editor

Dies entspricht einem expliziten union:

define viewer: { union: { child: [{ this: {} }, { computedUserset: { relation: "editor" } }] } }

5. intersection — UND-Kombination

Alle Unterregeln müssen gelten, damit die Relation wahr ist. Nützlich, wenn mehrere Bedingungen gleichzeitig erforderlich sind.

define restricted_viewer: [user] and approved

„Ein Benutzer ist ein restricted_viewer nur, wenn er ein explizites restricted_viewer-Tupel hat UND das Dokument eine approved-Relation für ihn hat.”

6. exclusion — Mengendifferenz (A ABER NICHT B)

Die Relation gilt für Subjekte in der ersten Menge, aber nicht in der zweiten. Nützlich für Deny-Listen.

define viewer: [user] but not blocked

„Ein Benutzer kann ansehen, wenn er ein viewer-Tupel hat, ES SEI DENN er hat auch ein blocked-Tupel.”

Der Check-Algorithmus

Wenn du POST /api/fga/check aufrufst, wertet die Engine aus, ob objectType:objectId#relation@subjectType:subjectId gilt.

Algorithmus auf hoher Ebene

check(object, relation, user): model = getActiveModel() ruleset = model.typeDefinitions[object.type].relations[relation] return evaluate(ruleset, object, user, visited={}) evaluate(rewrite, object, user, visited): if (object, rewrite) in visited: return false // Zyklus erkannt visited.add((object, rewrite)) switch rewrite.type: case "this": return tupleExists(object, relation, user) case "computedUserset": return evaluate(model.relations[rewrite.relation], object, user, visited) case "tupleToUserset": intermediateObjects = listTuples(object, rewrite.tupleset) return any(evaluate(model[intermediateObject.type][rewrite.relation], intermediateObject, user, visited) for intermediateObject in intermediateObjects) case "union": return any(evaluate(child, object, user, visited) for child in rewrite.children) case "intersection": return all(evaluate(child, object, user, visited) for child in rewrite.children) case "exclusion": return evaluate(rewrite.base, object, user, visited) and not evaluate(rewrite.subtract, object, user, visited)

Zykluserkennung

Das visited-Set verhindert Endlosschleifen in Modellen mit zirkulären Relationsreferenzen. Die maximale Rekursionstiefe ist auf 25 begrenzt.

Erklärungsmodus

Wenn explain: true an POST /api/fga/check übergeben wird, gibt die Engine einen Auflösungsbaum zurück, der genau zeigt, welche Regel übereinstimmt (oder fehlschlägt), bis hin zum spezifischen Tupel, das gefunden (oder nicht gefunden) wurde. Dies ist unschätzbar für das Debugging von Zugriffskontrollproblemen.

Modellierungsmuster

Google-Drive-Modell

Benutzer und Gruppen haben Zugriff auf Dokumente entweder direkt oder durch Vererbung von einem übergeordneten Ordner:

type user type group relations define member: [user] type folder relations define owner: [user] define editor: [user, group#member] or owner define viewer: [user, group#member] or editor type document relations define parent: [folder] define owner: [user] or owner from parent define editor: [user, group#member] or owner or editor from parent define viewer: [user, group#member] or editor or viewer from parent

Beispiel-Tupel:

folder:engineering#owner@user:charlie document:readme#parent@folder:engineering document:readme#viewer@user:alice

Ergebnis: Alice kann die readme einsehen (direktes Tupel). Charlie kann die readme besitzen, bearbeiten und einsehen (geerbt über Ordner-Ownership). Jedes Mitglied einer Gruppe, die Editor des Engineering-Ordners ist, kann die readme bearbeiten (geerbt über Ordner).

SaaS-Multi-Tenant-Modell

type user type organization relations define admin: [user] define member: [user] or admin type project relations define parent_org: [organization] define owner: [user] or admin from parent_org define contributor: [user, organization#member] or owner define viewer: [user, organization#member] or contributor

Beispiel-Tupel:

organization:acme#admin@user:alice project:alpha#parent_org@organization:acme project:alpha#owner@user:bob

Ergebnis: Alice ist Admin von acme, daher Owner aller acme-Projekte (über admin from parent_org). Alle acme-Mitglieder können Projekt alpha einsehen (über organization#member).

GitHub-artiger Repository-Zugriff

type user type team relations define member: [user] type organization relations define admin: [user] define member: [user] or admin type repository relations define parent_org: [organization] define admin: [user, team#member] or admin from parent_org define writer: [user, team#member] or admin define reader: [user, team#member] or writer or member from parent_org

Von RBAC zu FGA migrieren

Du musst nicht das eine oder das andere wählen. Auris unterstützt die gleichzeitige Verwendung von RBAC und FGA:

RBAC behandelt: Grob-granularen Funktionszugriff (view:invoices, manage:users)

FGA behandelt: Fein-granularen ressourcenebenen Zugriff (document:X#editor@user:Y)

Migrationsschritte

  1. RBAC für Funktions-Gates beibehalten: Weiterhin POST /api/roles/check für Berechtigungsprüfungen wie create:reports verwenden, die global gelten.

  2. FGA für ressourcenebene Prüfungen hinzufügen: Wenn ein Benutzer ein Dokument erstellt, ein Tupel document:X#owner@user:Y schreiben. Dieses Tupel prüfen, wenn der Benutzer das Dokument bearbeiten möchte.

  3. Die FGA-Engine aktivieren: FGA_ENGINE_ENABLED=true in deinen Auris-Deployment-Umgebungsvariablen setzen.

  4. Ein Autorisierungsmodell erstellen: deine Objekttypen und Relationen in der Auris-Konsole unter Administration → Fine-Grained Authorization → Modelle definieren.

  5. Das Modell aktivieren: Auf deinem Modell auf „Aktivieren” klicken — dies macht es zum Live-Modell für alle Check/Expand/List-Objects-Operationen.

  6. Tupel schreiben: Wenn Objekte in deiner Anwendung erstellt und geteilt werden, Tupel in den FGA-Speicher über POST /api/fga/tuples oder POST /api/fga/tuples/bulk schreiben.

  7. Prüfungen integrieren: In deinem Ressourcen-Server POST /api/fga/check aufrufen, bevor Operationen auf bestimmten Ressourcen erlaubt werden.

Die Auris-Bridge-Schicht (resource-access.ts in der Auris-API) leitet Autorisierungsprüfungen automatisch an die FGA-Engine weiter, wenn FGA_ENGINE_ENABLED=true, und fällt auf die Legacy-Keto (ReBAC)-Schicht zurück, wenn false. Dies ermöglicht eine schrittweise Migration ohne Unterbrechung bestehender Autorisierungslogik.

Debugging mit der Konsole

Die Auris-Konsole bietet einen visuellen FGA-Debugger unter Administration → Fine-Grained Authorization → Debugger. Er unterstützt drei Operationen:

Check: Objekt, Relation und Subjekt eingeben — sehen, ob die Prüfung besteht oder fehlschlägt, mit dem vollständigen Auflösungsbaum, der jede ausgewertete Regel zeigt.

Expand: Objekt und Relation eingeben — alle Subjekte sehen, die diese Relation haben, und dabei die Rewrite-Regeln rekursiv verfolgen.

List Objects: Subjekt und Relation eingeben — alle Objekte eines bestimmten Typs sehen, auf die das Subjekt zugreifen kann.

Der visuelle Auflösungsbaum verwendet Farbkodierung:

  • Grüne Knoten: Regel wurde als wahr ausgewertet
  • Rote Knoten: Regel wurde als falsch ausgewertet
  • Blaue Knoten: Intermediate berechnete Usersets

Dies macht es einfach zu erkennen, warum ein Benutzer Zugriff auf eine bestimmte Ressource hat oder nicht hat.

Verwandte Konzepte