Задача
Есть пара зон в Amazon Route53 и несколько сертификатов (в том числе и wild-card) в Amazon Certificate Manager.
С недавних пор Amazon перестал выпускать (и обновлять) сертификаты для зоны .ru.
Необходимо:
- Выпустить сертификаты от Let's Encrypt.
- Настроить Lambda-функцию для периодической проверки срока действия сертов и их обновления.
Решение
Я нашел проект на GitHub, представляющий собой модуль для terraform, который создает Lambda-функцию со свей необходимой обвязкой (политики, таймер для запуска).
Однако - он не очень-то заработал со свежим terraform, а также выполнял только сохранение сертов в S3.
Я переделал его под свои нужды - https://github.com/KindDevOps/terraform-aws-certbot-lambda/tree/acm_only (ветка acm_only).
В директории examples есть пример рабочего кода.
Данный код получает на входе:
- Регион AWS
- Список доменов. Домены второго уровня нужно указывать с точкой - .domain.com
- Адрес почты для регистрации в Let's Encrypt
Предполагается, что в AWS Route53 уже созданы зоны с этими доменами.
В итоге код делает вот что:
- Создает самоподписанный сертификат в ACM
- Создает Lambda-функцию, которой в качестве параметров передаются список имен для сертификата, id зоны в Route53, и arn сертификата в ACM.
- Создает разрешения для Lambda-функции.
Для выпуска серта - достаточно выполнить:
terraform init terraform plan terraform apply
И затем - запустить созданную Lambda-функцию.
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "${var.aws_region}"
}
module "certbot-lambda" {
source = "github.com/KindDevOps/terraform-aws-certbot-lambda?ref=acm_only"
name = "${local.lambda-name}"
contact_email = "${var.certbot_contact_email}"
certificate_domains = "${local.certificate_domains}"
# Route53 Zones list to apply policy
hosted_zones_ids = data.aws_route53_zone.zones
certificate_arn = aws_acm_certificate.cert.arn
function_trigger_schedule_expression = "cron(12 20 * * ? *)"
}
locals {
lambda-name = replace(regex("\\.*(.*)", "${var.certificate_domains[0]}")[0], ".", "-")
certificate_domains = join(",", [ for domain in var.certificate_domains : regex("\\.*(.*)", domain)[0] ] )
}
data "aws_route53_zone" "zones" {
for_each = toset( var.certificate_domains )
name = regex(".*?[[:punct:]]*(.*)", each.value)[0]
private_zone = false
}
resource "tls_private_key" "stub" {
algorithm = "RSA"
}
resource "tls_self_signed_cert" "stub" {
private_key_pem = tls_private_key.stub.private_key_pem
subject {
common_name = "${var.certificate_domains[0]}"
organization = "ACME Examples, Inc"
}
validity_period_hours = 12
allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
]
}
resource "aws_acm_certificate" "cert" {
private_key = tls_private_key.stub.private_key_pem
certificate_body = tls_self_signed_cert.stub.cert_pem
}
variables.tf
variable "aws_region" {
type = string
default = "us-east-2"
}
variable "certificate_domains" {
type = list
default = [
".voximplant.com",
"*.voximplant.com",
".voximplant.ru",
"*.voximplant.ru"
]
# Front dots should be used to allow automatic Route53 zone id detection for second level domains.
}
variable "certbot_contact_email" {
type = string
default = "usik@voximplant.com"
}
Discussion