コンテンツにスキップ

Get all Terraform resources by Rego

背景

OPA による Terraform のポリシー評価を実行する際、外部モジュールを含めたすべてのリソースを対象にしたいです。
しかし、planned_values にはモジュールのリソース情報はネストされているため、階層を揃えて取得できるようにする必要があります。

Terraform plan 内容

plan の内容を JSON 形式に変換するには、以下のコマンドを実行します。

Terminal window
terraform plan --out tfplan.binary && terraform show --json tfplan.binary > tfplan.json

JSON の内容としては以下の様な内容になります。

tfplan.json
{
"format_version": "1.2",
"terraform_version": "1.6.2",
"planned_values": {},
"resource_changes": {},
"configuration": {},
"timestamp": "2024-01-01T00:00:00Z",
"errored": false
}

planned_values の概要

Terraform が管理するすべてのリソースのうち、プランニングによって計算されたリソースの新しい状態が含まれています。
そのため、外部モジュールで作成されたリソースも含まれます。

planned_values には次のような情報が含まれます:

tfplan.json
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_instance.example",
"type": "aws_instance",
"name": "example",
"values": {
"instance_type": "t2.micro",
"ami": "ami-12345678",
"tags": {
"Name": "example-instance"
}
}
}
]
}
}
}

モジュールを使用した場合の情報は、以下のような構造になります:

tfplan.json
{
"planned_values": {
"root_module": {
"resources": [],
"child_modules": [
{
"resources": [],
"child_modules": []
}
]
}
}
}

OPA での使用

Policy 作成

planned_values の情報を再帰的に取得し、すべてのリソースを取得する関数 (all_resources) を作成するには、以下のように処理を行います。

policies/terraform.rego

policies/terraform.rego
package terraform
import rego.v1
all_resources contains r if {
some path, value
walk(input.planned_values, [path, value])
rs := module_resources(path, value)
some r in rs
}
module_resources(path, value) = rs if {
reverse_index(path, 1) == "resources"
reverse_index(path, 2) == "root_module"
rs := value
}
module_resources(path, value) = rs if {
reverse_index(path, 1) == "resources"
reverse_index(path, 3) == "child_modules"
rs := value
}
reverse_index(path, idx) = value if {
value := path[count(path) - idx]
}

全リソースの取得と使用

作成した all_resources 関数を使用したチェックのサンプルコードは以下のとおりです。

policies/example.rego

policies/example.rego
package example
import rego.v1
all_resources := data.terraform.all_resources
deny_resource contains msg if {
some msg
some resource in all_resources
not allow_resoure_type(resource.type)
msg = sprintf("resource.%v", [resource.address])
}
allow_resoure_type(type) if {
not contains(type, "_iam_binding")
}

以下のように実行します。
サンプルコードでは、resource.type_iam_binding という文字列が含まれているリソースが failure として結果に出力されます。

Terminal window
$ opa eval -f pretty -i ./data/tfplan.json -b ./policies data.example.deny_resource
[
"resource.google_project_iam_binding.test",
"resource.module.test2.google_project_iam_binding.module",
"resource.module.test2.module.test2.google_project_iam_binding.module2"
]