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
Déjà 13 avis pertinents dans L’option ControlMaster de ssh_config
Les commentaires sont fermés.
Ça m’arrive aussi souvent de vouloir me reconnecter sur une machine derrière un VPN après un changement de connexion (wifi -> ethernet par exemple) sans pour autant fermer le socket pour ne pas avoir à me reconnecter sur mes différent term; la connexion fini par se rétablir avec un peu de temps (merci TCP), mais quand on a besoin de faire une action rapidement sur la machine, il y a l’option ‘-S’ de ssh qui est bien pratique. Un « ssh -S none user@machine » permet de désactiver temporairement le partage des connexions.
Je rajouterais deux petits points:
* pour le gain de temps, quand on se connecte via mot de passe au lieu d’utiliser une clé ssh, ça évite de devoir retaper le mot de passe à chaque nouvelle connexion vu qu’on réutilise la même, y compris quand on utilise ‘scp’, ce qui commence à ne plus être si négligeable quand on doit scp des fichiers au fur à mesure qu’ils arrivent sans avoir à rentrer le mot de passe (oui bon, la vrai bonne solution c’est d’utiliser une clé ssh, mais disons que c’est toujours pratique pour certains cas).
* si l’on s’est connecté plusieurs fois à une machine et que l’on termine sa session sur la première connexion (celle qui à mis en place le partage de connexion), « ssh » ne va pas rendre la main tant qu’il reste d’autres connexion qui utilise le socket. C’est bien de le savoir pour ne pas couper les autres connexions toujours en cours quand on a le CTRL^C un peu rapide (c’est du vécu ^^).
Ouais… les problèmes de sécurité remixés à toutes les sauces je commence à trouver ça ridicule. C’est une évidence que si un mec a accès à ta session, ça peut poser de gros problèmes, c’est aussi intéressant et pertinent de dire qu’on risque de sentir le vent souffler à côté d’une fenêtre ouverte.
Prend pas la remarque pour toi mais il y a problème de sécurité et problème de sécurité.
Tcho !
Merci pour ton commentaire intéressant, je compte creuser les moyens pour ne pas perdre la connexion à mes serveurs en cas de perte de réseau. Je commence à tâtonner du côté des options ServerAlive*.
Tcho !
Pour ce qui est des reconnexions automatiques en cas de perte de réseau ponctuelle ou de switch d’une interface à l’autre, je recommande mosh, qui est pour moi le complément indispensable de ssh pour gagner en confort sur une liaison lente (retours claviers instantanés car « anticipés ») et/ou intermittente (reconnexions automatiques). mosh s’appuie sur ssh pour lancer sa session (authentification) mais s’en détache ensuite (ce qui implique de gérer en plus (NAT, pare-feu) quelques ports UDP à partir de 60000 en plus du TCP 22). J’ai testé : il ne crée pas de socket avec la config présentée ici. Bon, après, mosh sera préféré pour des sessions interactives là ou ssh sera utilisé pour lancer des commandes distantes ponctuelles.
Je connais mosh, c’est bien que tu en parles pour ceux qui connaissent pas. Dans mon esprit mosh est pour d’autres besoins genre mobilité au milieu de nulle part en 4G. Je suis loin d’en être là, je vais chercher des solutions dans les options de SSH auparavant.
Tcho !
Merci pour cette découverte, je n’ai jamais trop pris le temps de regarder les options de ssh, cette option est super.
Personnellement, j’ai préféré utiliser un filesystem en RAM et personnelle comme /run/user/$idUser pour le stockage des sockets.
Quels sont les éléments de réflexion qui t’on amenés à choisir 4h pour la persistance ? Tu travailles en demie-journées ?
4h me paraît suffisant et assez large, j’ai aussi raisonné en demi-journée de travail (pause-déj au bout de 4h).
Tcho !
Intéressant: Keychain (https://www.funtoo.org/Keychain) – Pour les Mas users, voir https://github.com/funtoo/keychain/issues/106#issuecomment-448706710
Yep pour autossh (Most => mosh), concernant Keychain : https://www.bloglibre.net/2018/05/13/autour-de-ssh/
Tcho !
que se passe-t-il si la directive ControlPath est ABSENTE ?
Merci d’avance pour l’éclaircissement que vous pourriez me fournir.
man ssh_config puis /ControlMaster : « If the ControlPath cannot be opened, ssh(1) will continue without connecting to a master instance ».
On le confirme aisément en faisant
time ssh monserveur.bogosse.net exit
puis un autretime ssh monserveur.bogosse.net exit
, le temps nécessaire pour se connecter est sensiblement identique. Lorsqu’on renseigne ControlPath et qu’on relance 2 foistime ssh monserveur.bogosse.net exit
, on voit que la seconde connexion est faite dix fois plus vite.Tcho !