Terraform:创建和验证多个ACM证书

Aar*_*one 5 ssl-certificate amazon-web-services amazon-route53 terraform aws-certificate-manager

我遇到了一个非常令人困惑的Terraform资源问题,它会自动生成ACM中(由Terraform管理的)托管区域列表的SSL证书的生成和DNS验证。代码也可以在这个要点中找到。

我首先通过引导托管区域来引用此特定于环境的变量。

hosted_zones = [
    {
        domain = "site1.com"
        zone_id = "MANUALLY FILL"
    }
]
Run Code Online (Sandbox Code Playgroud)

我用来构建区域的块似乎工作可靠。

resource "aws_route53_zone" "zones" {
    count = "${length(var.hosted_zones)}"
    name  = "${lookup(var.hosted_zones[count.index], "domain")}"
}
Run Code Online (Sandbox Code Playgroud)

在构建区域之后,由于HCL的局限性以及我缺乏经验,因此我没有想出一种自动的方法来自动将区域ID复制到变量中。

我可以使用...为每个托管区域可靠地生成裸证书和splat证书。

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}"
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试自动执行证书的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]}"]
}
Run Code Online (Sandbox Code Playgroud)

我在第一次运行时看到的错误是:

* 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'
Run Code Online (Sandbox Code Playgroud)

令人讨厌的是,如果我评论validation资源,则apply成功,然后取消注释并重新运行也成功。

我已经尝试过(感觉上)的每个排列element() lookup()list()map()通过第一个资源块的输出中的索引来针对证书来定位证书,但是遇到了书面“限制列表”限制,这是我最接近成功的方法。我想了解为什么需要这种解决方法,所以我可以消除它。这感觉像是语法问题,或者我试图使HCL的行为更像是OO语言。

感谢您提供的任何经验!

Jin*_*nKo 6

我有一个类似的场景,解决这个问题的关键是使用localsflatten()。该方法也应该为您工作,这样您就不需要两次通过就可以创建资源。

在这种情况下有多个域,每个域都有子域,这些子域将出现在证书的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
Run Code Online (Sandbox Code Playgroud)

为此,我们首先设置一些变量:

variable "domains" {
    type = "list"
    default = [
        "demo.example.com",
        "preview.example.com",
        "staging.example.com",
        "example.com"
    ]
}
variable "subdomains" {
    type = "list"
    default = [
        "app",
        "www"
    ]
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们创建包含作为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)
    )
  }"]
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们将需要一个局部变量来展平最终的域和子域集。这是必需的,因为从0.11.7版本开始,terraform不支持​​嵌套列表语法,既不通过element()插值也不通过`list [count]。

locals {
  dvo = "${flatten(aws_acm_certificate.cert.*.domain_validation_options)}"
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们需要查找Route 53区域,我们可以在后续的Route 53记录中使用该区域:

data "aws_route53_zone" "zone" {
  count        = "${length(var.domains) > 0 ? 1 : 0}"
  name         = "example.com."
  private_zone = false
}
Run Code Online (Sandbox Code Playgroud)

然后,我们创建Route 53 DNS记录,该记录将使用来自证书资源的数据进行DNS验证。我们将一个添加到子域中,以便我们也有一个未包含在子域列表中的基本域的记录。

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")}"]
}
Run Code Online (Sandbox Code Playgroud)

最后,我们创建证书验证资源,该资源将等待证书颁发。

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}"]
}
Run Code Online (Sandbox Code Playgroud)

最后一项资源的一个警告是,它将为每个请求的证书创建资源的一个实例,但是每个实例将取决于所有域和子域中的所有FQDN。这不会影响AWS中的任何内容,但是在发布所有证书之前,terraform代码不会继续/完成。

-target尽管显然存在一个问题,即通过terraform进行验证需要多长时间才能完成,但这应该可以在一次应用运行中进行,而无需在第一遍中进行任何操作,这显然是一个已知问题,因此可能需要第二次通过,尽管无需更改代码或计划/应用调用。


Aar*_*one 2

因此,经过一些实验后,我最终采用了-target=aws_acm_certificate.cert一种解决方法来避免我看到的缺失属性错误。我上面使用的语法是正确的,错误是由于apply在验证步骤可以引用生成的属性之前需要完成证书而导致的。

此外,我找到了一个MANUAL FILL使用 的步骤的优雅解决方案zipmap。结果看起来像这样......

多变的:

hosted_zones = [
  "foo.com"
]
Run Code Online (Sandbox Code Playgroud)

模块输出hosted_zones

output "hosted_zone_ids" {
  value = "${zipmap(var.hosted_zones, aws_route53_zone.zones.*.zone_id)}"
}
Run Code Online (Sandbox Code Playgroud)

然后,我的证书生成/验证模块如下所示,其中创建托管区域域名到分配区域 ID 的映射var.hosted_zone_map的前一个模块的输出在哪里: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]}"]
}
Run Code Online (Sandbox Code Playgroud)

splat 的定位绝对是追踪此问题中最棘手且记录最少的部分,因此希望这对其他人有帮助。