Terraform: создание и проверка нескольких сертификатов ACM - PullRequest
0 голосов
/ 27 апреля 2018

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

Я могу надежно генерировать открытые и сплат-сертификаты для каждой размещенной зоны, используя ...

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 вести себя больше как язык ОО, чем он есть.

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

Ответы [ 2 ]

0 голосов
/ 09 мая 2018

У меня был похожий сценарий, и ключом к его решению было использование localals и 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}"]
}

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

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

0 голосов
/ 03 мая 2018

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

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

Variable:

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

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

...