Terraform: создание и проверка нескольких сертификатов ACM

Я столкнулся с действительно запутанной проблемой ресурсов Terraform, автоматизирующей создание и проверку DNS сертификатов SSL в ACM для списка (управляемых 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 и моего отсутствия опыта работы с ним.

Я могу надежно генерировать голые сертификаты и сертификаты 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}"
    }
}

Все становится непросто, когда я пытаюсь автоматизировать проверку 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 вести себя больше как объектно-ориентированный язык, чем он есть.

Спасибо за любой опыт, который может помочь!


comment
Возможный дубликат синтаксиса интерполяции Terraform для строки   -  person ydaetskcoR    schedule 03.05.2018
comment
Этот связанный вопрос, вероятно, нуждается в приведении в порядок, чтобы указать, что с ресурсом aws_acm_certificate происходит какое-то безумие, из-за которого он ведет себя немного странно, но принятый ответ, похоже, работает именно для того, что вы здесь делаете.   -  person ydaetskcoR    schedule 03.05.2018
comment
Спасибо за ссылку @ydaetskcoR. Я фактически пришел к приемлемому уровню обходного пути, используя -target, чтобы не комментировать вещи на первом проходе, но все еще кажется, что синтаксис API странный (как вы предположили) и лишь частично задокументирован. Я опубликую здесь, где я приземлился, на случай, если кто-то еще наткнется на этот пост.   -  person Aaron Stone    schedule 03.05.2018


Ответы (2)


У меня был похожий сценарий, и ключом к его решению было использование местных жителей и flatten (). Этот подход также должен работать для вас, так что вам не потребуется два прохода для создания ресурсов.

В этом сценарии есть несколько доменов, каждый из которых имеет поддомены, которые будут отображаться в разделе 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)
    )
  }"]
}

Затем нам понадобится локальная переменная, чтобы сгладить результирующий набор доменов и поддоменов. Это необходимо, потому что terraform не поддерживает синтаксис вложенных списков, начиная с версии 0.11.7, ни через интерполяцию element(), ни через `list [count].

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

Далее нам понадобится поиск зоны Route 53, которую мы можем использовать в последующих записях Route 53:

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

Затем мы создаем DNS-записи Route 53, которые будут заполнены данными из ресурса сертификата для проверки 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")}"]
}

Наконец, мы создаем ресурс проверки сертификата, который будет ждать выдачи сертификата.

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}"]
}

Единственное предостережение для этого последнего ресурса заключается в том, что он будет создавать один экземпляр ресурса для каждого запрошенного сертификата, но каждый экземпляр будет зависеть от всех полных доменных имен во всех доменах и поддоменах. Это не повлияет ни на что в AWS, но код terraform не будет продолжен / завершен, пока не будут выпущены все сертификаты.

Это должно работать за один проход применения без необходимости -target каких-либо ресурсов на первом проходе, хотя очевидно известная проблема связана с сколько времени требуется для завершения проверки при выполнении через terraform, и по этой причине может потребоваться второй проход, хотя и без изменения кода или вызова плана / применения.

person JinnKo    schedule 09.05.2018
comment
Спас мою жизнь! Я открыл вопрос о ti: github.com/hashicorp/terraform/issues/18359 - person mzhaase; 29.06.2018

Поэтому, немного поэкспериментировав, я решил использовать -target=aws_acm_certificate.cert в качестве обходного пути, чтобы избежать ошибок, связанных с отсутствием атрибутов, которые я видел. Синтаксис, который я использовал выше, был правильным, и ошибка возникла из-за того, что apply необходимо было завершить для сертификата, прежде чем шаги проверки смогут ссылаться на сгенерированные атрибуты.

Вдобавок я нашел элегантное решение для шага MANUAL FILL, используя zipmap. Результат выглядит так ...

Переменная:

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 - это результат предыдущего 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]}"]
}

Расположение знака определенно было самой сложной и наименее документированной частью отслеживания этого, так что, надеюсь, это поможет кому-то другому.

person Aaron Stone    schedule 03.05.2018