Back to Basics : Le Bastion SSH
Le bastion SSH est un élément courant d’architecture. Souvent présenté comme améliorant la sécurité de votre infrastructure, cela n’est pas la seule raison pour laquelle vous pourriez avoir besoin d’un bastion, sans le savoir.
Principes de base
Pour se connecter au serveur, il est ici indispensable de passer d’abord par le bastion.
Un bastion SSH est une brique d’infrastructure qui permet à une connexion SSH de “rebondir” avant d’atteindre sa cible.
Une connexion SSH permet d’ouvrir un Shell via un canal de communication sécurisé sur une machine, le plus souvent distante. Majoritairement présent sous Linux (il y a un article d’Arnaud Mazin sur le sujet bien évidemment), il est tout de même possible d’installer un serveur SSH sous Windows, où le protocol RDP sera néanmoins privilégié pour l’accès à distance. Dans ce cas, une Remote Desktop Gateway pourrait correspondre à notre “bastion SSH”.
Prenons un cas d’architecture simple, dont l’énoncé est le suivant :
- Un client (au sens SSH du terme) souhaite se connecter en SSH sur le port 22 via internet, à l’un de ses deux serveurs, en passant par un bastion.
- Les serveurs autres que le bastion, n’ont pas d’accès à internet. Ce dernier est le seul à avoir une IP publique (ici, 62.23.55.220). Les autres IPs, commençant par 192.168 sont des IPs privées, comme spécifié dans la RFC 1918. Ces IPs ne sont accessibles que depuis le réseau privé, et non depuis l’internet.
- Dans notre exemple, nous ne nous occuperons pas, dans un premier temps, des autorisations (noms d’utilisateur, mots de passe, ou clefs SSH…).
Le client initie une connexion SSH sur son bastion, qui est le seul de son réseau privé à autoriser les connexions entrantes sur le port 22 depuis internet.
# Connexion depuis le client vers le serveur Bastion client> ssh user@62.23.55.220
Une fois sur le bastion, le client peut se connecter sur le Serveur A ou B, en passant par le port 22, qui est autorisé en provenance du bastion.
# Connexion depuis le bastion vers le ServeurA bastion> ssh user@192.168.0.10
Ce cas d’usage étant courant, OpenSSH inclut une option (-J) pour cela :
# Utilisation de la directive "ProxyJump" en ligne de commande client> ssh -J user@62.23.55.220 user@192.168.0.10
Concernant ProxyJump
Cette directive n’est disponible qu’à partir d’OpenSSH 7.3, sorti en août 2016. D’autres moyens de se connecter sont décrits plus loin dans l’article pour les anciennes versions.
Pour rebondir sur plusieurs serveurs, vous pouvez les spécifier en les séparant par des virgules :
client> ssh -J user1@Bastion1,user2@Bastion2 user3@serveur
Et avec l’authentification ?
Nous avons désormais vu rapidement le principe d’un bastion. Nous allons aller un peu plus loin, en étudiant le problème de l’authentification. Nous n’aborderons cependant pas les notions de clefs SSH (privées et publiques), que vous pouvez découvrir dans cet article.
Partons désormais du principe que l’accès à nos serveurs est protégé par des clefs. Viennent alors plusieurs solutions : stocker les clefs privées sur le bastion, utiliser l’agent ssh, ou initier toutes les connexions depuis le client.
A la copie de clefs privées, nous préférerons utiliser l’agent ssh. Ainsi nous évitons les problématiques liées à la gestion de déploiement de clefs privées en gardant nos secrets sur les postes administrateurs qui accèdent au bastion, et non directement sur celui-ci (qui est exposé aux dangers d’internet).
# Initialisation des variables d’environnement eval $(ssh-agent -s)
# Ajout des clefs à votre agent SSH ssh-add -c ~/.ssh/my_private_key
Vous pouvez désormais utiliser le Agent Forwarding, qui vous permettra d’utiliser votre agent SSH local depuis votre session distante, et ainsi utiliser vos clefs privées sans les copier sur le bastion.
Mais cela peut créer une potentielle faille de sécurité : les utilisateurs ayant les droits suffisants sur le serveur (par exemple, root) pourront avoir accès à votre agent ssh, et donc utiliser vos clefs à votre insu, le temps de la session. Spécifier l’option “-c” lors de l’ajout de la clef à votre agent permet de limiter le risque, en vous demandant confirmation avant chaque utilisation de la clef.
# Utilisation de l’option "AgentForwarding" en spécifiant le flag "-A" client> ssh -A user@62.23.55.220
# Vous pouvez désormais vous connecter sur le serveur cible : les clefs dans le SSH agent de votre client local seront utilisées. bastion> ssh user@192.168.0.10
Bien évidemment, cela nécessite d’ajouter vos clefs publiques sur toutes les machines cibles.
Pour vous faciliter la vie, vous pouvez formaliser ces méthodes de connexion dans un fichier de configuration : celui par défaut (~/.ssh/config) ou bien un fichier qui vous spécifierez explicitement comme ceci :
client> ssh -F $FICHIER_DE_CONF_SSH
Pour notre exemple, cela donnera le fichier suivant :
Host bastion Hostname 62.23.55.220 IdentityFile ~/.ssh/myPrivateKey User user
Host serveurA ProxyJump bastion Hostname 192.168.0.10 IdentityFile ~/.ssh/myPrivateKey User user
Vous pourrez ainsi vous connecter directement à vos machines, sans spécifier la mécanique derrière, comme suit :
client > ssh serveurA
En utilisant ProxyJump, vous n’effectuez pas de connexion depuis le bastion, car toutes vos connexions sont initiées directement depuis le client.
Notez que lors de l’utilisation du ProxyJump (ou du ProxyCommand) les clefs dans l’agent SSH sont testées pour la connexion. L’agent SSH a un usage plus général que le forwarding : les connexions initiées depuis un client iront utiliser les clefs présentes dans l’agent.
(Attention à ne pas dépasser la limite de tentative de clefs (MaxAuthTries) à cause d’un ssh-agent trop rempli !)
Par défaut, l’agent n’est pas transféré (“AgentForwarding no”), “pour des raisons évidentes de sécurité”.
Ce n’était pas mieux avant ; c’était différent
Comme nous l’avons évoqué précédemment, “ProxyJump” n’est apparu que récemment. Si votre version d’OpenSSH est supérieure à la 5.4 (mais inférieure à 7.3), vous pouvez toujours utiliser “ProxyCommand” avec l’option “-W” (que nous détaillons plus bas), comme ceci :
# Sans ssh-agent client> ssh -o ProxyCommand="ssh -W %h:%p user@62.23.55.220 -i ~/.ssh/myPrivateKey" root@192.168.0.10 -i ~/.ssh/myPrivateKey
# Avec la clef dans le ssh-agent client> ssh -o ProxyCommand="ssh -W %h:%p root@62.23.55.220" root@192.168.0.10
ProxyCommand permet de spécifier à SSH la commande à utiliser comme canal de transport qui servira pour sa connexion à la cible. En temps normal, TCP utilise une socket réseau pour ses entrées / sorties. Ici, les I/Os seront le standard input (stdin) et standard output (stdout) de la commande. L’option “-W” permet d’utiliser le netcat mode, et ainsi utiliser une version de netcat directement incorporée dans OpenSSH comme commande de proxy.
Les variables %h et %p sont rendues accessibles par ssh et référencent respectivement l’hôte (192.168.0.10) et le port (par défaut, 22) cible.
Et si nous souhaitons simplifier nos commandes, notre ~/.ssh/config ressemblera à cela :
Host bastion Hostname 62.23.55.220 User root
Host serveurA Hostname 192.168.0.10 User root ProxyCommand ssh -W %h:%p bastion
Attention : si vous utilisez un fichier de configuration spécifié via le flag “-F”, vous devrez répéter ce flag dans vos commandes, par exemple :
Host serveurA ... ProxyCommand ssh -W %h:%p -F ssh_config bastion
Et si votre version de OpenSSH est inférieure à la 5.4, vous pouvez : éliminer ce serveur par le feu... Ou bien utiliser explicitement le binaire nc (netcat, qui doit alors être installé) :
# Exemple avec la clef dans le ssh-agent client> ssh -o ProxyCommand="ssh root@62.23.55.220 nc %h %p" root@192.168.0.10
Un bastion, pour quoi faire ?
Maintenant que nous avons compris comment ça marche, peut-être est-il temps de se demander si on en a vraiment besoin.
Prenons le cas d’architecture suivant :
Le load-balancer, en frontal, est le seul à avoir une IP publique. Il transfert les requêtes qu’il reçoit indifféremment à l’un des deux serveurs applicatifs du réseau privé, qui utilisent une même base de donnée.
Mais il est alors impossible d’accéder à nos serveurs pour y faire des tâches d’administration, ou des déploiements, en SSH. Une solution serait d’attribuer une IP publique à chacune de nos machines. Mais une IP publique, cela coûte de l’argent. De plus, nos machines ne sont actuellement pas “visibles” depuis internet, et non accessibles. Et nous voulons garder cet état !
Ajoutons donc un bastion :
Ici, seul le bastion (en plus du load-balancer) possède une IP publique. Au lieu de trois supplémentaires, nous n’en avons utilisée qu’une seule. En plus de réduire les coûts, cela contribue à combattre la pénurie d’adresses IPv4.
Le principe d’un bastion peut facilement se comparer à celui, plus historique, du “réseau d’administration”. Ce réseau est en place uniquement pour l’administration, comme son nom l’indique, et ne porte pas de service.
KISS and tricks
Notre bastion ne doit, d’ailleurs, pas porter de service.
Cela permet de l’éteindre, ou de couper l’accès avec des règles firewall (ou des Security Group Rules sur un cloud provider), sans affecter le service rendu. Une fois éteint, le réseau d’administration ne sera plus accessible, et donc votre application sera moins vulnérable aux attaques. On ne pourra plus rentrer sur votre réseau que via le ou les ports ouverts sur le Load-Balancer, taillé pour affronter les attaques venant d’Internet.
Vous pouvez également profiter de ce point d’entrée unique pour appliquer une politique plus stricte de durcissement. Cet hôte n’hébergeant pas de service, cela ne devrait donc pas l’affecter : ciphers plus modernes, port-knocking, fail2ban, …
Il pourra également adopter des politiques de mises à jour plus agressives, n’ayant pas peur de rebooter pendant quelques minutes, le temps de les appliquer : vous n’aurez plus accès à votre bastion. Mais votre service, lui, continuera à être rendu.
Nous avons par ailleurs présenté une architecture avec un seul bastion. Notez que cet élément peut tout à fait être redondé.
J’aime pô ça, c’pô bon
Alors, pourquoi s’en passer ? Faisons un petit tour d’horizon de ce qui pourrait déplaire dans un bastion.
Effectivement, cela ajoute des machines de plus à gérer. De plus, vous devez désormais diffuser de la configuration pour vous connecter en SSH à vos serveurs, via les fichiers de configuration SSH vus plus haut.
D’aucun pourrait argumenter également que cela n’apporte pas beaucoup plus de sécurité que d’interdire l’accès à vos VMs directement via des règles firewall.
Néanmoins, cela ne retirera pas les avantages fondamentaux d’un bastion :
- la simplicité et le faible coût de mise en place ;
- l’économie d’IPs publiques ;
- le non accès de vos machines depuis Internet quoi qu’il arrive ;
- la possibilité d’éteindre votre “réseau d’administration” lorsque vous ne vous en servez pas.
Sources :