Built in Rust · Zanzibar-inspired · Multi-tenant

Authorization
that scales
with your graph

Kraalzibar is a production-ready Relationship-Based Access Control server inspired by Google's Zanzibar paper. Define your authorization model once, evaluate permissions with recursive graph traversal.

Relationship graph: user:alice and user:bob have direct relations to document:readme; user:carol is a member of group:eng which also has viewer access to document:readme owner viewer viewer member user alice user bob group eng user carol document readme view edit delete relationship tuples computed permissions
281+ tests passing
gRPC+REST dual protocol
Rust 1.85 edition 2024
multi-tenant per-schema isolation
depth 6 graph traversal

The authorization problem

Why classic models break at scale

As applications grow, rigid permission models collapse under the weight of real-world relationships. Authorization becomes a maintenance nightmare.

RBAC

Role-Based Access Control

Roles like admin, editor, and viewer work fine for small teams. But sharing a document with a specific user, a group, or temporarily requires new roles for every combination — leading to role explosion.

✗  Breaks when permissions need context

PBAC / ABAC

Policy & Attribute-Based Control

Policies like resource.owner == user.id || user.dept IN allowed_depts encode business logic into evaluation engines. Policies grow complex, become hard to audit, and require deep knowledge to change safely.

✗  Hard to reason about at runtime

ReBAC ✦ Kraalzibar

Relationship-Based Access Control

Permissions flow through a graph of relationships. alice owns document:readme because the tuple document:readme#owner@user:alice exists — no role explosion, no complex policies, just data.

✓  Self-evident, composable, auditable

The Zanzibar model

Relationships are the permission model

Google's Zanzibar paper (2019) described the authorization system powering Drive, Maps, and YouTube — serving billions of ACL checks per second. Kraalzibar brings that model to your infrastructure.

At its core, the model is simple: a tuple object#relation@subject asserts that a subject holds a relation to an object. Permissions are then evaluated by traversing the graph of these tuples — with support for union, intersection, exclusion, and indirect relations through arrow operators.

🔗

Composable by design

Relations can refer to other relations. A group's members inherit access granted to the group — no duplication needed.

🕒

Consistent snapshots

Snapshot tokens prevent the "new enemy" problem: a write returns a token; reads can be pinned to that exact snapshot.

🏗️

Schema-driven

Types, relations, and permissions are declared in a DSL. Breaking changes are rejected unless you force-override them.

🔍

Bidirectional queries

Ask "can Alice view this?" and "who can view this?" and "what can Alice view?" — all natively supported.

Relationship tuples
// object#relation@subject

document:readme#owner@user:alice
document:readme#viewer@user:bob
document:readme#viewer@group:eng#member
group:eng#member@user:carol

// Permission checks evaluate the graph:
// alice  → view document:readme?  ✓ allowed
// bob    → edit document:readme?  ✗ denied
// carol  → view document:readme?  ✓ allowed (via group:eng)
Check result
"subject": "user:carol",
"permission": "view",
"resource": "document:readme"

→ traversal path:
  user:carol
    └─ member of group:eng
         └─ viewer of document:readme
              └─ view = viewer + owner
                   ✓ ALLOWED

Model comparison

Choosing the right model for the job

Each model has its place — but when you need fine-grained, contextual, scalable access control, ReBAC is the clear choice.

The challenge with RBAC
  • Sharing document:quarterly-report with a specific external contractor requires a new role, say contractor-doc-viewer-12
  • Temporary access? Add another role. Project-scoped access? More roles. Role count grows exponentially.
  • Permission inheritance is opaque — which role granted this access? Hard to audit or revoke safely.
  • Hierarchical resources (folders containing folders) require duplicating role assignments at every level.
What you want instead

A direct tuple: document:quarterly-report#viewer@user:contractor

  • No new role needed — the relationship is the permission
  • Revoking access is deleting a single tuple
  • Audit trail is just the tuple history
  • Scales to billions of objects without role proliferation
The challenge with PBAC/ABAC
  • Policies encode business logic: resource.owner == subject.id || (subject.clearance >= resource.level && !resource.restricted)
  • Policy engines evaluate at request time — logic is hidden in code, not visible in data
  • Impossible to answer "who has access to X?" without scanning all subjects against all policies
  • Changing one policy can silently break unrelated access rules
