ReBAC 实现:基于关系的访问控制
ReBAC(Relationship-Based Access Control)是 coomia-dip 三层权限模型的第三层,也是最强大的一层。它通过对象之间的图关系来推导访问权限,天然契合本体论驱动的平台架构。本文详解 ReBAC 的关系图模型、Zanzibar 风格的元组存储、关系遍历算法、权限继承与否定、性能优化策略,以及与 RBAC/ABAC 层的协同决策机制。
“系列:S6 平台工程 · 第 4 篇 | 难度:高级 | 阅读时间:18 分钟
ReBAC 实现:基于关系的访问控制
#TL;DR
ReBAC(Relationship-Based Access Control)是 coomia-dip 三层权限模型的第三层,也是最强大的一层。它通过对象之间的图关系来推导访问权限,天然契合本体论驱动的平台架构。本文详解 ReBAC 的关系图模型、Zanzibar 风格的元组存储、关系遍历算法、权限继承与否定、性能优化策略,以及与 RBAC/ABAC 层的协同决策机制。
#1. 为什么需要 ReBAC
#1.1 RBAC 和 ABAC 的局限
在大规模多租户平台中,RBAC 和 ABAC 各有盲区:
| 场景 | RBAC 能力 | ABAC 能力 | ReBAC 能力 |
|---|---|---|---|
| 用户 A 是文件夹 X 的 owner | 需创建专用角色 | 可通过属性匹配 | 直接关系查询 |
| 用户 A 属于组 G,组 G 有权访问项目 P | 需角色传递 | 不支持 | 关系链遍历 |
| 项目 P 嵌套在组织 O 中,O 的管理员能管理 P | 需层级角色 | 不支持 | 层级关系继承 |
| 文件 F 在文件夹 D 中,D 在项目 P 下 | 需为每个文件分配角色 | 需维护属性 | 自动层级继承 |
| 共享链接使特定用户获得只读权限 | 需专门角色 | 需专门策略 | 直接关系 |
ReBAC 的核心洞察是:权限本质上是对象之间的关系,而本体论平台天然维护着丰富的对象关系图。
#1.2 ReBAC 与 Ontology 的天然契合
coomia-dip 的 Ontology 已经定义了对象类型和关系类型。ReBAC 复用这些关系来推导权限:
Ontology 层定义:
ObjectType: Project
ObjectType: Dataset
RelationType: Project --contains--> Dataset
ReBAC 权限语义:
Project:proj1 --editor--> User:alice
Project:proj1 --contains--> Dataset:ds1
=> User:alice 对 Dataset:ds1 有 editor 权限(通过关系继承)
#1.3 Google Zanzibar 的启发
Google Zanzibar 论文(2019)定义了 ReBAC 的工业标准实现。coomia-dip 借鉴其核心思想:
- 元组存储:
(object, relation, user)三元组 - 命名空间配置:定义类型和关系的 schema
- Check API:判定用户是否有权
- Expand API:展开某关系的所有用户
- Watch API:订阅权限变更
#2. 关系图模型设计
#2.1 核心数据结构
ReBAC 的基础是关系元组(Relationship Tuple):
// 关系元组定义
message RelationshipTuple {
ObjectReference resource = 1; // 资源对象
string relation = 2; // 关系名称
SubjectReference subject = 3; // 主体(用户或对象集合)
}
message ObjectReference {
string type = 1; // 对象类型,如 "project", "dataset"
string id = 2; // 对象 ID
}
message SubjectReference {
ObjectReference object = 1; // 主体对象
string optional_relation = 2; // 可选的关系限定
}
关键设计:SubjectReference 的 optional_relation
这允许表达"对象集合"作为主体,例如:
(document:readme, viewer, project:alpha#member)
含义:project:alpha 的所有 member 都是 document:readme 的 viewer。
#2.2 命名空间配置(Type Definition)
每个对象类型需要定义其关系模式:
# 命名空间配置 — 类似 Zanzibar 的 namespace config
type_definitions:
- type: "organization"
relations:
admin:
description: "组织管理员"
directly_related_types:
- type: "user"
member:
description: "组织成员"
union:
- directly_related: { type: "user" }
- computed_relation: "admin" # admin 也是 member
- type: "project"
relations:
parent_org:
description: "所属组织"
directly_related_types:
- type: "organization"
admin:
description: "项目管理员"
union:
- directly_related: { type: "user" }
- tuple_to_userset:
tupleset: "parent_org"
computed_relation: "admin" # 组织 admin 也是项目 admin
editor:
description: "项目编辑者"
union:
- directly_related: { type: "user" }
- directly_related: { type: "team", relation: "member" }
- computed_relation: "admin" # admin 也是 editor
viewer:
description: "项目查看者"
union:
- directly_related: { type: "user" }
- computed_relation: "editor" # editor 也是 viewer
- type: "dataset"
relations:
parent_project:
description: "所属项目"
directly_related_types:
- type: "project"
owner:
description: "数据集拥有者"
directly_related: { type: "user" }
editor:
union:
- directly_related: { type: "user" }
- computed_relation: "owner"
- tuple_to_userset:
tupleset: "parent_project"
computed_relation: "editor"
viewer:
union:
- directly_related: { type: "user" }
- computed_relation: "editor"
- tuple_to_userset:
tupleset: "parent_project"
computed_relation: "viewer"
#2.3 关系的三种推导方式
coomia-dip 的 ReBAC 引擎支持三种关系推导:
1. 直接关系 (Direct Relation)
┌─────────┐ viewer ┌──────────┐
│ User:bob │──────────>│ Doc:file1 │
└─────────┘ └──────────┘
2. 计算关系 (Computed Relation / Implied)
定义: viewer 包含 editor
┌───────────┐ editor ┌──────────┐
│ User:alice │──────────>│ Doc:file1 │
└───────────┘ └──────────┘
=> alice 自动获得 viewer 权限
3. 元组到用户集 (Tuple-to-Userset)
┌──────────┐ parent ┌──────────────┐
│ Doc:file1 │──────────>│ Project:proj1 │
└──────────┘ └──────────────┘
│ editor
▼
┌───────────┐
│ User:carol │
└───────────┘
=> carol 通过 proj1 的 editor 关系继承 file1 的 editor 权限
#3. 元组存储引擎
#3.1 存储层设计
关系元组的存储需要高吞吐读写和快速查询:
┌─────────────────────────────────────────────────────────┐
│ Tuple Store Layer │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ Write Path │ │ Read Path │ │ Watch Path │ │
│ │ (gRPC API) │ │ (gRPC API) │ │ (Stream API) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌──────▼──────────────────▼──────────────────▼───────┐ │
│ │ Changelog (WAL) │ │
│ │ - Zookie/Token per write for consistency │ │
│ │ - Ordered sequence of tuple changes │ │
│ └──────┬────────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼────────────────────────────────────────────┐ │
│ │ Primary Store (PostgreSQL) │ │
│ │ - relation_tuples table │ │
│ │ - Indexed: (resource_type, resource_id, relation)│ │
│ │ - Indexed: (subject_type, subject_id) │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Cache Layer (Redis) │ │
│ │ - Check result cache with Zookie invalidation │ │
│ │ - Expand result cache │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
#3.2 数据库表设计
-- 核心关系元组表
CREATE TABLE relation_tuples (
id BIGSERIAL PRIMARY KEY,
resource_type VARCHAR(128) NOT NULL,
resource_id VARCHAR(256) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_type VARCHAR(128) NOT NULL,
subject_id VARCHAR(256) NOT NULL,
subject_relation VARCHAR(64), -- NULL 表示直接用户
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE, -- 软删除
zookie BIGINT NOT NULL, -- 一致性令牌
UNIQUE (resource_type, resource_id, relation,
subject_type, subject_id, subject_relation)
);
-- 高频查询索引
CREATE INDEX idx_tuples_resource ON relation_tuples
(resource_type, resource_id, relation)
WHERE deleted_at IS NULL;
CREATE INDEX idx_tuples_subject ON relation_tuples
(subject_type, subject_id)
WHERE deleted_at IS NULL;
CREATE INDEX idx_tuples_zookie ON relation_tuples (zookie);
-- 变更日志表(用于 Watch API 和一致性)
CREATE TABLE changelog (
zookie BIGSERIAL PRIMARY KEY,
operation VARCHAR(16) NOT NULL, -- TOUCH / DELETE
resource_type VARCHAR(128) NOT NULL,
resource_id VARCHAR(256) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_type VARCHAR(128) NOT NULL,
subject_id VARCHAR(256) NOT NULL,
subject_relation VARCHAR(64),
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
#3.3 一致性模型:Zookie
Zanzibar 引入 Zookie 解决"新敌人"问题(权限刚被撤销但缓存尚未更新):
写入流程:
1. 客户端写入元组 → 获得 zookie Z₁
2. 后续 Check 请求携带 zookie Z₁
3. 服务端确保评估时看到 Z₁ 及之前的所有写入
一致性级别:
- minimize_latency: 使用缓存,可能读到旧数据
- at_least_as_fresh(zookie): 至少和指定 zookie 一样新
- fully_consistent: 读最新快照,最慢但最安全
class ZookieManager:
"""Zookie 一致性令牌管理器"""
def __init__(self, store: TupleStore):
self._store = store
self._current_zookie = AtomicCounter()
def next_zookie(self) -> int:
"""生成下一个全局递增的 zookie"""
return self._current_zookie.increment()
def snapshot_at(self, zookie: int) -> TupleSnapshot:
"""获取指定 zookie 时刻的元组快照"""
return self._store.snapshot(zookie)
def is_at_least_as_fresh(self, requested: int, current: int) -> bool:
"""检查当前快照是否满足一致性要求"""
return current >= requested
#4. 权限检查算法
#4.1 Check API 实现
Check API 是 ReBAC 的核心:判定 (user, permission, resource) 是否成立。
class ReBACheckEngine:
"""ReBAC 权限检查引擎"""
def __init__(self, store: TupleStore, type_system: TypeSystem):
self._store = store
self._type_system = type_system
self._cache = CheckCache()
async def check(
self,
resource: ObjectRef,
permission: str,
subject: SubjectRef,
consistency: Consistency = Consistency.MINIMIZE_LATENCY,
) -> CheckResult:
"""
检查 subject 是否对 resource 拥有指定 permission。
算法思路:
1. 获取 resource 类型的关系定义
2. 根据定义的推导规则递归展开
3. 在展开过程中搜索 subject 是否可达
"""
cache_key = (resource, permission, subject)
if cached := self._cache.get(cache_key, consistency):
return cached
type_def = self._type_system.get_type(resource.type)
relation_def = type_def.get_relation(permission)
result = await self._evaluate_rewrite(
resource, relation_def.rewrite, subject,
visited=set(), depth=0
)
self._cache.set(cache_key, result)
return result
async def _evaluate_rewrite(
self,
resource: ObjectRef,
rewrite: RewriteRule,
subject: SubjectRef,
visited: set,
depth: int,
) -> CheckResult:
"""递归评估重写规则"""
if depth > MAX_DEPTH:
raise MaxDepthExceededError()
cycle_key = (resource, rewrite, subject)
if cycle_key in visited:
return CheckResult.DENIED # 防止循环
visited.add(cycle_key)
match rewrite:
case DirectRelation():
return await self._check_direct(resource, rewrite.relation, subject)
case ComputedRelation(relation=rel):
# 权限蕴含:如 editor 蕴含 viewer
rel_def = self._type_system.get_relation(resource.type, rel)
return await self._evaluate_rewrite(
resource, rel_def.rewrite, subject, visited, depth + 1
)
case TupleToUserset(tupleset=ts, computed=comp):
# 查找所有满足 tupleset 关系的父对象
parents = await self._store.query_tuples(
resource_type=resource.type,
resource_id=resource.id,
relation=ts,
)
for parent_tuple in parents:
parent_ref = ObjectRef(
type=parent_tuple.subject_type,
id=parent_tuple.subject_id
)
parent_type_def = self._type_system.get_type(parent_ref.type)
parent_rel_def = parent_type_def.get_relation(comp)
result = await self._evaluate_rewrite(
parent_ref, parent_rel_def.rewrite, subject,
visited, depth + 1
)
if result.allowed:
return result
return CheckResult.DENIED
case Union(children=children):
# 任一子规则允许即允许
tasks = [
self._evaluate_rewrite(resource, child, subject, visited.copy(), depth + 1)
for child in children
]
results = await asyncio.gather(*tasks)
return CheckResult.ALLOWED if any(r.allowed for r in results) else CheckResult.DENIED
case Intersection(children=children):
# 所有子规则都允许才允许
tasks = [
self._evaluate_rewrite(resource, child, subject, visited.copy(), depth + 1)
for child in children
]
results = await asyncio.gather(*tasks)
return CheckResult.ALLOWED if all(r.allowed for r in results) else CheckResult.DENIED
case Exclusion(base=base, subtract=sub):
# base 允许且 subtract 不允许
base_result = await self._evaluate_rewrite(
resource, base, subject, visited.copy(), depth + 1
)
if not base_result.allowed:
return CheckResult.DENIED
sub_result = await self._evaluate_rewrite(
resource, sub, subject, visited.copy(), depth + 1
)
return CheckResult.ALLOWED if not sub_result.allowed else CheckResult.DENIED
#4.2 Expand API 实现
Expand API 展开某个关系的所有主体,用于调试和审计:
async def expand(
self,
resource: ObjectRef,
relation: str,
) -> UsersetTree:
"""
展开指定资源和关系的完整用户集合树。
返回树形结构,叶子节点是具体用户。
"""
type_def = self._type_system.get_type(resource.type)
rel_def = type_def.get_relation(relation)
return await self._expand_rewrite(resource, rel_def.rewrite, visited=set())
async def _expand_rewrite(
self,
resource: ObjectRef,
rewrite: RewriteRule,
visited: set,
) -> UsersetTree:
match rewrite:
case DirectRelation():
tuples = await self._store.query_tuples(
resource_type=resource.type,
resource_id=resource.id,
relation=rewrite.relation,
)
leaf_users = []
intermediate_nodes = []
for t in tuples:
if t.subject_relation:
# 主体是用户集合,继续展开
child_tree = await self._expand_rewrite(
ObjectRef(t.subject_type, t.subject_id),
DirectRelation(t.subject_relation),
visited,
)
intermediate_nodes.append(child_tree)
else:
leaf_users.append(SubjectRef(t.subject_type, t.subject_id))
return UsersetTree(
type="leaf" if not intermediate_nodes else "union",
users=leaf_users,
children=intermediate_nodes,
)
case Union(children=children):
child_trees = []
for child in children:
tree = await self._expand_rewrite(resource, child, visited)
child_trees.append(tree)
return UsersetTree(type="union", children=child_trees)
#4.3 并行评估与短路优化
class ParallelCheckOptimizer:
"""并行评估优化器"""
async def check_with_early_exit(
self,
resource: ObjectRef,
union_rules: list[RewriteRule],
subject: SubjectRef,
) -> CheckResult:
"""
对 Union 规则使用 asyncio 并发评估,
任一返回 ALLOWED 即短路退出。
"""
done_event = asyncio.Event()
result_holder = {"result": CheckResult.DENIED}
async def check_branch(rule: RewriteRule):
r = await self._evaluate_rewrite(resource, rule, subject)
if r.allowed:
result_holder["result"] = CheckResult.ALLOWED
done_event.set()
tasks = [asyncio.create_task(check_branch(r)) for r in union_rules]
# 等待任一任务设置事件或所有任务完成
wait_task = asyncio.create_task(done_event.wait())
all_done = asyncio.gather(*tasks, return_exceptions=True)
await asyncio.wait(
[wait_task, all_done],
return_when=asyncio.FIRST_COMPLETED,
)
# 取消剩余任务
for t in tasks:
if not t.done():
t.cancel()
return result_holder["result"]
#5. 关系继承与否定
#5.1 层级关系继承
在 coomia-dip 中,对象的层级关系决定了权限继承路径:
Organization: acme
│ admin: [alice]
│ member: [bob, charlie]
│
├── Project: alpha
│ │ parent_org: acme
│ │ admin: [inherited from org:acme#admin → alice]
│ │ editor: [dave]
│ │ viewer: [eve]
│ │
│ ├── Dataset: sales_2024
│ │ parent_project: alpha
│ │ owner: [frank]
│ │ editor: [inherited from project:alpha#editor → dave]
│ │ viewer: [inherited from project:alpha#viewer → eve]
│ │
│ └── Dataset: customer_info
│ parent_project: alpha
│ owner: [grace]
│ editor: [inherited]
│ viewer: [inherited]
│
└── Project: beta
│ parent_org: acme
│ admin: [inherited from org:acme#admin → alice]
│ editor: [heidi]
#5.2 否定关系(Exclusion)
有时需要从继承的权限中排除特定用户:
# 敏感数据集:继承项目权限但排除外包人员
type: "sensitive_dataset"
relations:
parent_project:
directly_related_types: [{ type: "project" }]
blocked:
directly_related_types: [{ type: "user" }]
viewer:
exclusion:
base:
tuple_to_userset:
tupleset: "parent_project"
computed_relation: "viewer"
subtract:
direct_relation: "blocked"
#5.3 条件关系(Contextual Tuples)
某些关系只在特定上下文中成立:
class ContextualTuple:
"""上下文感知的关系元组"""
resource: ObjectRef
relation: str
subject: SubjectRef
condition: dict # 条件表达式
def evaluate_condition(self, context: dict) -> bool:
"""评估条件是否满足"""
for key, expected in self.condition.items():
actual = context.get(key)
if actual != expected:
return False
return True
# 使用示例:工作时间内才有编辑权限
contextual_tuple = ContextualTuple(
resource=ObjectRef("project", "alpha"),
relation="editor",
subject=SubjectRef("user", "bob"),
condition={"time_range": "business_hours", "network": "internal"},
)
#6. 与 RBAC/ABAC 的协同
#6.1 三层决策流水线
coomia-dip 的权限决策遵循三层流水线:
请求进入
│
▼
┌─────────────────────────────────┐
│ Layer 1: RBAC 粗粒度过滤 │
│ - 检查用户角色是否包含所需权限 │
│ - 快速拒绝无角色匹配的请求 │
│ Result: ALLOW / DENY / CONTINUE │
└──────────────┬──────────────────┘
│ CONTINUE
▼
┌─────────────────────────────────┐
│ Layer 2: ABAC 属性条件评估 │
│ - 评估主体/资源/环境属性条件 │
│ - 检查数据分类级别约束 │
│ Result: ALLOW / DENY / CONTINUE │
└──────────────┬──────────────────┘
│ CONTINUE
▼
┌─────────────────────────────────┐
│ Layer 3: ReBAC 关系图遍历 │
│ - 检查对象间的直接和间接关系 │
│ - 处理继承、否定、条件关系 │
│ Result: ALLOW / DENY │
└─────────────────────────────────┘
#6.2 决策合并策略
class UnifiedPolicyEngine:
"""统一策略引擎:合并 RBAC + ABAC + ReBAC"""
async def authorize(self, request: AuthzRequest) -> AuthzDecision:
# Phase 1: RBAC
rbac_result = await self.rbac_engine.check(
user=request.subject,
permission=request.permission,
)
if rbac_result == Decision.EXPLICIT_DENY:
return AuthzDecision.DENIED
if rbac_result == Decision.EXPLICIT_ALLOW and not request.needs_fine_grained:
return AuthzDecision.ALLOWED
# Phase 2: ABAC
abac_result = await self.abac_engine.evaluate(
subject_attrs=request.subject_attributes,
resource_attrs=request.resource_attributes,
action=request.action,
environment=request.environment,
)
if abac_result == Decision.EXPLICIT_DENY:
return AuthzDecision.DENIED
# Phase 3: ReBAC
rebac_result = await self.rebac_engine.check(
resource=request.resource,
permission=request.permission,
subject=request.subject_ref,
)
# 合并决策
return self._merge_decisions(rbac_result, abac_result, rebac_result)
def _merge_decisions(self, rbac, abac, rebac) -> AuthzDecision:
"""
合并策略:
- 任一 EXPLICIT_DENY → DENIED(deny 优先)
- ReBAC ALLOW 且 ABAC 无否定 → ALLOWED
- RBAC ALLOW 且 ABAC ALLOW → ALLOWED
- 默认 → DENIED(默认拒绝)
"""
if any(d == Decision.EXPLICIT_DENY for d in [rbac, abac, rebac]):
return AuthzDecision.DENIED
if rebac.allowed and abac != Decision.EXPLICIT_DENY:
return AuthzDecision.ALLOWED
if rbac == Decision.EXPLICIT_ALLOW and abac == Decision.EXPLICIT_ALLOW:
return AuthzDecision.ALLOWED
return AuthzDecision.DENIED
#7. 性能优化
#7.1 缓存策略
ReBAC 的图遍历成本高,必须有效缓存:
class ReBACheckCache:
"""分层缓存系统"""
def __init__(self):
self.l1_local = LRUCache(max_size=10000) # 进程内缓存
self.l2_redis = RedisCache(ttl_seconds=300) # 分布式缓存
def get(self, key: CheckCacheKey, consistency: Consistency) -> Optional[CheckResult]:
if consistency == Consistency.FULLY_CONSISTENT:
return None # 不使用缓存
if consistency == Consistency.AT_LEAST_AS_FRESH:
# 检查缓存的 zookie 是否满足要求
cached = self.l1_local.get(key)
if cached and cached.zookie >= consistency.min_zookie:
return cached.result
cached = self.l2_redis.get(key)
if cached and cached.zookie >= consistency.min_zookie:
self.l1_local.set(key, cached)
return cached.result
return None
# minimize_latency: 直接返回缓存
if result := self.l1_local.get(key):
return result.result
if result := self.l2_redis.get(key):
self.l1_local.set(key, result)
return result.result
return None
def invalidate_for_resource(self, resource: ObjectRef):
"""当资源的关系发生变更时,失效相关缓存"""
pattern = f"check:{resource.type}:{resource.id}:*"
self.l1_local.invalidate_pattern(pattern)
self.l2_redis.invalidate_pattern(pattern)
#7.2 图遍历深度限制
# 全局配置
MAX_TRAVERSAL_DEPTH = 15 # 最大递归深度
MAX_CONCURRENT_BRANCHES = 50 # 最大并发分支数
CHECK_TIMEOUT_MS = 500 # 单次 Check 超时
class DepthLimiter:
"""深度限制器,防止无限递归"""
def __init__(self, max_depth: int = MAX_TRAVERSAL_DEPTH):
self.max_depth = max_depth
self._metrics = MetricsCollector()
def check_depth(self, current_depth: int, context: str):
if current_depth > self.max_depth:
self._metrics.increment("rebac.depth_exceeded", tags={"context": context})
raise MaxDepthExceededError(
f"ReBAC traversal exceeded max depth {self.max_depth} at {context}"
)
#7.3 批量 Check 优化
class BatchCheckOptimizer:
"""批量权限检查优化"""
async def batch_check(
self,
checks: list[CheckRequest],
) -> list[CheckResult]:
"""
批量检查优化策略:
1. 去重:相同的 (resource, permission, subject) 只查一次
2. 预取:批量加载相关元组到缓存
3. 共享遍历:复用中间遍历结果
"""
# 去重
unique_checks = {c.cache_key: c for c in checks}
# 预取所有涉及的资源的元组
resources = {(c.resource.type, c.resource.id) for c in unique_checks.values()}
await self._prefetch_tuples(resources)
# 并发执行去重后的检查
tasks = {
key: asyncio.create_task(self.engine.check(c.resource, c.permission, c.subject))
for key, c in unique_checks.items()
}
results = {key: await task for key, task in tasks.items()}
# 映射回原始请求顺序
return [results[c.cache_key] for c in checks]
async def _prefetch_tuples(self, resources: set[tuple[str, str]]):
"""批量预取元组到缓存"""
tasks = [
self._store.prefetch(resource_type=rt, resource_id=ri)
for rt, ri in resources
]
await asyncio.gather(*tasks)
#8. gRPC API 设计
#8.1 服务定义
service ReBAAuthorizationService {
// 权限检查
rpc Check(CheckRequest) returns (CheckResponse);
rpc BatchCheck(BatchCheckRequest) returns (BatchCheckResponse);
// 关系展开
rpc Expand(ExpandRequest) returns (ExpandResponse);
// 关系管理
rpc WriteTuples(WriteTuplesRequest) returns (WriteTuplesResponse);
rpc DeleteTuples(DeleteTuplesRequest) returns (DeleteTuplesResponse);
rpc ReadTuples(ReadTuplesRequest) returns (ReadTuplesResponse);
// 变更监听
rpc Watch(WatchRequest) returns (stream WatchResponse);
// 反向查询:用户能访问哪些资源
rpc LookupResources(LookupResourcesRequest) returns (stream LookupResourcesResponse);
rpc LookupSubjects(LookupSubjectsRequest) returns (stream LookupSubjectsResponse);
}
message CheckRequest {
ObjectReference resource = 1;
string permission = 2;
SubjectReference subject = 3;
Consistency consistency = 4;
repeated RelationshipTuple contextual_tuples = 5;
}
message CheckResponse {
bool allowed = 1;
int64 checked_at_zookie = 2;
DebugInfo debug = 3; // 可选的调试信息
}
#8.2 LookupResources:反向查询
async def lookup_resources(
self,
resource_type: str,
permission: str,
subject: SubjectRef,
) -> AsyncIterator[ObjectRef]:
"""
查找 subject 对哪些 resource_type 类型的资源拥有 permission。
这是 Check 的反向操作,用于列出用户可访问的资源。
"""
# 策略1:从 subject 出发正向遍历
direct_tuples = await self._store.query_by_subject(
subject_type=subject.type,
subject_id=subject.id,
)
seen = set()
for tuple in direct_tuples:
if tuple.resource_type == resource_type:
# 验证该关系是否蕴含所需权限
if await self._relation_implies(
resource_type, tuple.relation, permission
):
if tuple.resource_id not in seen:
seen.add(tuple.resource_id)
yield ObjectRef(resource_type, tuple.resource_id)
# 策略2:考虑通过中间对象的间接关系
for tuple in direct_tuples:
# 查找以此对象为父对象的子资源
children = await self._store.query_tuples(
subject_type=tuple.resource_type,
subject_id=tuple.resource_id,
)
for child in children:
if child.resource_type == resource_type and child.resource_id not in seen:
# 完整 Check 验证
result = await self.check(
ObjectRef(child.resource_type, child.resource_id),
permission,
subject,
)
if result.allowed:
seen.add(child.resource_id)
yield ObjectRef(child.resource_type, child.resource_id)
#9. 测试与调试
#9.1 关系图可视化
class ReBAGraphVisualizer:
"""关系图可视化工具,用于调试权限继承"""
async def visualize_permissions(
self,
resource: ObjectRef,
max_depth: int = 5,
) -> str:
"""生成 Mermaid 格式的关系图"""
lines = ["graph TD"]
visited = set()
async def traverse(obj: ObjectRef, depth: int):
if depth > max_depth or obj in visited:
return
visited.add(obj)
node_id = f"{obj.type}_{obj.id}"
tuples = await self._store.query_tuples(
resource_type=obj.type,
resource_id=obj.id,
)
for t in tuples:
subject_id = f"{t.subject_type}_{t.subject_id}"
label = t.relation
if t.subject_relation:
label += f" (via #{t.subject_relation})"
lines.append(f" {subject_id} -->|{label}| {node_id}")
if t.subject_type != "user":
await traverse(
ObjectRef(t.subject_type, t.subject_id),
depth + 1,
)
await traverse(resource, 0)
return "\n".join(lines)
#9.2 权限解释
class PermissionExplainer:
"""权限决策解释器"""
async def explain(
self,
resource: ObjectRef,
permission: str,
subject: SubjectRef,
) -> ExplanationTree:
"""
解释为什么 subject 对 resource 有/没有 permission。
返回完整的决策树,包括每一步的评估结果。
"""
root = ExplanationNode(
description=f"Check: {subject} -> {permission} -> {resource}",
)
result = await self._check_with_explanation(
resource, permission, subject, root
)
root.result = result
return ExplanationTree(root=root)
#10. 生产部署考量
#10.1 多区域部署
┌─────────────────────────────────────────────┐
│ Region A (Primary) │
│ ┌────────────┐ ┌────────────────────┐ │
│ │ ReBAC API │────>│ PostgreSQL Primary │ │
│ │ (gRPC) │ │ (Write + Read) │ │
│ └────────────┘ └────────────────────┘ │
│ │ │ │
│ │ WAL Replication │
│ │ │ │
└────────│─────────────────────│───────────────┘
│ │
│ ┌──────▼───────────────┐
│ │ Region B (Replica) │
│ │ ┌────────────────┐ │
│ │ │ PostgreSQL │ │
│ │ │ (Read Replica) │ │
│ │ └────────────────┘ │
│ │ ┌────────────────┐ │
│ │ │ ReBAC API │ │
│ │ │ (Read-only) │ │
│ │ └────────────────┘ │
│ └──────────────────────┘
#10.2 监控指标
REBAC_METRICS = {
"rebac_check_total": Counter("总检查次数"),
"rebac_check_latency": Histogram("检查延迟", buckets=[1, 5, 10, 50, 100, 500]),
"rebac_check_allowed": Counter("允许次数"),
"rebac_check_denied": Counter("拒绝次数"),
"rebac_traversal_depth": Histogram("遍历深度", buckets=[1, 2, 3, 5, 8, 15]),
"rebac_cache_hit_rate": Gauge("缓存命中率"),
"rebac_tuple_count": Gauge("元组总数"),
"rebac_expand_latency": Histogram("展开延迟"),
"rebac_batch_size": Histogram("批量检查大小"),
}
#Key Takeaways
- ReBAC 将权限建模为对象关系图,天然契合本体论驱动的平台架构
- 三种关系推导方式(直接、计算、元组到用户集)覆盖所有权限继承场景
- Zanzibar 风格的 Zookie 机制解决分布式环境下的一致性问题
- 并行评估 + 短路优化 + 分层缓存确保 Check API 的低延迟
- 与 RBAC/ABAC 的三层流水线提供从粗到细的完整授权体系
- LookupResources 反向查询支持"用户能看到什么"的高效回答
- Expand API 和权限解释器为审计和调试提供完整的可观测性
#Next Article
下一篇 S6-05 策略即查询重写 将深入探讨如何将权限策略转化为数据库查询条件,实现行级和列级的数据访问控制,而无需在应用层逐行过滤。
#rebac #zanzibar #relationship-based-access-control #authorization #graph-traversal #coomia-dip #platform-engineering