Get all Terraform resources by Rego
背景
OPA による Terraform のポリシー評価を実行する際、外部モジュールを含めたすべてのリソースを対象にしたいです。
しかし、planned_values にはモジュールのリソース情報はネストされているため、階層を揃えて取得できるようにする必要があります。
Terraform plan 内容
plan の内容を JSON 形式に変換するには、以下のコマンドを実行します。
terraform plan --out tfplan.binary && terraform show --json tfplan.binary > tfplan.jsonJSON の内容としては以下の様な内容になります。
{ "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 には次のような情報が含まれます:
{ "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" } } } ] } }}モジュールを使用した場合の情報は、以下のような構造になります:
{ "planned_values": { "root_module": { "resources": [], "child_modules": [ { "resources": [], "child_modules": [] } ] } }}OPA での使用
Policy 作成
planned_values の情報を再帰的に取得し、すべてのリソースを取得する関数 (all_resources) を作成するには、以下のように処理を行います。
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
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 として結果に出力されます。
$ 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"]