web-dev-qa-db-ja.com

Terraform:複数のACM証明書の作成と検証

(Terraformが管理する)ホストゾーンのリストに対して、ACMでSSL証明書の生成とDNS検証を自動化するという非常に紛らわしいTerraformリソースの問題に直面しています。コードも見つけることができます この要点で

この環境固有の変数を参照するホストゾーンをブートストラップすることから始めます。

_hosted_zones = [
    {
        domain = "site1.com"
        zone_id = "MANUALLY FILL"
    }
]
_

ゾーンの構築に使用しているブロックは確実に機能しているようです。

_resource "aws_route53_zone" "zones" {
    count = "${length(var.hosted_zones)}"
    name  = "${lookup(var.hosted_zones[count.index], "domain")}"
}
_

ゾーンが構築された後、HCLの制限と経験不足の組み合わせを考慮して、ゾーンIDを自動化する賢い方法を思い付いていないため、手動でゾーンIDを変数にコピーしています。

を使用して、ホストされているゾーンごとにネイキッド証明書とスプラット証明書を確実に生成できます...

_resource "aws_acm_certificate" "cert" {
    count = "${length(var.hosted_zones)}"
    domain_name = "${lookup(var.hosted_zones[count.index], "domain")}"
    subject_alternative_names = ["*.${lookup(var.hosted_zones[count.index], "domain")}"]
    validation_method = "DNS"

    tags {
        Project = "${var.project}"
        Environment = "${var.environment}"
    }
}
_

物事が厄介になるのは、証明書のDNS検証を自動化しようとしたときです。単一のホストゾーンには ドキュメントの良い例 がありますが、複数のホストゾーンに正常に移植できませんでした。私の試み...

_resource "aws_route53_record" "cert_validation" {
    count = "${length(var.hosted_zones)}"

    name = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_name[count.index]}"
    type = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_type[count.index]}"
    zone_id = "${var.zone_override != "" ? var.zone_override : lookup(var.hosted_zones[count.index], "zone_id")}"
    records = ["${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value[count.index]}"]
    ttl = 60
}

resource "aws_acm_certificate_validation" "cert" {
    count = "${length(var.hosted_zones)}"

    certificate_arn = "${aws_acm_certificate.cert.*.arn[count.index]}"
    validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn[count.index]}"]
}
_

私が最初の実行で見ているエラーは次のとおりです。

_* module.acm.aws_route53_record.cert_validation: 1 error(s) occurred:
* module.acm.aws_route53_record.cert_validation: Resource 'aws_acm_certificate.cert' does not have attribute 'domain_validation_options.0.resource_record_value' for variable 'aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value'
_

厄介な部分は、validationリソースにコメントすると、applyが成功し、コメントを解除して再実行することも成功することです。

element()lookup()list()、およびmap()のすべての順列を試し、出力のインデックスで証明書をターゲットにしました。最初のリソースブロックからですが、文書化された「フラットリスト」の制限に直面しており、これは私が成功に到達した最も近いものです。回避策が必要な理由を理解して、それを排除できるようにしたいと思います。これは構文の問題のように感じます。または、HCLをOO言語のように動作させようとしています。

役立つかもしれないどんな経験にも感謝します!

6
Aaron Stone

同様のシナリオがあり、それを解決するための鍵は localsflatten() の使用でした。このアプローチは、リソースを作成するために2つのパスを必要としないように機能するはずです。

このシナリオでは、証明書のsubjectAltNameセクションに表示されるサブドメインを持つ複数のドメインがあります。例えば:

_├── preview.example.com
│   ├── app.preview.example.com
│   └── www.preview.example.com
├── demo.example.com
│   ├── app.demo.example.com
│   └── www.demo.example.com
├── staging.example.com
│   ├── app.staging.example.com
│   └── www.staging.example.com
└── example.com
    ├── app.example.com
    └── www.example.com
_

これを実現するために、最初にいくつかの変数を設定します。

_variable "domains" {
    type = "list"
    default = [
        "demo.example.com",
        "preview.example.com",
        "staging.example.com",
        "example.com"
    ]
}
variable "subdomains" {
    type = "list"
    default = [
        "app",
        "www"
    ]
}
_

次に、サブドメインをSANとして含む証明書リソースを作成します。

_resource "aws_acm_certificate" "cert" {
  count             = "${length(var.domains)}"
  domain_name       = "${element(var.domains, count.index)}"
  validation_method = "DNS"

  subject_alternative_names = ["${
    formatlist("%s.%s",
      var.subdomains,
      element(var.domains, count.index)
    )
  }"]
}
_

