Une compilation de documentations   { en , fr }

Les tunnels avec OpenSSH

Étiquettes:
Créé en:
Dernière modification:
Auteur:
Xavier Béguin

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 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 :

ssh -N -L3033:host1.example.org:80 hkrustofski@passerelle.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 commande kill %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'adresse 127.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 machine host1.example.org sur le port 80 en passant par la passerelle sur laquelle on se connecte en SSH. Notez que le nom DNS host1.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 nom localhost comme machine de destination pour vous y connecter. Ce nom désignera dans ce cas l'interface locale de la passerelle.
  • 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 :

ssh -N tunnel &

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 (ici 3033) 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 :

ssh -D 1080 hkrustofski@passerelle.example.org

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 :

server = 127.0.0.1
server_type = 5
server_port = 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) :

env LD_PRELOAD=/usr/lib/libtsocks.so ssh -v hkrustofski@192.168.102.1

On peut aussi utiliser la commande équivalente suivante  :

tsocks ssh -v hkrustofski@192.168.102.1

ou encore :

tsocks -on
ssh -v hkrustofski@192.168.102.1
tsocks -off

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 :

ssh host1.example.org

Cette configuration est bien sûr valable aussi pour des clients comme scp :

scp fichier.dat host1.example.org:vers/le/repertoire/

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 :

sshi() { ssh ${@:1:$(($#-1))} ${!#}.interne.example.org; }

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 :

sshi scruffy

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

scpi() { scp "${@/:/.interne.example.org:}"; }

Elle fonctionnera ensuite comme scp, mais en complétant le nom avant le caractère « : » par « .interne.example.org » :

scpi fichier.txt scruffy:
scpi -r scruffy:/le/repertoire/distant/ /un/repertoire/local/