L’option ControlMaster de ssh_config

D’habitude j’essaie d’être synthétique, ce ne sera pas le cas cette fois, l’article sera long. Je voudrais vous montrer comment je procède lorsque je creuse un sujet et les voies tortueuses pour apprendre, comprendre.

L’option ControlMaster

D’après le man ssh_config, elle permet d’activer le partage de multiples sessions à travers une seule connexion réseau. On ne le dit pas assez mais le man c’est brut de décoffrage, celui qui comprend à quoi cette option peut servir à partir de l’explication est un génie. Je vais faire la traduction en vous parlant de son usage et de son intérêt.

Lorsqu’on se connecte à un serveur en SSH, on passe par une phase d’authentification avant d’être connecté. C’est en général très rapide, j’ai fait quelques tests sur des serveurs au boulot, je tourne à 0.3s. Cela va dépendre de nombreux facteurs : la vitesse de votre connexion (fibre, ADSL…), votre méthode d’authentification (clés SSH, clés SSH et mot de passe…), l’emplacement géographique de votre serveur, etc. L’option ControlMaster va permettre de réutiliser la connexion que vous venez d’établir, la première connexion aura toujours cette durée autour de 0.3s (aucun changement) mais la seconde connexion au même serveur se fera beaucoup plus rapidement, je tourne à 0.03s.

Je n’ai pas vu un grand intérêt à cette option au départ puisqu’on parle de gagner du temps (connexion plus rapide) à partir de la seconde connexion à un même serveur, pourquoi se connecter plusieurs fois au même serveur ? En réalité, il y a de nombreuses occasions où vous allez vous connecter plusieurs fois au même serveur et un intérêt certain :
1/ On se connecte à un serveur à 9h00, on se déconnecte puis on y retourne 2h plus tard pour voir quelque chose. En temps normal vous aurez 2 phases d’authentification, à chaque fois 0.3s pour établir la connexion. Avec l’option ControlMaster activée, la première connexion sera de 0.3s, les autres dans la journée de 0.03s. À noter que certains lanceront un screen et resteront connectés toute la journée sur le serveur, ils se contenteront de rappeler leur session screen connectée au serveur (1 seule connexion)
2/ Lorsqu’on parle de connexion, pensez à vos scripts et tâches récurrentes : rsync, git, script qui va se connecter plusieurs fois à un serveur en lançant des commandes
3/ Dans mon job j’ouvre souvent plusieurs connexions sur le même serveur : Typiquement un terminal pour suivre les logs (less +F ou tail -f), le second pour éditer un fichier de conf, le dernier pour restart un service et lancer des tests (3 connexions). À noter que certains lanceront un screen et diviseront simplement le terminal en 3 (1 seule connexion) mais tout le monde ne sait/veut pas se servir de screen
4/ On peut toujours sourire devant des temps aussi courts mais à l’usage on voit et on sent la différence. Une fois goûté, difficile de s’en passer

Comprendre (et creuser)

Dans une majorité d’articles, vous aurez sensiblement cette configuration proposée à ajouter à votre ~/.ssh/config.

Host *
    ControlMaster auto
    ControlPath /tmp/socket-%r@%h:%p
    ControlPersist 3600

Host * signifie que les options suivantes s’appliqueront à tous les serveurs. On retrouve ControlMaster auto à l’identique dans tous les articles, je vous laisse chercher à quoi correspond auto et pourquoi c’est ce qu’il faut utiliser hé hé.

J’ai ajouté la partie suivante à mon ~/.ssh/config puis j’ai commencé à bosser avec. Pour bien comprendre quel est le rôle de chaque option, il est nécessaire de commencer par une config minimale.

Host *
    ControlMaster auto

Un jour je me connecte à un serveur puis je lance une seconde connexion dessus, je termine ma tâche et ferme ma première connexion, la seconde session (seconde connexion) est alors immédiatement fermée. Désagréable et potentiellement très problématique mais cela s’explique facilement. Souvenez-vous, on partage de multiples sessions à travers une seule connexion réseau. Là on a fermé la première session/connexion (par laquelle passe la seconde session), par conséquent les deux connexions sont coupées. On a besoin de l’option ControlPersist.

L’option ControlPersist

L’option ControlPersist va avoir deux utilités différentes en fonction du chiffre renseigné. Ce chiffre (3600 par exemple) spécifie le temps que la connexion maître doit rester ouverte en arrière-plan dans l’attente d’une future connexion. 3600 correspond à 3600s, on peut également écrire 1h (pour 1 heure) ou 60m (pour 60 minutes). Je préfère les notations claires et human-readable.

Maintenant utilisons dans ~/.ssh/config.

Host *
    ControlMaster auto
    ControlPersist 5

Je me connecte à un serveur puis je lance une seconde connexion dessus, je ferme ma première connexion, la seconde session reste ouverte. On vient de résoudre le problème cité plus haut (première utilité).

Renseignons dans ~/.ssh/config.

