1. Créer une clé pour le tunnel
Sur la machine qui démarre le tunnel, créez un compte local et une clé sans phrase secrète, car le service démarre sans personne. Enregistrez ensuite la clé d'hôte du serveur, pour que la première connexion ne s'arrête pas pour poser la question :
sudo apt install autossh
sudo useradd -m -s /usr/sbin/nologin tunnel
sudo -u tunnel mkdir -m 700 /home/tunnel/.ssh
sudo -u tunnel ssh-keygen -t ed25519 -N "" -C tunnel -f /home/tunnel/.ssh/id_ed25519
ssh-keyscan -t ed25519 server | sudo -u tunnel tee /home/tunnel/.ssh/known_hosts
Comparez cette clé d'hôte avec l'empreinte réelle du serveur avant de vous y fier. Voir known_hosts expliqué.
2. Restreindre le compte sur le serveur
Une clé sans phrase secrète sur une machine distante ne doit ouvrir que ce tunnel. Sur le serveur, créez un compte nommé tunnel et mettez la clé publique dans son ~/.ssh/authorized_keys :
restrict,port-forwarding ssh-ed25519 AAAA... tunnel
Ajoutez ensuite ceci à la fin de /etc/ssh/sshd_config :
Match User tunnel
AllowTcpForwarding remote
PermitListen localhost:2222
X11Forwarding no
ForceCommand /usr/sbin/nologin
sudo sshd -t && sudo systemctl reload ssh
Nous avons testé le résultat avec cette clé :
| Essai | Résultat |
|---|---|
-R 2222:localhost:22 | Fonctionne |
-R 2223:localhost:22 | remote port forwarding failed for listen port 2223 |
| Lancer une commande ou un shell | This account is currently not available. |
-L 9000:localhost:22 | Connexion fermée, rien ne passe |
La dernière ligne demande le bloc Match. Avec seulement permitlisten="localhost:2222" dans authorized_keys, une redirection -L fonctionnait encore lors de notre test, ce qui permettait à la clé d'atteindre les services sur la boucle locale du serveur. Plus de détails sur les options de clé dans comment restreindre une clé SSH.
3. Créer le service
Enregistrez ceci dans /etc/systemd/system/autossh-tunnel.service :
[Unit]
Description=Reverse SSH tunnel to server
Wants=network-online.target
After=network-online.target
[Service]
User=tunnel
Environment=AUTOSSH_GATETIME=0
ExecStart=/usr/bin/autossh -M 0 -N -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -i /home/tunnel/.ssh/id_ed25519 -R 2222:localhost:22 tunnel@server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now autossh-tunnel.service
systemctl status autossh-tunnel
Le rôle de chaque élément
-M 0désactive le port de surveillance propre à autossh : il ne relance doncsshque lorsquesshse termine. Le README d'autossh suggère les options de keepalive pour remplacer le port de surveillance.ServerAliveInterval=30etServerAliveCountMax=3font quittersshaprès environ 90 à 120 secondes de silence du serveur. Dans notre test, un intervalle de 5 secondes avec le même nombre a pris 20 secondes, et un gel de 100 secondes avec ces valeurs ne l'a pas déclenché.ExitOnForwardFailure=yesfait quittersshsi le port ne peut pas être ouvert. Sans lui, vous pouvez avoir une connexion active sans tunnel.AUTOSSH_GATETIME=0fait qu'autossh continue d'essayer même si la première tentative échoue, ce qui compte au démarrage.Restart=alwaysrelance le service si autossh lui-même meurt.- N'utilisez pas l'option
-fd'autossh ici, car systemd attend que le processus reste au premier plan.
4. Le voir se rétablir
| Ce que nous avons fait | Ce qui s'est passé |
|---|---|
| Tué la session sur le serveur | autossh a lancé aussitôt un nouveau ssh |
| Gelé le serveur pendant 28 secondes, avec un intervalle de 5 secondes | Timeout, server server not responding. après 20 secondes, puis une nouvelle connexion dès le retour du serveur |
Tué autossh avec kill -9, ainsi que son ssh | systemd a relancé le service, et NRestarts est passé à 1 |
Observez-le vous-même avec journalctl -u autossh-tunnel -f. Sur le serveur, ss -ltn | grep 2222 montre le port.
Si le port reste pris après une panne réseau
Après une vraie coupure, le serveur peut garder l'ancienne connexion et son port : la nouvelle échoue alors avec remote port forwarding failed jusqu'à l'expiration de l'ancienne. Dans le bloc Match du serveur, ajoutez :
ClientAliveInterval 30
ClientAliveCountMax 3
Le serveur abandonne alors les clients morts après environ 90 secondes, et les nouvelles tentatives réussissent.
Pour les autres raisons d'un port occupé, voir tunnel SSH : Address already in use et remote port forwarding failed.
Avez-vous vraiment besoin d'autossh
Avec systemd et les options de keepalive, ssh seul fonctionne aussi. ssh se termine, et Restart=always le relance. Remplacez la commande dans l'unité par :
ExecStart=/usr/bin/ssh -N -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -i /home/tunnel/.ssh/id_ed25519 -R 2222:localhost:22 tunnel@server
Dans notre test, cela s'est rétabli après une session tuée avec un seul redémarrage systemd, après les 10 secondes de RestartSec. autossh redémarre plus vite, et c'est l'option à prendre si vous n'avez pas systemd.
Une différence observée : systemctl restart a journalisé status=1/FAILURE pour autossh à son arrêt. Le service a quand même redémarré normalement.
Questions
Que fait autossh -M 0 ?
Il désactive le port de surveillance. autossh relance alors ssh seulement quand ssh se termine, et les options ServerAliveInterval et ServerAliveCountMax font terminer ssh quand la connexion meurt.
Faut-il autossh avec systemd ?
Pas toujours. Un simple ssh avec les options keepalive et Restart=always revient aussi. autossh redémarre plus vite et fonctionne sans systemd.
Pourquoi mon tunnel inverse échoue-t-il après une coupure réseau ?
Le serveur peut encore garder l'ancienne connexion et son port. Ajoutez ClientAliveInterval 30 et ClientAliveCountMax 3 côté serveur pour qu'il coupe les clients morts après environ 90 secondes.
Sources
- README d'autossh
- ssh_config(5), manuel OpenBSD
- sshd_config(5), manuel OpenBSD
- Manuel systemd.service(5)