はじめに

先日、Terraform 外で作成した AWS WAF (Web ACL) を terraform import で取り込んだ後、Web ACLに設定されているルールをそのまま引き継ぎつつ、その他の設定を Terraform で更新することがありました。
その際に terraform apply の実行でエラーが発生したのですが、エラー原因の特定に時間を要しました。

原因は Terraform の AWS Provider Version が古かったからだったのですが、経緯や対応方法について説明します。

terraform import についてはこちらの記事をご覧ください。
既存リソースの情報を Terraform に取り込み、更新する

事象が発生した環境

Terraform バージョン

$ terraform -version
Terraform v1.0.11
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v3.76.1

AWS WAF

Web ACL の Default Action は “Block” に設定しています。
正規表現で指定した条件の URI path へのアクセスを許可するルールを、ルールグループではなく Web ACL に直接設定しています。

main.tf

Default Action を”Allow” に更新します。
現在 Web ACL に適用されているルールを引き継がせるため、ignore_changes を設定しています。

resource "aws_wafv2_web_acl" "webacl" {
  lifecycle {
    ignore_changes = [
      rule
    ]
  }
  name  = "saeki-test-202309"
  scope = "CLOUDFRONT"

  default_action {
    allow {}
  }
  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "saeki-test-202309"
    sampled_requests_enabled   = true
  }
}

terraform import

現在の Web ACL のリソース情報を Terraform に取り込みます。
正常に終了しました。

$ terraform import aws_wafv2_web_acl.webacl xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saeki-test-202309/CLOUDFRONT
aws_wafv2_web_acl.webacl: Importing from ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saeki-test-202309/CLOUDFRONT"...
aws_wafv2_web_acl.webacl: Import prepared!
  Prepared aws_wafv2_web_acl for import
aws_wafv2_web_acl.webacl: Refreshing state... [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

エラー内容

terraform import での既存リソースのインポート、terraform plan ではエラーメッセージはありませんでしたが、terraform apply の際に、下記のエラーが発生しました。

╷
│ Error: Error updating WAFv2 WebACL: WAFInvalidParameterException: Error reason: EXACTLY_ONE_CONDITION_REQUIRED, field: STATEMENT, parameter: Statement
│ {
│   RespMetadata: {
│     StatusCode: 400,
│     RequestID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
│   },
│   Field: "STATEMENT",
│   Message_: "Error reason: EXACTLY_ONE_CONDITION_REQUIRED, field: STATEMENT, parameter: Statement",
│   Parameter: "Statement",
│   Reason: "You have used none or multiple values for a field that requires exactly one value."
│ }
│ 

メッセージからすると、「Statementの値(条件)がおかしいですよ」みたいなエラーのように見えます。

原因

使用していた AWS Provider のバージョンが “v3.76.1” と古く、Web ACL のルールで使用していた正規表現を指定するStatementに対応していないことが原因でした。

これが原因で、terraform import を行った際、ルールの Statement が tfstate に正しくインポートされていませんでした。

インポート後のtfstate

{
  "version": 4,
  "terraform_version": "1.0.11",
  "serial": 1,
  "lineage": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_wafv2_web_acl",
      "name": "webacl",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/webacl/saeki-test-202309/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
--- 省略 ---
            "name": "saeki-test-202309",
            "rule": [
              {
                "action": [
                  {
                    "allow": [
                      {
                        "custom_request_handling": null
                      }
                    ],
                    "block": [],
                    "count": []
                  }
                ],
                "name": "rule-1",
                "override_action": [],
                "priority": 0,
                "rule_label": [],
                "statement": [
                  {
                    "and_statement": null,
                    "byte_match_statement": null,
                    "geo_match_statement": null,
                    "ip_set_reference_statement": null,
                    "label_match_statement": null,
                    "managed_rule_group_statement": null,
                    "not_statement": null,
                    "or_statement": null,
                    "rate_based_statement": null,
                    "regex_pattern_set_reference_statement": null,
                    "rule_group_reference_statement": null,
                    "size_constraint_statement": null,
                    "sqli_match_statement": null,
                    "xss_match_statement": null
                  }
                ],
                "visibility_config": [
                  {
                    "cloudwatch_metrics_enabled": true,
                    "metric_name": "rule-1",
                    "sampled_requests_enabled": true
                  }
                ]
              }
            ],
--- 省略 ---
          },
          "sensitive_attributes": [],
          "private": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=="
        }
      ]
    }
  ]
}

