В наши дни сложно представить системы с общедоступными конечными точками API без защиты сертификатом TLS. Выдать сертификаты можно несколькими способами:
В контексте этой статьи я в основном буду обсуждать бесплатные сертификаты, которые можно использовать внутри AWS, но не только сервисами AWS. Очевидно, что использование чего-либо, кроме AWS Certificate Manager, не имеет смысла, если вы используете исключительно управляемые сервисы AWS и не предъявляете строгих требований к безопасности. AWS Certificate Manager предлагает очень удобный и быстрый метод выдачи сертификатов через DNS или HTTP-запросы; однако вы сталкиваетесь с базовыми ограничениями AWS, если вам нужно использовать эти сертификаты за пределами сервисов AWS (API Gateway, ALB, NLB и т. д.), например, экземпляр EC2, на котором работает Nginx, которому требуется физический файл сертификата. Кроме того, даже если вы этого запросите, AWS Certificate Manager не отображает содержимое сертификата.
На этом этапе самое время напомнить вам о LetsEncrypt , более широко используемом инструменте, чем диспетчер сертификатов — хотя бы потому, что он не зависит от облака. К сожалению, в AWS нет встроенных методов выдачи сертификатов LetsEncrypt. Инструмент certbot можно использовать для ваших служб EC2 или ECS, но в этом случае вам нужно будет подумать о том, как настроить процесс продления. Я также не хочу комбинировать разные стратегии, поскольку считаю, что лучше иметь одну процедуру для всего, поскольку это снижает сложность всей системы.
Принимая это во внимание, я создал функцию Lambda, которая автоматически выдает и обновляет сертификаты LetsEncrypt, не требуя сложной настройки. Сертификат можно использовать в любом сервисе AWS, использующем ARN, вместе с сертификатами AWS Certificate Manager после первоначальной выдачи сертификата. Кроме того, вы можете использовать версию физического сертификата, которая хранится в AWS Secrets Manager в любом месте по вашему выбору, будь то экземпляр EC2, на котором работает Nginx, или другое место.
В этой статье я буду предполагать, что ваша зона DNS управляется AWS Route53.
Функция Lambda, описанная в этой статье, написана на Go v1.22. Все выходные ресурсы, такие как записи DNS, секреты или сертификаты, контролируются ролью Amazon IAM, которая по умолчанию создается с помощью кода Terraform. Последовательность действий Lambda следующая:
aws_cloudwatch_event_target
. Пример события: { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true }
DNS-01
если количество дней до истечения срока действия меньше reImportThreshold
. На этом этапе Lambda создает запись TXT
, соответствующую доменному имени зоне AWS Route53, и ожидает готовности вашего сертификата.storeCertInSecretsManager
имеет значение true.
Код
Лямбда написана на Go 1.22. Использование как можно меньшего количества библиотек помогло мне сохранить мою цель — сохранить код сухим. Полный список необходимых библиотек Go:
URL-адрес | Описание |
---|---|
Библиотеки, примеры и инструменты, которые помогут разработчикам Go разрабатывать функции AWS Lambda. | |
AWS SDK для языка программирования Go. | |
Клиент и библиотека LetsEncrypt/ACME. | |
Разумная обработка значений, допускающих значение NULL. | |
Структурированное подключаемое ведение журналов для Go. |
Докер-образ
Я использовал gcr.io/distroless/static:nonroot в качестве базового образа докера. Для приложений Go, не требующих libc, этот образ идеален. Он не совсем scratch
и включает в себя следующее:
Процесс сборки
В крупных проектах по разработке программного обеспечения наблюдение за процессом сборки может превратиться в трудоемкую и трудоемкую работу. Makefiles могут помочь автоматизировать и упростить этот процесс, гарантируя, что ваш проект будет построен эффективно и последовательно. По этой причине я предпочитаю использовать Makefile для всех своих проектов на Golang. Файл простой:
##@ General help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) fmt: ## Run go fmt against code. go fmt ./... vet: ## Run go vet against code. go vet ./... ##@ Build build: fmt vet ## Build service binary. go build -o bin/lambda main.go run: vet ## Run service from your laptop. go run ./main.go ##@ Test lint: ## Run Go linter golangci-lint run ./... test: ## Run Go tests go test ./...
Со стороны CICD я использовал типичную настройку приложения Go.
GitHub Actions как непрерывная интеграция.
ghcr.io в качестве реестра докеров. По сравнению с DockerHub, этот предлагает две ключевые функции, которые я предпочитаю использовать:
kvendingoldo/semver-action : мой плагин GitHub Actions для автоматического управления версиями. Это действие GitHub, которое генерирует теги, совместимые с SemVer, для коммитов в репозитории. Действие может управлять версиями, создавать выпуски GitHub и контролировать ветки выпусков внутри репозитория. Он прекрасно работает как с одиночными, так и с монорепозиториями.
Автоматическое создание журнала изменений. Мне нравятся журналы изменений! В контексте других проектов OpenSource, которыми я управляю (например, https://github.com/tofuutils/tenv , https://github.com/tofuutils/tofuenv и т. д.), моя команда осознала важность информирования пользователей о изменения.
голангчи-линт . С моей точки зрения, весь код должен проверяться статическим анализатором кода. SonarQube не может быть настроен для всех проектов, однако golangci, на мой взгляд, достаточно для малых и средних проектов Go.
Код, обсуждаемый на этой странице, одинаков для Terraform и OpenTofu, но, начиная с Terraform v1.6, Hashicorp изменил лицензию Terraform на Business Source License (BSL) v1.1. Если вы разрабатываете что-то коммерческое на основе Terraform, перейди на OpenTofu как можно скорее.
Если вам нужно управлять несколькими версиями OpenTofu или Terraform, используйте
Дополнительные примеры Terraform/OpenTofu можно найти в папке примеров в репозитории Git .
Для работы с AWS LetsEncrypt Lambda через OpenTofu необходимо выполнить следующие шаги:
Добавьте модуль в ваш код OpenTofu/Terraform.
module "letsencrypt_lambda" { source = "[email protected]:kvendingoldo/aws-letsencrypt-lambda.git//files/terraform/module?ref=0.31.4" blank_name = "kvendingoldo-letsencrypt-lambda" tags = var.tags cron_schedule = var.letsencrypt_lambda_cron_schedule events = var.letsencrypt_lambda_events ecr_proxy_username = var.ecr_proxy_username ecr_proxy_access_token = var.ecr_proxy_access_token }
Укажите переменные
variable "tags" { default = { hackernoon : "demo" } } variable "ecr_proxy_username" { default = "kvendingoldo" } variable "ecr_proxy_access_token" { default = "ghp_xxx" } variable "letsencrypt_lambda_cron_schedule" { default = "rate(168 hours)" } variable "letsencrypt_lambda_events" { default = [ { "acmRegion" : "us-east-1", "route53Region" : "us-east-1", "domainName" : "hackernoon.referrs.me", "acmeUrl" : "stage", "acmeEmail" : "[email protected]", "reImportThreshold" : 100, "issueType" : "default", "storeCertInSecretsManager" : false } ] }
Обратите внимание на переменные ecr_proxy_username
и ecr_proxy_access_token
. По умолчанию AWS Lambda не может получать изображения из других источников, кроме AWS ECR. К счастью, команда AWS создала прокси-кеш ECR, который может получать изображения из общедоступных реестров, таких как DockerHub или GHCR, и хранить их внутри ECR. Несмотря на эту возможность, AWS не позволяет извлекать изображения без токена, даже из открытых общедоступных репозиториев, поэтому вам необходимо приобрести личный токен GitHub, чтобы получить доступ к предварительно созданным образам Docker. Альтернативно вы можете получить мой репозиторий GitHub, создать образ локально, а затем загрузить его в уже существующий репозиторий ECR. В этом сценарии пример можно изменить следующим образом:
module "letsencrypt_lambda" { source = "../../" blank_name = "kvendingoldo-letsencrypt-lambda" tags = var.tags cron_schedule = var.letsencrypt_lambda_cron_schedule events = var.letsencrypt_lambda_events ecr_proxy_enabled = false ecr_image_uri = "<YOUR_ACCOUNT_ID>.dkr.ecr.us-east-2.amazonaws.com/aws_letsencrypt_lambda:<VERSION>" }
Когда вы завершите изменение кода, выполните следующую команду, чтобы установить OpenTofu с помощью переключателя версий OpenTofu tenv :
$ tenv tofu install
И, наконец, выполните следующие команды, чтобы применить полученный код:
$ tofu init $ tofu plan $ tofu apply
Подождите, пока код будет развернут на AWS и не начнутся события. Через несколько минут вы увидите готовые сертификаты внутри менеджера сертификатов. Пример:
Начиная с этого момента AWS может использовать выданный сертификат в любых сервисах ARN.
Если вам нужно использовать сертификат вне сервисов AWS или иметь доступ к его содержимому, установите для параметра события storeCertInSecretsManager
значение true
. В этой ситуации, когда Lambda завершит базовое выполнение, сертификат будет сохранен в AWS Secrets Manager. Это дает пользователям больше гибкости: они могут проверять содержимое сертификата, работать с ним непосредственно из EC2 и т. д. Чтобы узнать больше об AWS Secrets Manager, прочтите официальное руководство.
Имя | Описание | Возможные значения | Значение по умолчанию | Пример | Необходимый |
---|---|---|---|---|---|
| Тип форматтера для журналов | JSON | ТЕКСТ | ТЕКСТ | JSON | ❌ |
| Режим приложения. Установите | облако | местный | облако | облако | ✅ |
| Уровень ведения журнала | паника|фатальная|ошибка|предупреждение|информация|отладка|отслеживание | предупреждать | предупреждать | ❌ |
| Регион AWS по умолчанию. После развертывания на AWS он настраивается автоматически. | <любой действительный регион AWS> | - | США-Восток-1 | ✅ |
| Доменное имя, для которого выдается или продлевается сертификат | любое действительное доменное имя | - | хакернун. Referrs.me | ✅ |
| Будет использоваться производственный URL-адрес LetsEncrypt, если для него установлено | прод | этап | подталкивать | подталкивать | ✅ |
| Адрес электронной почты, связанный с сертификатом LetsEncrypt | любой действительный адрес электронной почты | ✅ | ||
| Сертификат будет продлен, если его срок жизни (TTL) равен | любое целое > 0 | 10 | 10 | ✅ |
| Если | «правда» | "ЛОЖЬ" | "ЛОЖЬ" | "ЛОЖЬ" | ❌ |
В рамках работы с aws-letsencrypt-lambda вам может иногда потребоваться просмотреть журналы. Это довольно легко сделать:
/aws/lambda/kvendingoldo-letsencrypt-lambda
Перейдите к функции Lambda, созданной с помощью OpenTofu. Нажмите кнопку «Тесты».
Заполните Test Event
и нажмите Test
{ "domainName": "<YOUR_VALID_DOMAIN>", "acmeUrl": <stage | prod>, "acmeEmail": "<ANY_VALID_EMAIL>", "reImportThreshold": 10, "issueType": "<default | force>", "storeCertInSecretsManager" : <true | false> }
Пример №1:
{ "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default" }
Подождите, пока выполнение не завершится. Журнал выполнения доступен в Cloudwatch. Обычно первоначальный выпуск занимает около 5 минут.
Клонируйте репозиторий https://github.com/kvendingoldo/aws-letsencrypt-lambda на свой ноутбук.
Настройте учетные данные AWS Cli с помощью официального руководства .
Изучите раздел переменных среды и установите минимальное количество необходимых переменных. Поскольку LetsEncrypt ограничивает количество повторов в час для ACME_URL="prod"
, я советую использовать ACME_URL="stage"
для тестирования. Пример переменных среды:
export AWS_REGION="us-east-2" export MODE=local export DOMAIN_NAME="hackernoon.referrs.me" export ACME_URL="stage" export ACME_EMAIL="[email protected]" export REIMPORT_THRESHOLD=10 export ISSUE_TYPE="default" export STORE_CERT_IN_SECRETSMANAGER="true"
Выполните лямбду локально с помощью следующей команды:
go run main.go
После успешного выполнения Lambda появится следующий журнал.
INFO[0000] Starting lambda execution ... INFO[0000] Lambda will use STAGING ACME URL; If you need to use PROD URL specify it via 'ACME_URL' or pass in event body INFO[0000] Certificate found, arn is arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af. Trying to renew ... INFO[0000] Checking certificate for domain 'hackernoon.referrs.me' with arn 'arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af' INFO[0000] Certificate status is 'ISSUED' INFO[0000] Certificate in use by [] INFO[0000] Certificate valid until 2024-08-31 13:50:49 +0000 UTC (89 days left) INFO[0000] Try to get certificate for hackernoon.referrs.me domain 2024/06/02 17:56:23 [INFO] acme: Registering account for [email protected] 2024/06/02 17:56:24 [INFO] [hackernoon.referrs.me, www.hackernoon.referrs.me] acme: Obtaining bundled SAN certificate 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/12603809394 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/12603809404 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Could not find solver for: tls-alpn-01 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Could not find solver for: http-01 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: use dns-01 solver 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: Could not find solver for: tls-alpn-01 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: Could not find solver for: http-01 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: use dns-01 solver 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Preparing to solve DNS-01 2024/06/02 17:56:26 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:00 [INFO] [www.hackernoon.referrs.me] acme: Preparing to solve DNS-01 2024/06/02 17:57:00 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:30 [INFO] [hackernoon.referrs.me] acme: Trying to solve DNS-01 2024/06/02 17:57:30 [INFO] [hackernoon.referrs.me] acme: Checking DNS record propagation. [nameservers=109.122.99.130:53,109.122.99.129:53] 2024/06/02 17:57:34 [INFO] Wait for propagation [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:46 [INFO] [hackernoon.referrs.me] The server validated our request 2024/06/02 17:57:46 [INFO] [www.hackernoon.referrs.me] acme: Trying to solve DNS-01 2024/06/02 17:57:46 [INFO] [www.hackernoon.referrs.me] acme: Checking DNS record propagation. [nameservers=109.122.99.130:53,109.122.99.129:53] 2024/06/02 17:57:50 [INFO] Wait for propagation [timeout: 5m0s, interval: 4s] 2024/06/02 17:58:30 [INFO] [www.hackernoon.referrs.me] The server validated our request 2024/06/02 17:58:30 [INFO] [hackernoon.referrs.me] acme: Cleaning DNS-01 challenge 2024/06/02 17:58:30 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:59:09 [INFO] [www.hackernoon.referrs.me] acme: Cleaning DNS-01 challenge 2024/06/02 17:59:09 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:59:43 [INFO] [hackernoon.referrs.me, www.hackernoon.referrs.me] acme: Validations succeeded; requesting certificates 2024/06/02 17:59:43 [INFO] Wait for certificate [timeout: 30s, interval: 500ms] 2024/06/02 17:59:45 [INFO] [hackernoon.referrs.me] Server responded with a certificate. INFO[0203] Certificate has been successfully imported. Arn is arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af INFO[0204] Secret updated successfully. SecretId: arn:aws:secretsmanager:us-east-2:004867756392:secret:hackernoon.referrs.me-NioT77 INFO[0204] Lambda has been completed
Вот и все. Начиная с этого момента, AWS может использовать выданный сертификат в любых сервисах ARN или в других местах, где это физически необходимо, получив его от AWS Secrets Manager.
Я использую функцию Lambda в производстве уже почти четыре года. С течением времени различные аспекты первоначальной реализации изменились:
Ранее AWS запретила использование любых реестров, не относящихся к ECR, в качестве источников Lambda. Он не изменился, однако AWS добавила прокси-сервер ECR для GitHub, DockerHub и нескольких дополнительных реестров. Без этой функции нам приходилось вручную отправлять изображения Lambda в наш личный ECR и заменять URL-адрес изображения в коде Terraform. Теперь код OpenTofu делает это автоматически через ECR Proxy.
Вначале я рассматривал возможность внедрения различных задач, таких как http-01
или tls-alpn-01
, но никто не спрашивал меня об этом в течение четырех лет. Он по-прежнему присутствует в выпусках GitHub , и если такая возможность потребуется, мы можем вместе работать над ее созданием.
Я не хотел использовать сертификаты LetsEncrypt в экземплярах чистого EC2, когда проект изначально стартовал, но в наши дни это стандартная практика. Как я уже говорил ранее, в определенных ситуациях сертификат можно получить из AWS Secrets Managed с помощью интерфейса AWS.
За прошедшие годы я написал много нового кода на Go, поэтому могу сказать, что исходный код Lambda в моем репозитории не так хорош, как мог бы быть. Существует значительная разница между ним и моим последним проектом Go, tenv (OpenTofu, Terraform, Terragrunt и менеджер версий Atmos, написанный на Go), но в любом случае код по-прежнему в целом поддерживается, поэтому внесение в него изменений выиграло. Это будет слишком проблематично. Время от времени я буду проводить значительный рефакторинг, чтобы сделать код более элегантным.
Одна и та же Lambda уже много лет используется в нескольких разных проектах. Кроме того, я являюсь соучредителем платформы DevOps cloudexpress.app , где наша команда управляет сертификатами TLS для всех наших клиентов с помощью AWS LetsEncrypt Lambda для упрощения процессов автоматизации.
Теперь поговорим о цифрах. За 4 года этот проект помог многим людям и использовался в многочисленных OpenSource и более чем 30 коммерческих проектах . Lambda выдает более 2000 сертификатов и не хочет останавливаться на достигнутом.
AWS LetsEncrypt Lambda — подходящее решение для вас, если
Если вы обнаружили, что хотя бы один из этих пунктов применим к вашей ситуации, вы можете использовать AWS Lambda. Кроме того, если вы хотите принять участие в разработке, я всегда открыт для новых вопросов и запросов на включение на GitHub. URL-адрес проекта: https://github.com/kvendingoldo/aws-letsencrypt-lambda .