如何使用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" ]
}

第二步。搜索图表。

图可达函数的参数如下。

引数概要第1引数 (graph_data)手順1. で準備したグラフデータ第2引数 (start_points)グラフ探索のスタート位置となるノード名のセット

第二个参数的定义如下。

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设计理念中“声明性胜于命令性”的最佳功能示例,它不需要写循环等递归处理的描述。

广告
将在 10 秒后关闭
bannerAds