Bastion hosts AWS éphémères

Introduction

Dans toutes les entreprises, il y a toujours des situations où un administrateur, un développeur ou un DBA doit accéder à certaines ressources, soit pour faire de la maintenance, soit pour résoudre un problème. Les politiques de sécurité des entreprises sont normalement très restrictives et exigent que les ressources restent privées et qu'elles ne soient accessibles que depuis des environnements contrôlés avec des contrôles d’accès stricts.


Pour réduire la surface d’attaque des environnements, des proxys sont déployés dans des environnements privés avec des mesures qui permettent d’avoir :

  • Un seul point d’accès aux réseaux privés,
  • Des politiques de contrôle d’accès centralisé,
  • Des proxys sécurisés avec un processus de ‘hardening’ pour réduire la surface d'attaque.

Les bastions sont des machines (virtuelles ou ‘bare metal’) qui sont déployées soit dans un endroit centralisé, soit directement dans les environnements cibles, et qui ont pour but d'être le point d’entrée aux ressources jusqu'alors inaccessibles.


Pour pouvoir accéder aux ressources privées, un administrateur a besoin d’avoir l’accès à ces machines. Normalement l’authentification des utilisateurs se fait  via des clés SSH ou via un système d’authentification tiers. En sachant que les entreprises ont habituellement des environnements différents pour déployer leurs applicatifs (pour les entreprises de grande taille, cette situation peut se complexifier quand ils faut gérer plusieurs projets, chacun avec leurs environnements privés), le nombre de machines déployées peut rapidement compliquer la gestion de l’infrastructure (sans compter le coût de la maintenance et le coût relatif aux machines elles-mêmes). De plus, installer les clés SSH dans tous les hosts pour donner l’accès aux utilisateurs peut devenir un cauchemar, d’autant plus car ces clés-là, il convient de les faire “tourner” pour des raisons de sécurité.

Services managés à la rescousse

Au cours des dernières années, AWS a annoncé des services divers pour simplifier la problématique de l’accès aux ressources privées sans avoir besoin de gérer ni des clés SSH ni des bastions.


Tout d’abord, AWS a créé Systems Manager - Session Manager, qui donne l’accès aux instances aux utilisateurs sans utiliser des clés SSH et sans avoir besoin d’ouvrir des ports dans les security groups. La seule condition est d’avoir l’agent Session Manager installé dans la machine et connecté au service (il faut que l’instance soit configurée avec un ‘instance profile’ qui lui donne l’accès) pour permettre aux utilisateurs de s’y connecter avec des identifiants AWS IAM. De plus, dans le cas où les bastions servent de machine rebond afin de se connecter sur d’autres instances dans un subnet privé du VPC, ces bastions deviennent obsolètes ; en effet, on peut désormais s’y connecter directement avec Session Manager et en maintenant les ports SSH fermés.


En second lieu,  EC2 Instance Connect est apparu, permettant  de se connecter en utilisant des clés SSH dites “short lived”, créées, installées et supprimées automatiquement par le service. Il reste néanmoins l’obligation d’ouvrir des ports dans les security groups mais l’avantage est bien qu’il n’y a plus à gérer la maintenance de ces clés SSH.

Pour finir, à la fin de l’année 2020, AWS a annoncé AWS CloudShell, un terminal interactif créé directement dans un navigateur web avec des sessions utilisateurs. CloudShell est capable de stocker jusqu’à 1GB de données, persistées entre différentes sessions. Le host de base est géré par AWS donc aucun administrateur n'a besoin de le gérer comme pour les deux premiers services. AWS a promis d’améliorer le service dans les itérations suivantes, en ajoutant la possibilité de déployer le host directement dans un VPC, ce qui n’est pas possible pour le moment.

Une autre approche

Malgré toutes ces solutions disponibles fournies par AWS, des améliorations sont encore possibles. Les bastions restent toujours la solution pour les cas où un utilisateur nécessite l’accès aux machines privées ou quand un DBA/développeur a besoin de lancer des requêtes SQL dans une base de données privée. AWS Systems Manager - Session Manager et EC2 Instance Connect simplifient le problème d’accès pour les administrateurs et développeurs mais ils ont encore besoin d'une instance démarrée en continu à laquelle se connecter et qui doit suivre une maintenance périodique pour des raisons de sécurité. AWS CloudShell n’a pas ces inconvénients et il a le potentiel de devenir le service à utiliser une fois que certaines fonctionnalités, comme la possibilité de se connecter à un VPC, seront disponibles.

