系统设计 / 访问控制 · 从 RBAC 查表到 policy 判定函数 / 治理:角色会腐烂,权限只增不减 待审核 9 / 23
治理 · role explosion / SoD

治理:角色会腐烂,权限只增不减

为什么:在 User 和 Permission 之间插一层 Role 给出的是模型正确的起点。真实系统烂掉不是因为模型错,而是因为它每周都在被改:新部门要一套权限、某个人要一个例外、上线前临时开一个口子。这一页讲的是让它别烂的几件事。

1 · 角色数的乘法

角色本该只描述职能。一旦部门、数据范围也被编进角色名,角色数就成了几个维度的乘积:3 个部门、3 种职能、2 档范围已经是 18 个角色,名字长成 editor-marketing-grouprole explosion 的成因几乎总是同一个:把本该当参数传的东西塞进了角色本身。

图 1-1 · 逐组合建角色与把部门/范围参数化两种做法的角色数对比。可调部门数、职能数与范围档数,观察两侧的增长速度差。

注 · 本系列的 perm.ts 自己藏着一处相关的实现意外:ROLES 里 admin 的 grants 写作 CATALOG.map((p) => [p.key, p.scopes[0]])。这意味着往能力清单里新增一条 permission,admin 立刻拥有它,不经任何分配动作;而它拿到的 scope 是该条 scopes 数组的第一项——content:article:createscopes 只写了 ['Self'],于是 admin 对新建文章的范围是 Self 而非 Full。清单数组的书写顺序于此成了实际策略,这类「顺序即策略」的写法在真实系统里同样常见,且不会有任何报错提醒。

2 · 继承边带来的意外提权

role hierarchy 让一个角色继承另一个角色的全部权限,senior-editor ⊃ editor 省掉一份重复清单。代价是提权路径变得不可见:给 reviewer 加一条「继承 editor」只是为了让它能读全部文章,editor 名下的 content:article:export 也跟着进来了,而导出恰恰是数据外泄风险最高的那类 action。

继承还会形成环。A ⊃ BB ⊃ CC ⊃ A 三条边各自看都合理,合起来让三个角色的权限集合坍缩成同一个。判定侧不会崩,只是所有人都比预期多了权限。

警示 · 继承的语义要在写下第一条边之前定死:A ⊃ B 是「A 拥有 B 的全部权限」还是「A 的成员自动成为 B 的成员」?两种读法在有用户级 Deny 时结论不同——前者把 B 的权限并进 A 再减 Deny,后者让用户同时持有两个角色、两套 Deny 各自生效。最终生效集 的公式只覆盖后者。

3 · 一个人不能既申请又审批

separation of duty 是审计侧最硬的一条约束,落到权限模型上有两种强度。静态形态限制持有:同一个用户不得同时拥有这两个角色,分配时就拦住。动态形态限制行使:两个角色都可以有,但在同一张单据上不得既 submitapprove,判定时才拦。

表 3-1 · 两种职责分离约束的拦截时机与代价。
形态 拦在哪 判据 代价
静态 角色分配界面 用户的角色集合不含互斥对 小团队里常常无人可用
动态 每次审批判定 当前单据的 submitted_by ≠ me 判定要读单据的历史字段

静态约束对小团队往往不可行:只有两个人的部门做不到申请与审批分离。动态约束更实用,代价是它把「谁提交的」这一历史事实变成了授权输入,而这条信息属于 what 那一轴的资源属性,不归 subject。

4 · 回收侧的缺口

权限的增长是单向的:开口子有人催,收口子没人提。三件事能把它拉回来。授予带到期时间,临时权限过期自动失效,而不是等人想起来删;高危 action 改为 just-in-time 申请,用完即收,把常态持有变成短时持有;break-glass 账号留给故障现场,平时锁死、启用即告警、全程录审计。

recertification 是最后一道:每季度把每个人的生效集发给他的直属负责人确认,未在期限内确认的自动收回。这条规则的威力全在「不确认就收回」——反过来做(不确认就保留)等于没做。

5 · 改动之前先算影响面

策略改动的评审对象不该是 diff,而该是这次改动翻转了哪些决策。做法是把请求空间枚举成一个样本网格,改动前后各跑一遍,比对结果。

本系列的 8 条规则、4 个 subject、4 个 resource、5 个 action 组成 80 条请求(均用默认 context),逐条停用规则测得的影响面里,r-mfar-abroad 都是 0 条:它们读的是设备与网络位置,而默认 context 恒为公司内网加托管设备。把 context 扩成 4 种、样本变成 320 条,同样两条规则的影响面成了 18 条与 11 条。

图 5-1 · 停用一条或多条规则后,决策在请求样本网格上的翻转统计。可单击规则停用,并切换样本的 context 网格,对照每条规则单独停用时的影响面。

建议 · 影响面为 0 有两种截然不同的含义:改动确实无效,或者样本没覆盖到它。前者可以放心合并,后者是评审失效。因此样本网格的每一维都要显式列出取值,尤其 context 这一维:它不出现在任何一条 role 或 permission 的定义里,最容易在测试里被固定成一个值。

6 · 参考文献

  1. Ferraiolo, D. F., & Kuhn, D. R. (1992). Role-Based Access Controls. 15th NIST-NCSC National Computer Security Conference, 554–563. RBAC 与职责分离约束的原始论述。csrc.nist.gov
  2. Sandhu, R., Coyne, E. J., Feinstein, H. L., & Youman, C. E. (1996). Role-Based Access Control Models. IEEE Computer, 29(2), 38–47. RBAC0–RBAC3 四层模型,role hierarchy 与约束各占一层。
  3. American National Standards Institute. (2004). Information Technology — Role Based Access Control. ANSI/INCITS 359-2004. 把静态与动态职责分离写进标准的那一版。