Hoje em dia, é um desafio imaginar sistemas que tenham endpoints de API públicos sem proteção de certificado TLS. Existem várias maneiras de emitir certificados: Certificados curinga pagos que podem ser comprados de qualquer grande provedor de TLS Certificados raiz pagos que assinam todos os certificados downstream emitidos por sistemas PKI corporativos Certificados gratuitos emitidos por provedores de TLS como LetsEncrypt ou AWS Certificate Manager Certificados autoassinados, emitidos por ou outra ferramenta OpenSSL No contexto desta postagem, discutirei principalmente certificados gratuitos que podem ser usados dentro da AWS, mas não apenas pelos serviços da AWS. Claramente, usar qualquer coisa diferente não faz sentido se você usa exclusivamente serviços gerenciados da AWS e não tem requisitos rígidos de segurança. O AWS Certificate Manager oferece um método muito conveniente e rápido de emissão de certificados por meio de desafios DNS ou HTTP; no entanto, você enfrentará limitações básicas da AWS se precisar usar esses certificados fora dos serviços da AWS (API Gateway, ALB, NLB, etc.), como uma instância EC2 executando Nginx que precisa de um arquivo de certificado físico. Além disso, mesmo que você solicite, o AWS Certificate Manager não exibe o conteúdo do certificado. do AWS Certificate Manager Neste ponto, é um bom momento para lembrá-lo do , uma ferramenta mais usada que o Certificate Manager – pelo menos porque não depende da nuvem. Infelizmente, não existem técnicas integradas de emissão de certificado LetsEncrypt disponíveis na AWS. É possível utilizar a ferramenta certbot para seus serviços EC2 ou ECS, mas nesse cenário, você precisará considerar como configurar o processo de renovação. Também não quero combinar estratégias diferentes, pois acho melhor ter um procedimento único para tudo, pois reduz toda a complexidade do sistema. LetsEncrypt Levando isso em consideração, criei uma função Lambda que emite e renova automaticamente certificados LetsEncrypt sem exigir configurações complexas. O certificado pode ser utilizado em qualquer serviço AWS usando ARN juntamente com certificados AWS Certificate Manager após a emissão inicial do certificado. Além disso, você pode usar uma versão de certificado físico que é mantida no em qualquer local escolhido, seja uma instância EC2 executando Nginx ou outro local. AWS Secrets Manager Como funciona o AWS LetsEncrypt Lambda Neste artigo, presumirei que sua zona DNS é gerenciada pelo AWS Route53. A função Lambda descrita neste artigo foi escrita em Go v1.22. Todos os recursos de resultado, como registros DNS, segredos ou certificados, são controlados pela função do Amazon IAM, que é criada por meio do código Terraform por padrão. A sequência de ações do Lambda é a seguinte: Obtenha um evento contendo uma lista de certificados. Normalmente, esse evento pode ser resultado de execução manual ou execução por cron feita por meio de . Exemplo de evento: aws_cloudwatch_event_target { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "alexander.sharov@cloudexpress.app", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true } Verifique se o certificado existe no AWS Certificate Manager. Se sim, confirme a data de validade. Inicie o desafio LetsEncrypt se o número de dias até a data de expiração for menor que . Esta etapa envolve o Lambda criando um registro que corresponde ao nome de domínio à zona AWS Route53 e aguardando que seu certificado esteja pronto. DNS-01 reImportThreshold TXT O Lambda atualiza o certificado no AWS Certificate Manager quando estiver pronto. O Lambda armazenará arquivos de certificado dentro do AWS Secrets Manager se for verdadeiro. storeCertInSecretsManager Detalhes de implementação do Lambda O código O Lambda está escrito em Go 1.22. Usar o mínimo de bibliotecas possível me ajudou a manter meu objetivo de manter o código seco. A lista completa de bibliotecas go necessárias: URL Descrição github.com/aws/aws-lambda-go Bibliotecas, exemplos e ferramentas para ajudar os desenvolvedores Go a desenvolver funções do AWS Lambda. github.com/aws/aws-sdk-go-v2 AWS SDK para a linguagem de programação Go. github.com/go-acme/lego Cliente e biblioteca LetsEncrypt / ACME. github.com/guregu/null Tratamento razoável de valores anuláveis. github.com/sirupsen/logrus Registro estruturado e conectável para Go. Imagem do Docker Usei como uma imagem básica do docker. Para aplicativos Go que não requerem libc, esta imagem é perfeita. Não está completamente vazio como e inclui o seguinte: gcr.io/distroless/static:nonroot scratch Certificados CA: não há necessidade de copiá-los de qualquer outro estágio. /etc/passwd: contém usuários e grupos como não-root. /tmp pasta. tzdata: caso você queira definir um fuso horário diferente do UTC. Processo de construção Em grandes projetos de software, supervisionar o processo de construção pode se tornar uma tarefa trabalhosa e demorada. Makefiles podem ajudar a automatizar e agilizar esse processo, garantindo que seu projeto seja construído de forma eficiente e consistente. Por esse motivo, prefiro usar Makefile para todos os meus projetos Golang. O arquivo é simples: ##@ 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 ./... Do lado do CICD, usei a configuração típica do aplicativo Go como integração contínua. Ações do GitHub como registro docker. Comparado ao DockerHub, este oferece dois recursos principais que o tornam minha preferência de uso: ghcr.io Os fluxos de trabalho de construção, teste e implantação podem ser automatizados mais facilmente diretamente do repositório GitHub, graças à integração suave do GHCR com GitHub Actions. Isso pode aumentar a produtividade e simplificar o processo de desenvolvimento. O GHCR aproveita o modelo de permissão do GitHub, permitindo que os usuários gerenciem o acesso às imagens de contêiner usando as mesmas equipes e permissões que usam para seus repositórios de código. Isso simplifica o gerenciamento de usuários e aumenta a segurança. : meu plugin GitHub Actions para versionamento automático. É uma ação do GitHub que gera tags compatíveis com para commits de repositório. A ação pode gerenciar versões, gerar lançamentos GitHub e controlar ramificações de lançamento dentro do repositório. Funciona maravilhosamente bem com single e monorepos. kvendingoldo/semver-action SemVer Geração automática de changelog. Eu gosto de changelogs! No contexto de outros projetos OpenSource que gerencio (por exemplo, , , etc.), minha equipe reconheceu a importância de informar os usuários sobre mudanças. https://github.com/tofuutils/tenv https://github.com/tofuutils/tofuenv . Da minha perspectiva, todo código deve ser revisado por um analisador de código estático. O SonarQube não pode ser configurado para todos os projetos, no entanto, o golangci, na minha opinião, é suficiente para projetos Go de pequeno a médio porte. golangci-lint Além da verificação do código, é uma boa ideia verificar a gramática, principalmente se você tiver uma grande quantidade de documentação. . códigospell.yml Como implantar Lambda na AWS via Terraform/OpenTofu O código discutido nesta página é o mesmo para Terraform e OpenTofu, mas a partir do Terraform v1.6, a Hashicorp modificou a licença do Terraform para Business Source License (BSL) v1.1. Terraform, mude para OpenTofu o mais rápido possível. Se você precisar gerenciar várias versões do OpenTofu ou Terraform, use o - Gerenciador de versões OpenTofu, Terraform, Terragrunt e Atmos, escrito em Go. dezv Mais exemplos do Terraform/OpenTofu podem ser encontrados na pasta de exemplos do . repositório Git Para trabalhar com AWS LetsEncrypt Lambda via OpenTofu você precisa seguir as seguintes etapas: Adicione o módulo ao seu 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 } Especifique variáveis 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 atenção às variáveis e . Por padrão, o AWS Lambda não pode extrair imagens de fontes diferentes do AWS ECR. Felizmente, a equipe da AWS criou o cache ECR Proxy, que pode buscar imagens de registros disponíveis publicamente, como DockerHub ou GHCR, e armazená-las dentro do ECR. Apesar dessa possibilidade, a AWS não permite que imagens sejam extraídas sem token, mesmo de repositórios públicos abertos, portanto, você deve adquirir um token GitHub pessoal para obter acesso a imagens Docker pré-construídas. Alternativamente, você pode extrair meu repositório GitHub, construir a imagem localmente e depois carregá-la em seu repositório ECR pré-existente. Neste cenário, o exemplo pode ser modificado da seguinte forma: 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>" } Ao concluir a alteração do código, execute o seguinte comando para instalar o OpenTofu pelo OpenTofu version switcher : tenv $ tenv tofu install E por fim, execute os seguintes comandos para aplicar o código produzido: $ tofu init $ tofu plan $ tofu apply Aguarde até que o código seja implantado na AWS e os eventos sejam acionados. Após alguns minutos, você verá certificados prontos dentro do gerenciador de certificados. Exemplo: A partir de agora, a AWS pode usar o certificado emitido em qualquer serviço da ARN. Se você precisar usar o certificado fora dos serviços da AWS ou tiver acesso ao seu conteúdo, defina a opção de evento como . Nessa situação, quando o Lambda concluir a execução básica, o certificado será salvo no AWS Secrets Manager. Dá aos usuários mais flexibilidade: eles podem inspecionar o conteúdo do certificado, trabalhar com ele diretamente do EC2, etc. Para saber mais sobre o AWS Secrets Manager, leia o guia oficial. storeCertInSecretsManager true Variáveis ambientais Nome Descrição Valores possíveis Valor padrão Exemplo Obrigatório FORMATTER_TYPE Tipo de formatador para logs JSON | TEXTO TEXTO JSON ❌ MODE Modo de aplicação. Defina o modo para execução da AWS e o modo para testes locais. cloud local nuvem | local nuvem nuvem ✅ LOG_LEVEL Nível de registro pânico | fatal | erro | avisar | informações | depurar | rastreamento avisar avisar ❌ AWS_REGION Região padrão da AWS. Após a implantação na AWS, ele é configurado automaticamente. <qualquer região válida da AWS> - nós-leste-1 ✅ DOMAIN_NAME Nome de domínio para o qual o certificado está sendo emitido ou renovado qualquer nome de domínio válido - meio-dia. refere.me ✅ ACME_URL A URL LetsEncrypt de produção será utilizada se estiver definida como ; caso contrário, o URL do estágio será usado. prod produzir | estágio cutucar cutucar ✅ ACME_EMAIL Endereço de e-mail vinculado ao certificado LetsEncrypt qualquer e-mail válido alexander.sharov@cloudexpress.app alexander.sharov@cloudexpress.app ✅ REIMPORT_THRESHOLD O certificado será renovado se seu tempo de vida (TTL) for igual . REIMPORT_THRESHOLD qualquer int > 0 10 10 ✅ STORE_CERT_IN_SECRETSMANAGER Se , o Lambda manterá o certificado no Certificate Manager e no Secrets Manager. true “verdadeiro” | "falso" "falso" "falso" ❌ Como verificar os logs do LetsEncrypt Lambda No escopo do trabalho com ocasionalmente você pode querer revisar os logs. É muito fácil de realizar: aws-letsencrypt-lambda, Vá para AWS Cloudwatch, clique em “Grupos de log” Encontre o grupo de logs com o nome especificado no código OpenTofu. Por exemplo, no meu caso é /aws/lambda/kvendingoldo-letsencrypt-lambda Vá para o grupo, selecione o stream desejado na lista e revise os logs. Como acionar o Lambda manualmente por meio da UI da AWS Vá para a função Lambda que foi criada via OpenTofu. Clique no botão “Testes”. Preencha e clique em Test Event Test { "domainName": "<YOUR_VALID_DOMAIN>", "acmeUrl": <stage | prod>, "acmeEmail": "<ANY_VALID_EMAIL>", "reImportThreshold": 10, "issueType": "<default | force>", "storeCertInSecretsManager" : <true | false> } Exemplo 1: { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "alexander.sharov@cloudexpress.app", "reImportThreshold": 10, "issueType": "default" } Aguarde até que a execução seja concluída. Você pode ver o log de execução disponível no Cloudwatch. Normalmente, o problema inicial leva cerca de 5 minutos. Como testar o Lambda localmente Clone o repositório para o seu laptop https://github.com/kvendingoldo/aws-letsencrypt-lambda Configure as credenciais do AWS Cli por meio do . guia oficial Examine a seção de variáveis de ambiente e defina o número mínimo de variáveis necessárias. Como LetsEncrypt limitará a quantidade de novas tentativas por hora para , aconselho usar para teste. Exemplo de variáveis de ambiente: 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" Execute o lambda localmente por meio do seguinte comando: go run main.go Após a execução bem-sucedida do Lambda, o log a seguir aparecerá. 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 É isso. A partir de agora, a AWS pode usar o certificado emitido em qualquer serviço da ARN ou em outros locais onde seja fisicamente necessário, obtendo-o no AWS Secrets Manager. Experiência prática de uso ao longo de mais de 4 anos na AWS Uso a função Lambda em produção há quase quatro anos. Ao longo dos anos, vários aspectos da implementação inicial mudaram: Anteriormente, a AWS proibia o uso de quaisquer registros não ECR como fontes Lambda. Isso não mudou, no entanto, a AWS adicionou proxy ECR para GitHub, DockerHub e alguns registros adicionais. Sem essa funcionalidade, tivemos que enviar manualmente as imagens Lambda para nosso ECR pessoal e substituir o URL da imagem no código Terraform. Agora o código OpenTofu faz isso automaticamente via ECR Proxy. No início, pensei em introduzir vários desafios, como ou , mas ninguém me questionou sobre isso durante quatro anos. e, se esse recurso for necessário, podemos trabalhar juntos para criá-lo. http-01 tls-alpn-01 Ele ainda está presente nos problemas do GitHub Eu não queria utilizar certificados LetsEncrypt em instâncias EC2 puras quando o projeto foi iniciado originalmente, mas hoje em dia é uma prática padrão. Como afirmei anteriormente, em certas situações, um certificado pode ser recuperado do AWS Secrets Managed usando o AWS cli. Escrevi muitos códigos Go novos ao longo dos anos, então posso dizer que o código Lambda original em meu repositório não é tão sofisticado quanto poderia ser. Há uma diferença significativa entre ele e meu projeto Go mais recente, (gerenciador de versões OpenTofu, Terraform, Terragrunt e Atmos, escrito em Go), mas em qualquer caso, o código ainda é geralmente suportado, portanto, fazer modificações nele ganhou não seja muito problemático. Ocasionalmente, realizarei refatorações significativas para tornar o código mais elegante. tenv O mesmo Lambda está sendo usado há anos em diversos projetos diferentes. Além disso, sou cofundador da plataforma DevOps , onde nossa equipe gerencia certificados TLS para todos os nossos clientes usando o para simplificar os processos de automação. cloudexpress.app AWS LetsEncrypt Lambda Agora vamos falar sobre números. Durante um período de , este projeto ajudou muitas pessoas e foi usado em vários projetos OpenSource e . O Lambda emite mais de certificados e não quer parar por aí. 4 anos em mais de 30 projetos comerciais 2.000 Conclusão é uma solução adequada para você, se AWS LetsEncrypt Lambda Você deve ter uma versão física do certificado e usá-lo em serviços nativos não AWS, como EC2 Nginx. Você não deseja depender do AWS Certificate Manager para gerenciar o processo de emissão e renovação de certificados TLS (verificar logs, definir datas de renovação, etc.). Você gostaria de receber notificações por e-mail do LetsEncrypt quando seu certificado expirar ou expirar em breve. Você deseja personalizar a solução alterando o código Golang (por exemplo, alterando o desafio LetsEncrypt, armazenando o certificado no Hashicorp Vault, etc.). Se você descobriu que pelo menos um desses pontos se aplica à sua situação, você pode usar o AWS Lambda. Além disso, se você deseja participar do desenvolvimento, estou sempre aberto a novos problemas e solicitações pull no GitHub. URL do projeto: . https://github.com/kvendingoldo/aws-letsencrypt-lambda