AWS App Mesh - Partie 1 : Terminaison TLS et chiffrement bout en bout
Comment utiliser App Mesh pour chiffrer le traffic entre le load balancer et les services
En Septembre 2024, AWS a annoncé la fin de vie de App Mesh et propose de migrer vers ECS Service Connect ou VPC Lattice.
J’avais initialement prévu de faire une deuxième partie de cet article, mais elle ne verra finalement pas le jour. Pour de nouveaux projets, je vous recommande donc de vous orienter ces autres services.
Table des matières
Contexte
Traditionnellement, lorsque nous déployons nos applications Web, nous avons un load balancer (ou un reverse proxy)
qui fait la terminaison TLS avec un certificat associé à notre domaine: https://mon-application.com
.
Le traffic est ainsi chiffré entre les utilisateurs et notre application. Derrière ce load balancer, nos
services reçoivent les requêtes via une communication qui n’est généralement pas chiffrée: http://x.x.x.x:8080
.
Maintenant, pour des raisons réglementaires, nous pourrions avoir besoin de chiffrer cette communication entre le load balancer vers les services pour empêcher des tiers indésirables d’intercepter nos données en transit. Dans ce cas, en plus de la terminaison TLS se faisant toujours au niveau du load balancer, nos services effectueront également une terminaison TLS permettant donc d’avoir du “chiffrement de bout en bout.”
Une terminaison SSL reste toujours nécessaire au niveau du load balancer applicatif qui a besoin de lire le contenu des requêtes pour prendre une décision de routage ou pour bloquer des requêtes en fonction de règles.
Plusieurs solutions s’offrent à nous pour mettre en place notre terminaison SSL dans un environment conteneurisé :
- L’utilisation d’un sidecar consistant à un ajouter un conteneur supplémentaire et à déléguer la terminaison SSL à celui-ci plutôt qu’à nos services qui pourront se concentrer sur le besoin métier. Cette approche à l’avantage de ne pas modifier le code applicatif et est assez pratique dans un environnement technologique hétérogène. En contrepartie, nous consommerons des resources supplémentaires et paierons le coût de ce “proxy” dans le traitement de nos requêtes.
- L’implémentation directement l’application. Cette approche est dépendante des frameworks utilisés et peut présenter un coût plus important si le parc applicatif comprend des applications hétérogènes. Pour chaque framework utilisé, il faudra implémenter les mécanismes de chiffrement et renouvellement de certificats. Elle a l’avantage de nécessiter moins de resources serveur et évite l’ajout d’un nouveau composant qu’il faudra également monitorer.
Vous l’aurez sans doute compris, dans cet article nous parlerons de l’approche sidecar avec AWS App Mesh et un service ECS derrière un load balancer. Le code source complet en Terraform de cette première partie se trouve dans ce projet GitHub.
Comment ça marche?
Voici le schéma de l’application que nous allons déployer et détailler par la suite:
AWS App Mesh pour la terminaison TLS et bien plus
Le composant principal de notre infrastructure est AWS App Mesh, l’offre de service mesh d’AWS. Un service mesh (maillage de services en français) est couche d’infrastructure permettant de gérer les communications entre différents services. Au lieu que les services communiquent directement entre eux, ils passent par cette couche d’infrastructure de façon généralement transparente.
Cette communication entre services se fait via un proxy qui “intercepte” les appels réseaux entre services.
Dans l’exemple suivant, le traffic vers le port 5000 de notre application est redirigé vers le
proxy qui écoute sur le port 15000 (ProxyIngressPort
).
App Mesh utilise Envoy Proxy pour cela et fournit des APIs pour contrôler Envoy sans pour autant exposer toutes les fonctionnalités offertes par celui-ci : les concepts d’App Mesh sont traduits en configuration pour Envoy. Vous n’avez cependant pas besoin de connaître de Envoy pour utiliser App Mesh.
Un service mesh offre tout un tas d’avantages parmi lesquels la gestion de la sécurité (mTLS par example), gestion du traffic (routage, auto-retry, circuit breaker, tracing, déploiement canary, rate limiting). Dans cette première partie, nous allons utiliser une petite partie des fonctionnalités d’App Mesh à savoir un nœud virtual (virtual node) pour faire du chiffrement TLS.
Un nœud virtuel est la représentation de notre service ECS dans App Mesh. Lors de la création de celui-ci (le nôtre sera
appelé vn-alpla
), nous devons spécifier entre autres:
- Une méthode de découverte de service (service discovery) pour que le proxy puisse
communiquer avec notre service. Nous utiliserons l’intégration native avec AWS Cloud Map
dans lequel notre service ECS va s’enregistrer dans un namespace dédié
demo-app-mesh.local
. Le service sera visible sous le hostalpha.demo-app-mesh.local
dans le VPC. Concrètement, le proxy se basera sur les enregistrements DNS gérés par CloudMap pour communiquer avec notre conteneur. - Un listener précisant le protocole exposé et le port d’écoute du service : respectivement http et 5000. La terminaison TLS se configure à ce niveau ainsi que le certificat TLS à utiliser (voir plus bas).
Intégrer un service ECS existant dans App Mesh, revient donc à rajouter un nouveau conteneur à la task definition et à le connecter au mesh. Cette opération ne nécessite pas de récréation du service dans ECS et peut ainsi se faire sans interruption en rajoutant le proxy en mode désactivé ou permissif dans un premier temps.
Côté Terraform, sera revient à rajouter le block proxy_configuration {}
et un nouveau conteneur dans le container_definitions
de la ressource aws_ecs_task_definition
dont voici un exemple simplifié :
resource "aws_ecs_task_definition" "task_definitions" {
# ....
proxy_configuration {
type = "APPMESH"
container_name = local.envoy_container_name
properties = {
AppPorts = "5000"
EgressIgnoredIPs = "169.254.170.2,169.254.169.254" # Addresses IP pour récupérer les méta données de la tâche/instance
IgnoredUID = local.envoy_side_car_uuid
ProxyEgressPort = 15001 # Ce port est utilisé pour intercepter le traffic sortant.
ProxyIngressPort = 15000
}
}
container_definitions = jsonencode([
{ # 1. Conteneur applicatif
# ...
dependsOn : [ # Le proxy doit être healthy avant qu'on puisse démarrer le conteneur applicatif
{
"containerName" : local.envoy_container_name,
"condition" : "HEALTHY"
}
]
},
{ # 2. Sidecar
name : local.envoy_container_name,
image : "public.ecr.aws/appmesh/aws-appmesh-envoy:v1.24.1.0-prod",
user : local.envoy_side_car_uuid,
essential : true,
healthCheck : {
"retries" : 3,
"command" : [
"CMD-SHELL", "curl -s http://localhost:${local.envoy_admin_port}/server_info | grep state | grep -q LIVE"
],
# ...
},
logConfiguration : {
# ...
}
environment : [
{
"name" : "APPMESH_RESOURCE_ARN",
"value" : aws_appmesh_virtual_node.node.arn # ARN du noeud virtual associé au service
},
{
"name" : "ENVOY_LOG_LEVEL",
"value" : "info"
}
]
}
])
}
Le rôle IAM de la tâche doit également avoir les permissions pour accéder au nœud virtuel ainsi que le certificat ACM. Voir les fichiers modules/ecs-meshed-service/iam.tf et modules/ecs-meshed-service/data.tf.
AWS ACM comme autorité de certification
Pour faire nos terminaisons SSL, nous aurons besoin de deux certificats:
- Un certificat pour notre load balancer publique utilisant l’autorité de certification publique d’Amazon. Ces
certificats sont gratuits mais ne sont pas exportables en dehors de l’écosystème AWS et ne peuvent pas être utilisés
par App Mesh. Il sera associé au domaine
demo-app-mesh.${votre_zone_dns}
. - Pour notre seconde terminaison TLS, nous utiliserons une autorité de certification privée via AWS Private Certificate Authority. D’autres sources de certificats sont supportées comme un certificat autosigné chargé à partir du système de fichiers. Plus d’informations ici.
Le certificat crée via cette autorité de certification devra correspondre au host associé au service via CloudMap
à savoir alpha.demo-app-mesh.local
. Son renouvellement et son intégration avec le proxy sera automatiquement géré par
AWS.
ECS et l’Application Load Balancer
Notre service ECS alpha
sera déployé sur Fargate avec un ALB public qui exposera quelques endpoints à des fins
de démonstration :
GET /
: Page HTML avec quelques meta données sur le conteneur applicatif et la tâche ECSGET /meta
: Equivalent JSON du précédent endpoint mais avec plus de données sur l’application et le proxy.GET /headers
: Renvoie les headers reçuesPOST /healthcheck
: Endpoint de healthcheck
Le target group associé à l’ALB écoute les requêtes sur le port 5000 en HTTPs. Les logs applicatifs sont envoyés dans
deux logs groupes: demo-app-mesh-envoy-alpha
pour les logs du proxy et demo-app-mesh-alpha
pour l’application.
L’image Docker du service sera hébergée sur ECR.
Déploiement et tests
Vous trouverez dans ce projet GitHub le code Terraform nécessaire pour
déployer l’architecture décrite plus haut dans le dossier part-1. Le projet est découpé en module pour faciliter la
réutilisation : un sous module common
pour les ressources communes à plusieurs services (ALB, cluster ECS etc…) et
autre sous module ecs-meshed-service
qui crée un service ECS destiné à lancer dans un mesh.
Pour lancer cette stack, il faut au préalable avoir :
- Un VPC avec des sous-réseaux publics et privés. On partira du principe qu’ils sont bien configurés : table de routage, nat gateway, internet gateway, communication entre les sous réseaux publics et privés, etc.
- Une autorité de certification sur AWS PCA. J’ai préféré ne pas l’inclure dans la stack Terraform vu son coût.
- Une zone publique sur Route53 pour accéder au load balancer en HTTPs avec une URL “user-friendly”.
- Un utilisateur IAM pour lancer Terraform. Cet utilisateur devra au moins avoir des droits étendus sur les services ECS, IAM, Cloudwatch, Route53 et ACM.
- Terraform
>=1
pour déployer l’infrastructure. Un backend local est utilisé par défaut pour des raisons de simplicité. Dans un environnement de production, il faudra un autre backend tel que S3 avec le versioning activé. - Docker pour construire l’image de l’application et la pousser sur ECR et la CLI AWS pour se connecter à ECR
Ensuite, il faut lancer les commandes suivantes :
git clone https://github.com/haidaraM/aws-app-mesh-demo
cd part-1
# Export des variables obligatoires. A remplacer par les bonnes valeurs
export TF_VAR_vpc_id="vpc-xxxxxx"
export TF_VAR_private_subnets='["private-subnet-a", "private-subnet-b", "private-subnet-c"]'
export TF_VAR_public_subnets='["public-subnet-a", "public-subnet-b", "public-subnet-c"]'
export TF_VAR_private_ca_arn='arn:aws:acm-pca:eu-west-1:12345678910:certificate-authority/xxxxxx'
export TF_VAR_r53_zone_name='your-domain.com'
# Lancement de Terraform
terraform init
terraform apply
Si tout s’est bien passé, vous aurez en sortie quelque chose qui ressemble à ça:
alb_endpoint = "https://demo-app-mesh.votre_zone_dns"
ecr_repo_url = "12345678910.dkr.ecr.eu-west-1.amazonaws.com/demo-app-mesh"
logs = {
"app" = "demo-app-mesh-alpha"
"proxy" = "demo-app-mesh-envoy-alpha"
}
A ce stade, l’application n’est pas encore disponible, il faut construire l’image et la pousser vers ECS :
# Connexion à ECR
ECR_REPO=$(terraform output -raw ecr_repo_url)
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REPO
cd ../app
docker build -t ${ECR_REPO} .
docker push ${ECR_REPO}
Après 2-3mn, l’application devrait être accessible sur https://demo-app-mesh.votre_zone_dns
.
Si cela ne marche toujours pas, accéder à la console AWS pour vérifier que le service ECS
démarre correctement et regarder les logs dans CloudWatch.
Vérification
Pour vérifier que nos requêtes passent bien par le proxy, nous pouvons :
- Regarder les headers des réponses renvoyées. Vous devez retrouver les headers
sever: envoy
etx-envoy-upstream-service-time: xx
. Exemple avec /headers: - Accéder au log group du proxy dans CloudWatch
demo-app-mesh-envoy-alpha
. En plus des healthchecks, vous verrez également vos requêtes.
Conclusion
Dans cet article, nous avons vu comment déployer un seul service dans un mesh et faire du chiffrement au niveau du service. Dans la seconde partie, nous allons déployer un second service et les faire communiquer via le mesh en rajoutant des “backends” à nos noeuds virtuels.
Pour aller plus loin
Si vous êtes curieux, sachez qu’il est possible d’exporter la configuration d’Envoy en utilisant l’interface d’administration config_dump. Il est important de rappeler que le port d’administration ne doit être JAMAIS être exposé publiquement sachant qu’aucun mécanisme de protection n’est encore implémentée.
Pour exporter la configuration :
- Passer la variable Terraform
expose_envoy_admin_port = true
au niveau de l’appel au moduleecs-meshed-service
et relancerterraform apply
. Ceci va rajouter une nouvelle règle au security group exposant le port d’administration 9901. - Depuis une machine au sein de votre VPC, vous pouvez faire une requête vers
http://alpha.demo-app-mesh.local:9901/config_dump
pour dumper la configuration d’Envoy en JSON.
Références
Cet article se base sur les sources suivantes :
- TLS avec App Mesh
- Workshop AWS sur App Mesh
- Enable traffic encryption between services in AWS App Mesh using AWS Certificate Manager or customer-provided certificates
Vous également trouverez la roadmap de App Mesh sur GitHub.