Faire passer son trafic dans un tunnel SSH

Il y a un petit moment j'avais présenté une méthode pour envoyer des mails à travers un tunnel SSH. Après un an de bons et loyaux services, j'en arrive à vouloir étendre ce système bien pratique à un plus grand nombre de ports. Alors VPN ? Ben non, encore ce bon vieux SSH mais utilisé d'une "nouvelle" manière.

Le besoin

Le besoin d'origine était de pouvoir, de mon portable et de n'importe où, envoyer un mail de manière sécurisée. J'avais à l'époque joué avec la capacité de redirection de ports de SSH. J'envoyais alors mon courrier sur le port 25 local et il était émis à travers un tunnel chiffré sur le port 25 de mon serveur de courrier.

Mais récemment, j'ai été amené à étendre cette logique à d'autres services. Étant obligé pendant un temps de passer par une connexion 3G, j'ai ainsi voulu appliquer le même principe à la réception de mail (IMAP) mais aussi au chat (bitlbee & IRC) et au web. En gros à tout mon trafic externe quoi.

Pour ceux qui se demanderait quel intérêt peut on avoir de s'enquiquiner de la sorte, une petite liste non exhaustive des raisons :

  • Déjà pour accéder à des services sur une machine distante que l'on ne souhaite pas ouvrir sur le net généralement par soucis de sécurité ET de simplicité. C'est typiquement le cas de mon service SMTP. Passer par un tunnel me permet d'envoyer du courrier comme si j'étais sur mon réseau local, de manière sûre et sans problématique de sécurité supplémentaire.
  • Ensuite, pour ne plus se soucier d'emprunter des connexions douteuses. En effet en mode "nomade", on est déjà content d'avoir un réseau, on ne va pas en plus faire le difficile. Et pourtant, entre la 3G d'un opérateur qui porte une couleur comme marque et qui pratique le DPI à ses heures perdues, la connexion d'un hôtel dont tout les routeurs ont admin/admin comme identifiant/mot de passe, la borne WIFI du voisin imprudent qui ne l'est peut-être pas, il vaut mieux être un minimum méfiant. Avoir son trafic ainsi chiffré est une véritable paix de l'esprit dans ce genre de situation.
  • Enfin simplement pour améliorer la qualité des connexions. On ne parle pas non plus de miracle, mais un tunnel SSH dispose de deux caractéristiques bien pratiques. D'abord il est possible d'activer la compression des données, ce qui dans certain cas est très significatif. Un fichier texte (ex. un mail) se comprime à 30% de son volume initial... Ensuite le tunnel dispose de mécanisme permettant de vérifier que la connexion est toujours viable et permet même de récupérer sa connexion en cas de coupure (pas trop longue).

Autant de raisons qui font du tunnel chiffré un précieux outil. Reste maintenant à voir comment généraliser la bidouille d'origine à tout le trafic sans pour autant passer par un VPN pur et dur ou par une avalanche de TUN/TAP/iptables.

SOCKS

SOCKS est un protocole qui permet (en simplifié) à une application cliente d'accéder aux ressources réseau d'une machine distante. Ainsi, au lieu d'accéder directement au net, l'application cliente va uniquement causer au service SOCKS qui lui va parler au net. Dans sa version 5 (RFC 1928) ce protocole permet outre la transmission TCP, la transmission UDP, l'authentification, la résolution des noms de domaine et la prise en charge d'IPv6.

Or il se trouve que dans sa grande générosité, SSH offre le protocole SOCKS V5. Une application cliente va ainsi pouvoir parler SOCKS dans un port ouvert par SSH pour cet usage, ce dernier va faire transiter les demandes par le tunnel SSH et ces dernières seront "exécutées" sur la machine distante.

Pour tester cela en 10 secondes, rien du plus simple. Il vous faut une machine distante accessible et bien évidemment connectée au net et un compte sur cette machine. Si tel est le cas, dans un terminal, lancez la commande suivante

gaston$ssh -nvNT -C -D 1080 gaston@mon_serveur_distant
...
debug1: Local connections to LOCALHOST:1080 forwarded to remote address SOCKS:0
...
debug1: Entering interactive session

Voilà c'est gagné. Vous avez maintenant un service local SOCKS sur le port 1080 prêt à discuter bout de net avec la machine distante.

On s'arrête un peu sur les arguments utilisés. -n indique juste qu'il n'y a aucune saisie clavier à attendre de notre part (dit autrement /dev/null redirigé sur stdin). -N pour signifier à SSH qu'il n'y a aucune commande à lancer, on ne cherche qu'à créer un tunnel pour le proxy SOCKS. Et pour enfoncer le clou, -T désactive toute allocation de pseudo terminal.