What you want instead

Permission logic is declared in the schema, not buried in policy strings:

  • permission view = viewer + owner — composable, readable
  • Lookup queries answer "who can view X?" natively and efficiently
  • Schema changes are validated for breaking changes before applying
  • Access state is data, not code — easy to inspect and audit
How Kraalzibar handles it
  • Declare types & relations in a schema DSL once
  • Write tuples to grant access — no role creation required
  • Permission checks traverse the graph recursively (max depth 6)
  • Groups, nested groups, and indirect access work automatically
Built-in capabilities
  • check — does subject have permission on object?
  • lookup_resources — what can this subject access?
  • lookup_subjects — who can access this resource?
  • expand — debug the full permission tree

Schema DSL

Express your authorization model in code

schema.kz
// Define your object types
definition user {}

definition group {
  relation member: user
}

definition folder {
  relation owner:  user
  relation editor: user | group#member
  relation viewer: user | group#member

  permission view   = viewer + editor + owner
  permission edit   = editor + owner
  permission delete = owner
}

definition document {
  relation parent: folder
  relation owner:  user
  relation viewer: user | group#member

  // Inherit folder permissions via arrow →
  permission view   = viewer + owner + parent->view
  permission edit   = owner  + parent->edit
  permission delete = owner
}

Four building blocks

The schema DSL maps directly to the authorization semantics. What you write is what gets checked.

definition

Declares an object type (user, document, group). Every resource in your system maps to a defined type.

relation

A named edge in the graph. viewer: user | group#member means the viewer can be a direct user or any member of a group.

permission

A computed boolean from union (+), intersection (&), and exclusion (-) of relations and other permissions.

arrow →

parent->view traverses the parent relation and checks view on the target — enabling permission inheritance.

Breaking change protection: WriteSchema validates the new schema against existing tuples. Removing a type or relation that's in use is rejected unless you pass force: true.

Features

Everything you need in production

🦀

Written in Rust

Memory-safe, zero-overhead, blazing-fast. Edition 2024 with native async traits. No GC pauses in your hot check path.

🏗️

Multi-tenant isolation

Each tenant gets a separate PostgreSQL schema. Data, schema, and API keys are fully isolated — share one cluster, zero cross-tenant leakage.

gRPC + REST APIs

Both protocols, consistent behavior. Rust, Go, and TypeScript SDKs included. Protobuf definitions are versioned under proto/.

📸

Snapshot consistency

Writes return a zed token. Subsequent reads can be pinned to that snapshot — preventing the "new enemy" problem of stale cache reads.

🚀

Schema & check caching

Schema cache with TTL + local invalidation on write. Check cache keyed by snapshot token — immutable entries never go stale.

📊

Prometheus metrics

Request counters, latency histograms, and cache hit/miss rates at /metrics. Drop-in for Grafana dashboards.

🔍

OpenTelemetry tracing

Optional OTLP export (feature-gated). Per-request spans for check, lookup, and schema operations.

📋

Structured audit log

Structured JSON events for every schema write, relationship write, and authentication attempt. Routed to stdout — 12-factor compliant.

🐳

Docker-ready

Pre-built images on Docker Hub. Full docker compose stack with PostgreSQL, Prometheus, Tempo, Grafana, and OTel Collector included.

Quick start

Up and running in minutes

No database needed to start. The dev mode runs entirely in-memory with no authentication required.

01Start the server
02Write a schema
03Create relationships
04Check permissions
05Lookup subjects
06Production setup
01

Start the server

Build and run with Cargo or pull the Docker image. Starts on gRPC :50051 and REST :8080.

shell
# From source
cargo run --release

# Docker (dev mode — in-memory, no auth)
docker run -p 50051:50051 -p 8080:8080 \
  pubkraal/kraalzibar-server:latest
02

Write an authorization schema

Define your types, relations, and computed permissions.

POST /v1/schema
curl -s -X POST http://localhost:8080/v1/schema \
  -H 'Content-Type: application/json' \
  -d '{
    "schema": "definition user {}\n\ndefinition document {\n  relation owner: user\n  relation viewer: user\n  permission view = viewer + owner\n  permission edit = owner\n}"
  }'

