Toutes les pages
Sur cette page

Joindre le relais par un tunnel SSH

Le relais peut ouvrir une connexion SSH vers un serveur à vous et demander à ce serveur de lui renvoyer un port. Vous joignez alors le relais à l’adresse du serveur, et rien chez vous n’écoute internet. Le relais démarre, surveille et redémarre lui-même le processus ssh.

Choisissez cette solution quand vous avez déjà un serveur avec une adresse publique et un accès SSH. Si vous n’avez pas de serveur, le Cloudflare Tunnel intégré n’en demande aucun.

Ce qu’il vous faut #

  • L’hôte du relais a un programme ssh dans son PATH (ou vous en désignez un autre avec command, voir plus bas).
  • Une connexion SSH au serveur qui fonctionne avec une clé et sans invite. Le relais lance ssh en mode batch ; il ne peut donc saisir ni mot de passe ni phrase de passe de clé. Une clé avec phrase de passe ne fonctionne que si un agent SSH la détient. Testez d’abord depuis l’hôte du relais : ssh relay@relay.example.com doit se connecter sans rien demander.
  • Un port libre sur le serveur pour le tunnel (remotePort).
  • Quelque chose sur le serveur qui sert ce port en HTTPS : un serveur web ou un proxy inverse comme Caddy ou nginx. Le tunnel transporte du HTTP en clair ; sans cela, votre token et vos conversations traversent internet sans chiffrement dès que le port est joignable. Voir Faire tourner le relais derrière un proxy inverse.

Le relais n’a besoin d’aucun réglage sshd particulier. Avec remoteBind à loopback, il demande le transfert sans adresse de liaison ; c’est donc le réglage GatewayPorts du serveur qui décide où le port s’ouvre. À sa valeur par défaut, no, c’est le serveur lui-même seulement, et le proxy inverse est la seule chose exposée à internet. Avec public, le relais demande toutes les interfaces (*:), et cette demande ne prend effet que si le sshd du serveur l’autorise (GatewayPorts réglé sur clientspecified ou yes).

La mise en place #

Ajoutez un bloc sshTunnel à config.json (voir Où se trouve le fichier) et redémarrez le relais avec oar restart.

"sshTunnel": {
  "mode": "managed",
  "required": false,
  "user": "relay",
  "host": "relay.example.com",
  "remotePort": 4444,
  "remoteBind": "loopback",
  "identityFile": "~/.ssh/id_ed25519"
}

Sur le serveur, envoyez le HTTPS vers le port du tunnel. Avec Caddy :

relay.example.com {
    reverse_proxy localhost:4444
}

Ouvrez https://relay.example.com/ dans un navigateur et connectez-vous avec votre token d’accès. La bannière de démarrage du relais affiche une ligne Remote: et oar url affiche une ligne Tunnel URL: avec un QR code. Les deux valent https:// suivi de la valeur de host, sans port et sans vérifier que le tunnel est actif ; elles ne sont donc justes que si votre proxy sert HTTPS sur le port standard.

Les clés sshTunnel #

Toutes les clés se trouvent dans le bloc sshTunnel. Le tableau concorde avec config.json, qui a la même liste.

Clé Type Par défaut Ce qu’elle fait
mode "disabled" ou "managed" "disabled" managed fait démarrer et redémarrer le tunnel par le relais.
user chaîne aucune L’utilisateur SSH sur le serveur. Obligatoire en mode géré.
host chaîne aucune Le nom d’hôte ou l’adresse du serveur. Obligatoire en mode géré.
remotePort nombre aucune Le port qui s’ouvre sur le serveur, de 1 à 65535. Obligatoire en mode géré.
remoteBind "loopback" ou "public" "loopback" loopback demande le port sans adresse de liaison (le GatewayPorts du serveur décide, voir Ce qu’il vous faut) ; public demande toutes les interfaces. Toute autre valeur compte comme loopback. Indépendant de localhostOnly.
identityFile chaîne aucune Le fichier de clé privée ; ~ est développé. Sans lui, ssh utilise votre agent et ses clés par défaut.
command chaîne ssh Le programme SSH, utilisé aussi pour le nettoyage sur le serveur. Un nom est cherché dans le PATH ; un chemin avec une barre oblique est utilisé tel quel, et un chemin relatif part du dossier du programme du relais.
required booléen false Tant que le tunnel n’est pas connecté, le relais ne prend aucun message dans la queue. Voir Requis.
autoReclaimPort booléen true Quand le port distant est déjà occupé, lancer un nettoyage sur le serveur et réessayer. Seul false le désactive.
reclaimStaleSshSessions booléen false Dans le cadre de ce nettoyage, mettre fin aussi à vos propres sessions SSH inactives sur le serveur.
remoteCleanupCommand chaîne intégrée Votre propre commande de nettoyage, lancée sur le serveur à la place de celle intégrée.

