如何使用graph函数替代Open Policy Agent中的递归处理
我是日立美国IoT Edge实验室的大崎。本篇文章是日立集团OSS Advent Calendar 2021的第19天。
本篇文章将介绍如何使用graph函数及其用法来避免Open Policy Agent无法实现递归处理的限制,并实现等效的逻辑。
Open Policy Agent是什么?
开放策略代理(以下简称OPA)是一个通用的策略引擎。它被用于决定Kubernetes资源创建的权限判定和Web服务的授权判定。
Rego语言是为OPA开发的一种策略描述语言。 Rego语言的特点是旨在以声明性的方式进行描述。与命令性描述应该如何处理不同,它专注于声明性地描述应该判断什么。因此,可以认为它能够简洁地实现策略。
Rego是一种声明式语言,因此策略作者可以专注于查询应该返回什么,而不是如何执行查询。相比起命令式语言,这些查询更简单、更简洁。
对于OPA的递归处理的限制以及令人困扰的情况。
Open Policy Agent和它的策略描述语言Rego,有时会遇到一个问题,就是无法编写递归处理。
以下是一個例子來說明在哪種情況下可能會遇到困難。
假設政策制定者們彼此擁有相互依賴的多個服務,並且希望找到所有與服務A相依賴的服務。假定依賴關係在名為service_dependency的JSON數據中被定義。
{
"web": [
"database"
],
"database": [],
"kafka": []
}
在这里,假设我们要将服务名为 “web” 输入,并且实现一个业务逻辑规则,它会找到所有依赖的服务(例如[“web”, “database”]),然后输出。在 OPA 中,通常会使用规则来实现类似函数的处理方式(如果需要的话,也可以编写函数)。
我們將嘗試實施以下類似的all_dependencies規則。
package main
service_dependency = {
"web": [
"database"
],
"database": [],
"kafka": []
}
all_dependencies[serviceName] = rtn {
service = service_dependency[serviceName][_]
rtn := {serviceName} + all_dependencies[service]
}
将此文件保存为名为main.rego的文件,并在同一文件夹中使用opa命令对此规则进行评估。结果会输出一个名为”recursive”的错误信息,这表明此规则无法在opa中进行评估。
opa eval -d . 'data.main.all_dependencies["web"]' --format pretty
1 error occurred: main.rego:11: rego_recursion_error: rule all_dependencies is recursive: all_dependencies -> all_dependencies
原因是在all_dependencies规则中,使用all_dependencies自身,opa不允许递归处理,因此检测到这样的实现会输出错误。
rtn := {serviceName} + all_dependencies[service]
绕过递归处理的限制方法
政策制定者的目标是“希望找到所有依赖的服务”的业务逻辑。OPA不支持递归处理的实现,但提供了另一种实现相同目标的方式,那就是“图”。
总结一下,不是让用户来实现像for循环这样的递归处理,而是只定义想要用递归处理搜索的图信息。剩下的搜索过程由OPA的图隐去。
我要对main.rego的实现进行以下更改。
package main
service_dependency = {
"web": [
"database"
],
"database": [],
"kafka": []
}
# all_dependencies[serviceName] = rtn {
# service = service_dependency[serviceName][_]
# rtn := {serviceName} + all_dependencies[service]
# }
all_dependencies[serviceName] = rtn {
service_dependency[serviceName]
rtn := graph.reachable(service_dependency, {serviceName})
}
以下是对 graphl.reachable() 部分的本地化汉语翻译结果,这是OPA提供的graph函数。现在我们立即进行重新评估。
opa eval -d . 'data.main.all_dependencies["web"]' --format pretty
[
"web",
"database"
]
可以看到,正确输入服务名称”web”会输出依赖于该服务的所有服务名称的列表([“web”,”database”])。由于没有使用递归处理,所以不会输出错误信息。
回避策在某些情境下是有效的使用案例。
在以下情况下,可以使用graph函数。
-
- 入れ子になっている階層の探索
たとえば組織Aの中に組織Bがあり、組織BにCさんが所属する場合には、所属関係はC->B->Aというリンクを持つ有向グラフで表すことができます。Cさんが所属する組織を洗い出す場合、グラフを探索し、すべての組織 BとAを見つけることができます。
ワークフローのような相互依存関係があるジョブの探索
たとえばAが終了していないとBが開始できない、CはBの後、というような場合に、C->B->Aというリンクを張っておくと、これも有向グラフになります。Cを実行するための前提条件となるジョブを洗い出す場合、グラフを探索し、Cから接続するすべてのジョブ BとAを見つけることができます。
图表的用法
图表的规格在下面的文档中有详细说明。
以下是具体的步骤。
第一步. 使用JSON描述图形。
OPA可以读取的图形数据格式是以下这种JSON格式。”Parent node *”和”Child node *”都是字符串形式的名称。
{
"Parent node 1": [ "Child node 1", "Child node 2" ],
"Parent node 2": [ "Child node 3", "Child node 4" ]
}
上述的样本表示链接节点”Parent node 1″与两个链接目标”Child node 1″和”Child node 2″相连。即使手头没有上述格式的数据,也可以从包含有向图链接源和链接目标信息的其他数据格式在OPA上创建出来。
我会使用OPA来读取上述JSON的图表描述。
graph_data = {
"Parent node 1": [ "Child node 1", "Child node 2" ],
"Parent node 2": [ "Child node 3", "Child node 4" ]
}
第二步。搜索图表。
图可达函数的参数如下。
第二个参数的定义如下。
start_points = { "Parent node 1", "Parent node 2" }
调用函数时,可以获得一个包含所有可访问节点名称的集合作为返回值。
nodes := graph.reachable(graph_data, start_points)
此外,还有其他一些函数可供使用,如graph.walk等。简单来说,可以通过graph.walk(graph_data)这样的方式调用,它会返回一个将路径(例如[“web”])的位置信息映射到其值(”database”)的字典(键-值类型对象)。({“[\”web\”]”: “database”})
总结
这次我们介绍了OPA的graph函数。
通过使用OPA的图数据搜索用的graph函数,我们可以实现使用递归处理的业务逻辑。只需要使用JSON格式来描述组织、工作等互相依赖的事物,就可以应用这种方法。
我认为,我们这次介绍的graph函数是OPA设计理念中“声明性胜于命令性”的最佳功能示例,它不需要写循环等递归处理的描述。