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: Certificados comodín pagados que se pueden comprar en cualquier gran proveedor de TLS Certificados raíz pagados que firman todos los certificados posteriores emitidos por sistemas PKI corporativos. Certificados gratuitos emitidos por proveedores de TLS como LetsEncrypt o AWS Certificate Manager Certificados autofirmados, emitidos por u otra herramienta OpenSSL 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 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. AWS Certificate Manager Llegados a este punto es un buen momento para recordarte , 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. LetsEncrypt 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 en cualquier ubicación que elija, ya sea una instancia EC2 que ejecuta Nginx u otro lugar. AWS Secrets Manager ¿Cómo funciona AWS LetsEncrypt Lambda? 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: Obtenga un evento que contenga una lista de certificados. Normalmente, este evento puede ser el resultado de una ejecución manual o una ejecución mediante cron realizada a través de . Ejemplo de evento: aws_cloudwatch_event_target { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "alexander.sharov@cloudexpress.app", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true } Verifique si el certificado existe en AWS Certificate Manager. En caso afirmativo, confirme la fecha de vencimiento. Inicie el desafío LetsEncrypt si el número de días hasta la fecha de vencimiento es menor que . Este paso implica que Lambda cree un registro que haga coincidir el nombre de dominio con la zona de AWS Route53 y espere a que su certificado esté listo. DNS-01 reImportThreshold TXT Lambda actualiza el certificado en AWS Certificate Manager cuando esté listo. Lambda almacenará archivos de certificados dentro de AWS Secrets Manager si es verdadero. storeCertInSecretsManager Detalles de implementación de Lambda 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 github.com/aws/aws-lambda-go Bibliotecas, ejemplos y herramientas para ayudar a los desarrolladores de Go a desarrollar funciones de AWS Lambda. github.com/aws/aws-sdk-go-v2 AWS SDK para el lenguaje de programación Go. github.com/go-acme/lego Cliente y biblioteca LetsEncrypt / ACME. github.com/guregu/null Manejo razonable de valores que aceptan valores NULL. github.com/sirupsen/logrus Registro estructurado y conectable para Go. imagen acoplable Utilicé como imagen básica de Docker. Para aplicaciones Go que no requieren libc, esta imagen es perfecta. No está completamente e incluye lo siguiente: gcr.io/distroless/static:nonroot scratch Certificados CA: no es necesario copiarlos de ninguna otra etapa. /etc/passwd: contiene usuarios y grupos como nonroot. Carpeta /tmp. tzdata: en caso de que desee configurar una zona horaria distinta a UTC. 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. como integración continua. GitHub Actions como registro de Docker. En comparación con DockerHub, este ofrece dos características clave que hacen que prefiera su uso: ghcr.io Los flujos de trabajo de compilación, prueba e implementación se pueden automatizar más fácilmente directamente desde el repositorio de GitHub gracias a la integración fluida de GHCR con GitHub Actions. Esto puede aumentar la productividad y simplificar el proceso de desarrollo. GHCR aprovecha el modelo de permisos de GitHub, lo que permite a los usuarios administrar el acceso a las imágenes de contenedores utilizando los mismos equipos y permisos que usan para sus repositorios de código. Esto simplifica la gestión de usuarios y mejora la seguridad. : mi complemento de GitHub Actions para control de versiones automático. Es una acción de GitHub que genera etiquetas compatibles con 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. kvendingoldo/semver-action SemVer 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, , , etc.), mi equipo reconoció la importancia de informar a los usuarios sobre cambios. https://github.com/tofuutils/tenv https://github.com/tofuutils/tofuenv . 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. golangci-lint Además de verificar el código, es una buena idea verificar la gramática, especialmente si tiene una gran cantidad de documentación. . codespell.yml Cómo implementar Lambda en AWS a través de Terraform/OpenTofu 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 - Gestor de versiones de OpenTofu, Terraform, Terragrunt y Atmos, escrito en Go. tenv 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 = "git@github.com: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" : "alexander.sharov@cloudexpress.app", "reImportThreshold" : 100, "issueType" : "default", "storeCertInSecretsManager" : false } ] } Preste atención a las variables y . 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: ecr_proxy_username ecr_proxy_access_token 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 en . 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. storeCertInSecretsManager true Variables de entorno Nombre Descripción Valores posibles Valor por defecto Ejemplo Requerido FORMATTER_TYPE Tipo de formateador para registros JSON | TEXTO TEXTO JSON ❌ MODE Modo de aplicación. Configure el modo para la ejecución de AWS y el modo para las pruebas locales. cloud local nube | local nube nube ✅ LOG_LEVEL Nivel de registro pánico|fatal|error|advertencia|información|depuración|rastreo advertir advertir ❌ AWS_REGION 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 ✅ DOMAIN_NAME 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 ✅ ACME_URL La URL de producción de LetsEncrypt se utilizará si está configurada en ; de lo contrario, se utilizará la URL del escenario. prod pinchar | escenario pinchar pinchar ✅ ACME_EMAIL Dirección de correo electrónico vinculada al certificado LetsEncrypt cualquier correo electrónico válido alexander.sharov@cloudexpress.app alexander.sharov@cloudexpress.app ✅ REIMPORT_THRESHOLD El certificado se renovará si su tiempo de vida (TTL) es igual a . REIMPORT_THRESHOLD cualquier entero > 0 10 10 ✅ STORE_CERT_IN_SECRETSMANAGER Si es , Lambda mantendrá el certificado tanto en el Administrador de certificados como en el Administrador de secretos. true “verdadero” | "FALSO" "FALSO" "FALSO" ❌ Cómo comprobar los registros de LetsEncrypt Lambda En el ámbito del trabajo con es posible que desee revisar los registros ocasionalmente. Es bastante fácil de lograr: aws-letsencrypt-lambda, Vaya a AWS Cloudwatch, haga clic en "Grupos de registro" Busque el grupo de registros con el nombre que especificó en el código OpenTofu. Por ejemplo, en mi caso es /aws/lambda/kvendingoldo-letsencrypt-lambda Vaya al grupo, seleccione la transmisión deseada de la lista y revise los registros. Cómo activar Lambda manualmente a través de la interfaz de usuario de AWS Vaya a la función Lambda que se creó a través de OpenTofu. Haga clic en el botón "Pruebas". Complete y haga clic en Test Event 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": "alexander.sharov@cloudexpress.app", "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. Cómo probar Lambda localmente Clona el repositorio en tu computadora portátil https://github.com/kvendingoldo/aws-letsencrypt-lambda 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 , recomiendo usar para realizar pruebas. Ejemplo de variables de entorno: 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="alexander.sharov@cloudexpress.app" 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 alex.sharov@referrs.me 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. Experiencia práctica usándolo durante más de 4 años en AWS 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 o , pero nadie me preguntó al respecto durante cuatro años. y, si se requiere esta capacidad, podemos trabajar juntos para crearla. http-01 tls-alpn-01 Todavía está presente en los problemas de GitHub 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, (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. tenv La misma Lambda se utiliza desde hace años en varios proyectos diferentes. Además, soy cofundador de la plataforma DevOps , donde nuestro equipo administra certificados TLS para todos nuestros clientes utilizando para simplificar los procesos de automatización. cloudexpress.app AWS LetsEncrypt Lambda Ahora hablemos de números. Durante un período de , este proyecto ha ayudado a muchas personas y se ha utilizado en numerosos . Lambda emite más de certificados y no quiere detenerse ahí. 4 años proyectos OpenSource y más de 30 comerciales 2000 Conclusión es una solución adecuada para usted, si AWS LetsEncrypt Lambda Debe tener una versión física del certificado y la utilizará desde servicios nativos que no sean de AWS, como EC2 Nginx. No desea depender de AWS Certificate Manager para administrar el proceso de emisión y renovación del certificado TLS (verificar registros, establecer fechas de renovación, etc.). Le gustaría recibir notificaciones por correo electrónico de LetsEncrypt cuando su certificado caduque o caduque pronto. Desea personalizar la solución cambiando el código Golang (por ejemplo, cambiando el desafío LetsEncrypt, almacenando el certificado en Hashicorp Vault, etc.). 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