L’ancienne forme "enabled": true fonctionne encore et signifie "mode": "managed", mais seulement quand mode n’est pas réglé sur disabled ou managed.

Le relais transfère le remotePort du serveur vers son propre port sur 127.0.0.1 (3333 sauf si vous avez modifié port). Avec public, le transfert est demandé sous la forme *:remotePort, sinon sous la forme remotePort.

Ces réglages sont fixes et n’ont pas de clé : le relais lance ssh -N avec ServerAliveInterval=30, ServerAliveCountMax=3, BatchMode=yes, StrictHostKeyChecking=accept-new et ExitOnForwardFailure=yes. ssh abandonne une connexion après trois vérifications sans réponse, espacées de 30 secondes.

Si une valeur obligatoire manque (user, host ou un remotePort valide en mode géré), le relais démarre normalement, indique dans le journal laquelle, et laisse le tunnel désactivé.

Son comportement #

Démarrage. Le relais démarre le tunnel dès qu’il écoute. Il considère le tunnel comme connecté quand ssh tourne depuis 1,2 seconde et que le serveur n’a pas refusé le transfert. Le journal indique alors Tunnel up to user@host remote port N.

Reconnexion. Quand ssh se termine, le relais le relance après une attente qui s’allonge : 5, 10, 20, 40 puis 60 secondes, et reste à 60. Le compte repart de zéro quand une connexion qui a duré plus de 30 secondes tombe. Arrêter le relais arrête le tunnel et les nouvelles tentatives.

Clés d’hôte. Un serveur que l’hôte du relais n’a jamais vu est accepté et mémorisé à la première connexion. Ensuite, une clé d’hôte modifiée fait que ssh refuse de se connecter tant que vous n’avez pas retiré l’ancienne entrée de known_hosts.

Requis #

Avec required: true, la queue reste en pause du démarrage jusqu’à ce que le tunnel soit connecté, et de nouveau chaque fois qu’il tombe. Les messages que vous envoyez attendent dans la queue et partent une fois le tunnel actif. Utilisez-le quand le relais ne vous sert à rien sans le tunnel. Un tunnel qui ne peut pas démarrer à cause d’une valeur manquante maintient aussi la queue en pause.

Quand le port distant est occupé #

Si le serveur dit que le port ne peut pas être lié, la cause habituelle est une session restante d’un tunnel précédent dont le serveur n’a pas remarqué qu’elle était morte. Sauf si autoReclaimPort vaut false, le relais se connecte alors une seconde fois au serveur et y lance une commande de nettoyage.

Ces options lancent des commandes sur votre serveur

Le nettoyage tourne sous votre connexion SSH et tue des processus. Il met fin à tout processus de cet utilisateur qui écoute sur le port distant, qu’il s’agisse ou non d’un tunnel restant. Avec reclaimStaleSshSessions, il met aussi fin à chaque session SSH inactive de cet utilisateur, ce qui inclut d’autres tunnels ssh -N -R de la même connexion sur d’autres ports. remoteCleanupCommand lance tout ce que vous écrivez. Lisez ce que fait chaque étape avant de les laisser activées.

Le nettoyage intégré fait ceci, dans l’ordre :

  1. Il vérifie si le port est utilisé (avec ss, ou à défaut en lisant la liste des sockets en écoute du noyau). Sinon, il s’arrête et signale un succès.
  2. Il trouve les processus qui écoutent sur le port (avec lsof, ou à défaut fuser, selon ce qui existe), leur envoie un signal de terminaison (kill) et attend une seconde. Un processus auquel votre connexion n’a pas le droit d’envoyer de signal reste. Si le port est maintenant libre, il signale un succès.
  3. Seulement avec reclaimStaleSshSessions: true, et seulement si le port est encore utilisé : il envoie un signal de terminaison à chaque session sshd de votre utilisateur qui n’a pas de processus enfant, puis attend une seconde. Ce sont les sessions qu’un ssh -N -R laisse derrière lui. Les sessions qui font tourner un shell, le serveur distant d’un éditeur ou tout autre programme ont des enfants et sont ignorées, de même que la session qui exécute le nettoyage lui-même.
  4. Si le port est encore utilisé, le nettoyage se termine avec le code 3, que le relais journalise comme is still held.

Quand remoteCleanupCommand est réglée, votre commande tourne à la place de tout cela, et reclaimStaleSshSessions n’a aucun effet. Le relais la lance sous la forme sh -lc '<command>' par une seconde connexion ssh avec la même clé, en mode batch. Elle doit se terminer avec 0 quand le port est libre et avec un autre code quand il est encore occupé.

Après un nettoyage qui a libéré le port, le relais réessaie au bout d’une seconde, jusqu’à trois fois de suite. Après un nettoyage qui a échoué, ou avec autoReclaimPort désactivé, il utilise l’attente normale décrite plus haut.

Voir l’état du tunnel #