次に、結果のドメインとサブドメインのセットをフラット化するためのローカル変数が必要になります。これが必要なのは、バージョン0.11.7以降、element()補間も `list [count]も経由せずに、terraformがネストされたリスト構文をサポートしていないためです。

_locals {
  dvo = "${flatten(aws_acm_certificate.cert.*.domain_validation_options)}"
}
_

次に、後続のRoute53レコードで使用できるRoute53ゾーンのルックアップが必要になります。

_data "aws_route53_zone" "zone" {
  count        = "${length(var.domains) > 0 ? 1 : 0}"
  name         = "example.com."
  private_zone = false
}
_

次に、DNS検証用の証明書リソースからのデータが入力されるRoute 53DNSレコードを作成します。サブドメインに1つ追加して、サブドメインのリストに含まれていないベースドメインのレコードも作成します。

_resource "aws_route53_record" "cert_validation" {
  count   = "${length(var.domains) * (length(var.subdomains) + 1)}"
  zone_id = "${data.aws_route53_zone.zone.id}"
  ttl     = 60

  name    = "${lookup(local.dvo[count.index], "resource_record_name")}"
  type    = "${lookup(local.dvo[count.index], "resource_record_type")}"
  records = ["${lookup(local.dvo[count.index], "resource_record_value")}"]
}
_

最後に、証明書が発行されるのを待つ証明書検証リソースを作成します。

_resource "aws_acm_certificate_validation" "cert" {
  count                   = "${length(var.domains) * (length(var.subdomains) + 1)}"
  certificate_arn         = "${element(aws_acm_certificate.cert.*.arn, count.index)}"
  validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn}"]
}
_

この最後のリソースの1つの注意点は、要求された証明書ごとにリソースの1つのインスタンスを作成することですが、各インスタンスはすべてのドメインとサブドメインのすべてのFQDNに依存します。これはAWSの何にも影響しませんが、すべての証明書が発行されるまで、テラフォームコードは続行/完了しません。

これは、最初のパスでリソースを_-target_する必要がなく、1回の適用実行で機能するはずですが、 検証が完了するまでにかかる時間 実行時に明らかに既知の問題があります。 terraformを介して、このため、コードを変更したり、呼び出しを計画/適用したりしなくても、2回目のパスが必要になる場合があります。

6
JinnKo

そのため、少し実験した後、私が見た属性の欠落エラーを回避するための回避策として-target=aws_acm_certificate.certを利用することになりました。上記で使用していた構文は正しく、エラーは、検証手順で生成された属性を参照する前に、証明書を完了する必要があるapplyの結果でした。

さらに、zipmapを使用したMANUAL FILLステップの洗練されたソリューションを見つけました。結果は次のようになります...

変数:

hosted_zones = [
  "foo.com"
]

hosted_zonesモジュールからの出力:

output "hosted_zone_ids" {
  value = "${zipmap(var.hosted_zones, aws_route53_zone.zones.*.zone_id)}"
}

次に、証明書の生成/検証モジュールは次のようになります。ここで、var.hosted_zone_mapは、ホストされたゾーンドメイン名から割り当てられたゾーンIDへのマップを作成する前のzipmapの出力です。

resource "aws_acm_certificate" "cert" {
    count                       = "${length(keys(var.hosted_zone_map))}"
    domain_name                 = "${element(keys(var.hosted_zone_map), count.index)}"
    subject_alternative_names   = ["*.${element(keys(var.hosted_zone_map), count.index)}"]
    validation_method           = "DNS"

    tags {
        Project     = "${var.project}"
        Environment = "${var.environment}"
    }
}

resource "aws_route53_record" "cert_validation" {
    count   = "${length(keys(var.hosted_zone_map))}"

    zone_id = "${lookup(var.hosted_zone_map, element(keys(var.hosted_zone_map), count.index))}"
    name = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_name[count.index]}"
    type = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_type[count.index]}"
    records = ["${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value[count.index]}"]
    ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
    count                   = "${length(keys(var.hosted_zone_map))}"

    certificate_arn         = "${aws_acm_certificate.cert.*.arn[count.index]}"
    validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn[count.index]}"]
}

感嘆符の配置は、これを追跡する上で間違いなく最もトリッキーで文書化されていない部分だったので、うまくいけば、これは他の誰かを助けるでしょう。

1
Aaron Stone