OPA 重新定义规则引擎

https://gocn.vip/topics/10055
OPA,全称OpenPolicyAgent, 底层用Go实现,它灵活而强大的声明式语言全面支持通用策略定义。



目前国内资料还比较少。



个人因为工作接触比较多,打算陆续分享些教程介绍下。



私以为规则引擎的技术选型完全可以多这个选择
https://www.openpolicyagent.org/docs/latest/philosophy/#what-is-opa

主要关键词是:



轻量级的通用策略引擎
可与服务共存
集成方式可以是 sidecar、主机级守护进程或库引入
opa



文字图片还是不够生动,看看 OPA 作者怎么说:



优点
强大的声明式策略



上下文感知
表达性强
快速
可移植
输入和输出支持任意格式



配合强大的声明式策略语言Rego,描述任意规则都不是问题



全面支持规则和系统解耦
如图



集承方式多
Daemon 式服务
Go 类库引入
决策快
rule indexing
partial evaluation
应用广泛
除了集成做Auth外,还可以应用到k8s,terraform,docker,kafka,sql,linux等上做规则决策



工具齐全
有命令行,有交互式运行环境
支持测试,性能分析(底层Go实现)
有强大的交互式编辑器扩展vscode-opa
有playground分享代码
下面从一个 RBAC 鉴权例子来了解下OPA



一个 RBAC 例子
以下 json 配置了 role 能操作的资源和 user 的绑定关系



// data.json
{
“roles”: [
{
“operation”: “read”,
“resource”: “widgets”,
“name”: “widget-reader”
},
{
“operation”: “write”,
“resource”: “widgets”,
“name”: “widget-writer”
}
],
“bindings”: [
{
“user”: “inspector-alice”,
“role”: “widget-reader”
},
{
“user”: “maker-bob”,
“role”: “widget-writer”
}
]
}
当一个请求读取 widgets 的 user(如下 json)过来操作资源,怎么判定他是否可以呢?



// input.json
{
“action”:{
“operation”:”read”,
“resource”:”widgets”
},
“subject”:{
“user”:”inspector-alice”
}
}
可能你习惯性在想用自己趁手的语言和框架,一顿遍历循环搞定。



且慢,OPA告诉我们:



几行代码就可以!(当然代码少不是重点。。。)



这里是可以在线运行的代码示例



example_rbac



我们先抛开语法,代码其实就是描述了一条规则:



用户是否有角色,角色是否有权限操作的资源



下面我们开始学习OPA如何定义这条规则



基本语法
OPA基于一种数据查询语言Datalog实现了描述语言Rego



OPA的Rego基本语法如下表:



语法 例子
上下文 data
输入 input
索引取值 data.bindings[0]
比较 “alice” == input.subject.user
赋值 user := input.subject.user
规则 < Header > { < Body > }
规则头 < Name > = < Value > { … } 或者 < Name > { … }
规则体 And 运算的一个个描述
多条同名规则 Or 运算的一个规则
规则默认值 default allow = false
函数 fun(x) { … }
虚拟文档 doc[x] { … }
一点也不多。函数和虚拟文档我们后边再开文章展开,今天主要看明白他的规则定义。



首先输入会挂在input对象下,用到的上下文(就是规则决策基于的源数据)会挂在data对象下



rule
当定义规则时:



每条规则都会有返回值



格式 1:< Name > { … }
不声明返回值,则只返回 true 或 false



格式 2 < Name > = < Value > { … }
声明返回值 < Value > 则返回其值



规则体内每条描述会逐条And运算,全部成立才会返回值



多条同名规则相互之间是Or运算,满足其一即可



具体到代码中规则allow, 默认值是 false



要求user_has_role和role_has_permission同时满足



两者的role_name也是一样。



你可能发现,局部变量role_name 没声明啊!



Rego里可以省略声明局部变量, 直接使用。



Tips: 但要这样的变量可以被同名的全局变量修改。 局部变量必要时还是应该使用some声明 如 some role_name
default allow = false



allow will be true when user has role and role has permission


allow {
user_has_role[role_name]
role_has_permission[role_name]
}
然后其中user_has_role[role_name]这种带参数的结构不是规则,叫虚拟文档 (文档:可被查询的集合)



check user role binding exist


user_has_role[role_name] {
role_binding = data.bindings[]
role_binding.role = role_name
role_binding.user = input.subject.user
}
Tips: 仔细同学会发现,线上运行版有with : role_binding = data.bindings[
] with data.bindings as data_context.bindings with 是用来替换输入 input 或者上下文 data 里的数据。 因为线上版没法指定上边的 data.json, 所以通过变量data_context替换传入的。
集合里边role_binding = data.bindings[_]是遍历data.bindings



Rego的遍历语法类似 python,这里遍历流程是



将data.bindings一个值赋值给role_binding



进行后续处理,处理完后再赋下一个值



Tips: 是特殊变量名,当需要变量占位又不需要后边引用时使用(类似 Go 的
至于role_binding.role = role_name这条你应该能猜到是判断请求过来的 role 名是否和配置一致



可是为什么是=操作符,不应该是==?



这里是一个有趣的点!



unification
Rego中实际只有=,而且作用是为变量赋值使等式成立,叫Unification



而:=局部变量赋值,==比较,是=的语法糖,为了实现局部变量赋值和比较,和编译错误更容易区分



所以=更像是数据查询。(毕竟Rego是一个数据查询语言嘛)



这里举个例子就好理解了:



[x, “world”] = [“hello”, y]
# 之后,x值为hello,y为world
总结一下,本文介绍什么是OPA,并借一个简单的 RBAC 例子初探了Rego强大的声明规则语法。



下一篇,将会介绍如何本地优雅的开发OPA,感兴趣同学可以先在OPA的 playground 玩玩。


Category golang