De nos jours, il est difficile d’imaginer des systèmes dotés de points de terminaison d’API publics sans protection par certificat TLS. Il existe plusieurs manières de délivrer des certificats : Certificats génériques payants pouvant être achetés auprès de n'importe quel grand fournisseur TLS Certificats racines payants qui signent tous les certificats en aval émis par les systèmes PKI d'entreprise Certificats gratuits émis par des fournisseurs TLS comme LetsEncrypt ou AWS Certificate Manager Certificats auto-signés, émis par ou un autre outil OpenSSL Dans le cadre de cet article, je discuterai principalement des certificats gratuits qui peuvent être utilisés au sein d'AWS, mais pas uniquement par les services AWS. De toute évidence, utiliser autre chose qu'AWS n'a aucun sens si vous utilisez exclusivement des services AWS gérés et n'avez pas d'exigences de sécurité strictes. AWS Certificate Manager offre une méthode très pratique et rapide pour émettre des certificats via des défis DNS ou HTTP ; cependant, vous êtes confronté à des limitations AWS de base si vous devez utiliser ces certificats en dehors des services AWS (API Gateway, ALB, NLB, etc.), comme une instance EC2 exécutant Nginx qui nécessite un fichier de certificat physique. De plus, même si vous le demandez, AWS Certificate Manager n'affiche pas le contenu du certificat. Certificate Manager À ce stade, c'est le bon moment pour vous rappeler , un outil plus largement utilisé que Certificate Manager, du moins parce qu'il ne dépend pas du cloud. Malheureusement, aucune technique intégrée d'émission de certificat LetsEncrypt n'est disponible dans AWS. Il est possible d'utiliser l'outil certbot pour vos services EC2 ou ECS, mais dans ce scénario, vous devrez réfléchir à la manière de configurer le processus de renouvellement. Je ne veux pas non plus combiner différentes stratégies car je pense qu'il est préférable d'avoir une seule procédure pour tout car cela réduit la complexité de l'ensemble du système. LetsEncrypt En tenant compte de cela, j'ai créé une fonction Lambda qui émet et renouvelle automatiquement les certificats LetsEncrypt sans nécessiter de configuration complexe. Le certificat peut être utilisé sur n'importe quel service AWS utilisant l'ARN avec les certificats AWS Certificate Manager après l'émission initiale du certificat. De plus, vous pouvez utiliser une version de certificat physique conservée dans quel que soit l'emplacement de votre choix, qu'il s'agisse d'une instance EC2 exécutant Nginx ou d'un autre endroit. AWS Secrets Manager, Comment fonctionne AWS LetsEncrypt Lambda Dans cet article, je suppose que votre zone DNS est gérée par AWS Route53. La fonction Lambda décrite dans cet article est écrite sur Go v1.22. Toutes les ressources de résultat telles que les enregistrements DNS, les secrets ou les certificats sont contrôlées par le rôle Amazon IAM, créé par défaut via le code Terraform. La séquence des actions Lambda est la suivante : Obtenez un événement contenant une liste de certificats. En règle générale, cet événement peut être le résultat d'une exécution manuelle ou d'une exécution par cron effectuée via . Exemple d'événement : aws_cloudwatch_event_target { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "alexander.sharov@cloudexpress.app", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true } Vérifiez si le certificat existe dans AWS Certificate Manager. Si oui, confirmez la date d'expiration. Démarrez le défi LetsEncrypt si le nombre de jours jusqu'à la date d'expiration est inférieur au . Cette étape implique que Lambda crée un enregistrement correspondant au nom de domaine à la zone AWS Route53 et attend que votre certificat soit prêt. DNS-01 reImportThreshold TXT Lambda met à jour le certificat dans AWS Certificate Manager lorsqu'il est prêt. Lambda stockera les fichiers de certificat dans AWS Secrets Manager si est vrai. storeCertInSecretsManager Détails de la mise en œuvre de Lambda Le code Le Lambda est écrit sur Go 1.22. Utiliser le moins de bibliothèques possible m'a aidé à maintenir mon objectif de garder le code au sec. La liste complète des bibliothèques go requises : URL Description github.com/aws/aws-lambda-go Bibliothèques, exemples et outils pour aider les développeurs Go à développer des fonctions AWS Lambda. github.com/aws/aws-sdk-go-v2 Kit SDK AWS pour le langage de programmation Go. github.com/go-acme/lego Client et bibliothèque LetsEncrypt / ACME. github.com/guregu/null Gestion raisonnable des valeurs nullables. github.com/sirupsen/logrus Journalisation structurée et enfichable pour Go. Image Docker J'ai utilisé comme image Docker de base. Pour les applications Go qui ne nécessitent pas de libc, cette image est parfaite. Il n’est pas complètement et comprend les éléments suivants : gcr.io/distroless/static:nonroot scratch Certificats CA : pas besoin de les copier à partir d’une autre étape. /etc/passwd : contient les utilisateurs et les groupes tels que les non-root. Dossier /tmp. tzdata : au cas où vous souhaiteriez définir un fuseau horaire autre que UTC. Processus de construction Dans les grands projets logiciels, superviser le processus de construction peut se transformer en une tâche laborieuse et chronophage. Les Makefiles peuvent aider à automatiser et à rationaliser ce processus, garantissant que votre projet est construit de manière efficace et cohérente. Pour cette raison, je préfère utiliser Makefile pour tous mes projets Golang. Le fichier est 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 ./... Du côté CICD, j'ai utilisé la configuration typique pour l'application Go en tant qu'intégration continue. Actions GitHub comme registre Docker. Par rapport à DockerHub, celui-ci offre deux fonctionnalités clés qui font que je préfère l'utiliser : ghcr.io Les workflows de construction, de test et de déploiement peuvent être plus facilement automatisés directement à partir du référentiel GitHub grâce à l'intégration fluide de GHCR avec GitHub Actions. Cela peut augmenter la productivité et simplifier le processus de développement. GHCR exploite le modèle d'autorisation de GitHub, permettant aux utilisateurs de gérer l'accès aux images de conteneurs en utilisant les mêmes équipes et autorisations qu'ils utilisent pour leurs référentiels de code. Cela simplifie la gestion des utilisateurs et améliore la sécurité. : mon plugin GitHub Actions pour le versioning automatique. C'est une action GitHub qui génère des balises compatibles pour les validations du référentiel. L'action peut gérer les versions, générer des versions GitHub et contrôler les branches de version à l'intérieur du référentiel. Cela fonctionne à merveille avec les dépôts simples et monorepos. kvendingoldo/semver-action SemVer Génération automatique du journal des modifications. J'apprécie les journaux de modifications ! Dans le contexte d'autres projets OpenSource que je gère (par exemple, , , etc.), mon équipe a reconnu l'importance d'informer les utilisateurs sur changements. https://github.com/tofuutils/tenv https://github.com/tofuutils/tofuenv . De mon point de vue, tout le code doit être examiné par un analyseur de code statique. SonarQube ne peut pas être configuré pour tous les projets, cependant golangci, à mon avis, est suffisant pour les petits et moyens projets Go. golangci-lint En plus de la vérification du code, c'est une bonne idée de vérifier la grammaire, surtout si vous disposez d'une grande quantité de documentation. . codespell.yml Comment déployer Lambda sur AWS via Terraform/OpenTofu Le code discuté dans cette page est le même pour Terraform et OpenTofu, mais à partir de Terraform v1.6, Hashicorp a modifié la licence Terraform en Business Source License (BSL) v1.1. Si vous développez quelque chose de commercial sur Terraform, passez à OpenTofu dès que possible. Si vous devez gérer plusieurs versions d'OpenTofu ou Terraform , utilisez le - Gestionnaire de versions OpenTofu, Terraform, Terragrunt et Atmos, écrit en Go. tenv D'autres exemples Terraform/OpenTofu peuvent être trouvés dans le dossier examples . du référentiel Git Pour travailler avec AWS LetsEncrypt Lambda via OpenTofu, vous devez suivre les étapes suivantes : Ajoutez un module à votre code 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 } Spécifier les 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 } ] } Faites attention aux variables et . Par défaut, AWS Lambda ne peut pas extraire d'images de sources autres qu'AWS ECR. Heureusement, l'équipe AWS a créé le cache ECR Proxy, qui peut récupérer des images à partir de registres accessibles au public tels que DockerHub ou GHCR et les stocker dans ECR. Malgré cette possibilité, AWS n'autorise pas l'extraction d'images sans jeton, même à partir de référentiels publics ouverts. Vous devez donc acquérir un jeton GitHub personnel pour accéder aux images Docker prédéfinies. Alternativement, vous pouvez extraire mon référentiel GitHub, créer l'image localement, puis la télécharger sur votre référentiel ECR préexistant. Dans ce scénario, l'exemple peut être modifié comme suit : 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>" } Lorsque vous avez terminé de modifier le code, exécutez la commande suivante pour installer OpenTofu by OpenTofu version switcher : tenv $ tenv tofu install Et enfin, exécutez les commandes suivantes pour appliquer le code produit : $ tofu init $ tofu plan $ tofu apply Attendez que le code soit déployé sur AWS et que les événements soient déclenchés. Après quelques minutes, vous verrez les certificats prêts dans le gestionnaire de certificats. Exemple: À partir de maintenant, AWS peut utiliser le certificat émis sur n'importe quel service par ARN. Si vous devez utiliser le certificat en dehors des services AWS ou avoir accès à son contenu, définissez l'option d'événement sur . Dans cette situation, lorsque Lambda termine l'exécution de base, le certificat sera enregistré dans AWS Secrets Manager. Il offre aux utilisateurs plus de flexibilité : ils peuvent inspecter le contenu du certificat, travailler avec celui-ci directement depuis EC2, etc. Pour en savoir plus sur AWS Secrets Manager, lisez le guide officiel. storeCertInSecretsManager true Variables d'environnement Nom Description Valeurs possibles Valeur par défaut Exemple Requis FORMATTER_TYPE Type de formateur pour les journaux JSON | TEXTE TEXTE JSON ❌ MODE Mode d'application. Définissez le mode pour l'exécution AWS et le mode pour les tests locaux. cloud local nuage | locale nuage nuage ✅ LOG_LEVEL Niveau de journalisation panique | fatale | erreur | avertir | info | debug | trace avertir avertir ❌ AWS_REGION Région AWS par défaut. Après le déploiement sur AWS, les paramètres sont automatiquement définis. <toute région AWS valide> - nous-est-1 ✅ DOMAIN_NAME Nom de domaine pour lequel le certificat est délivré ou renouvelé tout nom de domaine valide - hackernoon. référents.moi ✅ ACME_URL L'URL de production LetsEncrypt sera utilisée si elle est définie sur ; sinon, l'URL de l'étape sera utilisée. prod production | scène prod prod ✅ ACME_EMAIL Adresse email liée au certificat LetsEncrypt tout email valide alexander.sharov@cloudexpress.app alexander.sharov@cloudexpress.app ✅ REIMPORT_THRESHOLD Le certificat sera renouvelé si sa durée de vie (TTL) est égale . REIMPORT_THRESHOLD tout entier > 0 dix dix ✅ STORE_CERT_IN_SECRETSMANAGER Si , Lambda conservera le certificat dans Certificate Manager et Secrets Manager. true « vrai » | "FAUX" "FAUX" "FAUX" ❌ Comment vérifier les journaux LetsEncrypt Lambda Dans le cadre de votre travail avec vous souhaiterez peut-être occasionnellement consulter les journaux. C'est assez simple à réaliser : aws-letsencrypt-lambda, Accédez à AWS Cloudwatch, cliquez sur « Groupes de journaux » Recherchez le groupe de journaux portant le nom que vous avez spécifié dans le code OpenTofu. Par exemple, dans mon cas, c'est /aws/lambda/kvendingoldo-letsencrypt-lambda Accédez au groupe, sélectionnez le flux souhaité dans la liste et consultez les journaux. Comment déclencher Lambda manuellement via l'interface utilisateur AWS Accédez à la fonction Lambda créée via OpenTofu. Cliquez sur le bouton « Tests ». Remplissez et cliquez sur Test Event Test { "domainName": "<YOUR_VALID_DOMAIN>", "acmeUrl": <stage | prod>, "acmeEmail": "<ANY_VALID_EMAIL>", "reImportThreshold": 10, "issueType": "<default | force>", "storeCertInSecretsManager" : <true | false> } Exemple 1: { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "alexander.sharov@cloudexpress.app", "reImportThreshold": 10, "issueType": "default" } Attendez que l'exécution soit terminée. Vous pouvez le journal d'exécution est disponible dans Cloudwatch. Habituellement, le problème initial prend environ 5 minutes. Comment tester Lambda localement Clonez le référentiel sur votre ordinateur portable https://github.com/kvendingoldo/aws-letsencrypt-lambda Configurez les informations d'identification AWS Cli via le . guide officiel Examinez la section des variables d'environnement et définissez le nombre minimum de variables nécessaires. Étant donné que LetsEncrypt limitera le nombre de tentatives par heure pour , je conseille d'utiliser pour les tests. Exemple de variables d'environnement : 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" Exécutez le lambda localement via la commande suivante : go run main.go Une fois l'exécution réussie de Lambda, le journal suivant apparaîtra. 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 C'est ça. À partir de maintenant, AWS peut utiliser le certificat émis sur n'importe quel service par ARN ou dans d'autres emplacements où cela est physiquement nécessaire en l'obtenant auprès d'AWS Secrets Manager. Expérience pratique de son utilisation sur plus de 4 ans chez AWS J'utilise la fonction Lambda en production depuis près de quatre ans. Au fil des années, divers aspects de la mise en œuvre initiale ont changé : Auparavant, AWS interdisait l'utilisation de registres non ECR comme sources Lambda. Cela n'a pas changé, mais AWS a ajouté un proxy ECR pour GitHub, DockerHub et quelques registres supplémentaires. Sans cette fonctionnalité, nous devions transférer manuellement les images Lambda vers notre ECR personnel et remplacer l'URL de l'image dans le code Terraform. Désormais, le code OpenTofu le fait automatiquement via le proxy ECR. Au début, j'avais envisagé d'introduire divers challenges comme ou , mais personne ne m'a posé de questions à ce sujet pendant quatre ans. , et si cette fonctionnalité est requise, nous pouvons travailler ensemble pour la créer. http-01 tls-alpn-01 Elle est toujours présente sur les tickets GitHub Je ne voulais pas utiliser les certificats LetsEncrypt sur des instances EC2 pures au début du projet, mais de nos jours, c'est une pratique courante. Comme je l'ai indiqué précédemment, dans certaines situations, un certificat peut être récupéré à partir d'AWS Secrets Managed à l'aide de l'AWS cli. J'ai écrit beaucoup de nouveau code Go au fil des ans, je peux donc dire que le code Lambda original dans mon référentiel n'est pas aussi sophistiqué qu'il pourrait l'être. Il y a une différence significative entre celui-ci et mon projet Go le plus récent, (gestionnaire de versions OpenTofu, Terraform, Terragrunt et Atmos, écrit en Go), mais dans tous les cas, le code est toujours généralement pris en charge, donc y apporter des modifications est gagné. ça ne sera pas trop problématique. Parfois, j'entreprendrai une refactorisation importante pour rendre le code plus élégant. tenv Le même Lambda est utilisé depuis des années dans plusieurs projets différents. De plus, je suis co-fondateur de la plateforme DevOps , où notre équipe gère les certificats TLS pour tous nos clients à l'aide d' pour simplifier les processus d'automatisation. cloudexpress.app AWS LetsEncrypt Lambda Parlons maintenant des chiffres. Sur une période de , ce projet a aidé de nombreuses personnes et a été utilisé dans de nombreux projets OpenSource et . La Lambda délivre plus de certificats et ne veut pas s'arrêter là. 4 ans plus de 30 projets commerciaux 2000 Conclusion est une solution adaptée pour vous, si AWS LetsEncrypt Lambda Vous devez disposer d'une version physique du certificat et l'utiliserez à partir de services natifs non AWS tels que EC2 Nginx. Vous ne souhaitez pas compter sur AWS Certificate Manager pour gérer le processus d'émission et de renouvellement des certificats TLS (vérifier les journaux, définir les dates de renouvellement, etc.). Vous souhaitez recevoir des notifications par e-mail de LetsEncrypt lorsque votre certificat expire ou sera bientôt expiré. Vous souhaitez personnaliser la solution en modifiant le code Golang (par exemple, modifier le challenge LetsEncrypt, stocker le certificat dans Hashicorp Vault, etc.). Si vous découvrez qu'au moins un de ces points s'applique à votre situation, vous pouvez utiliser AWS Lambda. De plus, si vous souhaitez participer au développement, je suis toujours ouvert aux nouveaux problèmes et aux Pull Requests sur GitHub. URL du projet : . https://github.com/kvendingoldo/aws-letsencrypt-lambda