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 :
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 Certificate Manager 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.
À ce stade, c'est le bon moment pour vous rappeler LetsEncrypt , 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.
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 AWS Secrets Manager, quel que soit l'emplacement de votre choix, qu'il s'agisse d'une instance EC2 exécutant Nginx ou d'un autre endroit.
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 :
aws_cloudwatch_event_target
. Exemple d'événement : { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true }
DNS-01
si le nombre de jours jusqu'à la date d'expiration est inférieur au reImportThreshold
. Cette étape implique que Lambda crée un enregistrement TXT
correspondant au nom de domaine à la zone AWS Route53 et attend que votre certificat soit prêt.storeCertInSecretsManager
est vrai.
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 |
---|---|
Bibliothèques, exemples et outils pour aider les développeurs Go à développer des fonctions AWS Lambda. | |
Kit SDK AWS pour le langage de programmation Go. | |
Client et bibliothèque LetsEncrypt / ACME. | |
Gestion raisonnable des valeurs nullables. | |
Journalisation structurée et enfichable pour Go. |
Image Docker
J'ai utilisé gcr.io/distroless/static:nonroot 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 scratch
et comprend les éléments suivants :
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
Actions GitHub en tant qu'intégration continue.
ghcr.io comme registre Docker. Par rapport à DockerHub, celui-ci offre deux fonctionnalités clés qui font que je préfère l'utiliser :
kvendingoldo/semver-action : mon plugin GitHub Actions pour le versioning automatique. C'est une action GitHub qui génère des balises compatibles SemVer 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.
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, https://github.com/tofuutils/tenv , https://github.com/tofuutils/tofuenv , etc.), mon équipe a reconnu l'importance d'informer les utilisateurs sur changements.
golangci-lint . 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.
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
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 = "[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 }
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" : "[email protected]", "reImportThreshold" : 100, "issueType" : "default", "storeCertInSecretsManager" : false } ] }
Faites attention aux variables ecr_proxy_username
et ecr_proxy_access_token
. 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 :
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 storeCertInSecretsManager
sur true
. 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.
Nom | Description | Valeurs possibles | Valeur par défaut | Exemple | Requis |
---|---|---|---|---|---|
| Type de formateur pour les journaux | JSON | TEXTE | TEXTE | JSON | ❌ |
| Mode d'application. Définissez le mode | nuage | locale | nuage | nuage | ✅ |
| Niveau de journalisation | panique | fatale | erreur | avertir | info | debug | trace | avertir | avertir | ❌ |
| 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 | ✅ |
| Nom de domaine pour lequel le certificat est délivré ou renouvelé | tout nom de domaine valide | - | hackernoon. référents.moi | ✅ |
| L'URL de production LetsEncrypt sera utilisée si elle est définie sur | production | scène | prod | prod | ✅ |
| Adresse email liée au certificat LetsEncrypt | tout email valide | ✅ | ||
| Le certificat sera renouvelé si sa durée de vie (TTL) est égale | tout entier > 0 | dix | dix | ✅ |
| Si | « vrai » | "FAUX" | "FAUX" | "FAUX" | ❌ |
Dans le cadre de votre travail avec aws-letsencrypt-lambda, vous souhaiterez peut-être occasionnellement consulter les journaux. C'est assez simple à réaliser :
/aws/lambda/kvendingoldo-letsencrypt-lambda
Accédez à la fonction Lambda créée via OpenTofu. Cliquez sur le bouton « Tests ».
Remplissez Test Event
et cliquez sur 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": "[email protected]", "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.
Clonez le référentiel https://github.com/kvendingoldo/aws-letsencrypt-lambda sur votre ordinateur portable
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 ACME_URL="prod"
, je conseille d'utiliser ACME_URL="stage"
pour les tests. Exemple de variables d'environnement :
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"
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 [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
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.
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 http-01
ou tls-alpn-01
, mais personne ne m'a posé de questions à ce sujet pendant quatre ans. Elle est toujours présente sur les tickets GitHub , et si cette fonctionnalité est requise, nous pouvons travailler ensemble pour la créer.
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, tenv (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.
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 cloudexpress.app , où notre équipe gère les certificats TLS pour tous nos clients à l'aide d' AWS LetsEncrypt Lambda pour simplifier les processus d'automatisation.
Parlons maintenant des chiffres. Sur une période de 4 ans , ce projet a aidé de nombreuses personnes et a été utilisé dans de nombreux projets OpenSource et plus de 30 projets commerciaux . La Lambda délivre plus de 2000 certificats et ne veut pas s'arrêter là.
AWS LetsEncrypt Lambda est une solution adaptée pour vous, si
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 .