🔥

【Terraform】ACMでSSL証明書を作成しDNS認証するときに遭遇したエラーたち

に公開

Terraformを使ってACM で SSL 証明書を発行しようとした際にいくつかエラーにはまったので、記事にしておきます。

ゴール

  • Terraform を使って ACM で SSL 証明書を発行する
  • 認証方法は Route53 を使用した DNS 認証

現状

現在の tf ファイルの中身はこんな感じ。

# ホストゾーンの参照
data "aws_route53_zone" "host_zone" {
  # 便宜上、"example.com"としておきます
  name =  "example.com"

}

# ACMのDNS検証用レコード
resource "aws_route53_record" "cert" {
  name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name
  type = aws_acm_certificate.cert.domain_validation_options[0].resource_record_type
  records = aws_acm_certificate.cert.domain_validation_options[0].resource_record_value
  zone_id = data.aws_route53_zone.host_zone.zone_id
  ttl = 60
}


# ACM証明書を定義
resource "aws_acm_certificate" "cert" {
  # 便宜上、"www.example.com"としておきます
  domain_name = "www.example.com"
  validation_method = "DNS"
  provider = aws.virginia

  lifecycle {
    create_before_destroy = true
  }
}

# レコードのチェック
resource "aws_acm_certificate_validation" "cert" {
  certificate_arn = aws_acm_certificate.cert.arn
  validation_record_fqdns = [ aws_route53_record.cert.fqdn ]
  provider = aws.virginia
}

※Cloudfront で使用するためバージニア北部で SSL 証明書を作成しています。

このまま terraform plan をすると、エラーをはきます。

エラー ①:DNS レコードの定義でエラー

エラーの内容確認

$ terraform plan
╷
│ Error: Invalid index
│
│   on route53.tf line 22, in resource "aws_route53_record" "cert":22:   name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name
│
│ Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of
│ the set.
╵
╷
│ Error: Invalid index
│
│   on route53.tf line 23, in resource "aws_route53_record" "cert":23:   type = aws_acm_certificate.cert.domain_validation_options[0].resource_record_type
│
│ Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of
│ the set.
╵
╷
│ Error: Invalid index
│
│   on route53.tf line 24, in resource "aws_route53_record" "cert":24:   records = aws_acm_certificate.cert.domain_validation_options[0].resource_record_value
│
│ Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of
│ the set.

インデックス系のエラーがでてますね。

DeepL でエラー文を翻訳してみると、、、

セットの要素は、その値によってのみ識別され、選択するための個別のインデックスやキーを持たないため、セットのすべての要素に対してのみ操作を行うことが可能です。

うーーん、いまいちよくわからない。。。

原因調査

エラー文で検索したところ、以下の記事を発見しました。

https://dev.classmethod.jp/articles/terraform-aws-certificate-validation/

aws provider 3.0.0 以降domain_validation_options が Set 型で返ってくるようになりました。

それ以前はList 型だったのでインデックスを指定できたのですが、 Set 型になり順序付けされなくなったため、インデックスを指定不可となったようです。

コード修正

下記のように aws_route53_record のコードを修正しました。

# aws_route53_record のみ抜粋

# ACMのDNS検証用レコード
resource "aws_route53_record" "cert" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

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

for 文で map 型へ変換して、それを name, type, records に設定しています。

詳しい解説は以下をご覧ください

https://dev.classmethod.jp/articles/terraform-aws-certificate-validation/#toc-5

これで、terraform plan をしてみると、、、まだエラーが出ています。。。

エラー ②:検証待ちの FQDN 指定でエラー

エラーの内容確認

│ Error: Missing resource instance key
│
│   on acm.tf line 15, in resource "aws_acm_certificate_validation" "cert":15:   validation_record_fqdns = [ aws_route53_record.cert.fqdn ]
│
│ Because aws_route53_record.cert has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│     aws_route53_record.cert[each.key]

DNS 検証用のレコードを指定している部分(validation_record_fqdns)でエラーになっています

どうやら aws_route53_record.cert も Set 型で返ってきているみたいです。

コード修正

# aws_acm_certificate_validation のみ抜粋

# レコードのチェック
resource "aws_acm_certificate_validation" "cert" {
  certificate_arn = aws_acm_certificate.cert.arn
  validation_record_fqdns = flatten([ values(aws_route53_record.cert)[*].fqdn ])
  provider = aws.virginia
}

values 関数を使用して、aws_route53_record.cert を Set→List へ変換しています。

そして、Splat 演算子([*])で aws_route53_record.cert 内のすべての要素から fqdn を取り出しています。

それらをflatten 関数で囲むことで取り出した fqdn をリスト内に並べています。

これで無事 plan, apply 両方通るコードになりました!

修正後のコード

最後に、修正後のコード全体を載せておきます

# ホストゾーンの参照
data "aws_route53_zone" "host_zone" {
  # 便宜上、"example.com"としておきます
  name =  "example.com"

}

# ACMのDNS検証用レコード
resource "aws_route53_record" "cert" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

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


# ACM証明書を定義
resource "aws_acm_certificate" "cert" {
  # 便宜上、"www.example.com"としておきます
  domain_name = "www.example.com"
  validation_method = "DNS"
  provider = aws.virginia

  lifecycle {
    create_before_destroy = true
  }
}

# レコードのチェック
resource "aws_acm_certificate_validation" "cert" {
  certificate_arn = aws_acm_certificate.cert.arn
  validation_record_fqdns = flatten([ values(aws_route53_record.cert)[*].fqdn ])
  provider = aws.virginia
}

参考文献

https://dev.classmethod.jp/articles/terraform-aws-certificate-validation/

https://developer.hashicorp.com/terraform/language/functions/flatten

https://developer.hashicorp.com/terraform/language/functions/values

https://qiita.com/Tocyuki/items/57093c28c50e39ad4e1e

Discussion