Model application authorization as relationships, then let Anvil answer the question that matters: may this subject perform this action on this object?
zanzibar is a typed Rust API for schemas, relationship tuples, consistency,
and permission checks backed by Anvil 0.6.0. It suits applications whose access
rules grow beyond a role column: shared documents, nested teams, delegated
administration, tenant resources, public objects, and other relationship-based
policies.
Anvil remains the durable authority and evaluates permissions server-side. The crate provides the application-facing model, authenticated connection, token refresh, exact schema bindings, atomic tuple mutations, and typed results.
| Capability | Available |
|---|---|
| Direct relations and derived permissions | Yes |
| Entity, userset, same-resource, exact, and public subject selectors | Yes |
| Nested userset evaluation | Yes |
| Atomic add/remove batches with idempotent retries | Yes, up to 1,000 mutations |
| Single and ordered batch permission checks | Yes, up to 1,000 checks |
| Latest, at-least, and exact-revision consistency | Yes |
| Filtered, revision-pinned tuple pagination | Yes |
| Automatic client-credential exchange and bearer refresh | Yes |
cargo add zanzibar@0.3
cargo add tokio --features macros,rt-multi-threadThe adapter targets Anvil 0.6.0. Start Anvil and provision a tenant owner by following Anvil's five-minute setup. That flow gives the application three values:
export ANVIL_ENDPOINT=http://127.0.0.1:50051
export ANVIL_STORAGE_TENANT=example
export ANVIL_CLIENT_ID=example-client
export ANVIL_CLIENT_SECRET='the-secret-selected-during-provisioning'Use the HTTP endpoint only for loopback development. For networked deployments,
put Anvil behind a TLS terminator and configure its https:// endpoint before
sending application credentials.
Applications authenticate with the client ID and secret. Anvil exchanges them
for short-lived bearer credentials; AnvilRebacEngine refreshes those
credentials before they expire.
This complete example declares a writable viewer relation and a derived
can_read permission. It binds the schema to one authorization realm, grants
Alice access, and checks the permission at or after the mutation's revision.
use std::collections::BTreeSet;
use zanzibar::anvil::{AnvilRebacConfig, AnvilRebacEngine};
use zanzibar::{
AuthzScope, CheckRequest, Consistency, MutateTuplesRequest,
NamespaceDefinition, Object, PermissionRule, RebacEngine,
RelationDefinition, SchemaBuilder, SchemaId, Subject, SubjectSelector,
Tuple, TupleUpdate, put_and_bind_schema,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let tenant = std::env::var("ANVIL_STORAGE_TENANT")?;
let engine = AnvilRebacEngine::connect(AnvilRebacConfig {
endpoint: std::env::var("ANVIL_ENDPOINT")?,
storage_tenant: tenant.clone().into(),
client_id: std::env::var("ANVIL_CLIENT_ID")?,
client_secret: std::env::var("ANVIL_CLIENT_SECRET")?,
})
.await?;
let schema = SchemaBuilder::new()
.namespace(
"document",
NamespaceDefinition::new()
.relation(
"viewer",
RelationDefinition::Direct {
allowed_subjects: BTreeSet::from([SubjectSelector::AnyObject {
namespace: "user".into(),
}]),
},
)
.relation(
"can_read",
RelationDefinition::Permission {
rules: BTreeSet::from([PermissionRule::Inherit {
relation: "viewer".into(),
}]),
},
),
)
.build();
// A realm is an independent authorization graph inside this storage tenant.
let scope = AuthzScope::new(tenant, "documents");
put_and_bind_schema(
&engine,
&scope,
SchemaId("document-access".into()),
schema,
None,
)
.await?;
let alice = Subject::Entity(Object {
namespace: "user".into(),
id: "alice".into(),
});
let roadmap = Object {
namespace: "document".into(),
id: "roadmap".into(),
};
let written = engine
.mutate_tuples(MutateTuplesRequest::new(
scope.clone(),
"grant-roadmap-alice-v1",
vec![TupleUpdate::Add(Tuple {
object: roadmap.clone(),
relation: "viewer".into(),
subject: alice.clone(),
})],
)?)
.await?;
let decision = engine
.check(
&scope,
CheckRequest {
subject: alice,
relation: "can_read".into(),
object: roadmap,
},
Consistency::AtLeast(written.revision),
)
.await?;
assert!(decision.allowed);
println!("allowed at authorization revision {}", decision.revision.0);
Ok(())
}The application that performs a realm's first schema binding becomes that realm's owner. Later calls are authorized by Anvil's Zanzibar policy just like the data being protected.
A direct relation accepts stored tuples. Its selectors say which typed subjects are legal. A permission is derived from direct relations or other permissions:
Inheritincludes another relation or permission on the same object.TupleToUsersetfollows a related object and evaluates a relation there.AnyUsersetpermits subjects such asgroup:engineering#member, enabling nested group and delegation models.Publicpermits Anvil's reserved anonymous principal as a tuple subject; access is granted only when that tuple is written, and protected resources remain private by default.
Schemas are immutable and content-addressed by their returned SchemaRef.
Publishing identical content is an idempotent replay. Binding uses a generation
check, so schema changes cannot silently overwrite a concurrent update.
Every tuple mutation and permission decision returns an AuthzRevision:
Consistency::Latestevaluates current authoritative state.Consistency::AtLeast(revision)requires state that includes a known write; if the serving state is still behind, Anvil returnsRevisionUnavailable.Consistency::Exact(revision)evaluates only that current revision and fails if it is no longer retained.
MutateTuplesRequest requires a caller-generated operation ID and a non-empty
batch. Reuse an operation ID only when retrying the same logical batch; a new
add or remove needs a new ID. Anvil applies up to 1,000 mutations atomically and
returns whether the call was replayed. check_many evaluates up to 1,000 checks
at one revision while preserving request order.
Tuple reads are filtered and paginated. A continuation token pins its filters and revision, preventing a multi-page result from drifting silently.
This release models opaque authorization object IDs. Anvil exact-path object references are not yet exposed by this crate. Object/subject discovery and ordered tuple watches are also outside the API; applications can use paged tuple reads for bounded inspection.
With Anvil 0.6.0, create and bind new custom realms while one cluster node is active, then expand the cluster. Existing bound realms continue to operate across the cluster.
Apache-2.0