# Response:
{ "breaking_changes_overridden": false }
03

Create relationships

Write tuples to grant access. Returns a snapshot token for causal consistency.

POST /v1/relationships/write
curl -s -X POST http://localhost:8080/v1/relationships/write \
  -H 'Content-Type: application/json' \
  -d '{
    "updates": [
      {
        "operation": "touch",
        "resource_type": "document", "resource_id": "readme",
        "relation": "owner",
        "subject_type": "user", "subject_id": "alice"
      },
      {
        "operation": "touch",
        "resource_type": "document", "resource_id": "readme",
        "relation": "viewer",
        "subject_type": "user", "subject_id": "bob"
      }
    ]
  }'

# Response:
{ "written_at": "1" }
04

Check permissions

Ask whether a subject has a permission on a resource. Fast, recursive graph traversal.

POST /v1/permissions/check
curl -s -X POST http://localhost:8080/v1/permissions/check \
  -H 'Content-Type: application/json' \
  -d '{
    "resource_type": "document", "resource_id": "readme",
    "permission": "edit",
    "subject_type": "user", "subject_id": "alice"
  }'

{ "allowed": true,  "checked_at": "1" }  ← alice is owner → can edit

curl -s -X POST http://localhost:8080/v1/permissions/check \
  -H 'Content-Type: application/json' \
  -d '{ ..., "subject_id": "bob", "permission": "edit" }'

{ "allowed": false, "checked_at": "1" }  ← bob is only viewer
05

Lookup subjects

Ask who has a given permission on a resource — or which resources a subject can access.

POST /v1/permissions/subjects
# Who can view document:readme?
curl -s -X POST http://localhost:8080/v1/permissions/subjects \
  -d '{
    "resource_type": "document", "resource_id": "readme",
    "permission": "view", "subject_type": "user"
  }'

{
  "subjects": [
    { "subject_type": "user", "subject_id": "alice" },
    { "subject_type": "user", "subject_id": "bob" }
  ],
  "looked_up_at": "1"
}
06

Production with PostgreSQL

Provision tenants, create API keys, and connect persistent storage. Each tenant is fully isolated.

shell
# 1. Run database migrations
kraalzibar-server migrate --config config.toml

# 2. Provision a tenant
kraalzibar-server provision-tenant --name acme --config config.toml

# 3. Create an API key
kraalzibar-server create-api-key --tenant-name acme --config config.toml
# → kraalzibar_a1b2c3d4_s5t6u7v8w9x0y1z2a3b4c5d6e7f8

# 4. Use the key in requests
curl -X POST http://localhost:8080/v1/permissions/check \
  -H 'Authorization: Bearer kraalzibar_a1b2c3d4_...' \
  -d '{ ... }'

Architecture

A clean workspace crate design

REST :8080 gRPC :50051 CRATE kraalzibar-server AuthzService · Middleware · Config CRATE kraalzibar-core Schema DSL · Graph Engine · Types CRATE kraalzibar-storage InMemory + PostgreSQL backends in-memory PostgreSQL CRATE kraalzibar-client Rust gRPC SDK sdks/go gRPC sdks/ts REST metrics + tracing Prometheus · OTLP · Grafana

Kraalzibar is a Cargo workspace with four crates and three client SDKs. The layered design keeps the domain logic free from transport and storage concerns.

Crate / SDK Responsibility
kraalzibar-coreSchema DSL parser, graph engine, domain types
kraalzibar-storageStorage trait, InMemory + PostgreSQL backends
kraalzibar-servergRPC + REST handlers, config, metrics, CLI
kraalzibar-clientRust gRPC SDK
sdks/goGo gRPC SDK
sdks/typescriptTypeScript REST SDK

12-factor compliant. All configuration via environment variables (KRAALZIBAR_ prefix). Structured JSON logs to stdout. Stateless server — all state in PostgreSQL.

Engine limits. Max traversal depth 6. Schema caps: 50 types, 30 relations, 30 permissions per type. Lookup limit 1000 results. Configurable.