Reverse SSH, pour se connecter à un ordinateur distant protégé par un pare-feu
Cet article est le troisième d'une série de quatre :
- SSH, pour se connecter en ligne de commande à un ordinateur distant
- SSH, se connecter sans mot de passe à l'aide de la cryptographie
- Reverse SSH, pour se connecter à un ordinateur distant protégé par un pare-feu
- SSH, des tunnels pour tous les services
Dans un article précédent, nous avons vu comment se connecter directement à un ordinateur distant présent sur le même réseau local, comment se connecter directement à un ordinateur distant situé derrière un pare-feu convenablement configuré, et comment se connecter à un ordinateur distant accessible par rebond via un ordinateur intermédiaire. Ne reste plus qu'à traiter, donc, le cas de l'ordinateur distant... injoignable. Ce cas est en fait le plus courant. Il s'agit d'un ordinateur distant installé derrière un routeur, lui-même inaccessible par SSH et empêchant toute connexion directe au serveur. Bref, le cas typique de l'ordinateur domestique installé derrière sa box Internet. Avec une adresse IP publique dynamique. Et des utilisateurs qui ont d'autres centres d'intérêts que l'administration réseau (et qui le vivent, semble-t-il, plutôt bien).
Autant il est impossible de se connecter au serveur distant, autant on peut faire en sorte que le serveur puisse se connecter au client. Car le client, c'est nous ; et là-dessus, on peut agir. Si on ouvre un tunnel SSH depuis le serveur vers le client, il est possible au client de remonter le tunnel pour accéder au serveur. C'est ce qu'on appelle le reverse SSH — ou SSH inversé. Ça peut sembler tordu, mais ça marche. Et encore, la situation est simple ; et présente d'ailleurs certains défauts. Par exemple, si le client est mobile et change de connexion Internet, le tunnel mis en place ne fonctionne plus. On pourrait imaginer un tunnel opérant via un serveur VPN, auquel le client est connecté en permanence. Néanmoins, l'accès serait alors uniquement possible depuis le poste client connecté au VPN. Une solution plus universelle est envisageable. Plus complexe, certes, mais qui présente de nombreux avantages.
Imaginons que SERVEUR_B, auquel on souhaite se connecter, est installé derrière un pare-feu qui empêche toute connexion entrante. Il n'est pas possible de se connecter au serveur, mais celui-ci a bel et bien accès au réseau. Ainsi, SERVEUR_B peut ouvrir un tunnel SSH vers SERVEUR_A, qui nous appartient et qui est convenablement configuré pour cela ; ce qui implique d'éventuelles règles de routage particulières, ou un potentiel accès direct. Si CLIENT peut également se connecter à SERVEUR_A, il peut alors aussi se connecter à SERVEUR_B, comme nous l'avons déjà vu. L'astuce réside donc dans l'usage d'un serveur intermédiaire, SERVEUR_A. Si SERVEUR_B peut se connecter à SERVEUR_A et que CLIENT peut aussi se connecter à SERVEUR_A, alors CLIENT peut se connecter à SERVEUR_B.
Toute l'intelligence du système repose sur SERVEUR_A. Mais attention ! SERVEUR_B, ainsi que la multitude d'ordinateurs qui ont accès à SERVEUR_A afin de pouvoir s'y connecter à distance par SSH inversé, n'entrent pas dans votre sphère de contrôle. Et tous ces ordinateurs, que l'on peut raisonnablement considérer comme hostiles, disposent d'un accès direct à SERVEUR_A ! Autant dire qu'il va falloir réduire un minimum leur pouvoir d'action... Pour cela, on pourrait créer un utilisateur système aux droits réduits. Mieux, on peut enfermer cet utilisateur dans un chroot minimaliste, grâce à Jailkit.
Situation initiale
Faisons le point.
SERVEUR_B, auquel on souhaite se connecter, n'est pas accessible directement. Il est néanmoins nécessaire d'y opérer une configuration initiale afin d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A.
SERVEUR_A, qui sert de relais, nécessite une configuration spéciale afin de limiter les droits des serveurs distants qui s'y connectent, considérés comme hostiles — tel que SERVEUR_B, notamment. SERVEUR_A doit être accessible par SSH, en permanence. L'objet de cet article n'est pas d'expliquer comment déployer des règles de routages particulières ni toute autre configuration système ou réseau pour y parvenir.
CLIENT, qui souhaite accéder à SERVEUR_B, a juste besoin de pouvoir se connecter à SERVEUR_A.
Configuration de SERVEUR_A
SERVEUR_A nécessite l'installation d'un serveur SSH, comme expliqué dans l'article précédemment cité. Afin de permettre l'utilisation directe du tunnel par CLIENT, il est nécessaire d'activer deux options de configuration du serveur SSH. Pour cela, il faut tout d'abord se connecter à SERVEUR_A en tant que super-utilisateur :
su -
Modifier la configuration du serveur SSH :
sed -i "s/#AllowTcpForwarding yes/AllowTcpForwarding yes/" /etc/ssh/sshd_config sed -i "s/#GatewayPorts no/GatewayPorts yes/" /etc/ssh/sshd_config
Puis la recharger :
systemctl reload ssh.service
SERVEUR_A nécessite également l'installation et la configuration de Jailkit afin de pouvoir isoler un utilisateur système, comme expliqué dans cet autre article.
La seule différence consiste en une initialisation minimaliste de l'environnement isolé :
jk_init -v -j /home/jail limitedshell
Il n'est ici pas utile de définir Bash comme shell interactif pour l'utilisateur JAIL_USER, puisque personne n'aura à se connecter en ligne de commande à SERVEUR_A ; ce dernier ne servant que de serveur relais pour la connexion à SERVEUR_B. D'ailleurs, Bash n'est même pas installé dans cet environnement isolé.
Configuration de SERVEUR_B
SERVEUR_B a uniquement besoin d'un client SSH. Les distributions GNU/Linux les plus répandues en étant dotées par défaut, nous devrions d'ores-et-déjà pouvoir nous connecter manuellement à SERVEUR_A depuis SERVEUR_B :
ssh USER_A@IP_SERVEUR_A
Puis nous déconnecter :
exit
Le but du jeu étant d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A, l'authentification par mot de passe n'est pas envisageable. Il faut configurer une authentification cryptographique, comme expliqué dans cet article. La seule différence — contrainte par ce même soucis d'automatisation — étant que la clef privée elle-même ne doit pas être protégée par un mot de passe.
Copier la clef publique sur SERVEUR_A
Comme expliqué dans l'article indiqué, la clef publique doit être copiée sur SERVEUR_A, pour le compte utilisateur JAIL_USER. Malheureusement, l'environnement isolé installé est trop restrictif pour permettre l'usage de ssh-copy-id. Il est évidemment possible d'y remédier, mais cela augmente notablement la surface d'attaque. S'agissant d'une opération peu fréquente, il est préférable de la réaliser manuellement. Pour cela, il faut tout d'abord, copier la clef publique dans le dossier personnel d'un utilisateur standard de SERVEUR_A :
scp .ssh/id_ed25519.pub USER_A@IP_SERVEUR_A:/home/USER_A/
Puis se connecter à ce compte utilisateur :
ssh USER_A@IP_SERVEUR_A
Se connecter en tant que super-utilisateur :
su -
Importer la clef dans le compte utilisateur JAIL_USER :
cd /home/jail/home/JAIL_USER umask 077 mkdir -p .ssh cat /home/USER_A/id_ed25519.pub >> .ssh/authorized_keys chown -R JAIL_USER: .ssh rm /home/USER_A/id_ed25519.pub
Se déconnecter (deux fois) :
exit
Création d'un tunnel SSH
Afin d'automatiser l'ouverture et le maintient d'un tunnel SSH vers SERVEUR_A, il nous faut installer le paquet autossh. Pour cela, il faut tout d'abord se connecter à SERVEUR_B en tant que super-utilisateur :
su -
Installer le paquet autossh :
apt install autossh
Créer un service systemd correspondant :
vim /etc/systemd/system/autossh.service
Et y coller ceci :
[Unit] Description=Keep a tunnel open on port 22 After=network.target [Service] User=USER_B ExecStart=/usr/bin/autossh -o ServerAliveInterval=60 -NR 22222:localhost:22 JAIL_USER@IP_SERVEUR_A Restart=on-failure [Install] WantedBy=multi-user.target
Le port 22222 correspond à l'entrée du tunnel sur SERVEUR_A, qui débouche sur le port 22 de SERVEUR_B. Autant le port d'entrée du tunnel peut être choisi — presque — librement, autant le port de sortie doit correspondre au port d'écoute du serveur SSH sur SERVEUR_B.
Activer le service au démarrage du système, et le démarrer :
systemctl --now enable autossh.service
Se déconnecter :
exit
Se connecter à SERVEUR_B depuis CLIENT
Depuis SERVEUR_A, on peut désormais se connecter à SERVEUR_B ainsi :
ssh -p 22222 USER_B@localhost
Où le port 22222 sur localhost est bien l'entrée du tunnel relié au port 22 de SERVEUR_B.
Puisque le but du jeu n'est pas de se connecter directement depuis SERVEUR_A, connectons-nous donc depuis CLIENT, via SERVEUR_A :
ssh -J JAIL_USER@IP_SERVEUR_A -p 22222 USER_B@localhost
Nous y voilà ! Nous pouvons enfin nous connecter à cette satanée machine installée derrière un pare-feu empêchant toute connexion externe... ou presque, donc Évidemment, si l'on souhaite créer d'autres tunnels SSH pour d'autres serveurs distants, il suffit d'utiliser des ports d'entrées différents que celui choisi pour SERVEUR_B.
Dans ce dernier exemple, nous nous sommes connectés à SERVEUR_B via SERVEUR_A, par rebond. Si, par des règles de routage particulières ou un accès direct à SERVEUR_A, il est possible de se connecter directement au port 22222 de SERVEUR_A, la connexion par rebond n'est pas indispensable.
Dans ce cas, la commande suivante suffit :
ssh -p 22222 USER_B@IP_SERVEUR_A
À noter l'utilisation a priori étrange de l'utilisateur USER_B sur IP_SERVEUR_A.
Dans cet article, nous avons vu comment créer un tunnel SSH pour se connecter en ligne de commande à un ordinateur distant ; comment relier le port d'une machine externe au port SSH de la machine à contacter, et ainsi contourner la protection d'un pare-feu empêchant, a priori, une telle connexion. Comme nous le verrons dans un prochain article, cette méthode peut être généralisée à l'usage de biens d'autres services que la seule connexion en ligne de commande.
Article sous licence Creative Commons BY-SA 3.0 France.
Publié le
Lundi 15 octobre 2018 à 16h34Ajouter un commentaire
Flux RSS des commentaires de cet article.
Commentaires
Olivier a répondu le Permalien
Merci pour cet excellent article qui permet de résoudre l'accès distant à un poste muni d'un accès internet mobile — et ne disposant donc pas d'une IP publique — mais capable de se connecter à un serveur relais muni d'un accès internet fixe et disposant donc d'une IP publique.
Bravo !
felix34 a répondu le Permalien
Bonjour et merci pour ce tuto.
J'ai une petite question : sous Windows, j'ai pas l'option -J d'OpenSSH. Comment configurer Putty pour contourner le problème ?
Merci. Félix.
Cyp a répondu le Permalien
Malheureusement, je n'ai aucune idée de l'équivalent de cette option OpenSSH dans Putty.
dave5632 a répondu le Permalien
Bonjour,
P'tite question : il sort d'où scp .ssh/id_ed25519.pub USER_A@IP_SERVEUR_A:/home/USER_A/ ?
Le id_ed25519.pub, je ne l'ai pas sur mon SERVEUR_B.
Merci.
Cyp a répondu le Permalien
Bonjour,
Il s'agissait d'une étape décrite dans l'article précédent : https://www.cypouz.com/article/181009/ssh-se-connecter-sans-mot-p...
greg a répondu le Permalien
Il faut que tu génères une paire de clés (privée et publique) :
À ce moment là, tu devrais trouver ta clé publique id_rsa.pub.
Cyp a répondu le Permalien
Bonjour,
On trouve de plus en plus de recommandations à l'usage de ED25519 plutôt que RSA. Le premier serait aussi sécurisé que le second pour des besoins nettement moindre en ressources. D'où l'usage de ED25519 dans cette série d'articles.
ced a répondu le Permalien
Bonjour
Super tuto très bien expliqué merci mais j'ai un problème
Quand je lance ssh -p 22222 USER_B@localhost depuis le serveur A j'ai un connection refused . pourtant en B le tunnel ssh à l'air de fonctionner
ced a répondu le Permalien
Reflexion faite le tunnel ne fonctionne pas correctement, le port 22222 semble refuser la connexion. Comment résoudre le problème ?
ced a répondu le Permalien
Ca y est ça fonctionne !!! Merci pour ce tuto de haut vol, vraiment !!