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-2024Der 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:
documentkann Relationen haben:owner,editor,viewerfolderkann Relationen haben:owner,viewerorganizationkann 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:charlieDas 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 editorJeder 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 editorDas bedeutet: „Ein Subjekt ist ein viewer eines Dokuments, wenn:
- Das Subjekt ein
user-Typ ist UND ein direktes Tupeldocument:X#viewer@user:Yexistiert, ODER - Das Subjekt ein
group#member(Mitglied einer Gruppe) ist UND ein direktes Tupeldocument:X#viewer@group:Zexistiert, ODER - Das Subjekt ein
editordes 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 editorDies 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 parentBeispiel-Tupel:
folder:engineering#owner@user:charlie
document:readme#parent@folder:engineering
document:readme#viewer@user:aliceErgebnis: 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 contributorBeispiel-Tupel:
organization:acme#admin@user:alice
project:alpha#parent_org@organization:acme
project:alpha#owner@user:bobErgebnis: 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_orgVon 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
-
RBAC für Funktions-Gates beibehalten: Weiterhin
POST /api/roles/checkfür Berechtigungsprüfungen wiecreate:reportsverwenden, die global gelten. -
FGA für ressourcenebene Prüfungen hinzufügen: Wenn ein Benutzer ein Dokument erstellt, ein Tupel
document:X#owner@user:Yschreiben. Dieses Tupel prüfen, wenn der Benutzer das Dokument bearbeiten möchte. -
Die FGA-Engine aktivieren:
FGA_ENGINE_ENABLED=truein deinen Auris-Deployment-Umgebungsvariablen setzen. -
Ein Autorisierungsmodell erstellen: deine Objekttypen und Relationen in der Auris-Konsole unter Administration → Fine-Grained Authorization → Modelle definieren.
-
Das Modell aktivieren: Auf deinem Modell auf „Aktivieren” klicken — dies macht es zum Live-Modell für alle Check/Expand/List-Objects-Operationen.
-
Tupel schreiben: Wenn Objekte in deiner Anwendung erstellt und geteilt werden, Tupel in den FGA-Speicher über
POST /api/fga/tuplesoderPOST /api/fga/tuples/bulkschreiben. -
Prüfungen integrieren: In deinem Ressourcen-Server
POST /api/fga/checkaufrufen, 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
- Fine-Grained Authorization Leitfaden — Praktischer Leitfaden zur FGA-Implementierung
- Rollen & Berechtigungen — Wie RBAC und FGA zusammenarbeiten
- FGA-Debugger — Autorisierungsprüfungen in der Konsole testen
- Fine-Grained Authorization API — Modelle, Tupel, Check, Expand und List-Objects-Endpunkte
- JavaScript SDK —
client.fga.check()und Tupel-Verwaltung