Host *
    ControlMaster auto
    ControlPersist 20

Pour voir le gain de temps lié à l’option ControlMaster, je vous invite à lancer time ssh cascador@monserveur.bogosse.net exit, j’ai sensiblement 0.3s. Relancez la commande, j’obtiens 0.03s. L’option ControlMaster fait son job. À noter que lorsque vous fermez une session « partagée » via l’option ControlMaster, vous avez le petit message : Shared connection to monserveur.bogosse.net closed.

À présent lancez time ssh monserveur.bogosse.net exit, attendez 1 minute (pour rappel ControlPersist est à 20s) puis relancez la commande, j’obtiens 0.3s à la première commande et la même chose à la seconde commande. On aborde là le point 1/ cité tout à l’heure (On se connecte à un serveur à 9h00, on se déconnecte puis on y retourne 2h plus tard…). L’option ControlPersist doit être réglée en conséquence, au minimum 2h pour notre exemple. On se connecte à 9h00 sur le serveur, à 9h05 on se déconnecte, à 11h02 on se connectera en 0.03s mais si ça avait été 11h10 alors la connexion se serait faite en 0.3s. Après quelques réflexions et tests, j’ai décidé de mettre ControlPersist 4h pour ma part. Résumons : Sur chaque serveur où je me connecte une première fois, je me connecterai bien plus rapidement à la prochaine connexion dans la limite de 4h (seconde utilité).

L’option ControlPath

Tout ceci fonctionne grâce aux sockets. Un socket de contrôle est utilisé pour le partage de connexion. L’option ControlPath spécifie le chemin d’accès au socket de contrôle.

Renseignons dans ~/.ssh/config.

Host *
    ControlMaster auto
    ControlPath /tmp/socket-%r@%h:%p
    ControlPersist 5m

Si je me connecte à mon serveur (ssh cascador@monserveur.bogosse.net) alors j’aurais un fichier /tmp/socket-cascador@monserveur:22 sur mon pc. D ‘après le man ssh_config toujours : %r the remote username, %h the remote hostname, %p the remote port. Cela permet d’identifier le socket utilisé pour la connexion à un serveur précis. Si on se déconnecte du serveur, le fichier /tmp/socket-cascador@monserveur:22 sera toujours présent… pendant 5 minutes (ControlPersist 5m). En supprimant ce fichier, on rompt la connexion au serveur et si on se connecte de nouveau au serveur, ça sera en 0.3s.

Pourquoi utiliser /tmp/socket-%r@%h:%p ? Bah justement c’est pas une bonne idée. Il est probable que vous soyez seul à utiliser votre pc, dans mon cas je partage mon pc portable avec Madame. Si je verrouille ma session utilisateur, qu’elle ouvre sa session puis fait ls -l /tmp : Elle saura quel nom d’utilisateur j’utilise, le nom de mon serveur et le port SSH. Pas glop si on considère qu’en matière de sécurité, moins l’attaquant a d’informations, mieux on se porte.

J’utilise ControlPath ~/.ssh/sockets/socket-%C. Donc mkdir -p ~/.ssh/sockets; chmod 700 ~/.ssh/sockets pour commencer. Les sockets sont dans mon home (chiffré), dans un répertoire (avec les droits qui vont bien) précis ~/.ssh/sockets/ et facilement retrouvable (il peut y avoir beaucoup de choses dans /tmp). %C Shorthand for %l%h%p%r, dans les faits ça donne un truc incompréhensible et c’est ce que je veux justement, exemple : ~/.ssh/sockets/socket-2416f65bfd78bq467a7887585sd1d4g456cc78e7. Certains diront mais du coup tu sais pas de quel serveur il est question, yes mais en gros je m’en fous et je m’y retrouve avec la date de modification du fichier (socket pour être précis).

À noter que certains préconisent /tmp car au reboot les sockets seront effacés… c’est le cas peu importe où vous les mettez, ça revient un peu à dire qu’une connexion réseau survit à un reboot…

Sockets, où ils sont mes sockets

On arrive au gros drame. Ce problème est tellement énorme qu’en ce qui me concerne j’ai failli rejeter l’utilisation de ControlMaster, attention cependant il s’agit d’un cas très particulier (perte de réseau) il est probable que ça concerne une minorité de personnes. Si vous bossez au bureau, ça ne vous arrivera qu’extrêmement rarement. Je bourlingue beaucoup : Datacenter, coworking, maison… les déconnexions Wi-Fi sont assez régulières. Plantons le décor : Je suis en datacenter, je suis déconnecté du Wi-Fi, je ne peux donc plus lancer de commandes sur les serveurs sur lesquels j’étais connecté, en général je déconnecte mon OpenVPN, je ferme mon terminal (plusieurs onglets, XX connexions) puis une fois reconnecté je relance mon OpenVPN. Drame, je lance mes connexions aux serveurs mais j’attends indéfiniment que les connexion se fassent. Que se passe-t-il Sherlock ?

