基于Open Policy Agent的Authz权限设计探索
2021-10-10 / modified at 2023-12-30 / 4.8k words / 18 mins

本文分析了数十款知名项目的权限架构,并提出了一款基于云原生权限框架的灵活配置思路。

快速结论

本文虽然介绍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,本文不涉及

权限的难点在于

  • 设计权限要遍历枚举所有动作,根据安全要求,在高阶设计阶段就需要尽可能地枚举威胁并加固
  • 组织间存在继承-覆盖关系颗粒度两个维度,是三个多对多的关系
  • 设计师会在低代码与强业务间反复纠结

安全理论

权限本质是安全,安全中最重要的有两个特点

  • 机密性:参考BLP模型,比如防泄密,遵循GDPR等
  • 完整性:参考Biba 模型,比如防止低权限设备提权

在安全领域中,由美国国防部发布的TCSEC的等级模型最为有名。关于“权限”,主要涉及到

  • C2:普通的RBAC
  • B1:嵌套权限,即包含Scope的概念
  • B2:每一个对象都要标签的等级,比如只有高管A才能访问机密数据B。

常见项目做到C2就够了,SaaS项目至少需要做到B1,而大数据项目需要尽可能做到B2。

权限设计高阶思路

当前市场中基本没有太多开箱即用的权限管理产品,在大规模、多租户的权限场景中,高阶设计如下

  • 组织:从组织架构推导出人员、组织、继承、SOD等关系,避免先入为主的扁平化/低颗粒度,避免双重汇报的组织架构,避免真实岗位和资源角色绑定
  • 资源:一定先枚举所有资源,这是SE的设计门槛,最好能确认颗粒度,而不要出现树形关系。
  • 角色/权限:建议参考AWS/Azure等树形权限设计

如果需要自行开发,参考流程如下

$2Authz用户/群组/机器人的基本属性√ 这些均可以SaaS化资源与角色继承,颗粒度操作权限(返回Boolean值)非嵌套RBAC/ACL嵌套的权限√ 尽可能转为ACLSQL数据权限(返回过滤的数据)例:只允许A用户查看自己部门的数据√ 纯编码方案√ 低代码方案

权限设计思路

本文中,按照“可以复用”,“必须开发”与“有改进潜能”三个维度进行设计评估

可复用的组件库

下面数据在任何项目都反复使用,这些都是upsert的工作量,不再赘述。需要注意隐私法规。

以下为管理组侧

数据来源功能
用户贴源层第三方IAM Provider系统(SAML/LDAP等)证明你是你
群组贴源层第三方IAM Provider系统(SAML/LDAP/Directory等)证明你在某个集合中
SecurityPrincipal上述用户与组的贴源数据的并集去重,可以加入额外属性使用侧的缓存

注意这里的群组均不涉及到角色(role)和范围(scope),而只是单纯的数组。同时需要做好上下游的数据防篡改,防止关键群组被提权。

关于扩展属性,简单场景可以用数据库的jsonb/字典表/大宽表,复杂场景可以缓存到Redis/ELK/Clickhouse等广义的列存储中提高性能

以下为资源组侧,可以通过菜单、数据源连接,API网关等抽象方案实现,数据管理也可以参考HW的数据湖治理方案。

操作资源来源功能媒介
Action业务菜单/操作权限对资源进行的读写删操作HTTP/GraphQL网关
DataActionSQL等语句,含Where条件的参数查询数据对应的视图DB/BI网关
Condition通过注解/扩展字段等方式录入表达式供ABAC使用的属性手动/第三方录入

一定先要做好功能(比如菜单)和数据(比如过滤参数)的防遗漏,比如MECE的枚举方法,然后再考虑导入相关人员角色与Mapping设计。

必须开发的业务代码

以下的工作量由于边界/颗粒度难以被公共处理,基本上要自己开发,很考验设计师的抽象能力

  • 组织架构的继承关系和颗粒度,比如多层嵌套组织,多层嵌套资源(项目)
  • 资源的颗粒度,比如API级,表级,列级。

网上也有一些SpringBoot的开箱即用的RBAC项目,这种黑盒的后期债务很重,一般来说是不推荐的。

权限设计选型

三种权限描述方案

权限关系实现解释对象代表方案优点缺点
硬编码真值表布尔值RBAC数据库数组操作;上手简单难以处理粒度/属性/覆盖问题
逻辑等价Logical equivalence布尔解释规则引擎/有向图/状态机形式证明;速度快;属性支持强不支持分支/递归;需要逻辑学基础
实质等价Material equivalence目标语言Java/自然语言符合直觉,堆人天,图灵完备代码翻译;上线繁琐;命题歧义

上述的区别主要就是分析时的“语境”等级,其中低语境的场景下只需要机器去静态的执行运算器即可(或者通过定时任务定期生成权限关系),而高语境的场景虽然很强,但是需要“动态解释”工作。三种并没有高低贵贱之分。

规则引擎类权限模型的设计不足

在传统的数据库判断权限中,底层本质是SQL的交叉运算。那么是否有一种数据结构,能更简化地描述这些细节呢?答案是有向图(以及它的退化Tries,甚至更退化的DFA)。