La solution suivante s’appuie sur AWS Systems Manager - Session Manager pour se connecter à une machine. Mais dans ce cas-là, la machine est démarrée à la volée et détruite une fois le travail fini. Cela offre des avantages comme :

  • Réduire le coût par rapport aux machines démarrées 24x7.
  • Supprimer la maintenance  des machines. À chaque fois qu'un host est démarré, il utilise la dernière version d’une image AMI avec les dernières mises à jour.
  • Éviter la création de différents utilisateurs dans la machine ou la création d’un utilisateur partagé par tous.
  • Toutes les sessions démarrent à partir d’un socle “propre”. Les fichiers et applications installés par les utilisateurs sont supprimés à la fin de la session.
  • Possibilité de créer des actions au démarrage. Ceux-là seront responsables d’installer les logiciels nécessaires pour chaque session. Une autre option serait de créer des images AMI différentes pour chaque cas d’usage mais l’entretien de ces images serait trop coûteux.
  • Option de déployer la machine dans un VPC pour accéder à des ressources privées.
  • Les machines peuvent être déployées dans des subnets privés pour améliorer la sécurité.
    En dehors de ceux-ci, il y a encore quelques pistes à suivre afin d’ augmenter la sécurité et la traçabilité des interactions avec les instances pour les entreprises qui en ont besoin.
  • Les commandes exécutées pourront être loguées et envoyées vers S3 pour garder une trace.
  • Un snapshot de l’instance pourrait être créé à la fin de la session pour faire de l'analyse.
  • Les fichiers créés pourront être envoyés vers S3 à la fin de la session pour chaque utilisateur, et leur donner la possibilité de  les télécharger automatiquement pendant le démarrage de la session suivante.

Architecture

La solution proposée suit la convention AWS en termes de séparation des responsabilités. Elle a comme prérequis l’utilisation d’un compte AWS central (appelé dans ce cas ‘Shared Services’) où les bastions sont déployés. Les bastions se connectent à chaque environnement, (chaque environnement est déployé dans un compte AWS différent), à travers une connexion VPC peering établie entre le compte ‘Shared Services’ et les environnements ciblés. Pour s’assurer que les bastion hosts peuvent se connecter uniquement aux environnements ciblés, ils sont déployés dans des subnets différents, chacun avec une table de routage ne donnant l’accès qu’à la plage CIDR de l’environnement. Les utilisateurs sont gérés en utilisant le service AWS Single Sign On qui peut être configuré pour se connecter via un AD existant ou utilisé tout seul.

Toutes les entreprises ne suivent pas cette approche. Certaines n’ont pas un compte AWS séparé par environnement. Certains pourraient préférer déployer un bastion directement dans chaque environnement. Même si cette solution n’a pas été  pensée pour ce genre d’infrastructure, elle peut être adaptée assez facilement. La plupart du travail est fait par des services comme Lambda, Systems Manager Session Manager, DynamoDB, IAM, CodeBuild, CloudWatch Events ou S3. La topologie réseau peut être adaptée aux exigences.

Une chose à garder en tête est que le déploiement est fait par un script Terraform, configuré pour utiliser S3 comme backend. Au besoin, le déploiement pourra être réécrit en CloudFormation~~.~~

Commençons par décrire les éléments de l’architecture par ordre d’utilisation.

Création du bastion host

  • Les utilisateurs qui ont besoin d’un bastion éphémère sont créés et contrôlés avec Single Sign On (SSO). Le service fournit un point d’accès centralisé pour donner aux administrateurs et développeurs l’accès aux environnements sur lesquels ils souhaitent se connecter.
  • Chaque utilisateur est associé avec un ou plusieurs permission sets SSO. Ces permission sets contiennent une policy IAM qui permet d’invoquer une fonction Lambda ainsi que l'exécution de Systems Manager - Session Manager.
  • Il y a une fonction Lambda ‘Create Instance’ par environnement. Chaque permission set décrit ci-dessous permet l’exécution d’une seule Lambda. Le but étant de pouvoir contrôler qui a accès à quels environnements. La fonction Lambda est responsable de deux choses :
  • Elle génère un UUID unique. Détaillé dans la suite.
  • Elle appelle le projet CodeBuild qui exécute le script Terraform.
  • Le projet CodeBuild ‘Create Instance Stack’ reçoit quelques variables depuis la Lambda comme l’environnement où le bastion doit être déployé ou l’UUID. L’UUID est utilisé comme identifiant pour Terraform. Afin que la stack créée soit détruite une fois que le bastion n’est plus nécessaire, on a besoin de maintenir un lien entre l’instance créée et le fichier Terraform tfstate qui contient l’information nécessaire pour la supprimer. De l’instance, on garde son ID une fois créée. Pour le fichier tfstate l’architecture utilise Terraform workspaces où le nom du workspace est l’UUID qu’on garde aussi. Ainsi, quand la stack doit être détruite, un autre projet CodeBuild peut simplement changer le workspace de la stack, en utilisant l’ID de l’instance pour trouver l’UUID associé et exécuter la commande “terraform destroy”.
  • Il y a une table DynamoDB qui garde toutes les références décrites ci-dessous. La création de l’entrée dans la table DynamoDB est faite par le script Terraform lui-même. L’information stockée dans l’item inclut :
  • L’environnement auquel le bastion doit se connecter.
  • L’ID de l’instance.
  • L’UUID pour identifier le tfstate Terraform.
  • La version du code source qui a créé la stack. Cette information permet de se prémunir de problèmes lors de la destruction de la stack, si des changements dans le code ont été faits entre temps.
  • Une fois que l’instance est créée et connectée à Systems Manager - Session Manager, les utilisateurs peuvent s’y connecter avec la CLI AWS. Comme la commande pour se connecter à l’instance nécessite son ID, le script Terraform ajoute un tag au bastion avec l’UUID généré par la fonction Lambda comme valeur, qui est inclus dans sa réponse. On peut facilement retrouver l’instance EC2 via des appels sur la CLI AWS en utilisant le tag comme filtre.