Il se passe que les sockets sont toujours présents dans ~/.ssh/sockets/, quand je lance une connexion à un serveur où j’étais précédemment connecté SSH utilise un de ces sockets qui ne fonctionne plus (car j’ai perdu la connexion réseau quelques instants auparavant). Je supprime tous les sockets dans ~/.ssh/sockets/, je peux de nouveau me connecter à mes serveurs.

Ce problème, je n’ai pas vu un article en parler et pourtant quelle saloperie ! J’ai mis en place une solution qui me paraît assez propre et à l’usage, ça le fait. J’utilise beaucoup deux alias, le premier pour lancer la connexion au VPN de ma boîte, le second pour kill cette connexion VPN (alias pko='sudo pkill openvpn'). J’ai simplement ajouté ce dont j’avais besoin à la suite donc alias pko='sudo pkill openvpn; for socket in $(find ~/.ssh/sockets/ -type s); do ssh -o ControlPath=$socket -O exit toto 2>/dev/null || rm $socket; done'.

Ce sera plus lisible ainsi.

for socket in $(find ~/.ssh/sockets/ -type s); do
    ssh -o ControlPath=$socket -O exit toto 2>/dev/null || rm $socket
done

C’est une boucle basique.
for socket in : Pour chaque socket
find ~/.ssh/sockets/ -type s : Trouver les sockets (-type s) dans le dossier ~/.ssh/sockets/
ssh -o ControlPath=$socket -O exit toto 2>/dev/null || rm $socket : On lance ssh -o ControlPath=$socket -O exit toto, si le code de sortie est différent de 0 alors on rm $socket

On pourrait me faire le reproche que c’est super-ultra-méga propre parce que 1/ On sait où chercher (~/.ssh/sockets/) et a priori il n’y aura que des sockets dans ce dossier 2/ un simple rm ~/.ssh/sockets/socket-* fait le job. Ouais c’est pour vous expliquer quelle est la bonne/élégante manière de faire. Je ne sais pas si c’est nécessaire, personne ne démonte ses partages réseaux (CIFS, NFS…) « proprement » avant d’éteindre son pc par exemple.

ssh -o ControlPath=$socket -O exit toto 2>/dev/null what ??

ssh -o ControlPath=$socket -O exit est la manière propre de fermer un socket, je vous invite à man ssh puis rechercher -O ctl_cmd. On peut également faire ssh -O exit cascador@monserveur.bogosse.net.

Pourquoi toto ? Parce que si j’avais mis autre chose (genre cascador@monserveur.bogosse.net), vous auriez pensé que la commande allait se connecter au serveur pour lui dire ferme le socket. La commande qu’on envoie ici est en local, le socket qu’on va exit est en local dans le dossier ~/.ssh/sockets/ MAIS la syntaxe d’une commande ssh étant ssh hostname au minimum, on est obligé de fournir un hostname sinon on aura une erreur. À noter que certains écrivent ssh -o ControlPath=$socket -O exit localhost, je trouve ça trompeur car on pourrait croire que localhost est la seule/bonne réponse alors que vous pouvez mettre indifféremment babar, troubadour, cunnilingus…

Autre subtilité faisons ssh -o ControlPath=/home/cascador/.ssh/sockets/socket-2416f65bfd78bq467a7887585sd1d4g456cc78e7 -O exit carambar. La commande s’exécute bien, on a le code retour 0 si on fait echo $? (code retour de la dernière commande) MAIS on a un message « Exit request sent » qui lui ne sort pas sur stdout mais stderr (d’où le 2>/dev/null). Ce qui veut dire qu’on peut avoir une commande qui s’exécute bien mais une sortie sur stderr. Je ne savais pas que c’était possible, ça se trouve c’est commun mais je n’avais jamais fait attention. Tout l’intérêt de creuser les choses, on découvre.

C’est bon là, t’as fini ?

Ouais c’est bon, j’espère avoir bien illustré la différence entre la documentation et la pratique. C’est en forgeant qu’on devient forgeron. Aujourd’hui j’utilise ça.

Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/socket-%C
    ControlPersist 4h

À noter que vous n’êtes bien sûr pas obligé d’utiliser ces options pour tous les serveurs, un dev qui met ces options pour sa connexion à son serveur de dev pour push/pull y aura déjà un intérêt.

Tcho !

Sources :
http://www.qanuq.com/2017/09/09/diminuer-temps-connexion-ssh/
https://developer.rackspace.com/blog/speeding-up-ssh-session-creation/
https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
http://www.anchor.com.au/blog/2010/02/ssh-controlmaster-the-good-the-bad-the-ugly/
https://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/
https://unix.stackexchange.com/questions/24005/how-to-close-kill-ssh-controlmaster-connections-manually
https://unix.stackexchange.com/questions/427189/how-to-cleanup-ssh-reverse-tunnel-socket-after-connection-closed

Vus : 460
Publié par blog-libre : 133