-D 1080, c'est la partie intéressante, on demande à SSH de créer un service SOCKS sur le port local 1080. À partir de là, toute requête faite sur le port 1080 de la machine locale, passera par le tunnel chiffré et sera exécutée sur la machine distante.

-C va quant à lui activer la compression du trafic et enfin -v est juste là pour rendre SSH plus bavard. Suivent enfin le nom d'utilisateur à utiliser pour la connexion et le nom de la machine distante.

Pour tester cela, prenez Iceweasel/Firefox et allez modifier les réglages dans Édition ⇨ Préférences ⇨ Avancées ⇨ Réseau ⇨ Réglages pour cocher Configuration manuelle du proxy. Dans le champs SOCKS host nous allons mettre localhost et 1080 dans le champ Port. Cochez SOCKS v5 puis validez. Il ne vous reste plus qu'à aller sur votre site préféré pour voir SSH jacasser dans son terminal, votre trafic passe maintenant par le tunnel SSH.

Petite note pour ceux qui comme moi sont amoureux de VIM et qui utilisent donc forcement le fantastique pentadactyl, il est possible d'activer/désactiver le proxy en ajoutant ceci à votre ~/.pentadactylrc

command! -nargs=1 proxy :set! network.proxy.type=<args>

Ceci fait, un coup de :restart et vous pouvez activer le proxy par :proxy 1 et le désactiver (et donc ne plus utiliser le tunnel) par :proxy 0.

Script de contrôle du tunnel

Tout ceci est bien sympa, reste à le rendre fonctionnel à travers un script qui pourra allumer, éteindre et vérifier l'état du tunnel. Dans la mesure où je suis un membre de la secte "moins je touche au système et plus j'en ai dans mon dossier ~, mieux je me porte, je crée ici un script qui sera exécutable sans droits particulier tout en fonctionnant de manière très similaire aux scripts présents en /etc/init.d :

#! /bin/bash

# nom du service
name=tunnel

# paramétres
port=1080
user=gaston
host=machine_distante

# PID et logs
pid_file=~/.local/var/run/$name
log_file=~/.local/var/logs/$name.log
mkdir -p $(dirname $pid_file) $(dirname $log_file)

# Arrêt du service
function stop {
  if [ -f $pid_file ] ; then
    echo -n "Arrêt de $name..."
    kill -9 $(cat $pid_file)
    rm $pid_file
    echo "OK"
  fi
}

# Démarrage du service
function start {
  stop
  echo -n "Démarrage de $name..."
  command="ssh -nvNT -C -D $port $user@$host"
  ($command &> $log_file) &
  echo $! > $pid_file
  until nc -zw30 localhost $port &> /dev/null ; do
    echo -n "."
    sleep 1
  done
  echo " OK"
}

# test
function check {
  if nc -zw30 localhost $port &> /dev/null ; then
    echo "actif"
    return 0
  else
    echo "inactif"
    return 1
  fi
}

# commandes
case $1 in
  "start")
    start ;;
  "stop")
    stop  ;;
  "check")
    check ; exit $? ;;
  *)
    echo "usage: $(basename $0) start|stop"
    exit 1
esac

Ce script fonctionne donc comme les scripts de démarrage de service système à la différence prés que le PID du processus SSH et ses logs sont stockés en local, sur mon compte, dans les dossiers ~/.local/var/logs et ~/.local/var/run/.

Le tunnel se lance par un tunnel start, s'arrête par un tunnel stop et son état peut être lu par un tunnel check. Cette dernier commande me permet d'afficher l'état du tunnel dans la barre d'info d'awesome ce qui est bien pratique.

Pour compléter le dispositif, vous pouvez configurer dans votre ~/.ssh/config un temps au bout duquel SSH devra vérifier si le tunnel est encore fonctionnel. Bien utile lorsque l'on passe par de la 3G.

Host *
  ServerAliveInterval 5
~/.ssh/config

Ouvrir le tunnel à la connexion

Il est possible de pousser un cran plus loin l'automatisation en lançant automatiquement le tunnel à la connexion et en le tuant à la déconnexion. En tout cas c'est possible avec l'excellentissime wicd. Pour ceux qui ne connaîtrait pas cet outil, c'est le gestionnaire de connexion le plus efficace pour qui construit un bureau minimaliste. Il permet de gérer les connexions filaires, les connexions USB/RNIS (modem 3G via android), et les connexions WIFI de manière fiable et robuste, à travers une interface en ligne de commande ou en ncurses (il y aussi une version graphique pour ceux que cela amuse).

