既存のACM証明書検証用Route53レコードをTerraformで管理する

Terraformでは既存のリソースをコードで管理するためにimportを実行しますが、ACMで証明書を取得したときに作成したDNS検証用のRoute53レコード(CNAME)をimportする際、import後にterraform planすると、既存のリソースをdestoyして再作成しますと出力されました。証明書のDNS検証用のレコードなら、検証が済んでいる状態なら再作成されても問題ないかもしれないと思いつつも、対象の証明書はECS用のALBで利用しており、証明書に問題が起こるとリカバリーが面倒そうだなと思い、既存のレコードが再作成されないようにTerraformに取り込む方法を検証しました。

# 追記

検証時にはACMでの証明書作成から時間がたっていたこともあり、証明書とその検証レコードの関係性が不明で、検証レコードの再作成が影響して証明書が失効したり検証状態が変更されるのが嫌だなと思ったのですが、あとあと考えてみると、証明書作成時点では検証レコードは存在せず、検証レコードを作成後一定時間で検証が完了した後は検証レコードは利用されていなそうなので、destroy/createが走っても問題なさそうと思いました

というか、レコードの用途が利用中のALBに紐づいてる証明書の検証なのでセンシティブに思ったけど、単にRoute53レコードのTerraformへの取り込みはimportなしで(```allow_overwrite=true```)でもできるよというだけの話な気がしてきました

# さらに追記
原因がわかりました。for_eachで作成したリソースのため、今回のケースでは```aws_route53_record.cert_validation["*.xxx.example.com"]```を指定しないといけなかった。

# 検証

既存の証明書の状態は以下のとおりです。

  - ドメイン名: *.xxx.example.com ※ワイルドカード証明書
  - 状況: 発行済み
  - 使用中?: はい
  - 状況の詳細: 証明書は 2021-XX-XXTXX:XX:XXUTC に発行されました
  - 検証状態: 成功

Terraformの定義は[acm_certificate_validationのリファレンス](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation)記載の以下のような形です。ACMで証明書の発行～DNS検証までの一連の操作(証明書作成とそのDNS検証用のRoute53レコードの作成)を自動的に実行してくれます。

```
resource "aws_acm_certificate" "example" {
  domain_name       = "*.xxx.example.com"
  validation_method = "DNS"
}

data "aws_route53_zone" "example" {
  name         = "xxx.example.com"
  private_zone = false
}

resource "aws_route53_record" "example" {
  for_each = {
    for dvo in aws_acm_certificate.example.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.example.zone_id
}

resource "aws_acm_certificate_validation" "example" {
  certificate_arn         = aws_acm_certificate.example.arn
  validation_record_fqdns = [for record in aws_route53_record.example : record.fqdn]
}
```

既存のACMの証明書はすでにimport済みとします(リソース指定がARNであることがちょっと特殊です)。

定義を作成後、まずは通常の方法で、リソースをimportしてからplanで差分確認します。
importを実行します。
(なお、リソース指定はゾーンID_レコード_レコードタイプとなっています。ACM証明書DNS検証用のRoute53レコードの先頭はアンダースコアなので、ゾーンIDとレコードを区切るアンダースコアのあとにアンダースコアが連続してつづくような指定となります)

```
> terraform import aws_route53_record.cert_validation XXXXXXXXXX__XXXXXXXXXX.xxx.example.com_CNAME

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.
```

成功しました。
つぎに上記の定義でplanを実行します。

```
> terraform plan

actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # module.common.aws_route53_record.cert_validation will be destroyed
      - fqdn    = "_XXXXXXXXXX.xxx.example.com" -> null
om_CNAME" -> null
      - name    = "_XXXXXXXXXX.xxx.example.com" -> null
d
  + resource "aws_route53_record" "cert_validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_XXXXXXXXXX.xxx.example.com"
      + records         = [
          + "_XXXXXXXXXX.XXXXXXXXXX.acm-validations.aws.",
        ]
      + ttl             = 300
      + type            = "CNAME"
      + zone_id         = "XXXXXXXXXX"
    }

Plan: 1 to add, 0 to change, 1 to destroy.
```
(aws_acm_certificate_validationについて、途中で記述を追加した関係で、ここには出力されていません)

既存のレコードをdestroyしてcreateするplanが出力されました。
これは避けたい状態なので、一旦ロールバックします。

```
> terraform state rm aws_route53_record.cert_validation

Removed aws_route53_record.cert_validation
Successfully removed 1 resource instance(s).
```

importなしの状態でplanします

```
> terraform plan

Terraform used the selected providers to generate the following execution plan. Resource     
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_acm_certificate_validation.cert will be created
  + resource "aws_acm_certificate_validation" "cert" {
      + certificate_arn         = "arn:aws:acm:ap-northeast-1:XXXXXXXXXX:certificate/XXXXXXXXXX"
      + id                      = (known after apply)
      + validation_record_fqdns = (known after apply)
    }

  # aws_route53_record.cert_validation["*.xxx.example.com"] will be created
  + resource "aws_route53_record" "cert_validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_XXXXXXXXXX.xxx.example.com"
      + records         = [
          + "_XXXXXXXXXX.XXXXXXXXXX.acm-validations.aws.",
        ]
      + ttl             = 300
      + type            = "CNAME"
      + zone_id         = "XXXXXXXXXX"
    }

Plan: 2 to add, 0 to change, 0 to destroy.
```

