Hoy en día, resulta difícil imaginar sistemas que tengan puntos finales de API públicos sin protección de certificado TLS. Hay varias formas de emitir certificados:
En el contexto de esta publicación, analizaré principalmente los certificados gratuitos que se pueden usar dentro de AWS, pero no solo en los servicios de AWS. Claramente, usar cualquier otra cosa que no sea AWS Certificate Manager no tiene sentido si utiliza exclusivamente servicios administrados de AWS y no tiene requisitos de seguridad estrictos. AWS Certificate Manager ofrece un método muy conveniente y rápido para emitir certificados a través de desafíos DNS o HTTP; sin embargo, enfrenta limitaciones básicas de AWS si necesita utilizar estos certificados fuera de los servicios de AWS (API Gateway, ALB, NLB, etc.), como una instancia EC2 que ejecuta Nginx y necesita un archivo de certificado físico. Además, incluso si lo solicita, AWS Certificate Manager no muestra el contenido del certificado.
Llegados a este punto es un buen momento para recordarte LetsEncrypt , una herramienta más utilizada que Certificate Manager, al menos porque no depende de la nube. Lamentablemente, no existen técnicas integradas de emisión de certificados LetsEncrypt disponibles en AWS. Es posible utilizar la herramienta certbot para sus servicios EC2 o ECS, pero en ese escenario, deberá considerar cómo configurar el proceso de renovación. Tampoco quiero combinar diferentes estrategias ya que creo que es mejor tener un procedimiento único para todo, ya que reduce la complejidad de todo el sistema.
Teniendo esto en cuenta, creé una función Lambda que emite y renueva automáticamente los certificados LetsEncrypt sin requerir una configuración compleja. El certificado se puede utilizar en cualquier servicio de AWS utilizando ARN junto con los certificados de AWS Certificate Manager después de la emisión inicial del certificado. Además, puede utilizar una versión de certificado físico que se guarda en AWS Secrets Manager en cualquier ubicación que elija, ya sea una instancia EC2 que ejecuta Nginx u otro lugar.
En este artículo, asumiré que su zona DNS está administrada por AWS Route53.
La función Lambda que se describe en este artículo está escrita en Go v1.22. Todos los recursos de resultados, como registros DNS, secretos o certificados, están controlados por la función de Amazon IAM, que se crea mediante el código Terraform de forma predeterminada. La secuencia de acciones Lambda es la siguiente:
aws_cloudwatch_event_target
. Ejemplo de evento: { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true }
DNS-01
si el número de días hasta la fecha de vencimiento es menor que reImportThreshold
. Este paso implica que Lambda cree un registro TXT
que haga coincidir el nombre de dominio con la zona de AWS Route53 y espere a que su certificado esté listo.storeCertInSecretsManager
es verdadero.
El código
Lambda está escrito en Go 1.22. Usar la menor cantidad de bibliotecas posible me ayudó a mantener mi objetivo de mantener el código seco. La lista completa de bibliotecas go requeridas:
URL | Descripción |
---|---|
Bibliotecas, ejemplos y herramientas para ayudar a los desarrolladores de Go a desarrollar funciones de AWS Lambda. | |
AWS SDK para el lenguaje de programación Go. | |
Cliente y biblioteca LetsEncrypt / ACME. | |
Manejo razonable de valores que aceptan valores NULL. | |
Registro estructurado y conectable para Go. |
imagen acoplable
Utilicé gcr.io/distroless/static:nonroot como imagen básica de Docker. Para aplicaciones Go que no requieren libc, esta imagen es perfecta. No está completamente scratch
e incluye lo siguiente:
Proceso de construcción
En grandes proyectos de software, supervisar el proceso de construcción puede convertirse en una tarea laboriosa y que requiere mucho tiempo. Makefiles puede ayudar a automatizar y optimizar este proceso, asegurando que su proyecto se construya de manera eficiente y consistente. Por esa razón, prefiero usar Makefile para todos mis proyectos de Golang. El archivo es simple:
##@ 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 ./...
Desde el lado de CICD utilicé la configuración típica para la aplicación Go.
GitHub Actions como integración continua.
ghcr.io como registro de Docker. En comparación con DockerHub, este ofrece dos características clave que hacen que prefiera su uso:
kvendingoldo/semver-action : mi complemento de GitHub Actions para control de versiones automático. Es una acción de GitHub que genera etiquetas compatibles con SemVer para confirmaciones del repositorio. La acción puede administrar versiones, generar lanzamientos de GitHub y controlar ramas de lanzamiento dentro del repositorio. Funciona de maravilla tanto con singles como con monorepos.
Generación automática de registros de cambios. ¡Disfruto de los registros de cambios! En el contexto de otros proyectos OpenSource que administro (por ejemplo, https://github.com/tofuutils/tenv , https://github.com/tofuutils/tofuenv , etc.), mi equipo reconoció la importancia de informar a los usuarios sobre cambios.
golangci-lint . Desde mi punto de vista, todo el código debería ser revisado por un analizador de código estático. SonarQube no se puede configurar para todos los proyectos; sin embargo, golangci, en mi opinión, es suficiente para proyectos Go pequeños y medianos.
El código analizado en esta página es el mismo para Terraform y OpenTofu, pero a partir de Terraform v1.6, Hashicorp modificó la licencia de Terraform a Business Source License (BSL) v1.1. Si está desarrollando algo comercial además de Terraform, cambia a OpenTofu lo antes posible.
Si necesita administrar varias versiones de OpenTofu o Terraform, utilice el
Se pueden encontrar más ejemplos de Terraform/OpenTofu en la carpeta de ejemplos dentro del repositorio de Git .
Para trabajar con AWS LetsEncrypt Lambda a través de OpenTofu, debe realizar los siguientes pasos:
Agregue el módulo a su código 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 }
Especificar variables
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 } ] }
Preste atención a las variables ecr_proxy_username
y ecr_proxy_access_token
. De forma predeterminada, AWS Lambda no puede extraer imágenes de fuentes distintas a AWS ECR. Afortunadamente, el equipo de AWS creó la caché ECR Proxy, que puede recuperar imágenes de registros disponibles públicamente como DockerHub o GHCR y almacenarlas dentro de ECR. A pesar de esta posibilidad, AWS no permite extraer imágenes sin un token, ni siquiera de repositorios públicos abiertos, por lo que debe adquirir un token de GitHub personal para obtener acceso a las imágenes de Docker prediseñadas. Alternativamente, puede extraer mi repositorio de GitHub, crear la imagen localmente y luego cargarla en su repositorio ECR preexistente. En este escenario, el ejemplo se puede modificar de la siguiente manera:
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>" }
Cuando termine de cambiar el código, ejecute el siguiente comando para instalar OpenTofu mediante el conmutador de versión OpenTofu tenv :
$ tenv tofu install
Y finalmente, ejecute los siguientes comandos para aplicar el código producido:
$ tofu init $ tofu plan $ tofu apply
Espere hasta que el código se implemente en AWS y se activen los eventos. Después de unos minutos, verá los certificados listos dentro del administrador de certificados. Ejemplo:
A partir de ahora, AWS puede utilizar el certificado emitido en cualquier servicio de ARN.
Si necesita utilizar el certificado fuera de los servicios de AWS o tener acceso a su contenido, configure la opción de evento storeCertInSecretsManager
en true
. En esta situación, cuando Lambda complete la ejecución básica, el certificado se guardará en AWS Secrets Manager. Ofrece a los usuarios más flexibilidad: pueden inspeccionar el contenido del certificado, trabajar con él directamente desde EC2, etc. Para obtener más información sobre AWS Secrets Manager, lea la guía oficial.
Nombre | Descripción | Valores posibles | Valor por defecto | Ejemplo | Requerido |
---|---|---|---|---|---|
| Tipo de formateador para registros | JSON | TEXTO | TEXTO | JSON | ❌ |
| Modo de aplicación. Configure el modo | nube | local | nube | nube | ✅ |
| Nivel de registro | pánico|fatal|error|advertencia|información|depuración|rastreo | advertir | advertir | ❌ |
| Región de AWS predeterminada. Después de la implementación en AWS, se configura automáticamente. | <cualquier región de AWS válida> | - | nosotros-este-1 | ✅ |
| Nombre de dominio para el cual se emite o renueva el certificado | cualquier nombre de dominio válido | - | tarde de piratas informáticos. referrs.me | ✅ |
| La URL de producción de LetsEncrypt se utilizará si está configurada en | pinchar | escenario | pinchar | pinchar | ✅ |
| Dirección de correo electrónico vinculada al certificado LetsEncrypt | cualquier correo electrónico válido | ✅ | ||
| El certificado se renovará si su tiempo de vida (TTL) es igual a | cualquier entero > 0 | 10 | 10 | ✅ |
| Si es | “verdadero” | "FALSO" | "FALSO" | "FALSO" | ❌ |
En el ámbito del trabajo con aws-letsencrypt-lambda, es posible que desee revisar los registros ocasionalmente. Es bastante fácil de lograr:
/aws/lambda/kvendingoldo-letsencrypt-lambda
Vaya a la función Lambda que se creó a través de OpenTofu. Haga clic en el botón "Pruebas".
Complete Test Event
y haga clic en Test
{ "domainName": "<YOUR_VALID_DOMAIN>", "acmeUrl": <stage | prod>, "acmeEmail": "<ANY_VALID_EMAIL>", "reImportThreshold": 10, "issueType": "<default | force>", "storeCertInSecretsManager" : <true | false> }
Ejemplo 1:
{ "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default" }
Espere hasta que se complete la ejecución. Puede que el registro de ejecución esté disponible en Cloudwatch. Normalmente, la emisión inicial tarda unos 5 minutos.
Clona el repositorio https://github.com/kvendingoldo/aws-letsencrypt-lambda en tu computadora portátil
Configure las credenciales de AWS CLI a través de la guía oficial .
Examine la sección de variables de entorno y establezca la cantidad mínima de variables necesarias. Dado que LetsEncrypt limitará la cantidad de reintentos por hora para ACME_URL="prod"
, recomiendo usar ACME_URL="stage"
para realizar pruebas. Ejemplo de variables de entorno:
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"
Ejecute lambda localmente mediante el siguiente comando:
go run main.go
Tras la ejecución exitosa de Lambda, aparecerá el siguiente registro.
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
Eso es. A partir de ahora, AWS puede utilizar el certificado emitido en cualquier servicio de ARN o en otras ubicaciones donde sea físicamente necesario, obteniéndolo de AWS Secrets Manager.
Llevo casi cuatro años utilizando la función Lambda en producción. A lo largo de los años, varios aspectos de la implementación inicial han cambiado:
Anteriormente, AWS prohibía el uso de registros que no fueran ECR como fuentes Lambda. No ha cambiado, sin embargo, AWS agregó un proxy ECR para GitHub, DockerHub y algunos registros adicionales. Sin esta funcionalidad, tuvimos que enviar manualmente imágenes Lambda a nuestra ECR personal y reemplazar la URL de la imagen en el código Terraform. Ahora el código OpenTofu lo hace automáticamente a través de ECR Proxy.
Al principio, consideré introducir varios desafíos como http-01
o tls-alpn-01
, pero nadie me preguntó al respecto durante cuatro años. Todavía está presente en los problemas de GitHub y, si se requiere esta capacidad, podemos trabajar juntos para crearla.
No quería utilizar certificados LetsEncrypt en instancias EC2 puras cuando el proyecto comenzó originalmente, pero hoy en día es una práctica estándar. Como dije anteriormente, en determinadas situaciones, se puede recuperar un certificado de AWS Secrets Managed mediante la CLI de AWS.
He escrito mucho código Go nuevo a lo largo de los años, por lo que puedo decir que el código Lambda original en mi repositorio no es tan sofisticado como podría ser. Hay una diferencia significativa entre este y mi proyecto Go más reciente, tenv (OpenTofu, Terraform, Terragrunt y Atmos version manager, escrito en Go), pero en cualquier caso, el código todavía es compatible en general, por lo que no fue posible realizar modificaciones. No será demasiado problemático. De vez en cuando, realizaré una refactorización importante para hacer que el código sea más elegante.
La misma Lambda se utiliza desde hace años en varios proyectos diferentes. Además, soy cofundador de la plataforma DevOps cloudexpress.app , donde nuestro equipo administra certificados TLS para todos nuestros clientes utilizando AWS LetsEncrypt Lambda para simplificar los procesos de automatización.
Ahora hablemos de números. Durante un período de 4 años , este proyecto ha ayudado a muchas personas y se ha utilizado en numerosos proyectos OpenSource y más de 30 comerciales . Lambda emite más de 2000 certificados y no quiere detenerse ahí.
AWS LetsEncrypt Lambda es una solución adecuada para usted, si
Si descubrió que al menos uno de estos puntos se aplica a su situación, puede utilizar AWS Lambda. Además, si desea participar en el desarrollo, siempre estoy abierto a nuevos problemas y solicitudes de extracción en GitHub. URL del proyecto: https://github.com/kvendingoldo/aws-letsencrypt-lambda .