Tunnel SSH à la demande grace à Launchd

Mac OS X 10.4 a introduit Launchd, une technologie plutôt intéressante et à fort potentiel.
Le démon launchd(8) remplace init(8), il émule inetd, crontab, et ajoute une panoplie de fonctions.

Pour passer outre certains pare-feu, il faut parfois recourir à des tunnels ssh. On peut aussi utiliser un tunnel ssh pour sécuriser une connexion entre deux machines. Quoi qu'il en soit, il est rapidement assez pénible d'avoir à ouvrir manuellement son ou ses tunnels à chaque fois qu'on en a besoin. C'est là qu'intervient launchd.

Malheureusement, ce dernier ne nous permet pas de créer un tunnel qui soit exploitable immédiatement par l'application qui a fait la demande. Dans le cas d'un agent launchd qui lancerait la commande complète de tunnel `ssh -N -L 1190:fournisseur.tld:119 login@passerelle-ssh.tld` par exemple, le client usenet doit être configuré pour lire les news sur localhost:1190. Le lancement du client usenet fait donc sa requête sur localhost. launchd détecte la tentative de connexion et lance launchproxy, qui ouvre alors le tunnel. Ce mode de fonctionnement ne permet à pas de "brancher" le client usenet sur le tunnel créé. Si par contre on relance le client, ou si on lui fait refaire une tentative de connexion, il trouve le tunnel existant et tout fonctionne.

Pour contourner ce problème, certains utilisent une clé ssh établissant une simple connexion avec lancement d'application sur la passerelle ssh. Cette méthode a l'inconvénient d'utiliser une clé ssh dédiée pour chaque connexion. Le fait que toute la commande de connexion distante soit intégrée à la clé peut aussi rendre les tests fastidieux.
L'approche choisie ici est donc d'utiliser une clé ssh générique, et de s'en servir pour lancer sur la passerelle distante une commande de connexion.

Je passe la phase de création et de l'installation de la clé ssh, seule contrainte : elle doit être sans mot de passe (je n'ai pas testé de combinaison avec ssh-agent). Le système launchd fonctionne à base de fichiers de configuration, et le démon s'attend à trouver ces fichiers à des endroits précis. Pour un agent à l'usage d'un seul utilisateur, on utilise le dossier ~/Library/LaunchAgents/ (qu'il faut créer).
Avec l'éditeur de son choix, on crée l'agent launchd :

vi ~/Library/LaunchAgents/tld.passerelle-ssh.news
# on colle :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Debug</key>
	<false/>
	<key>Label</key>
	<string>tld.passerelle-ssh.news</string>
	<key>OnDemand</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/bin/ssh</string>
		<string>login@passerelle-ssh.tld</string>
		<string>nc</string>
		<string>fournisseur.tld</string>
		<string>119</string>
	</array>
	<key>Sockets</key>
	<dict>
		<key>Listeners</key>
		<dict>
			<key>SockServiceName</key>
			<string>1190</string>
			<key>SockType</key>
			<string>stream</string>
		</dict>
	</dict>
	<key>StandardErrorPath</key>
	<string>/tmp/news.err</string>
	<key>inetdCompatibility</key>
	<dict>
		<key>Wait</key>
		<false/>
	</dict>
</dict>
</plist>

On remplace bien évidemment passerelle-ssh.tld par le nom de domaine de la passerelle ssh, tld.passerelle-ssh.news par le nom de fichier choisi, fournisseur.tld par l'adresse du serveur distant, et 119 par le port du serveur distant qu'on veut contacter. On peut aussi remplacer 1190 par le port local sur le quel on souhaite faire la connexion.

Une fois le fichier sauvegardé, il faut le charger dans launchd :

launchctl load ~/Library/LaunchAgents/tld.passerelle-ssh.news

On peut vérifier le chargement par la commande `launchctl list`. Ensuite on procède au premier lancement de l'application qui va utiliser le tunnel. Néanmoins, si on souhaite tester le tunnel manuellement, on peut utiliser telnet. Le serveur fournisseur.tld:119 doit alors répondre quelque chose comme ceci :

telnet localhost 1190
Trying ::1...
Connected to localhost.
Escape character is '^]'.
200 fournisseur.tld NNRP Service Ready - news@fournisseur.tld (posting ok).

Si tout ne se passe pas comme prévu, vous pourrez peut être trouver quelques informations dans le fichier /tmp/news.err défini comme valeur pour StandardErrorPath. On peut aussi passer Debug à "true" au début du fichier. Chaque modification du fichier oblige à recharger l'agent via `launchctl load...`.

Attention : la configuration proposée n'est pas la plus sûre. La clé ssh générique sans mot de passe n'est pas forcément envisageable dans tous les contextes. C'est néanmoins une configuration simple très souple, c'est ce que j'ai choisi de privilégier.

Attention - BIS : pour éviter que n'importe qui profite de votre tunnel, il est impératif d'ajouter ces lignes au dessus de la ligne du SockServiceName :

                        <key>SockNodeName</key>
                        <string>127.0.0.1</string>

Ainsi votre tunnel sera lié uniquement à localhost, et non plus à toutes vos interfaces réseau.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.