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).

~/.ssh/config
 

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.

 Ce texte est placé sous licence CC0

CC Attribution-Noncommercial-Share Alike 4.0 International Driven by DokuWiki
pratique/informatique/bastionssh.txt · Dernière modification : 09/05/2025 17:14 de Zatalyz