Wicd autorise la mise en place de scripts exécutés avant et après la connexion et la déconnexion. Ces scripts ne sont malheureusement pas dans un dossier ~/.wicd ce qui est bien triste mais dans le dossier /etc/wicd/scripts. Il suffit donc de lancer le tunnel en ajoutant le code suivant dans /etc/wicd/scripts/postconnect/tunnel.sh

#! /bin/bash

sudo -u gaston /home/gaston/bin/tunnel start
/etc/wicd/scripts/postconnect/tunnel.sh

Le second script je vous laisse l'écrire, il se place dans /etc/wicd/scripts/postdisconnect/tunnel.sh.

À partir de là, il suffit de lancer wicd-ncurses, de se connecter et hop, le tunnel est actif.

Et les autres applications ?

La grande majorité des applications qui utilisent le net offrent un support pour SOCKS v5. Une page bien pratique pour savoir comment faire est le manuel de configuration des applications pour le réseau TOR.

Cependant pour toutes les autres applications qui ne savent pas utiliser un proxy SOCKS (au hasard mutt), il existe une solution magique : proxychains

Proxychains est un superbe utilitaire qui va servir de lanceur pour les applications réseau. Son fonctionnement consiste à intercepter les appels aux sockets pour les rediriger sur le serveur mandataire qui va bien. Cela fonctionne très bien pour la grande majorité des applications. Cela permet aussi d'utiliser le proxy sur des applications qui prennent en charge SOCKS mais pour lesquels nous n'avons envie de nous prendre la tête.

Proxychains est en standard dans les dépôts Debian (et sûrement dans celui d'autres distribs). Pour l'installer vous devez donc lancer un sudo aptitude install proxychains.

Lorsque c'est fait, prenez votre éditeur préféré et éditez ~/.proxychains/proxychains.conf :

strict_chain
quiet_mode
proxy_dns
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5   127.0.0.1 1080

L'option strict_chain indique que les proxys doivent être chaînés dans l'ordre de leur déclaration. En effet, l'outil est velu et ne porte pas ce drôle de nom pour des prunes. Il permet en effet de chaîner toute une série de proxys les uns aux autres. Mais dans notre cas seul un proxy sera déclaré, pas besoin donc d'aller plus loin.

quiet_mode demande gentiment à proxychains d'arrêter de jacasser dans stdout. Mine de rien sans cela c'est assez pénible. proxy_dns va quant à lui permettre de faire aussi passer les requêtes DNS dans le tuyau du proxy. C'est un peu plus long mais ainsi tout passe dans le tunnel sans écoute possible.

Je passe sur les quelques timeouts de bonne hygiène pour en venir à l'essentiel, la déclaration de la liste des proxy qui dans notre cas ne contiendra que socks5 127.0.0.1 1080, le service que nous avons créé plus haut.

Il ne reste maintenant plus qu'à lancer une commande nécessitant l'accès au réseau :

gaston$proxychains wget -O - http://artisan.karma-lab.net

Et c'est tout. Comme vous le voyez, c'est plutôt simple d'usage et cela fonctionne sur à peu prés toutes les commandes (mutt, wget, telnet, etc). La seule application qui lui dit "crotte" jusqu'ici c'est weechat qui dispose de toute façon d'une très bonne prise en charge native de SOCKS.

Amélioration de la vérification du tunnel

Fort de notre connaissance de proxychains, nous pouvons améliorer le script d'ouverture du tunnel pour ne pas simplement dire actif lors d'un tunnel check mais de nous renvoyer plutôt la qualité du tunnel, ce qui est fort pratique.

Pour cela il nous suffit de modifier comme ceci

function check {
  if nc -zw30 localhost $port &> /dev/null ; then
    lattence=$(proxychains nmap -sP azuris.karma-lab.net|grep -Eo '[[:digit:]]+\\.[[:digit:]]+s' | tr -d 's')
    echo $lattence
    return 0
  else
    echo "inactif"
    return 1
  fi
}

Le principe est d'utiliser la commande nmap pour aller faire un ping sur le serveur distant à travers le tunnel via proxychains. Une fois le ping reçu, nmap fournit la latence exprimée en secondes que l'on n'a plus qu'à grepper.

Conclusion

Une fois de plus SSH s'impose en outil incontournable. Comme je le disais plus haut, chez moi le tunnel SOCKS fait maintenant partie intégrante de mon "bureau", se lance tout seul dés lors qu'une connexion sur le net est ouverte. Et comme tout ceci ne repose que sur SSH et des scripts "userland", cela fonctionne de manière propre et homogène sur toutes mes machines sans configuration (mon dossier home étant synchronisé d'une machine à l'autre via unison).

Vus : 3176
Publié par Artisan Numérique : 100