Golang Go语言中 OPA-重新定义规则引擎-入门篇
Golang Go语言中 OPA-重新定义规则引擎-入门篇
OPA
,全称OpenPolicyAgent
, 底层用Go
实现,它灵活而强大的声明式语言全面支持通用策略定义。
目前国内资料还比较少。
个人因为工作接触比较多,打算陆续分享些教程介绍下。
私以为规则引擎的技术选型完全可以多这个选择~~
什么是 OPA
具体看官方文档 OPA philosophy docs
主要关键词是:
- 轻量级的通用策略引擎
- 可与服务共存
- 集成方式可以是 sidecar、主机级守护进程或库引入
文字图片还是不够生动,看看 OPA 作者怎么说:
OPA: The Cloud Native Policy Engine - Torin Sandall, Styra
优点
-
强大的声明式策略
- 上下文感知
- 表达性强
- 快速
- 可移植
-
输入和输出支持任意格式
配合强大的声明式策略语言Rego
,描述任意规则都不是问题
- 全面支持规则和系统解耦
- 集承方式多
- Daemon 式服务
- Go 类库引入
- 决策快
- 应用广泛
除了集成做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
告诉我们:
几行代码就可以!(当然代码少不是重点。。。)
这里是可以在线运行的代码示例
我们先抛开语法,代码其实就是描述了一条规则:
用户是否有角色,角色是否有权限操作的资源
下面我们开始学习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 >
则返回其值 - 格式 1:
-
规则体内每条描述会逐条
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 玩玩。
了解更多:OPA 的 Rego 文档 本文代码详见:NewbMiao/opa-koans
文章首发公众号:newbmiao
推荐阅读:OPA 系列
更多关于Golang Go语言中 OPA-重新定义规则引擎-入门篇的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
那是 OPA 真香,用起来很方便
针对帖子“Golang Go语言中 OPA-重新定义规则引擎-入门篇”,以下是我的回复:
OPA(OpenPolicyAgent)是一个轻量级的通用策略引擎,底层用Go语言实现。它使用强大的声明式策略语言Rego,能够描述任意规则,并支持规则和系统解耦。OPA的集成方式多样,可以作为sidecar、主机级守护进程或库引入,与服务共存。
在Golang中使用OPA,可以充分利用其灵活性和强大的策略定义能力。OPA的Rego语言使得规则定义变得简洁明了,同时支持复杂的规则评估。通过OPA,开发者可以轻松地将业务决策逻辑从应用程序代码中分离出来,提高代码的可维护性和可扩展性。
对于想要在Golang项目中集成OPA的开发者,建议首先明确项目对规则引擎的具体需求,包括规则复杂度、性能要求等。然后,可以调研OPA的文档和社区资源,了解如何配置和使用OPA。此外,还可以参考OPA的示例和教程,逐步学习如何在项目中集成和使用OPA。
总之,OPA是一个功能强大的规则引擎,能够在Golang项目中发挥重要作用。通过学习和使用OPA,开发者可以更加高效地管理和维护业务规则,提高项目的灵活性和可扩展性。