Services Blog English

Combines de contournement de firewall par bastion, tunnels et proxy

| par jpic | devops network linux

Un réseau d’entreprise classique ne permet pas aux admins de se connecter directement aux machines qu’ils administrent, et obligent à la connexion à une machine intermédiaire: le bastion.

On ne va pas pouvoir être exhaustif sur ce guide tant il existent de techniques pour contourner ce type de limitation, nous nous contenterons ici de présenter les diverses options de SSH, ainsi qu’un proxy SOCKS séparé à savoir Dante (sockd), et comment les combiner dans divers scénarios d’entreprise.

Un proxy SOCKS (Socket Secure) est un protocole de niveau session (couche 5 du modèle OSI) qui permet de rediriger n’importe quel flux TCP, et parfois UDP, à travers un serveur intermédiaire sans que l’application cliente ait besoin de connaître la destination finale.

Sommaire

Nous allons voir les techniques suivantes:

  • proxy SOCKS pour encapsuler toute connection TCP via une connection SSH
  • Dante sockd: ouvrir un port proxy SOCKS qui permet de faire passer le traffic de la machine locale au travers d’une machine distante
  • forwarder un port local sur une machine distante via SSH: le port local 1234 devient accessible via le port 1234 sur la machine distante
  • à l’inverse, forwarder un port local d’une machine distante via SSH: le port distant 1234 devient accessible via le port 1234 sur la machine locale
  • sshuttle: le super mignon outil qui permet carrément de forwarder des réseaux accessibles depuis une machine distante sur une machine locale

Et comment combiner plusieurs de ces techniques.

Proxy SOCKS avec SSH

Le plus simple pour se faire un proxy, quand le serveur distant le permet, est d’utiliser ssh -D, extrait du man ssh:

-D [bind_address:]port

Active un forwarding de port local « dynamique » au niveau applicatif (Dynamic Port Forwarding). Cela consiste à allouer un socket d’écoute sur le port indiqué sur la machine locale (le client), éventuellement lié à une adresse spécifique via bind_address.

Dès qu’une connexion arrive sur ce port local, elle est transmise à travers le tunnel SSH sécurisé, puis l’application qui se connecte (généralement un navigateur ou tout autre client configuré pour utiliser un proxy SOCKS) décide elle-même de la destination finale.

OpenSSH implémente alors un vrai proxy SOCKS : les protocoles SOCKS4 et SOCKS5 sont tous deux supportés, et ssh joue le rôle de serveur proxy SOCKS qui relaie les requêtes vers les destinations demandées par le client.

[…]

Par défaut, le port d’écoute est lié selon le paramètre GatewayPorts.

Donc, dans un terminal, je me connecte à mon serveur avec -D 1236:

09/12 2025 13:51:23 jpic@jpic ~
$ ssh -D 1236 ci.yourlabs.io
[jpic@ci ~]$

Et dans un autre terminal, je sors toujours avec mon ip:

09/12 2025 13:51:30 jpic@jpic ~
$ curl ipconfig.io
98.110.80.162

Mais, si je passe par le proxy socks avec la variable d’environnement ALL_PROXY, alors je sors bien avec l’ip de mon serveur distant:

09/12 2025 13:51:44 jpic@jpic ~
$ ALL_PROXY=socks5://localhost:1236 curl ipconfig.io
163.172.69.187

Donc c’est clairement l’option la plus simple et la plus pratique, mais certains serveurs SSH d’entreprise ne le permettent pas de changer la configuration sshd, donc, nous devrons recourrir à d’autres pratiques.

En effet, le serveur peut refuser parce que AllowTcpForwarding est à no dans sshd_config, auquel cas les manipulations ressembleront à ça:

09/12 2025 14:00:52 jpic@jpic ~
$ ssh -D 1236 ci.yourlabs.io
[jpic@ci ~]$ channel 3: open failed: administratively prohibited: open failed
$ ALL_PROXY=socks5://localhost:1236 curl ipconfig.io
curl: (97) Failed to receive SOCKS response, proxy closed connection

Mais pas de panique, on va utiliser Dante sockd et forwarder le port depuis le bastion vers notre machine locale pour bénéficier de la même fonctionnalité!

Proxy SOCKS Dante

dante-server est un package disponnible sur RHEL par exemple, donc on va pouvoir se l’installer et se faire une petite configuration minimale:

logoutput: stderr
internal: 127.0.0.1 port = 1337
external: <l'ip du bastion>
debug: 2
clientmethod: none
socksmethod: none
client pass {
    from: 0.0.0.0/0 to: 0.0.0.0/0
}
socks pass {
    from: 0.0.0.0/0 to: 0.0.0.0/0
}

