Terraform - resource 内ブロック条件付き繰り返し

# 目的

AWS WAF に複数のルールを適用し、いくつかのルールに個別の設定がある場合に、
冗長な実装とならないようにする（以下のように`rule`ブロックを複数使用しない）

```terraform
resource "aws_wafv2_web_acl" "foo" {
    name        = "foo"
    description = "foo"
    scope       = "REGIONAL"
    default_action {
        block {}
    }
    rule {
      name = "AWS-AWSManagedRulesLinuxRuleSet"
      priority = 0
      override_action {
        count {}
      }
      statement {
        managed_rule_group_statement {
          name = "AWS-AWSManagedRulesLinuxRuleSet"
          vendor_name = "AWS"
        }
      }
      visibility_config {
        cloudwatch_metrics_enabled = false
        metric_name                 = "foo_name"
        sampled_requests_enabled   = false
      }
    }
    rule {
      name = "AWS-AWSManagedRulesSQLiRuleSet"
      priority = 1
      override_action {
        count {}
      }
      statement {
        managed_rule_group_statement {
          name = "AWS-AWSManagedRulesSQLiRuleSet"
          vendor_name = "AWS"
        }
      }
      visibility_config {
        cloudwatch_metrics_enabled = false
        metric_name                = "foo_name"
        sampled_requests_enabled   = false
      }
    }
    visibility_config {
      metric_name = "foo"
      sampled_requests_enabled = false
      cloudwatch_metrics_enabled = false
    }
```

# 実装

```terraform
variable "web_acl_association_arn" {}

locals {
  web_acl_name = "test-web-acl"

  # WAFに適用するルールのリスト
  # 個別設定がある場合は、それを判定できる（"bot_control" : true）フラグを加えておく
  rules = [
    { "name" : "AWSManagedRulesCommonRuleSet" },
    { "name" : "AWSManagedRulesAdminProtectionRuleSet" },
    { "name" : "AWSManagedRulesBotControlRuleSet", "bot_control" : true },
  ]
  # list → map (listの並び順のルール優先度とするためkeyをindexとしておく)
  rules_map = { for i, j in local.rules : i => j }
}

resource "aws_wafv2_web_acl" "web_acl" {
  default_action {
    allow {}
  }
  name  = local.web_acl_name
  scope = "REGIONAL"
  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = local.web_acl_name
    sampled_requests_enabled   = true
  }

  dynamic "rule" {
    for_each = local.rules_map

    content {
      name = "AWS-${rule.value.name}"
      override_action {
        count {}
      }
      priority = rule.key    # listのindexをルール優先度とする
      statement {
        managed_rule_group_statement {
          name        = rule.value.name
          vendor_name = "AWS"

          dynamic "managed_rule_group_configs" {
            # 個別設定がある（判定用フラグが立っている）場合、for_eachで1回ループを回して設定する
            for_each = can(rule.value.bot_control) ? [1] : []
            content {
              aws_managed_rules_bot_control_rule_set {
                inspection_level = "TARGETED"
              }
            }
          }
        }
      }
      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "AWS-${rule.value.name}"
        sampled_requests_enabled   = true
      }
    }
  }
}

# WAFのアタッチ
resource "aws_wafv2_web_acl_association" "web_acl_association" {
  resource_arn = var.web_acl_association_arn
  web_acl_arn  = aws_wafv2_web_acl.web_acl.arn
}
```

# 参考

- [Dynamically create multiple WAF rules with Terraform](https://stackoverflow.com/questions/66698460/dynamically-create-multiple-waf-rules-with-terraform)
- [【Terraform】（初心者向け）dynamic ブロックについて](https://www.guri2o1667.work/entry/2022/11/01/%E3%80%90Terraform%E3%80%91%EF%BC%88%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%EF%BC%89dynamic_%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)