本文分析了数十款知名项目的权限架构,并提出了一款基于云原生权限框架的灵活配置思路。
快速结论
本文虽然介绍OPA框架,但是后续把它给否决了
- 慎用规则引擎与DSL低代码,因为大规模开发下的培训与维护的门槛太高。
- 慎用云原生,很多都只是销售术语,技术上并没有突破。
- 后续方案仍然用业务代码来实现的,具体请参考我的英文版文章
背景
术语
在设计权限前,而是要明确各种概念与术语,本文定义如下
| 术语 | 定义 |
|---|---|
| User/SecurityPrincipal/token/Group/Team | 用户/机器人/凭证等实体或者集合,可以从LDAP等第三方系统获得的镜像数据。类似NAC(网络准入控制) |
| Tenant/Namespace/Org/management groups/VDC | 含继承关系的租户实体,拥有各自的资源配额,一般不会过于干涉到具体的数据权限 |
| Resource | 资源实体,比如代码仓库/EC2机器的操作/数据权限等 |
| ResourceGroup/project | 资源组实体的合集,甚至可以包含权限管理的操作,但是不能嵌套 |
| Capacity/Rule-unit/Action/Permission | 动作+资源的组合的定义,比如 read-report 表示能读取报告 |
| Policy/statement/Role | 租户在某个Scope下,可以操作的资源合集的实体,比如create-member,大部分用户只能分清“admin/member/readonly”三种角色 |
| Subscription | 租户成员来创建/订阅的资源关系,非计费场景用不上 |
| Authentication | 证明你是你(AuthN/IDaaS/LDAP),比如Auth0/onelogin/forgerock等IAM,本文不涉及 |
权限的难点在于
- 设计权限要基于MECE的思路去遍历枚举所有动作,根据安全要求,在高阶设计阶段就需要尽可能地枚举威胁并加固
- 组织间存在
继承-覆盖关系与颗粒度两个维度,是三个多对多的关系 - 设计师会在低代码与强业务间反复纠结
安全理论
权限本质是安全,安全中最重要的有两个特点
- 机密性:参考BLP模型,比如防泄密,遵循GDPR等
- 完整性:参考Biba 模型,比如防止低权限设备提权
在安全领域中,由美国国防部发布的TCSEC的等级模型最为有名。关于“权限”,主要涉及到
- C2:普通的RBAC
- B1:嵌套权限,即包含Scope的概念
- B2:每一个对象都要标签的等级,比如只有高管A才能访问机密数据B。
常见项目做到C2就够了,SaaS项目至少需要做到B1,而大数据项目的 row-level filter 需要尽可能做到B2。
权限设计高阶思路
当前市场中基本没有太多开箱即用的权限管理产品,在大规模、多租户的权限场景中,高阶设计如下
组织:从组织架构推导出人员、组织、继承、SOD等关系
- 避免先入为主的扁平化/低颗粒度
- 避免双重汇报的组织架构
- 避免真实岗位和资源角色绑定
- 动作权限与角色需要考虑SOD,比如某人不能自审自批
资源:一定先枚举所有资源,这是SE的设计门槛,最好能确认颗粒度,而不要出现树形关系。
角色/权限:建议参考AWS/Azure等树形权限设计
如果需要自行开发,参考流程如下
权限设计思路
本文中,按照“可以复用”,“必须开发”与“有改进潜能”三个维度进行设计评估
可复用的组件库
下面数据在任何项目都反复使用,这些都是upsert的工作量,不再赘述。需要注意隐私法规。
以下为管理组侧
| 数据 | 来源 | 功能 |
|---|---|---|
| 用户贴源层 | 第三方IAM Provider系统(SAML/LDAP等) | 证明你是你 |
| 群组贴源层 | 第三方IAM Provider系统(SAML/LDAP/Directory等) | 证明你在某个集合中 |
| SecurityPrincipal | 上述用户与组的贴源数据的并集去重,可以加入额外属性 | 使用侧的缓存 |
注意这里的群组均不涉及到角色(role)和范围(scope),而只是单纯的数组。同时需要做好上下游的数据防篡改,防止关键群组被提权。
关于扩展属性,简单场景可以用数据库的jsonb/字典表/大宽表,复杂场景可以缓存到Redis/ELK/Clickhouse等广义的列存储中提高性能
以下为资源组侧,可以通过菜单、数据源连接,API网关等抽象方案实现,数据管理也可以参考HW的数据湖治理方案。
| 操作资源 | 来源 | 功能 | 媒介 |
|---|---|---|---|
| Action | 业务菜单/操作权限 | 对资源进行的读写删操作 | HTTP/GraphQL网关 |
| DataAction | SQL等语句,含Where条件的参数 | 查询数据对应的视图 | DB/BI网关 |
| Condition | 通过注解/扩展字段等方式录入表达式 | 供ABAC使用的属性 | 手动/第三方录入 |
一定先要做好功能(比如菜单)和数据(比如过滤参数)的防遗漏,比如MECE的枚举方法,然后再考虑导入相关人员角色与Mapping设计。
必须开发的业务代码
以下的工作量由于边界/颗粒度难以被公共处理,基本上要自己开发,很考验设计师的抽象能力
- 组织架构的继承关系和颗粒度,比如多层嵌套组织,多层嵌套资源(项目)
- 资源的颗粒度,比如API级,表级,列级。
网上也有一些开箱即用的RBAC项目,这种黑盒设计的要么太抽象(比如shiro),要么太简陋(开源XXX系统)。建议还是自己实现。
权限设计选型
三种权限描述方案
| 权限关系实现 | 解释对象 | 代表方案 | 优点 | 缺点 |
|---|---|---|---|---|
| 硬编码真值表 | 布尔值 | RBAC数据库 | 数组操作;上手简单 | 难以处理粒度/属性/覆盖问题 |
| 逻辑等价Logical equivalence | 布尔解释 | 规则引擎/有向图/状态机 | 形式证明;速度快;属性支持强 | 不支持分支/递归;需要逻辑学基础 |
| 实质等价Material equivalence | 目标语言 | Java/自然语言 | 符合直觉,堆人天,图灵完备 | 代码翻译;上线繁琐;命题歧义 |
上述的区别主要就是分析时的“语境”等级,其中低语境的场景下只需要机器去静态的执行运算器即可(或者通过定时任务定期生成权限关系),而高语境的场景虽然很强,但是需要“动态解释”工作。三种并没有高低贵贱之分。
规则引擎类权限模型的设计不足
在传统的数据库判断权限中,底层本质是SQL的交叉运算。那么是否有一种数据结构,能更简化地描述这些细节呢?答案是有向图(以及它的退化Tries,甚至更退化的DFA)。
举例如下,假如需要实现如下的ABAC场景
部门需要开发一套可视化平台,要求如下:
- 如果用户是高级别领导,那么可以看到全部级别数据;如果用户是部门领导,那么只能看到自己部门的数据;如果用户是普通员工,只能看到自己项目的数据
- 所有人的权限级别要高于数据的秘密级别
经验上的流程图如下
如下是用传统过程式的代码实现,符合Java等语言的编程习惯
1 | # 命题: A&((B->G)|(C&F)->G)|(D&E)->G)) |
我们将其转换为逻辑命题
1 | 命题: |
如下是OPA(rego)的实现,它是非时序逻辑。由于不支持if语句,因此产生了重复的语句
1 | # 命题:((A&B)->G)|((A&C&F)->G)|((A&D&E)->G) |
完整Demo与用例在这里 https://play.openpolicyagent.org/p/WFbjph2KyY ,本部分的access_level仅供演示设计
通过上面的例子,我们可以看出声明语言的门槛:你需要手写遍历所有tries的可能性,类似于电路设计中不允许使用寄存器。
无论是Drools/Casbin/OPA,它们本质上都是通过维护rules描述出了一个Trie (Prefix Tree)的数据结构,通过遍历所有的分支,找出第一个返回为true的最短路径(Boolean interceptor),特点如下
- 对时序逻辑的的支持很差:只有数据结构,没有时间概念,即缓存与“优先级”
- 难以枚举:无法拥有值,而是拥有“计算的潜力”,是否拥有权限只有运行时才得知
但是当前即使在芯片设计中也在推广"低效"的Chisel而不是Verilog,恰恰说明了声明语言的高门槛。
上述的例子尽管从结果上可以看出两种命题是等价的,但是严格的形式证明可以参考《逻辑学导论》中的逻辑等价,或者笛卡尔积相关知识。这种编程风格的门槛非常高。
数据治理场景的权限(row-level filter)
数据主题指对where条件的限制,即Context based row-level filter,比如用户只能看到自己所在部门的数据,这种场景主要在数据治理与BI分析中非常常见。注意这里的filter指代where条件的过滤项。
大部分权限处理如下:
- 数据源的权限处理:需要对数据主题进行贴源,聚合,资产化,定级(比如内部公开,秘密,机密等),脱敏,GDPR等加工。敏感的数据不入库,降低权限开发负担。
- 机-机接口(IT租户)的权限:比如数据库表/视图、API-Token授权、消息队列对某个租户开放
- 业务级别权限:比如”员工只能看到自己部门的权限“,需要定制
非业务诉求如下:
- 需要尽可能提高开发效率,而不是天天写Controller
这类场景做到最后,就一定会演化到大数据场景,可以参考Huawei的数据湖治理中心服务,或者参考retool的低代码方案。
上述的定制成本均比较高,门槛在于业务专家上。如果追求快而去愿意掏钱的话,个人更建议
- 假如项目定位是度量或者报表,那么就是典型的大数据项目,问题在大数据建模上
- 数据录入与处理:购买DGC/Redshift等数据治理服务、ROMA等data Pipeline服务
- 数据展示侧:掏钱使用BI软件或者云服务,比如DLV等low-code方案。
如果自己有足够的时间又想用免费的组件,参考如下
- 将Context嵌入业务强相关逻辑中,并在SQL/ORM侧的模版引擎来改写SQL
- 甚至Mybatis的LanguageDriver/插件进行SQL定制,下游集成phoenix等聚合JDBC
- 参考redash的讨论,替换redash内置的jinja模版引擎升级为高级语言/rego,最终实现ABAC的过滤方案,但是它没有预编译能力,可能有注入或者性能问题
其他功能
如下要求是纯工程问题,可以自行决策是否使用
三权分立的策略管理界面(Zero-trust)
当你的SaaS项目中存在了很多重要数据时,如何让客户信任你呢?
当然是需要满足SOD(separation of duty,权限职责分离)与自举管理。比如下面是常见的三权分立
| 角色 | 查看业务数据 | IT日常维护 | 安全管理与审计 |
|---|---|---|---|
| 业务部门(租户) | Y | N | N |
| IT维护人员 | N | Y | N |
| 安全内控部 | N | N | Y* |
上面的需求本质是“权限的权限”,或者“policy ownership”,总体上就是把所有人当贼来防(这个只有大公司/SaaS服务才能这样折腾了),上述有三个问题
- 需要预置一些初始化账号,配置限制与审计策略(比如二次验证),防止内控部的人私自提权内盗
- 需要实现自举(self-compiling)
- 权利与义务对等,用户拥有数据后,也需要承担安全责任主体
此外,此权限方案的开发成本极高,每个动作入口与矩阵都需要进行二维矩阵式的枚举验证,用于写到安全白皮书里面。
真正要做到安全认证的话,这里还是要请安全团队进行渗透测试,业务侧的设计师很难面面俱到。
最终方案
经过多个技术调研,最终选择基于AntPathMatcher实现类似Azure的IAM的low-code的权限模型,最终全部RBAC合并到ACL中,全部代码只有一个Java文件。只提供一些checkBoolean的无状态接口,因为大部分权限实际开发后发现会和业务的if/where条件耦合。数据权限仍然无法摆脱mybatis或者jinja等模版引擎。
附录
策略引擎的选型
以下主要为基于规则引擎的方案,假如你接触过Drools,那么理解会比较容易
- 开源方案,通过遍历真值表进行验证,这里是形式化逻辑的领域,通过手写tries的AST来形式化判断权限。
- Casbin:偏学术化,而且它的DSL太烂了,就连Harbor这种级别的用户都只能抄官方demo,被用户评价为“读研时给导师打工”。
- Open Policy Agent (OPA):来自云原生项目且已毕业,通过实时执行rego策略代码(会优化为WASM代码),判断是否有权限,可以参考Demo介绍即可。
- 性能:它本身是无状态的,可以堆机器。
- 生态(Adopter):GitOps/K8S等云应用,但是没有太多业务级的线上场景
- 规则能力:对if嵌套逻辑的都不是很好,难以断点调试,也很反直觉。
- 缺少开源二次封装(比如GUI/管理编辑界面)与生产实践(比如版本自动发布)
- Serverless/Drools/ Cloudflare Worker: 可以将上述的编译产物扔到边缘计算中
- AntPathMatcher:Spring自带的工具,可以参考Artifactory开源的PermissonTarget权限模型,被采用。
- 收费/闭源产品
- HashiCorp Sentinel: 是proprietary and closed source,设计与文档都很优雅,但是没有API,rules与js代码差不多,仅供参考思路
- Retool: 是我目前见到的最完美的low-code(基于js实现) + 权限方案,既有admin级别,也有项目级别,项目内部有ACL与ABAC,不提供rules,毕竟是YC毕业的收费项目。假如你的项目可以用它的low-code完成,可以把应用本体开发工作量也算进去
- ory的keto :一款一站式权限方案,当前后台与API均开源,而前台为SaaS提供。用一种DSL来描述图的关系,实现了谷歌论文中图的遍历算法,比OPA的tries树遍历更强大。当前属于创业与重构阶段,可能无法生产使用。
- 另外可以参考美团的这篇自研规则引擎的文章。
知名软件项目选型一览
云项目设计
下图是参考Azure云服务术语反推出的ER逻辑图
值得注意的是,云项目中最细的粒度一般是服务实例级别(菜单级),而难以细化到实例内部的数据权限。比如你在租户下申请了K8S集群实例,想分给别人用,很难在管理侧细化到具体的namespace/secret等权限,而需要更细的数据权限方案。
同时也要注意,云项目一般过于复杂,尤其Role是三个多对多,需要规则引擎来实现,中小型快餐项目不推荐照搬使用。假如不涉及到费用、库存和限额,一般也没必要设计资源组的概念。
常见工具链项目
以下为应用层项目(均基于数据库软隔离的方案,即Sharded multi-tenant databases )
| 平台 | Gitlab | Sonarqube | Artifactory | SVN |
|---|---|---|---|---|
| SecurityPrincipal | User/Group | User/Group | User/Group | ACL |
| Resources | code | code | artifacts | code |
| Top-level-role | nested RBAC | RBAC | ACL | ACL |
| Repo-level-role | RBAC | ACL | ACL | ACL |
| Path priority & Inheritance | NA | NA | deny>allow,shortest first | longest first |
| comment | 总管理员可以看到代码 | Mapping关系复杂度为O(N2) | AntPathMatcher存储RawJSON | 参考 |
上文场景中的Role中绑定的是SecurityPrincipal,即个人与群组被作为相同的范畴,因此在Sonarqube等工具被看作ACL。
其中Gitlab组织中加入了“角色概念”而不是一个单纯的数组,是失败中的失败,在项目中枚举了50多种,耗费一周后放弃
常见APM/Low-code/可视化相关项目
| 平台 | retool | Sentry | Metabase-Paid | Redash OSS |
|---|---|---|---|---|
| SecurityPrincipal | User/Group | User | User/Group | User |
| Top-level-role | NA(SaaS) | RBAC | RBAC | ACL |
| Project-level-role | RBAC | nested ACL(by copy) | ACL | ACL |
| comment | 相对Sonar多了文件夹的概念 | 固定的策略 | 借助了模版引擎精确到ABAC的行级别支持 | 人均管理员 |
常见P层项目
| 平台 | mgr grp | res grp | mgr member | res member | 评价 |
|---|---|---|---|---|---|
| OpenLDAP+PAM | root | ACL | User,group | File | 基于chmod实现的Linux HPC |
| 开源版K8S | RBAC | RBAC | token | resources | 名义上RBAC,事实上都要大量定制 |
| AWS_K8S | RBAC | ABAC | user,group, role | resources | AWS的万能方案 |
| Harbor | RBAC | FullAccess | User | docker镜像 | 灵活度很差 |
| AWS_S3 | RBAC | ABAC | User, Group, role | 精确到文件夹 | AWS的万能方案 |
| HashiCorp OSS | ACL | ACL | token | capabilities的集合 | IaaS场景足够了 |
| HashiCorp Enterprise | ACL | ACL | token | sentinel | 万能且收费 |
上述可以得出一个结论
- 不涉及嵌套时,经典的RBAC/ABAC基本足够
- 涉及到嵌套,数据查看与过滤时,基于当前用户的属性(Role也算属性)进行管理,即ABAC是更好的方法,实现上可以转化为ACL
其它术语
权限模型相关术语
微软的RBAC文档写的最好,建议参考这里后再来阅读本文