Qu’on met dans un fichier, au hasard dante.conf et qui permet de lancer le serveur proxy avec la commande suivante:

sockd -f dante.conf

Ce qui ouvre un proxy socks sur le port 1337 du bastion, ce qui nous est complêtement inutile, jusqu’au moment ou on trouve un moyen de forwarder ce port du bastion vers un port local.

Nous allons donc voir 2 techniques permettant d’y arriver, puis, nous joindrons les deux bouts avec un example.

Port forwarding: local vers distant

C’est tout simplement l’option -R de la commande ssh, pour forwarded un port local vers un port distant, voici un extrait de la documentation de cette option dans man ssh:

-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket
-R [bind_address:]port

Indique que les connexions arrivant sur un port TCP donné ou sur un socket Unix côté distant (serveur SSH) doivent être redirigées vers la machine locale (le client SSH).

Cela fonctionne en créant un socket d’écoute sur la machine distante, soit sur un port TCP, soit sur un socket Unix. Dès qu’une connexion arrive sur ce port ou socket distant, elle est transmise à travers le tunnel SSH sécurisé, puis une connexion est établie depuis la machine locale (le client) vers la destination indiquée.

Pour tester, rien de plus simple, on lance un petit serveur en local sur le port 1234 avec la commande:

09/12 2025 13:26:52 jpic@jpic ~
python3 -m http.server 1234

Puis, on se trouve un serveur cible sur lequel on se connecte ainsi:

$ ssh -R 1234:localhost:1234 ci.yourlabs.io

Et on constate que le port 1234 de la machine jpic est maintenant accessible depuis le port 1234 de la machine ci:

[jpic@ci ~]$ curl -I localhost:1234
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.7
Date: Tue, 09 Dec 2025 12:33:39 GMT
Content-type: text/html
Content-Length: 4154
Last-Modified: Thu, 27 Feb 2025 21:49:05 GMT

Cependant, cela peut également échouer:

$ ssh -R 1234:localhost:1234 ci.yourlabs.io
Warning: remote port forwarding failed for listen port 1234
[jpic@ci ~]$

On peut parfois voir le problème en refaisant la commande ssh avec -v:

09/12 2025 13:34:49 jpic@jpic ~
$ ssh -v -R 1234:localhost:1234 ci.yourlabs.io
debug1: OpenSSH_10.2p1, OpenSSL 3.6.0 1 Oct 2025
[...]

debug1: Remote: Server has disabled port forwarding.
debug1: remote forward failure for: listen 1234, connect localhost:1234
Warning: remote port forwarding failed for listen port 1234
debug1: pledge: network

Et là c’est plutot clair: le serveur refuse le forwarding TCP. La solution: changer AllowTcpForwarding no en AllowTcpForwarding yes dans le fichier /etc/ssh/sshd_config et recharger le service sshd avec systemctl restart sshd.

Dans le cas d’un réseau d’entreprise, il est possible que ce soit un serveur sshd de centrify, auquel cas la configuration sera plutot dans /etc/centrifydc/ssh/sshd_config et le service à recharger centrify-sshd.

Port forwarding: distant vers local

Pour éffectuer l’opération inverse, c’est ssh -L:

-L [bind_address:]port:host:hostport
-L [bind_address:]port:remote_socket
-L local_socket:host:hostport
-L local_socket:remote_socket

Indique que les connexions vers un port TCP donné ou un socket Unix sur l’hôte local (le client) doivent être redirigées vers l’hôte et le port spécifiés, ou vers le socket Unix indiqué, côté distant (serveur SSH).

Cela fonctionne en allouant un socket d’écoute, soit sur un port TCP côté local (éventuellement lié à une adresse de bind spécifique avec bind_address), soit sur un socket Unix local.

Dès qu’une connexion arrive sur ce port ou socket local, elle est transmise à travers le tunnel SSH sécurisé, puis une nouvelle connexion est établie depuis la machine distante vers le host:port (hostport) ou vers le socket Unix remote_socket indiqué.

Pour essayer, on se connecte donc ainsi et puis on lance un serveur sur le port 1235 du serveur distant:

$ ssh -L 1235:localhost:1235 ci.yourlabs.io
[jpic@ci ~]$ python -m http.server 1235
Serving HTTP on 0.0.0.0 port 1235 (http://0.0.0.0:1235/) ...

Et comme vous pouvez le voir, on peut donc établir une connection sur le port 1235 de la machine locale pour acceder au port 1235 de la machine distante:

09/12 2025 13:42:33 jpic@jpic ~
$ curl -I localhost:1235
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.7
Date: Tue, 09 Dec 2025 12:34:21 GMT
Content-type: text/html; charset=utf-8
Content-Length: 2842

Si AllowTcpForwarding no, alors vous verrez quelque chose comme cela sur le serveur:

$ ssh -L 1235:localhost:1235 ci.yourlabs.io
[jpic@ci ~]$ python -m http.server 1235
Serving HTTP on 0.0.0.0 port 1235 (http://0.0.0.0:1235/) ...
channel 3: open failed: administratively prohibited: open failed

Et évidemment, une erreur de connection en local:

$ curl -I localhost:1235
curl: (56) Recv failure: Connection reset by peer

Dante sockd + SSH port forwarding

Combinons ce que nous avons appris, et voici notre plan:

  • ouvrir un port de proxy socks sur le port 1337 de notre bastion
  • ramener ce port pour qu’il soit accessible en localhost depuis notre machine de dev

La première étape sera d’être capables de nous connecter depuis le bastion vers notre machine de dev, à l’envers donc. C’est donc ce que nous allons essayer dans un premier temps en suivant les étapes suivantes sur votre machine de dev:

  • find /etc -name sshd_config pour trouver le chemin vers notre fichier de configuration sshd, si vous voyez un chemin comme /etc/centrifydc/ssh/sshd_config alors c’est probablement celui-là qu’il faut utiliser
  • assurez vous que AllowTcpForwarding est bien à yes dans ce fichier
  • cherchez DenyUser et assurrez vous que votre utilisateur n’est pas en DenyUser, car si c’est le cas il faudra supprimer la ligne
  • profitez-en donc pour passer LogLevel à DEBUG, cela vous sera utile si vous avez besoin de débugger
  • aussi, regardez bien la valeur de AuthorizedKeysFile, par défaut c’est .ssh/authorized_keys, mais cela peut varier dans des configurations d’entreprise
  • enfin, démarrez ou recharger votre serveur ssh local

Puis, toujours sur votre machine de dev, créez donc une clef ssh avec la commande suivante par exemple:

ssh-keygen -t ed25519 -a 100

Elle devrait atterir dans ~/.ssh/id_ed25519, et la clef publique dans ~/.ssh/id_ed25519.pub. Il faudra, pour pouvoir se connecter depuis votre bastion vers votre machine de dev, envoyer la paire de clef sur le bastion, mais aussi ajouter la clef publique en .pub au fichier AuthorizedKeysFile.

Si AuthorizedKeysFile est à .ssh/authorized_keys, alors procédez ainsi:

cat ~/.ssh/id_ed25519.pub >> .ssh/authorized_keys

# et, très important pour que sshd accepte de lire le fichier, le mettre en
# user-writable seulement:
chmod 600 .ssh/authorized_keys

Mais, si vous avez, par exemple, AuthorizedKeys /etc/ssh/keys/%u.key, alors il faudra plutot proceder ainsi:

cat ~/.ssh/id_ed25519.pub | sudo tee -a /etc/ssh/keys/${USER}.key
sudo chmod 600 /etc/ssh/keys/${USER}.key

Puis, copiez votre paire de clef sur votre bastion:

scp .ssh/id_ed* mon-bastion:/tmp

Ensuite, vous devez absolument arriver à vous connecter depuis votre bastion vers votre serveur local, essayez donc ainsi:

[ma-dev] $ ip a  # regarder l'ip de ma-dev
[ma-dev] $ ssh mon-bastion
[mon-bastion] $ ssh -i /tmp/id_ed25519 mon-uid@mon-ip

Si ça ne marche pas, regardez les journaux du serveur sshd sur votre machine de dev avec l’une des commandes suivantes, selon votre serveur sshd:

sudo journalctl -fu sshd

# ou, dans le cas de centrify:
sudo journalctl -fu centrify-sshd

Si LogLevel est bien à DEBUG sur votre serveur sshd alors l’erreur devrait être proprement indiquée dans ces journaux.

Cela fait, lançons un multiplexeur comme tmux ou screen, dans un premier terminal lancer le proxy socks:

while :; do sockd -f dante.conf; done

Et dans un deuxième terminal à l’interieur de notre multiplexer, nous connecter vers notre machine en forwardant le port 1337:

while :; do ssh -R 1337:localhost:1337 <notre user>@<notre dev>; done

Ainsi, sur votre machine de dev, vous pourrez faire sortir vos connections par le bastion avec:

export ALL_PROXY=socks5://localhost:1337

Bonus: sshuttle

C’est un package pip tout mignon qui permet de forwarder tout le traffic de la machine sur un bastion, TCP + UDP + DNS par exemple avec la commande suivante:

sshuttle --dns -r user@serveur 0.0.0.0/0

Je vous laisse regarder le README du projet sshuttle pour découvrir tous les trucs sympas qu’on peut faire!

Ils nous font confiance

Contact

logo