[Route53レコードのリファレンス](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record)で、allow_overwriteをtrueにすると、既存で同じレコードがあれば上書きするようです。しかし、上書きといっても、terraform上でdestroy扱いでないなら、リソースは削除されずに動作するはず(と思いたい)です。

> allow_overwrite - (Optional) Allow creation of this record in Terraform to overwrite an existing record, if any. This does not affect the ability to update the record in Terraform and does not prevent other resources within Terraform or manual Route 53 changes outside Terraform from overwriting this record. false by default. This configuration is not recommended for most environments.

applyします。

```
> terraform apply

Terraform used the selected providers to generate the following execution plan. Resource     
actions are indicated with the following symbols:
  + create
      + certificate_arn         = "arn:aws:acm:ap-northeast-1:XXXXXXXXXX:certificate/XXXXXXXXXX"
      + id                      = (known after apply)
      + validation_record_fqdns = (known after apply)
    }

  # aws_route53_record.cert_validation["*.xxx.example.com"] will be created
  + resource "aws_route53_record" "cert_validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_XXXXXXXXXX.xxx.example.com"
      + records         = [
          + "_eacbe743751c2f4f3d1aff8c466acc27.jddtvkljgg.acm-validations.aws.",
        ]
      + ttl             = 300
      + type            = "CNAME"
      + zone_id         = "XXXXXXXXXX"
    }

Plan: 2 to add, 0 to change, 0 to destroy.
aws_route53_record.cert_validation["*.xxx.example.com"]: Creating...      
aws_route53_record.cert_validation["*.xxx.example.com"]: Still creating... [10s elapsed]
aws_route53_record.cert_validation["*.xxx.example.com"]: Still creating... [20s elapsed]
aws_route53_record.cert_validation["*.xxx.example.com"]: Still creating... [30s elapsed]
aws_route53_record.cert_validation["*.xxx.example.com"]: Still creating... [40s elapsed]
aws_route53_record.cert_validation["*.xxx.example.com"]: Creation complete after 46s [id=XXXXXXXXXX__XXXXXXXXXX.xxx.example.com._CNAME]
aws_acm_certificate_validation.cert: Creating...
aws_acm_certificate_validation.cert: Creation complete after 1s [id=2021-05-21 00:34:19 +0000 UTC]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```

この状態で、apply前後の証明書の状態を確認すると、まったく変更は行われていませんでした。

- ドメイン名: *.xxx.example.com ※ワイルドカード証明書
- 状況: 発行済み
- 使用中?: はい
- 状況の詳細: 証明書は 2021-XX-XXTXX:XX:XXUTC に発行されました
- 検証状態: 成功

念のためplanを実行します。

```
> terraform plan
Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform 
apply":

  # aws_route53_record.cert_validation["*.xxx.example.com"] has been chang        id              = "XXXXXXXXXX__XXXXXXXXXX.xxx.example.com        name            = "_XXXXXXXXXX.xxx.example.com"
        # (6 unchanged attributes hidden)
    }

Unless you have made equivalent changes to your configuration, or ignored the relevant       
attributes using ignore_changes, the following plan may include actions to undo or respond   
to these changes.

──────────────────────────────────────────────────────────────────────────────────────────── 


Your configuration already matches the changes detected above. If you'd like to update the   
Terraform state to match, create and apply a refresh-only plan:
  terraform apply -refresh-only
```

設定に差異はないがtfstateに差分があるという出力なので、refreshを実行します。

```
> terraform apply -refresh-only

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform 
apply":

  # aws_route53_record.cert_validation["*.xxx.example.com"] has been changed
  ~ resource "aws_route53_record" "cert_validation" {
        id              = "XXXXXXXXXX__XXXXXXXXXX.xxx.example.com._CNAME"
        name            = "_XXXXXXXXXX.xxx.example.com"
        # (6 unchanged attributes hidden)
    }

This is a refresh-only plan, so Terraform will not take any actions to undo these. If you    
were expecting these changes then you can apply this plan to record the updated values in    
the Terraform state without changing any remote objects.

Would you like to update the Terraform state to reflect these detected changes?
  Terraform will write these changes to the state without modifying any real infrastructure. 
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes


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

再度planを実行します。

```
> terraform plan

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no      
differences, so no changes are needed.
```

変更なしとなりました。これでOKです。
対象のRoute53のレコードはimportせず、```allow_overwrite=true``` で既存レコードを上書き利用することが可能、ということが分かりました。
ただし今回はACM検証用のCNAMEレコードのみ試しているので、他のレコードタイプについては未確認です。

# 参考
ゾーン作成時のNS/SOAレコードについては、terraform importをする必要はないと[Route53レコードのリファレンス](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record)に記載があります