举例如下,假如需要实现如下的ABAC场景

部门需要开发一套可视化平台,要求如下:

  1. 如果用户是高级别领导,那么可以看到全部级别数据;如果用户是部门领导,那么只能看到自己部门的数据;如果用户是普通员工,只能看到自己项目的数据
  2. 所有人的权限级别要高于数据的秘密级别

经验上的流程图如下

$2用户权限比数据秘密级别高YesNo用户权限级别?ok是自己部门的数据okfail是自己项目的数据okfailfail高级领导普通员工部门领导

如下是用传统过程式的代码实现,符合Java等语言的编程习惯

1
2
3
4
5
6
7
8
9
10
11
12
13
# 命题: A&((B->G)|(C&F)->G)|(D&E)->G))
function test(User input){
if(input.access_level < input.query.param.access_level){
return false;
}
if (input.access_level >= 50){
return true;
} else if (input.access_level < 50 && input.access_level >=40){
return input.dept == input.query.param.dept;
} else if (input.access_level < 40 && input.access_level >=30){
return input.projects.contains(input.query.param.project);
}
}

我们将其转换为逻辑命题

1
2
3
4
5
6
7
8
命题:
A: 用户权限比数据秘密级别高
B: 用户是高级领导
C: 用户是部门领导
D: 用户是普通员工
E: 用户在看自己部门的数据
F: 用户在看自己项目的数据
G: 用户可以访问数据

如下是OPA(rego)的实现,它是非时序逻辑。由于不支持if语句,因此产生了重复的语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 命题:((A&B)->G)|((A&C&F)->G)|((A&D&E)->G)
default allow = false
# 如果满足A与B,那么G命题成立
allow {
input.access_level >= input.query.param.access_level
input.access_level >= 50
}

allow {
input.access_level >= input.query.param.access_level
input.access_level >= 40
input.access_level < 50
input.dept == input.query.param.dept
}

allow {
input.access_level >= input.query.param.access_level
input.access_level >= 30
input.access_level < 40
input.query.param.project == input.projects[_]
}

完整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日常维护安全管理与审计
业务部门(租户)YNN
IT维护人员NYN
安全内控部NNY*

上面的需求本质是“权限的权限”,或者“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逻辑图

$2ManagementGroupSecurityPrincipalSubscriptionResourceGroupResourceActionRolenestedassign

值得注意的是,云项目中最细的粒度一般是服务实例级别(菜单级),而难以细化到实例内部的数据权限。比如你在租户下申请了K8S集群实例,想分给别人用,很难在管理侧细化到具体的namespace/secret等权限,而需要更细的数据权限方案。

同时也要注意,云项目一般过于复杂,尤其Role是三个多对多,需要规则引擎来实现,中小型快餐项目不推荐照搬使用。假如不涉及到费用、库存和限额,一般也没必要设计资源组的概念。

常见工具链项目

以下为应用层项目(均基于数据库软隔离的方案,即Sharded multi-tenant databases )

平台GitlabSonarqubeArtifactorySVN
SecurityPrincipalUser/GroupUser/GroupUser/GroupACL
Resourcescodecodeartifactscode
Top-level-rolenested RBACRBACACLACL
Repo-level-roleRBACACLACLACL
Path priority & InheritanceNANAdeny>allow,shortest firstlongest first
comment总管理员可以看到代码Mapping关系复杂度为O(N2)AntPathMatcher存储RawJSON参考

上文场景中的Role中绑定的是SecurityPrincipal,即个人与群组被作为相同的范畴,因此在Sonarqube等工具被看作ACL。

其中Gitlab组织中加入了“角色概念”而不是一个单纯的数组,是失败中的失败,在项目中枚举了50多种,耗费一周后放弃

常见APM/Low-code/可视化相关项目

平台retoolSentryMetabase-PaidRedash OSS
SecurityPrincipalUser/GroupUserUser/GroupUser
Top-level-roleNA(SaaS)RBACRBACACL
Project-level-roleRBACnested ACL(by copy)ACLACL
comment相对Sonar多了文件夹的概念固定的策略借助了模版引擎精确到ABAC的行级别支持人均管理员

常见P层项目

平台mgr grpres grpmgr memberres member评价
OpenLDAP+PAMrootACLUser,groupFile基于chmod实现的Linux HPC
开源版K8SRBACRBACtokenresources名义上RBAC,事实上都要大量定制
AWS_K8SRBACABACuser,group, roleresourcesAWS的万能方案
HarborRBACFullAccessUserdocker镜像灵活度很差
AWS_S3RBACABACUser, Group, role精确到文件夹AWS的万能方案
HashiCorp OSSACLACLtokencapabilities的集合IaaS场景足够了
HashiCorp EnterpriseACLACLtokensentinel万能且收费

上述可以得出一个结论

  • 不涉及嵌套时,经典的RBAC/ABAC基本足够
  • 涉及到嵌套,数据查看与过滤时,基于当前用户的属性(Role也算属性)进行管理,即ABAC是更好的方法,实现上可以转化为ACL

其它术语

权限模型相关术语

微软的RBAC文档写的最好,建议参考这里后再来阅读本文