Définition d'un tunnel
Lorsque, pour une raison quelconque, on souhaite accéder par un réseau informatique à une machine qui n'est pas accessible directement mais uniquement par l'intermédiaire d'une machine tierce, on va généralement créer ce qu'on appelle un tunnel. Les informations envoyées à l'entrée d'un tunnel seront encapsulées dans un autre protocole et acheminées vers la machine où se situe sa sortie. De là, elles seront envoyées à la machine destinatrice comme si elle avaient été émises depuis la machine où se situe la sortie du tunnel.
Cette technique est notamment employée dans le cas où l'on souhaite accéder à une machine dans un réseau protégé. Il est alors courant de devoir se connecter d'abord à une machine située à la frontière de ce réseau (on parle de passerelle : elle est accessible de l'extérieur du réseau mais a accès également aux machines normalement accessibles uniquement depuis l'intérieur de celui-ci). Pour faciliter ce rebond sur la machine passerelle, on peut mettre en place un tunnel qui rendra cette opération quasiment transparente.
Ouverture d'un tunnel « statique » en ligne de commande
Informations requises
Avant d'ouvrir un tunnel SSH qu'on appellera ici statique (par opposition à un tunnel dynamique décrit plus bas), nous devons disposer de trois informations :
- le nom DNS de la passerelle SSH sur laquelle se connecter et où sera situé la sortie du tunnel, et l'identifiant du compte à utiliser sur cette machine. Ces informations vous aurons bien entendu été fournies par l'administrateur du réseau auquel vous vous connectez lorsqu'il vous a accordé l'autorisation d'y accéder ;
- le port et l'interface de votre machine où sera située l'entrée du tunnel :
- le choix de ce port est libre, tant qu'il n'est pas déjà utilisé et qu'il
est supérieur à 1024 si vous utilisez un utilisateur non-privilégié (donc
autre que
root
) qui ne peut utiliser ces ports, - vous devrez aussi déterminer sur quelle interface réseau sera ouvert ce
port. Ce choix est notamment important en terme de sécurité, car si vous
utilisez une interface connectée à un réseau, celui-ci pourra
potentiellement envoyer des données dans le tunnel si le port n'est pas
protégé par un pare-feu local (voir encadré ci-dessous au sujet de la
sécurité). Si aucune interface n'est précisée, tout se passe comme si le
choix
localhost
avait été fait, et le tunnel ne sera accessible qu'aux utilisateurs de votre machine ;
- le choix de ce port est libre, tant qu'il n'est pas déjà utilisé et qu'il
est supérieur à 1024 si vous utilisez un utilisateur non-privilégié (donc
autre que
- le nom DNS ou l'adresse IP et le port de la machine et où seront envoyées les données encapsulées à partir de la sortie du tunnel : cette forme de tunnel nécessite en effet de déterminer à l'ouverture du tunnel vers quelle machine et quel port du réseau protégé et accessible à la passerelle SSH seront envoyées les données reçues à l'entrée du tunnel (c'est pour cette raison qu'on a choisi dans ce document de qualifier ce type de tunnel de statique).
Notez bien qu'une fois le tunnel ouvert, aucune authentification supplémentaire n'est effectuée sur les données transitant dans le tunnel. Autrement dit, OpenSSH va encapsuler et faire transiter dans le tunnel toutes les données qu'il reçoit sur le port d'entrée du système et les envoyer à la machine cible depuis l'autre côté du tunnel.
Il faut garder ceci à l'esprit lorsqu'on utilise pour l'entrée du tunnel une interface connectée à un réseau : tout le réseau pourra potentiellement envoyer des données dans le tunnel si son port d'entrée n'est pas protégé par un pare-feu local.
Il est donc recommandé d'installer l'entrée du système sur l'interface
localhost
(correspondant à l'adresse 127.0.0.1
ou ::1
) utilisée par
défaut si aucune interface n'est précisée, car celle-ci n'est accessible
qu'aux utilisateurs du système. Même dans ce cas, il faut s'assurer que tous
les utilisateurs du système sont de confiance.
C'est le rôle du protocole encapsulé dans le tunnel de s'assurer si nécessaire que les données sont envoyées par un utilisateur de confiance (par exemple, si on encapsule une connexion SSH dans le tunnel SSH, c'est le serveur de destination dans le réseau protégé qui devra authentifier l'utilisateur).
Commande d'ouverture du tunnel
Nous supposerons dans ce document que vous disposez d'un compte d'identifiant
hkrustofski
accessible en SSH sur une machine passerelle.example.org
ayant
le rôle de passerelle et qui a accès à un réseau protégé dont les machines
utilisent le suffixe DNS .example.org
.
La commande suivante permet l'ouverture d'un tunnel vers le port 80 d'une
machine du réseau interne protégé par un pare-feu, appelée host1.example.org
:
&
Pour comprendre cette commande, notons que :
- l'option
-N
, qui est facultative, est employée ici pour empêcher l'ouverture d'un shell sur la machine distante. Le tunnel sera alors ouvert jusqu'à ce qu'on tue le processus SSH qu'on a lancé ici en tâche de fond (via la commandekill %1
dans la même session shell, par exemple). Sans cette option, la connexion exécutera un interpréteur shell sur la machine, et le tunnel se fermera lorsque vous terminerez ce shell ; - l'option
-L
est celle qui demande la création du tunnel et précise son entrée et sa sortie:- ici, l'entrée s'effectuera sur le port TCP
3033
(c'est un exemple pris au hasard). Aucun nom DNS se résolvant en une adresse IP locale du système ou d'adresse IP locale n'est précisé avant le port, c'est donc comme si avait été indiquélocalhost:3033
, ce qui signifie que le port TCP 3033 sera ouvert sur l'interface locale (d'adresse127.0.0.1
en IPv4,::1
en IPv6) de votre machine (on peut aussi utiliser*:3033
pour que le port soit ouvert sur toutes les interfaces réseau de la machine), - les données envoyées dans le tunnel (donc ici reçues sur le port TCP
localhost:3033
) de votre machine locale seront transmises à la machinehost1.example.org
sur le port80
en passant par la passerelle sur laquelle on se connecte en SSH. Notez que le nom DNShost1.example.org
sera résolu à la sortie du tunnel, sur la passerelle SSH (ce nom a d'ailleurs de fortes chances de ne pas être visible en dehors du réseau protégé distant). Bien entendu, si le service que vous cherchez à joindre est situé sur la machine où débouche le tunnel, vous pouvez préciser le nomlocalhost
comme machine de destination pour vous y connecter. Ce nom désignera dans ce cas l'interface locale de la passerelle.
- ici, l'entrée s'effectuera sur le port TCP
hkrustofski@passerelle.example.org
représente bien sûr le nom DNS et l'identifiant de votre compte sur la passerelle SSH, que son administrateur vous aura autorisé à utiliser et sur laquelle vous devrez vous authentifier à l'aide d'un mot de passe ou d'une clef SSH préalablement mise en place.
Notez enfin que pour ouvrir un tunnel fonctionnant dans l'autre sens (c'est à
dire qui va transmettre les données envoyées à un port distant vers un port
local), il faut utiliser l'option -R
en ligne de commande, qui s'utilise de
façon identique à -L
.
Définition du tunnel dans le fichier de configuration de ssh
Pour plus de commodité si on doit utiliser ce tunnel régulièrement, on peut
le définir à l'aide d'une entrée dans le fichier de configuration du client
d'OpenSSH ~/.ssh/config
, ce qui évitera d'avori à préciser les options du
tunnel en ligne de commande.
On peut définir dans ce fichier une entrée avec un nom de connexion (ici
tunnel
) qui s'utilisera comme un nom de machine distante. Cette entrée
précise toutes les options du tunnel, qui sera ouvert dès qu'on utilisera ce
nom de connexion avec le client SSH. L'option IdentityFile
utilisée ici n'est
nécessaire que si vous avez besoin de désigner une clef SSH pour la connexion
autre qu'un fichier de clef par défaut comme ~/.ssh/id_rsa
.
Exemple de configuration (à ajouter à votre fichier ~/.ssh/config
):
Host tunnel
Hostname passerelle.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
LocalForward 3033 host1.example.org:80
Une fois cette configuration effectuée, la commande suivante ouvrira le tunnel
vers la machine passerelle.example.org
, et celui-ci sera configuré de manière
identique à la commande ssh
donnée en exemple dans la section précédente :
&
Les arguments fournis avec la commande -L
utilisée en ligne de commande le
seront ici avec la directive LocalForward
, qui prend deux arguments :
- le premier,
[adresse:]port
(ici3033
) est l'adresse (ou le nom DNS se résolvant en une adresse IP locale du système) et le port TCP de l'entrée du tunnel ; - le second,
adresse:port
est l'adresse IP (ou un nom DNS résolu à la sortie du tunnel) et le port TCP vers lesquels les données ayant transité dans le tunnel seront envoyées à la sortie du tunnel.
La directive correspondant à l'option de ligne de commande -R
est
RemoteForward
et fonctionne de manière identique, à la différence que
l'entrée du tunnel sera située sur la machine distante (la passerelle SSH
dans notre exemple) et sa sortie sur la machine locale.
Tunnel « dynamique » ou proxy SOCKS
Différence vis-à-vis d'un tunnel « statique »
On l'a vu, un tunnel SSH obtenu avec l'option -L
permet uniquement de se
connecter à une adresse et un port distants précisés de manière statique à
l'ouverture du tunnel.
Le proxy SOCKS consiste en une généralisation de ce type de tunnel SSH puisqu'il permet d'envoyer le trafic transitant dans le tunnel à un destinataire de l'autre côté du tunnel précisé de manière dynamique en utilisant pour ça le protocole SOCKS.
Le programme utilisant ce tunnel doit préciser la machine et le port où seront envoyées les données à la sortie du tunnel à l'aide de ce protocole SOCKS (les versions 4 et 5 sont acceptées). OpenSSH transmettra alors les données fournies sur le port d'entrée du tunnel vers cette destination.
Pour la machine de destination, tout se passe là aussi comme si la connexion avait été initiée sur la machine où se trouve la sortie du tunnel.
Ouverture du proxy SOCKS
Le proxy SOCKS peut être ouvert à l'aide de l'option -D
du client SSH, donc en
lançant une commande du type :
Dans cet exemple, l'option -D
ne désigne qu'un port sans préciser l'interface
locale à utiliser (avec un nom DNS dont l'adresse correspondrait à une
interface locale, une adresse IP locale, ou avec *
pour désigner toutes les
interfaces), c'est donc le port TCP 1080
de l'interface localhost
qui sera
utilisée, comme si localhost:1080
avait été utilisé.
On peut aussi utiliser la directive DynamicForward 1080
dans une entrée du
fichier de configuration du client SSH ~/.ssh/config
(ici, aucun nom DNS ou
adresse IP d'interface réseau de la machine n'est précisé, ce qui équivaut à
localhost:1080
) :
Host passerelle.example.org
User hkrustofski
DynamicForward 1080
Voici une traduction libre de la documentation relative à cette directive fournie par la page de manuel ssh_config(5) :
DynamicForward
Précise qu'un port TCP sur la machine locale doit être
transféré via le canal sécurisé, et le protocol de l'application
est ensuite utilisé pour déterminer où se connecter depuis la
machine distante.
L'argument doit être [bind_address:]port. (...)
Actuellement, les protocoles SOCKS4 et SOCKS5 sont acceptés,
et ssh(1) agira comme un serveur SOCKS. Plusieurs transferts
peuvent être précisés, et des transferts supplémentaires peuvent
être donnés en ligne de commande. Seul le superutilisateur peut
transférer des ports privilégiés.
DynamicForward
Specifies that a TCP port on the local machine be forwarded over
the secure channel, and the application protocol is then used to
determine where to connect to from the remote machine.
The argument must be [bind_address:]port. (...)
Currently the SOCKS4 and SOCKS5 protocols are supported, and
ssh(1) will act as a SOCKS server. Multiple forwardings may be
specified, and additional forwardings can be given on the command
line. Only the superuser can forward privileged ports.
On peut ensuite utiliser le proxy SOCKS4/5 disponible sur le port TCP 1080
(par défaut sur l'interface locale) à l'aide d'applications permettant son
utilisation (voir ci-dessous).
Utilisation du proxy SOCKS
Usage général
Si vous souhaitez utiliser le proxy SOCKS avec une application (navigateur web, client de messagerie instantanée, etc.), vérifiez d'abord dans ses paramètres si celle-ci accepte de préciser un proxy de ce type. C'est par exemple le cas avec le navigateur Firefox (dont la configuration avec un proxy SOCKS est décrite plus bas).
Pour utiliser un tel proxy avec une application qui n'en prévoit pas nativement l'utilisation, il est possible sous GNU/Linux (et probablement d'autres systèmes) d'utiliser des outils indépendants comme proxychains ou tsocks qui vont intercepter les appels système relatifs aux connexions réseau effectués par l'application et de les rediriger vers le proxy configuré.
Par exemple, avec le programme tsocks, il faudra préalablement configurer le
proxy SOCKS dans son fichier de configuration /etc/tsocks.conf
dont voici un
exemple minimal :
5
1080
Attention : avec tsocks
, la résolution DNS ne se fait pas de l'autre côté
du tunnel, il faudra alors utiliser les adresses IP pour désigner le
destinataire au lieu du nom DNS lorsque le nom du serveur n'est pas visible
depuis la machine locale.
On peut ensuite lancer l'application souhaitée avec la variable d'environement
LD_PRELOAD
correctement renseignée (tsocks -on
permet de le faire dans le
shell courant), ou en argument de la commande tsocks
(cet exemple est
purement démonstratif étant donné que ssh
peut utiliser le proxy SOCKS
directement) :
On peut aussi utiliser la commande équivalente suivante :
ou encore :
Par défaut un appel de la commande tsocks
sans option ouvre un nouveau shell
interactif avec LD_PRELOAD
renseigné.
Mise à jour : le programme tsocks n'est aujourd'hui plus maintenu et n'est plus disponible sur certains systèmes récents. Il peut être remplacé par les programmes suivants qui fournissent une fonctionnalité équivalente (ils n'ont pas été évalués et sont cités sans ordre particulier) :
Utilisation du proxy avec Firefox
Firefox peut faire passer ses requêtes à travers un proxy SOCKS en modifiant
ses préférences dans Avancé
/ Onglet Réseau
, bouton Paramètres
dans la
section Connexion
. Choisissez Configuration manuelle du proxy
, et précisez
l'Hôte SOCKS
(dans l'exemple donné ci-dessus, c'est l'hôte localhost
et le
port 1080
).
Pour activer la résolution DNS à l'autre bout du tunnel, il faut cocher la case
DNS distant
(dans les versions plus anciennes de Firefox, il fallait pour
cela manuellement modifier la clef network.proxy.socks_remote_dns
dans la
page about:config
et lui donner la valeur true
).
Utilisation transparente du proxy avec SSH
Un client OpenSSH peut très facilement utiliser un tunnel SOCKS déjà défini.
On utilisera pour ça la directive ProxyCommand
dans le fichier de
configuration ~/.ssh/config
, ou l'option -o "ProxyCommand ..."
en ligne de
commande.
Dans les arguments de ProxyCommand
, les valeurs %h
et %p
sont remplacées
par OpenSSH respectivement par le nom de machine et le port du proxy SOCKS que
le client OpenSSH devra utiliser. Voir les informations sur ProxyCommand
dans la page du manuel
ssh_config(5)
(page en anglais).
Depuis la version fournie sous Debian 7 (donc au moins depuis la version 6.0
d'OpenSSH, et peut-être quelques versions antérieures), il est même possible
d'ouvrir automatiquement le proxy SOCKS lorsqu'on initie une connexion qui doit
l'utiliser et d'y rediriger les connexions, grâce à l'option -W
du client
OpenSSH. Ceci va donc permettre l'ouverture et la fermeture transparentes du
proxy lors de l'utilisation d'une connexion qui en a besoin.
Dans le fichier de configuration du client ssh ~/.ssh/config
, on va donc
définir deux entrées pour servir d'exemple :
- la première correspondant au proxy connecté à la passerelle SSH (ici
passerelle.example.org
) ; - et la seconde pour une machine derrière le pare-feu, accessible via le proxy
(
host1.example.org
) :
Host passerelle.example.org
User hkrustofski
DynamicForward 1080
Host host1.example.org
User hkrustofski
ProxyCommand ssh -W %h:%p passerelle.example.org
On peut ensuite ouvrir les connexions passant par le proxy de manière tout à fait transparente :
Cette configuration est bien sûr valable aussi pour des clients comme scp
:
Notez que dans tous les cas, la résolution DNS se fera de l'autre côté du tunnel, c'est à dire sur la passerelle SSH.
Si l'on dispose de plus d'une machine dans le réseau protégé auxquelles on veut
se connecter, il n'est pas obligatoire de toutes les définir dans le fichier
comme on l'a fait pour host1.example.org
pour simplifier l'exemple ci-dessus.
On peut définir une entrée valable pour toutes les machines dont le nom
correspond par exemple au motif *.example.org
qui imposera l'ouverture et
l'utilisation du proxy. Cette configuration est décrite dans la section
ci-dessous.
Note: pour les versions précédentes d'OpenSSH, il était nécessaire d'utiliser l'outil netcat (seulement sa version openbsd, fournie sous Debian par le paquet netcat-openbsd) pour transférer les connexions vers le proxy SOCKS, et d'ouvrir manuellement le proxy avant de lancer les connexions l'utilisant.
La configuration ressemblait donc à ceci :
Host passerelle.example.org
User hkrustofski
DynamicForward=1080
Host host1.example.org
User hkrustofski
ProxyCommand /bin/nc.openbsd -X 5 -x 127.0.0.1:1080 %h %p
Son utilisation consistait par exemple en les commandes suivantes (ouverture du tunnel avant la connexion l'utilisant) :
ssh -N passerelle.example.org &
ssh host1.example.org
Utilisation du proxy par un ensemble de connexions SSH
Pour une utilisation plus large, au lieu d'ajouter une entrée pour chaque
machine du réseau protégé comme c'est le cas dans l'exemple de la section
ci-dessus, il est possible de définir une entrée générique correspondant aux
machines de ce réseau protégé auxquelles on doit se connecter via la passerelle
SSH, si elles partagent un même suffixe DNS. L'entrée pourra par exemple être
valable pour toutes les machines dont le nom correspond au motif
*.interne.example.org
.
Voici un exemple un peu plus réaliste de fichier ~/.ssh/config
définissant
l'ouverture transparente du proxy SOCKS et son utilisation pour toute machine
dont le nom se termine en .example.org
:
Host passerelle
Hostname passerelle.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
DynamicForward=1080
Host *.example.org !passerelle.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
ProxyCommand ssh -W %h:%p passerelle
La première entrée définit l'accès au serveur SSH permettant l'accès aux
machines du réseau interne distant (ce qu'on a appelé la passerelle SSH). La
seconde sera utilisée pour toutes les machines dont le nom se termine en
example.org
, sauf passerelle.example.org
, et elle ouvrira et utilisera le
tunnel grâce à la directive ProxyCommand
.
Une fois cette configuration mise en place, toute connexion à une machine telle
que host.example.org
entraînera l'ouverture et l'utilisation du proxy
SOCKS qui relaiera les connexions via le serveur passerelle.example.org
.
Note : il est nécessaire d'exclure la machine passerelle.example.org
de
la seconde entrée de configuration, car depuis la version d'OpenSSH version 6.6
(et peut-être quelques versions avant celle-ci), OpenSSH va appliquer à
nouveau les configurations du fichier ~/.ssh/config
pour le nom de machine
correspondant à un alias, comme ci-dessus pour passerelle.example.org
. Sans
cette exclusion, toute connexion à un serveur en .example.org
entraînerait
une boucle infinie puisque le client chercherait à ouvrir le proxy pour se
connecter au proxy lui-même.
Astuce pour une utilisation courante
Pour se connecter en SSH à une machine interne sans avoir à taper son nom DNS
complet (qui doit pourtant être utilisé pour correspondre à la configuration
SSH *.interne.example.org
), on peut avoir recours à une fonction shell, comme
par exemple la fonction suivante qui complète son dernier argument par le nom
de domaine des machines internes :
Une fois cette ligne placée dans votre fichier .bash_aliases
ou .bashrc
, il
vous suffira de lancer la commande suivante pour vous connecter à la machine
scruffy.interne.example.org :
Pour scp, la fonction suivante, bien qu'imparfaite, devrait fonctionner dans la
plupart des cas (attention, elle remplace dans tous ses arguments « :
» par
« .interne.example.org:
», ce qui pourrait poser problème) :
Elle fonctionnera ensuite comme scp, mais en complétant le nom avant le
caractère « :
» par « .interne.example.org
» :