対応方法

Terraform AWS Provider をバージョンアップした後、再度 terraform import から実行します。

Terraform AWS Provider のバージョンアップ

Terraform の AWS Provider のバージョンをアップグレードします。
バージョンを制限している場合は、必要に応じてバージョン制限を更新します。

$ terraform init -upgrade                                                                                    

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.17.0...
- Installed hashicorp/aws v5.17.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

再度 terraform import

再度 terraform import を実行します。
同じリソースの情報がすでにある場合はエラーになるため、tfstateファイルから対象リソースの情報を削除しておきます。

$ terraform import aws_wafv2_web_acl.webacl xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saeki-test-202309/CLOUDFRONT
aws_wafv2_web_acl.webacl: Importing from ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saeki-test-202309/CLOUDFRONT"...
aws_wafv2_web_acl.webacl: Import prepared!
  Prepared aws_wafv2_web_acl for import
aws_wafv2_web_acl.webacl: Refreshing state... [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

tfstateの確認

tfstateファイルを確認し、インポートされたリソースの情報を確認します。
regex_match_statement で指定したルールが正しくインポートされています。

{
  "version": 4,
  "terraform_version": "1.0.11",
  "serial": 8,
  "lineage": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_wafv2_web_acl",
      "name": "webacl",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/webacl/saeki-test-202309/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
--- 省略 ---
            "name": "saeki-test-202309",
            "rule": [
              {
                "action": [
                  {
                    "allow": [
                      {
                        "custom_request_handling": null
                      }
                    ],
                    "block": [],
                    "captcha": [],
                    "challenge": [],
                    "count": []
                  }
                ],
                "captcha_config": [],
                "name": "rule-1",
                "override_action": [],
                "priority": 0,
                "rule_label": [],
                "statement": [
                  {
                    "and_statement": [],
                    "byte_match_statement": [],
                    "geo_match_statement": [],
                    "ip_set_reference_statement": [],
                    "label_match_statement": [],
                    "managed_rule_group_statement": [],
                    "not_statement": [],
                    "or_statement": [],
                    "rate_based_statement": [],
                    "regex_match_statement": [
                      {
                        "field_to_match": [
                          {
                            "all_query_arguments": [],
                            "body": [],
                            "cookies": [],
                            "headers": [],
                            "json_body": [],
                            "method": [],
                            "query_string": [],
                            "single_header": [],
                            "single_query_argument": [],
                            "uri_path": [
                              {}
                            ]
                          }
                        ],
                        "regex_string": "^/test/.*",
                        "text_transformation": [
                          {
                            "priority": 0,
                            "type": "NONE"
                          }
                        ]
                      }
                    ],
                    "regex_pattern_set_reference_statement": [],
                    "rule_group_reference_statement": [],
                    "size_constraint_statement": [],
                    "sqli_match_statement": [],
                    "xss_match_statement": []
                  }
                ],
                "visibility_config": [
                  {
                    "cloudwatch_metrics_enabled": true,
                    "metric_name": "rule-1",
                    "sampled_requests_enabled": true
                  }
                ]
              }
            ],
--- 省略 ---
          },
          "sensitive_attributes": [],
          "private": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=="
        }
      ]
    }
  ]
}

terraform plan

terraform plan を実行し、更新内容が正しいか確認します。
Default Action を "Allow" にする部分のみの変更となっています。

$ terraform plan                                                                                             
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_wafv2_web_acl.webacl will be updated in-place
  ~ resource "aws_wafv2_web_acl" "webacl" {
        id            = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        name          = "saeki-test-202309"
        tags          = {}
        # (6 unchanged attributes hidden)

      ~ default_action {
          + allow {
            }

          - block {
            }
        }


        # (2 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

terraform apply

terraform apply を実行し、デプロイします。
正常に終了しました。

$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_wafv2_web_acl.webacl will be updated in-place

--- 省略 ---

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

更新後のリソース状態

Default Action が “Allow” に更新され、設定されているルールは更新されませんでした。

おわりに

terraform import した際にはエラーメッセージが表示されておらず、インポートは正しく終了していると思い込んでいたため、原因を突き止めるのに時間を要しました。

バージョン違いによるトラブルを避けるため、開発時に Terraform や Provider のバージョンを制限することは多いと思います。
バージョンが古いことにより本記事のようなエラーが発生することがありますので、ご注意ください。