A implementação Zanzibar mais madura
SpiceDB é o produto da Authzed, fundada por engenheiros vindos do Red Hat/CoreOS. Open-source desde 2021, Apache 2.0. É hoje a implementação ReBAC mais alinhada ao paper Zanzibar — mantém zookies (rebatizados ZedTokens), userset rewrites, namespaces, APIs Check/Expand/Lookup, e ainda adicionou caveats (ABAC condicional).
Schema Language: declarando o domínio
A primeira coisa a desenhar é o schema. SpiceDB tem uma DSL própria, type-safe, com tooling (zed CLI, playground.authzed.com). Exemplo de schema Drive-like:
definition user {}
definition organization {
relation admin: user
relation member: user
permission manage = admin
permission view = admin + member
}
definition folder {
relation parent: folder | organization
relation owner: user
relation editor: user
relation viewer: user
permission write = owner + editor + parent->write
permission read = viewer + write + parent->read
}
definition document {
relation parent: folder
relation owner: user
relation editor: user
relation viewer: user
permission write = owner + editor + parent->write
permission read = viewer + write + parent->read
permission share = owner + parent->write
}Essa DSL substitui o protobuf de namespace config do paper Zanzibar — mesma expressividade, leitura humana. aplica em prod; versionado em git.
Os 4 verbos da API
| API | Quando usar | Latência alvo |
|---|---|---|
| WriteRelationships | Criar/deletar tuplas (compartilhar doc, adicionar membro) | ~10-30ms (storage commit) |
| CheckPermission | Pode user X fazer ação Y no recurso Z? — path do request HTTP | <10ms p99 (cache hot) |
| LookupResources | "Quais docs Tom pode ler?" — UI de listagem do user | Stream; depende do fan-out |
| LookupSubjects | "Quem tem acesso ao doc X?" — UI de admin/share | Stream; depende do grafo |
| ExpandPermission | Debug: quem deriva essa perm e por quê | ~50ms; uso esporádico |
| ReadRelationships | Listar tuplas brutas (compliance, audit) | Stream paginado |
Exemplo end-to-end: compartilhar e checar
import { v1 } from '@authzed/authzed-node';
const client = v1.NewClient('YOUR_API_TOKEN', 'spicedb.example.com:50051');
// 1. Alice compartilha doc:report-q1 com Bob como editor
const writeResp = await client.promises.writeRelationships(
v1.WriteRelationshipsRequest.create({
updates: [
v1.RelationshipUpdate.create({
operation: v1.RelationshipUpdate_Operation.CREATE,
relationship: v1.Relationship.create({
resource: { objectType: 'document', objectId: 'report-q1' },
relation: 'editor',
subject: { object: { objectType: 'user', objectId: 'bob' } },
}),
}),
],
}),
);
// IMPORTANTE: guardar o zedToken retornado
const zedToken = writeResp.writtenAt; // ex: 'GhUKEzE2Nz...'
await db.documents.update({
where: { id: 'report-q1' },
data: { authzZedToken: zedToken.token },
});
// 2. Bob faz GET /docs/report-q1 — middleware checa
const check = await client.promises.checkPermission(
v1.CheckPermissionRequest.create({
resource: { objectType: 'document', objectId: 'report-q1' },
permission: 'read',
subject: { object: { objectType: 'user', objectId: 'bob' } },
consistency: v1.Consistency.create({
requirement: { oneofKind: 'atLeastAsFresh',
atLeastAsFresh: { text: doc.authzZedToken },
},
}),
}),
);
if (check.permissionship !== v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION) {
throw new ForbiddenError();
}O persistido junto ao recurso é o que garante consistência. Sem ele, você pode ler dados com permissões obsoletas — exatamente o new enemy problem.
Caveats: ABAC dentro de ReBAC
A maior limitação histórica do ReBAC puro: como expressar "Bob pode ler MAS apenas do escritório (IP no CIDR corporativo)"? Em SpiceDB pós-2023, caveats resolvem isso via expressões CEL avaliadas em runtime.
caveat ip_allowlist(user_ip string, allowed_cidrs list<string>) {
user_ip.isIpAddress() && user_ip.ipInCidrList(allowed_cidrs)
}
definition document {
relation viewer: user with ip_allowlist
permission read = viewer
}// Write da tupla COM contexto fixo
await client.promises.writeRelationships({
updates: [{
operation: 'CREATE',
relationship: {
resource: { objectType: 'document', objectId: 'sensitive' },
relation: 'viewer',
subject: { object: { objectType: 'user', objectId: 'bob' } },
optionalCaveat: {
caveatName: 'ip_allowlist',
context: { allowed_cidrs: ['10.0.0.0/8', '192.168.0.0/16'] },
},
},
}],
});
// Check passando contexto runtime
await client.promises.checkPermission({
resource: { objectType: 'document', objectId: 'sensitive' },
permission: 'read',
subject: { object: { objectType: 'user', objectId: 'bob' } },
context: { user_ip: req.ip }, // contexto dinâmico do request
});Arquitetura de deployment
Performance: como tirar sub-ms
Tuning crítico: e . Sem isso, você não chega aos números do paper. Em single-node dev é fine, mas em produção dispatch cluster é obrigatório.
Modelagem real: multi-tenant SaaS
definition user {}
definition organization {
relation admin: user
relation billing_admin: user
relation member: user
permission manage = admin
permission view_billing = admin + billing_admin
permission view = admin + billing_admin + member
}
definition project {
relation org: organization
relation owner: user
relation contributor: user
permission manage = owner + org->admin
permission write = manage + contributor
permission read = write + org->member
}
definition issue {
relation project: project
relation assignee: user
relation reporter: user
permission manage = reporter + project->manage
permission write = manage + assignee + project->write
permission read = project->read
} Note como herança org → project → issue é declarativa. Adicionar "guest" ao projeto não exige role nova nem mudança de schema — basta tupla project:web#contributor@user:guest. Esse é o ganho fundamental sobre RBAC.
Operando: zed CLI, dev container, testing
schema: |
definition user {}
definition doc {
relation viewer: user
relation editor: user
permission read = viewer + editor
}
relationships: |
doc:report#editor@user:alice
doc:report#viewer@user:bob
assertions:
assertTrue:
- doc:report#read@user:alice # editor pode ler
- doc:report#read@user:bob # viewer pode ler
assertFalse:
- doc:report#read@user:charlie # nem editor nem viewerQuando NÃO usar SpiceDB
- App pequeno com RBAC puro: 5 roles fixas, sem sharing → SQL bem modelado é mais simples e tem 1 dependência a menos.
- Decisões puramente sobre código/infra: K8s admission, terraform — use OPA, é o nicho dele.
- Sem orçamento de operação para storage stateful: SpiceDB precisa de Postgres com replication. Se você não tem time/orçamento, considere OpenFGA managed (Auth0 FGA) ou AVP.
- Compliance que exige policy-as-code legível por auditor: Cedar e OPA escrevem regras em texto. SpiceDB é estrutural + dado — alguns auditores estranham.
Resumo executivo
- SpiceDB (Authzed, Apache 2.0) é a implementação Zanzibar mais madura — fiel ao paper.
- Schema language declarativa, type-safe, versionada em git. Substitui o protobuf do paper.
- APIs Check/Expand/Lookup{Resources,Subjects}/Write — 4 verbos resolvem o caso de uso completo.
- ZedTokens portam Zookies: persistir junto ao recurso, enviar em CheckPermission para consistência.
- Caveats (2023) trazem ABAC condicional via CEL — preenchem o gap histórico do ReBAC puro.
- Storage: Postgres ou CockroachDB. Dispatch cluster + cache Ristretto = sub-ms checks.
- Use para: SaaS multi-tenant com sharing, Drive/Notion-like, IAM granular. Evite para: 5 roles fixas, infra-policy.