Le tunnel SSH n’apparaît pas aux endroits où apparaît le tunnel Cloudflare : oar status et oar doctor ne signalent que le tunnel Cloudflare, et la pastille d’état ne passe pas à l’ambre pour un tunnel SSH (voir La pastille d’état). Regardez plutôt ici :

  • Le journal du relais. Chaque ligne commence par [ssh-tunnel]. Il montre la ligne de commande ssh lancée, ce que ssh affiche, Tunnel up to ..., Reconnecting in Ns (attempt N)... et les étapes de récupération du port. L’emplacement du journal dépend de la façon dont le relais tourne ; voir Fichiers et dossiers.
  • La bannière de démarrage, avec une ligne Remote: et une ligne Tunnel: mode=loopback (ou public) quand le tunnel géré est configuré. Elles montrent la configuration, pas si le tunnel est actif. oar url affiche la même adresse sous la forme Tunnel URL:.
  • /api/status, qui contient un objet sshTunnel avec connected, connectedSince, reconnectAttempts, lastError, blocking, required, mode, valid, host, remotePort et remoteBindMode. Récupérez-le avec votre token.

lastError vaut exit:<code> après la fin de ssh, remote-forward-bind-failed:<code> après que le serveur a refusé le port, invalid-config ou le message de valeur manquante quand le bloc est incomplet, et il est vide tant que le tunnel est actif.

Sécurité #

Une fois le tunnel actif, le relais est joignable depuis internet, et le token d’accès est tout ce qui se dresse entre un inconnu et vos agents, qui exécutent des commandes sur votre machine. Donc :

  • Gardez remoteBind à loopback et placez HTTPS devant, sur le serveur. Avec public, quiconque peut joindre le port du tunnel sur le serveur joint le relais en HTTP en clair.
  • Le token d’accès est le seul verrou. Voir Le token d’accès et qui peut joindre le relais et Ce qu’OAR expose.
  • Le relais refuse les requêtes vers ses chemins internes de worker de session quand elles portent l’en-tête marqueur de Cloudflare cf-ray ou un en-tête que vous listez dans tunnelMarkerHeaders. Un simple proxy inverse n’en ajoute aucun ; listez donc l’en-tête qu’ajoute votre proxy, s’il en ajoute un que vous voulez faire bloquer. Voir config.json.

Dépannage #

Regardez d’abord les lignes [ssh-tunnel] du journal. Les lignes commençant par stderr: sont ce que ssh lui-même a affiché.

Vous voyez Ce que cela signifie Que faire
sshTunnel.user is required when sshTunnel.mode is "managed" (de même pour host) Une valeur obligatoire manque. Le tunnel reste désactivé. Réglez-la et lancez oar restart.
sshTunnel.remotePort must be a positive integer when sshTunnel.mode is "managed" Le port manque ou n’est pas un nombre de 1 à 65535. Corrigez-le et redémarrez.
Tunnel mode disabled; running direct relay only. mode n’est pas managed. Réglez "mode": "managed".
stderr: ... Permission denied (publickey) Le serveur n’a pas accepté votre clé. En mode batch, il n’y a pas d’invite de mot de passe. Réglez identityFile, et testez à la main le même ssh user@host sur l’hôte du relais.
Process exited (code=N). Scheduling reconnect... qui se répète ssh n’a pas pu se connecter ou s’authentifier. Lancez à la main la commande ssh journalisée et lisez son erreur.
stderr: ... remote port forwarding failed for listen port N, puis Attempting remote reclaim ... Le port est utilisé sur le serveur. Attendez la récupération. Si elle se termine par is still held, libérez le port vous-même ou choisissez un autre remotePort.
Remote reclaim finished: listen port N is still held (code=3) Le nettoyage n’a pas pu libérer le port. Trouvez qui l’occupe sur le serveur, essayez reclaimStaleSshSessions: true, ou réglez remoteCleanupCommand.
stderr: ... Host key verification failed ou REMOTE HOST IDENTIFICATION HAS CHANGED La clé du serveur diffère de celle enregistrée. Si le changement est attendu, retirez l’ancienne entrée de known_hosts. Sinon, ne vous connectez pas.
Reconnecting in 60s (attempt N) encore et encore, et le tunnel n’indique jamais Tunnel up La connexion échoue à chaque fois. Lisez les lignes stderr: qui précèdent chacune.
Le tunnel indique Tunnel up mais l’adresse ne répond pas Le côté relais va bien ; le côté serveur non. Vérifiez que le proxy inverse tourne et pointe vers le remotePort, et que remoteBind correspond à la façon dont le proxy le joint.
Le tunnel se connecte, tombe et se reconnecte en boucle Quelque chose sur le serveur ou le réseau met fin à la connexion. Le journal montre le code de sortie et les messages de ssh. Consultez le journal sshd du serveur.
Les messages restent en file required vaut true et le tunnel n’est pas connecté. Réparez le tunnel, ou passez required à false.