Bastion SSH
Un bastion SSH permet de sécuriser l'accès à des serveurs distants. C'est un peu de contrainte à la mise en place, après ça va tout seul.
Fonctionnement quasi invisible : on continue de se connecter au serveur en faisant “ssh marcel”, sauf qu'en réalité la connexion va au bastion, qui vérifie les accréditations, avant de nous renvoyer vers Marcel le serveur.
En cours d'écriture, gros brouillon.
Pré-requis :
- Savoir gérer SSH : génération de clé, configuration de sshd, usage de
.ssh/config
Attention à la fausse sécurité
Avant de commencer, un point de vigilance : un bastion mal configuré peut faire plus de dégâts qu'une architecture sans bastion. En effet, depuis le bastion, il est potentiellement possible d'accéder à toutes les machines situées derrière ce dernier. Le bastion est un point de contrôle, pas une cloison de sécurité par défaut. Il faut donc veiller à ce que tout soit sécurisé à tous les points, depuis les machines autorisées à se connecter jusqu'aux machines qui vont accepter les connexions.
Rebonds de bastion en bastion
L'architecture que je souhaite mettre en place est celle-ci :
Utilisatrice ⇒ Bastion 1 ⇒ Bastion 2 ⇒ Serveur final.
Le Bastion 1 est un serveur en ligne des plus classique. Ce sera le seul point d'accès pour les divers Bastions 2. Les bastions 2, eux, sont en réalité sur les hyperviseurs et donnent accès au réseau local. Cela demande par contre que les Bastion 2 soient vu comme des composants critiques ; une élevation de privilège “là” pourrait avoir de sacrés conséquences.
Conventions
Dans l'exemple, nous aurons
- “alice”, utilisatrice “de base” (pas de droit à bidouiller sur les bastions !). Elle est membre du groupe “jump”
- “lapin”, the sysadmin qui bidouille les bastions. Il est membre du groupe “wheel”.
- “101.1.1.1” est l'adresse du Bastion1 (oui cette ip est foireuse, et osef).
- “202.1.1.2” est l'adresse du Bastion2 (et oui, je devrais sans doute aussi faire ça en ipv6).
- 10.0.0.10, 10.0.0.11 et 10.0.0.12 sont des serveurs derrière le Bastion2 et sur un réseau local.
Configuration côté Bastion1
Ce bastion, le premier de la chaine, est donc celui auquel on se connecte depuis “n'importe où” (mais avec la bonne clé et la bonne utilisatrice). Il est situé quelque part sur les zinternets (adresse ip publique et dans un datacenter).
- Port personnalisé (non 22)
- Désactivation des authentifications inutiles (mot de passe, PAM, etc.)
- `AllowUsers` ou `Match User` pour restreindre les accès
- Désactivation du shell si non nécessaire (`PermitTTY no`, `ForceCommand`, etc.)
- Ajout des clés dans `~/.ssh/authorized_keys`
- Autoriser uniquement les connexions sortantes SSH vers Bastion2 (port personnalisé)
- Bloquer les autres sorties SSH/TCP si possible
- Paramétrer sshguard, fail2ban/réaction
- Activation de la journalisation SSH
Pour les passagères
Ceci concerne les utilisatrices qui ne font que passer, et donc qui n'auront aucun moyen de s'arrêter ici.
- Désactiver le shell
Pour les admins
Là c'est différent : les admins ne rebondissent pas, elles peuvent avoir un shell, et peuvent tout péter.
Configuration côté Bastion2
Ce bastion est l'entrée pour le réseau local. Il sert donc des adresses locale ensuite.
Même trucs que sur Bastion1, en fait.
- Autoriser uniquement les connexions vers le réseau local ou vers des IPs précises (serveur final)
sshd_config
Concernant le port ssh, dans le cas que je traite, Bastion2 est derrière une box. Cette dernière n'accepte les connexions ssh que sur un port exotique (par exemple le port 2242) mais redirige ensuite en local sur le port 22 de la machine du bastion. Donc pas besoin de modifier le port sur cette dernière. C'est aussi possible de le faire directement avec le pare-feu sur une machine exposée sur internet, mais plus pénible, dans ce cas autant directement utiliser un autre port pour ssh sur la machine.
- /etc/ssh/sshd_config.d/bastion
# Protocole et sécurité de base Protocol 2 StrictModes yes # CHANGER : Port 22 # Chiffrement et algorithmes HostKey /etc/ssh/ssh_host_ed25519_key KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256 Ciphers chacha20-poly1305@openssh.com,aes128-gcm@openssh.com,aes256-gcm@openssh.com MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com PubkeyAcceptedAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com # Authentification PermitRootLogin no AllowGroups wheel jump PasswordAuthentication no PermitEmptyPasswords no KbdInteractiveAuthentication no PubkeyAuthentication yes UsePAM yes # Paramètres de session et de connexion MaxAuthTries 6 LoginGraceTime 30 ClientAliveInterval 3m ClientAliveCountMax 5 # Comportement à la connexion, environnement PrintLastLog yes PrintMotd no PermitUserEnvironment no AcceptEnv LANG LC_* # Interdiction de session interactive PermitTTY no X11Forwarding no AllowTcpForwarding no PermitTunnel no # Bloque tout shell interactif ForceCommand /bin/false # Journalisation LogLevel VERBOSE # Match précise qui a droit à quoi Match Group jump # AllowTcpForwarding nécessaire pour que ce soit transmis, # Renseigner ensuite les adresses et ports. AllowTcpForwarding yes PermitOpen 10.0.0.10:22 PermitOpen 10.0.0.11:22 PermitOpen 10.0.0.12:22 Match Group wheel PermitTTY yes ForceCommand none
On peut aussi ajouter dans notre config ssh ce bloc :
# Par défaut, interdire tous le monde DenyUsers * # Réautoriser les utilisateurs depuis Bastion1 uniquement Match Address 101.1.1.1 AllowGroups wheel jump
Vérifier comment ça réagit, je tâtonne. Potentiellement ça interdit vraiment sauf si on vient du bastion.
Si on veut un accès direct aux sysadmin :
# Par défaut, interdire tous le monde AllowGroups wheel # Réautoriser les utilisateurs depuis Bastion1 uniquement Match Address 101.1.1.1 AllowGroups wheel jump
Mais j'aime pas. Je ne le sens pas.
Nftables
Il faut aussi paramétrer nftables pour n'accepter que les connexions ssh en provenance de Bastion1. Une config complète est dispo ici, ce qui nous intéresse est dans ces lignes
- /etc/nftables.conf
# Autoriser SSH si : # - source = Bastion1 (externe). mettre son ip, c'est pas 101.1.1.1 # - OU source dans le LAN 192.168.1.0/24 (UNIQUEMENT quand on est derrière une box) # SSH ne sera sans doute pas sur le port 22, adapter... ip saddr { 101.1.1.1, 192.168.1.0/24 } tcp dport 22 ct state new accept
Créer les utilisatrices
Il faut permettre à Alice de passer en rebondissant et sans s'arrêter, et à Lapin de creuser son trou.
Tout en root/sudo :
groupadd wheel groupadd jump useradd alice -m -G jump mkdir /home/alice/.ssh nano /home/alice/.ssh/authorized_keys # Mettre la clé publique dans ce fichier chown -R alice: /home/alice/.ssh chmod 0700 /home/alice/.ssh/ chmod 0600 /home/alice/.ssh/authorized_keys useradd lapin -m -G sudo,wheel -s /bin/bash passwd lapin mkdir /home/lapin/.ssh nano /home/lapin/.ssh/authorized_keys # Mettre la clé publique dans ce fichier chown -R lapin: /home/lapin/.ssh chmod 0700 /home/lapin/.ssh/ chmod 0600 /home/lapin/.ssh/authorized_keys
L'hyperviseur a-t-il les bonnes adresses ?
Bastion2 étant l'hyperviseur, il peut avoir sa façon (unique) de gérer les adresses, suivant le pont etc. Pour vérifier si la VM “192.168.1.10” est réellement joignable sous ce nom :
ip route get 192.168.1.10 ping 192.168.1.10 ip neigh
Si ça ne trace pas la route, si ça ne ping pas… la dernière commande liste les ip voisines, ça peut donner des pistes.
Configuration côté Serveur final
- sshd_config
- Accepter uniquement des connexions depuis Bastion2
- sshd_config/perso
# Protocole et sécurité de base Protocol 2 StrictModes yes # Chiffrement et algorithmes HostKey /etc/ssh/ssh_host_ed25519_key KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com Ciphers chacha20-poly1305@openssh.com,aes128-gcm@openssh.com,aes256-gcm@openssh.com MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com PubkeyAcceptedAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com # Authentification PermitRootLogin no AllowGroups wheel PasswordAuthentication no PermitEmptyPasswords no KbdInteractiveAuthentication no PubkeyAuthentication yes UsePAM yes # Paramètres de session et de connexion MaxAuthTries 6 LoginGraceTime 30 ClientAliveInterval 3m ClientAliveCountMax 5 # Comportement à la connexion, environnement PrintLastLog yes PrintMotd no PermitUserEnvironment no AcceptEnv LANG LC_* # Redirections et accès à distance AllowTcpForwarding no X11Forwarding no
Cas particulier d'un tunnel
Dans un cas particulier, j'ai hélas besoin de pouvoir parfois utiliser un tunnel (fichue box qui ne se gère pas direct en ssh). On peut ajouter ceci depuis le serveur qui va permettre le tunnel :
Match User alice AllowTcpForwarding yes PermitTunnel yes
⇒ seul l'utilisatrice “alice” pourra faire ce tunnel.
Les bastions ont aussi besoin de permettre ce tunnel :
Match User alice PermitOpen 192.168.1.42:80 AllowTcpForwarding yes
Configuration côté utilisatrice
Combien de clés ssh différentes faut-il générer ?
- Une clé par serveur cible
- Une clé pour accéder à la maintenance des bastions (pour les personnes autorisées bien sûr).
On différencie bien l'usage du bastion (qui sert juste à “rebondir” jusqu'au serveur final), et l'administration de ces derniers (méga critique : qui a accès au bastion en tant qu'admin peut ensuite y faire plein de choses).
On peut ensuite avoir des clés différentes pour chaque serveur cible, par contre cela demande d'adapter sa façon de travailler. Personnellement je préfère utiliser une clé pour un ensemble de serveur de la même galaxie.
En dehors des clés, le seul truc réellement à bidouiller est le fichier “~/.ssh/config” qui simplifiera l'administration. On continuera ensuite de faire “ssh serveur_final” pour aller sur ce dernier, et toute la magie opérera en coulisse.
Génération des clés :
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_alice -C "alice" ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_lapin -C "lapin"
- -t : type, donc ed25519.
- -f : précise le nom du fichier ; permet de retrouver plus facilement ce qui sert “où”
- -C : ajoute un commentaire ; sans rien préciser ce sera votre utilisateur@host du système où vous êtes.
Copier les clés sur les bastions dans “~/.ssh/authorized_keys” (chacun son home).
La partie Alice (au pays des serveurs)
Alice n'a accès (au final) qu'aux VM ; mais elle doit passer par les deux bastions pour ça.
Génération de sa clé :
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_alice -C "alice"
Copier la clé publique sur la/les VM auxquelles elle a accès ainsi que dans les deux bastions, dans /home/alice/.ssh/authorized_keys
.
Son fichier de config avec un seul bastion (tout en local, donc adresse locale !!!). Ça, ça marche.
- ~/.ssh/config
# Bastion2 Host bastion2 HostName 192.168.1.22 User alice IdentityFile ~/.ssh/id_ed25519_alice ForwardAgent no # Accès à la VM finale (via les deux bastions) Host vm-finale HostName 192.168.1.11 User alice IdentityFile ~/.ssh/id_ed25519_alice ProxyJump bastion2
- ~/.ssh/config
# Bastion1 - 1er saut Host bastion1 HostName 101.1.1.1 User alice IdentityFile ~/.ssh/id_ed25519_alice ForwardAgent no # Bastion2 - 2e saut Host bastion2 HostName 192.168.1.2 User alice IdentityFile ~/.ssh/id_ed25519_alice ProxyJump bastion1 ForwardAgent no # Accès à la VM finale (via les deux bastions) Host vm-finale HostName 192.168.1.11 User alice IdentityFile ~/.ssh/id_ed25519_alice ProxyJump bastion2
Pour se connecter :
ssh vm-finale
Attention si on utilise un script (genre Ansible pour créer les utilisatrices. Une utilisatrice admin sur la VM ne doit PAS être admin sur le bastion.