aws ec2 describe-instances \
  --region "eu-west-1" \
  --filters "Name=tag:StackId,Values=1234567890" \
  --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \
  --output text | tr -d '[:space:]'

Il y a un script dans le code source qui s’occupe de faire l’orchestration de l’appel à la fonction Lambda, de récupérer l’UUID dans la réponse et l’utiliser pour se connecter à l’instance avec Session Manager.

Destruction des bastion hosts

  • Toutes les 10 minutes (configurable) un CloudWatch Event est lancé qui appelle la fonction Lambda ‘Destroy Stack’
  • Cette Lambda récupère toutes les sessions de Session Manager encore actives ainsi que les informations relatives à toutes les stacks disponibles au moment de son exécution (qui sont stockées dans DynamoDB). Avec toutes ces informations, elle est capable de trouver les bastions qui n’ont pas une session active en ce moment. Ensuite, elle lance le projet CodeBuild ‘Destroy Instance stack’ pour chacune d’entre elles.
  • Le projet CodeBuild prend l’UUID et l’environnement passé comme variables, récupère le code Terraform associé au stack et enfin lance la commande ‘terraform destroy’ après avoir changé au niveau du workspace associé à l’ID de l’instance.

Actions de bootstrap

Comme décrit parmi la liste d’avantages de cette architecture, il y a l’option d'inclure des actions de démarrage. Il est réalisé en utilisant le ‘user data’ d’EC2 qui exécute un script à la fin du démarrage de l’instance.

L’architecture permet la création de scripts sous la forme de templates variabilisés, passés à l’instance. Les variables et le nom du template à utiliser arrivent de l’utilisateur à la fonction Lambda, qui les envoie au projet CodeBuild qui les utilise comme variables d’entrée du script Terraform. Le script Terraform remplace alors les variables dans le template avant d'exécuter la création de la stack.

Le diagramme suivant décrit le processus.

Il y a actuellement 2 templates disponibles avec la possibilité d'en créer d'autres si besoin.

  • database_tunnel: il installe l’application Linux socat. Une fois le bastion démarré, il crée un service socat qui fait un port forwarding de la machine vers une URL (dans ce cas-là l’URL de connexion à une BDD). L’utilisateur doit alors se connecter au host en utilisant l’option ‘port forwarding’ de Systems Manager Session Manager. Ainsi, l’utilisateur peut configurer une connexion BDD vers l'adresse locale et le port sélectionné de sa machine qui se connecte via le service socat à l’URL de la BDD.
  • install_git_terraform: il installe Git et Terraform dans le bastion host et il met à jour la configuration avec son utilisateur Git. Il peut aussi inclure une liste de repositories qui seront clonés lors du démarrage de l’instance
    Deux scripts sont fournis dans le code source pour simplifier l’utilisation de ces templates.

Conclusion

L'écosystème AWS contient plusieurs services avec beaucoup de fonctionnalités pour simplifier la vie des développeurs et des administrateurs. L’éternelle et lourde tâche de maintenir les systèmes sécurisés mais en même temps accessibles pour les administrateurs, est aujourd’hui beaucoup plus simple grâce aux outils que proposent les fournisseurs cloud. Ils se chargent de la partie lourde nécessaire pour fournir des services de contrôle d’accès, que l'on utilise pour gérer nos ressources privées. Mais comme pour tout, il y a encore de la place pour l’amélioration, et un travail supplémentaire a encore besoin d’être délivré pour certains cas d’usage.

La solution présentée, qui se base sur des outils déjà disponibles, a comme but de répondre à ces cas d’usages encore non couverts, en attendant qu’une solution totalement managée soit proposée par les fournisseurs cloud.

Liens