使用 walk 以 rego 递归聚合 terraform 状态中的资源
Using walk to recursively aggregate resources in a terraform state with rego
我正在使用 Open Policy Agent 针对我的 terraform 状态的 JSON 输出编写策略。
这是状态文件的结构:
{
"format_version": "0.1",
"terraform_version": "0.12.28",
"values": {
"root_module": {
"resources": [],
"child_modules": [
{
"resources": [],
"address": "",
"child_modules": [
{
"resources": [],
"address": "",
"child_modules": [
{}
]
}
]
}
]
}
}
}
我定义了这个讨厌的规则来实现我想要的,但这显然不是聚合这些资源的理想方式。
resources[resource_type] = all {
some resource_type
resource_types[resource_type]
rm := tfstate.values.root_module
# I think the below can be simplified with the built in "walk" function TODO: do that.
root_resources := [name |
name := rm.resources[_]
name.type == resource_type
]
cmone_resources = [name |
name := rm.child_modules[_].resources[_]
name.type == resource_type
]
cmtwo_resources = [name |
name := rm.child_modules[_].child_modules[_].resources[_]
name.type == resource_type
]
cm := array.concat(cmone_resources, cmtwo_resources)
all := array.concat(cm, root_resources)
}
我已经阅读了内置函数walk(x, [path, value])
的文档。文档 here。我相信这个函数可以完成我想要它做的事情,但是根据给出的文档和我在其他地方找到的公认的稀疏示例,我无法弄清楚如何让它按照我的期望工作。
我已经包含了一个 playground,其中包含一个非常基本的设置和我定义的当前规则。任何帮助都将不胜感激。
您的方向是对的,使用 walk
绝对是收集任意嵌套子资源的好方法。
首先,我们要探索步行的作用。它实际上将遍历我们正在走过的对象中的所有节点,并为每个节点提供“路径”和当前节点值。该路径将是一个键数组,就像对象一样:
{"a": {"b": {"c": 123}}}
如果我们走过去(下面的示例使用 opa run
REPL:
> [path, value] = walk({"a": {"b": {"c": 123}}})
+---------------+-----------------------+
| path | value |
+---------------+-----------------------+
| [] | {"a":{"b":{"c":123}}} |
| ["a"] | {"b":{"c":123}} |
| ["a","b"] | {"c":123} |
| ["a","b","c"] | 123 |
+---------------+-----------------------+
我们看到我们有 path
和 value
值的每个路径和值组合。您可以在部分规则(如您的 resources
规则)或理解中迭代时捕获这些值中的任何一个。
所以.. 把它带到 terraform 的东西上。如果我们修改 playground 示例以遍历示例输入(略微修改以赋予事物一些唯一的名称),我们得到:
walk_example[path] = value {
[path, value] := walk(tfstate)
}
https://play.openpolicyagent.org/p/2u5shGbrV2
如果您查看 walk_example
的结果值,我们可以看到我们期望必须处理的所有路径和值。
从那里开始进行过滤,类似于您在 resources
规则中为 resource_types
所做的事情。我们不会对集合进行迭代,而是将其用作查找来检查每种类型是否正常,我们将首先构建一整套 all 资源(不按类型分组).原因是遍历输入 json 的所有节点非常昂贵,所以我们只想做一次。随后,我们可以通过第二次按类型分组(根据需要)更快地遍历每个资源的完整列表。
更新后的版本类似于:
walk_resources[resource] {
[path, value] := walk(tfstate)
# Attempt to iterate over "resources" of the value, if the key doesn't
# exist its OK, this iteration for walk will be undefined, and excluded
# from the results.
# Note: If you needed to be sure it was a "real" resource, and not some
# key you can perform additional validation on the path here!
resource := value.resources[_]
# check if the resource type was contained in the set of desired resource types
resource_types[resource.type]
}
https://play.openpolicyagent.org/p/TyqMKDyWyh
^ 操场输入已更新,在示例中包含另一层嵌套和类型。您可以看到原始 resources
输出缺少深度 3 资源,但 walk_resources
集包含所有预期的资源。
最后一部分,如果您想按类型对它们进行分组,请添加一个完整的规则,例如:
# list of all resources of a given type. given type must be defined in the resource_types variable above
resources = { resource_type: resources |
some resource_type
resource_types[resource_type]
resources := { resource |
walk_resources[resource]
resource.type == resource_type
}
}
https://play.openpolicyagent.org/p/RlRZwibij9
用迭代每个资源类型的理解替换原始的 resources
规则,然后收集与该类型匹配的资源。
一个额外的指针,我认为这是这些 terraform 资源帮助程序规则中的一个问题,您将要引用该“完整”规则,请参阅 https://www.openpolicyagent.org/docs/latest/policy-language/#complete-definitions 了解有关其含义的一些详细信息,而不是“部分”规则(在这种情况下,构建一组资源与将值分配给理解结果的规则)。问题在于,在撰写本文时,OPA 将在内部缓存“完整”规则的值,而部分规则则不会。所以如果你然后去写一堆规则,比如:
deny[msg] {
r := resources["foo"]
# enforce something for resources of type "foo"...
...
}
deny[msg] {
r := resources["bar"]
# enforce something for resources of type "bar"...
...
}
您要确保它每次都使用 resources
的缓存值而不是重新计算集合。您的 resources
规则的原始版本以及我在这些示例中展示的 walk_resources
规则都会遇到该问题。需要注意的事项,因为如果您的输入 tfplan 很大,它会对性能产生相当大的影响。
我正在使用 Open Policy Agent 针对我的 terraform 状态的 JSON 输出编写策略。
这是状态文件的结构:
{
"format_version": "0.1",
"terraform_version": "0.12.28",
"values": {
"root_module": {
"resources": [],
"child_modules": [
{
"resources": [],
"address": "",
"child_modules": [
{
"resources": [],
"address": "",
"child_modules": [
{}
]
}
]
}
]
}
}
}
我定义了这个讨厌的规则来实现我想要的,但这显然不是聚合这些资源的理想方式。
resources[resource_type] = all {
some resource_type
resource_types[resource_type]
rm := tfstate.values.root_module
# I think the below can be simplified with the built in "walk" function TODO: do that.
root_resources := [name |
name := rm.resources[_]
name.type == resource_type
]
cmone_resources = [name |
name := rm.child_modules[_].resources[_]
name.type == resource_type
]
cmtwo_resources = [name |
name := rm.child_modules[_].child_modules[_].resources[_]
name.type == resource_type
]
cm := array.concat(cmone_resources, cmtwo_resources)
all := array.concat(cm, root_resources)
}
我已经阅读了内置函数walk(x, [path, value])
的文档。文档 here。我相信这个函数可以完成我想要它做的事情,但是根据给出的文档和我在其他地方找到的公认的稀疏示例,我无法弄清楚如何让它按照我的期望工作。
我已经包含了一个 playground,其中包含一个非常基本的设置和我定义的当前规则。任何帮助都将不胜感激。
您的方向是对的,使用 walk
绝对是收集任意嵌套子资源的好方法。
首先,我们要探索步行的作用。它实际上将遍历我们正在走过的对象中的所有节点,并为每个节点提供“路径”和当前节点值。该路径将是一个键数组,就像对象一样:
{"a": {"b": {"c": 123}}}
如果我们走过去(下面的示例使用 opa run
REPL:
> [path, value] = walk({"a": {"b": {"c": 123}}})
+---------------+-----------------------+
| path | value |
+---------------+-----------------------+
| [] | {"a":{"b":{"c":123}}} |
| ["a"] | {"b":{"c":123}} |
| ["a","b"] | {"c":123} |
| ["a","b","c"] | 123 |
+---------------+-----------------------+
我们看到我们有 path
和 value
值的每个路径和值组合。您可以在部分规则(如您的 resources
规则)或理解中迭代时捕获这些值中的任何一个。
所以.. 把它带到 terraform 的东西上。如果我们修改 playground 示例以遍历示例输入(略微修改以赋予事物一些唯一的名称),我们得到:
walk_example[path] = value {
[path, value] := walk(tfstate)
}
https://play.openpolicyagent.org/p/2u5shGbrV2
如果您查看 walk_example
的结果值,我们可以看到我们期望必须处理的所有路径和值。
从那里开始进行过滤,类似于您在 resources
规则中为 resource_types
所做的事情。我们不会对集合进行迭代,而是将其用作查找来检查每种类型是否正常,我们将首先构建一整套 all 资源(不按类型分组).原因是遍历输入 json 的所有节点非常昂贵,所以我们只想做一次。随后,我们可以通过第二次按类型分组(根据需要)更快地遍历每个资源的完整列表。
更新后的版本类似于:
walk_resources[resource] {
[path, value] := walk(tfstate)
# Attempt to iterate over "resources" of the value, if the key doesn't
# exist its OK, this iteration for walk will be undefined, and excluded
# from the results.
# Note: If you needed to be sure it was a "real" resource, and not some
# key you can perform additional validation on the path here!
resource := value.resources[_]
# check if the resource type was contained in the set of desired resource types
resource_types[resource.type]
}
https://play.openpolicyagent.org/p/TyqMKDyWyh
^ 操场输入已更新,在示例中包含另一层嵌套和类型。您可以看到原始 resources
输出缺少深度 3 资源,但 walk_resources
集包含所有预期的资源。
最后一部分,如果您想按类型对它们进行分组,请添加一个完整的规则,例如:
# list of all resources of a given type. given type must be defined in the resource_types variable above
resources = { resource_type: resources |
some resource_type
resource_types[resource_type]
resources := { resource |
walk_resources[resource]
resource.type == resource_type
}
}
https://play.openpolicyagent.org/p/RlRZwibij9
用迭代每个资源类型的理解替换原始的 resources
规则,然后收集与该类型匹配的资源。
一个额外的指针,我认为这是这些 terraform 资源帮助程序规则中的一个问题,您将要引用该“完整”规则,请参阅 https://www.openpolicyagent.org/docs/latest/policy-language/#complete-definitions 了解有关其含义的一些详细信息,而不是“部分”规则(在这种情况下,构建一组资源与将值分配给理解结果的规则)。问题在于,在撰写本文时,OPA 将在内部缓存“完整”规则的值,而部分规则则不会。所以如果你然后去写一堆规则,比如:
deny[msg] {
r := resources["foo"]
# enforce something for resources of type "foo"...
...
}
deny[msg] {
r := resources["bar"]
# enforce something for resources of type "bar"...
...
}
您要确保它每次都使用 resources
的缓存值而不是重新计算集合。您的 resources
规则的原始版本以及我在这些示例中展示的 walk_resources
规则都会遇到该问题。需要注意的事项,因为如果您的输入 tfplan 很大,它会对性能产生相当大的影响。