Le SSH public via un relais échoue sur deux frontières sans rapport, et corriger l'une révèle généralement l'autre. Soit le bord renvoie une réponse HTTP au lieu de transporter un flux, soit le relais ajoute en tête un en-tête que le backend n'a jamais accepté de lire. Trois conditions décident laquelle vous regardez : ce à quoi l'enregistrement public pointe réellement, ce que le bloc de flux envoie vers l'amont et quel nom le client SSH vérifie. Traitez-les dans cet ordre.

Deux frontières, deux symptômes

Une défaillance au bord ressemble à une page d'état HTTP qui arrive là où devrait se trouver une bannière SSH. La connexion aboutit au niveau TCP et les premiers octets sont du texte appartenant à un serveur web. C'est une inadéquation de protocole, pas un paquet perdu. Cela vous dit aussi que le bord sert délibérément autre chose pour ce nom.

Une défaillance en amont se présente différemment. La connexion atteint sshd, sshd lit des octets qui ne sont pas l'échange d'identification attendu, et la session meurt ou reste bloquée sans jamais proposer de clé.

Les deux se confondent facilement parce que tous deux produisent un client qui se connecte puis échoue. La première réponse de protocole que vous recevez est ce qui les distingue, et c'est pourquoi elle appartient au ticket.

L'en-tête PROXY est un message, pas un commutateur

Dans le module de flux, proxy_protocol on demande à NGINX d'écrire un en-tête de protocole PROXY vers l'amont avant la charge utile. Cela n'active pas le proxy TCP et n'a rien à voir avec le chiffrement SSH. C'est une note adressée au récepteur. Si le récepteur ne peut pas la lire, elle devient un débris au début du flux.

Le service récepteur doit consommer cet en-tête délibérément. Un sshd normal attend l'échange d'identification SSH comme premiers octets de la connexion, donc un préambule inattendu casse la poignée de main d'une manière qui ressemble à un serveur corrompu.

stream {
    server {
        listen 2222;
        proxy_pass 10.77.0.20:22;
    }
}

Ce fragment constitue à lui seul un relais fonctionnel pour un backend joignable à travers le VPN. Aucune directive PROXY n'apparaît parce que le récepteur ne parle pas ce protocole, et cette absence est une décision, pas un oubli.

Séparez aussi les deux directions. Régler proxy_protocol sur la ligne listen signifie que NGINX attend que son propre client envoie l'en-tête en premier ; l'activer pour un client SSH ordinaire produit exactement l'inverse du problème que les gens cherchent habituellement à résoudre.

Ce que le nom d'hôte public atteint réellement

Une page HTTP 400 dans une session SSH est une preuve concernant le bord. Quelque chose sur le chemin parle HTTP, et aucun travail sur la clé ou le compte côté backend n'y changera rien. Arrêtez de modifier le backend tant que le bord n'est pas réglé.

L'enregistrement proxifié ordinaire de Cloudflare transporte son trafic HTTP et HTTPS pris en charge sur des ports définis. Activer le proxy pour un nom d'hôte ne crée pas un relais TCP général, et déplacer SSH vers le port 443 n'en fait pas du HTTPS.

Deux directions praticables existent. Un enregistrement DNS seul peut pointer vers votre propre relais TCP, ce qui place la décision d'exposition entre vos mains. Sinon, utilisez un produit qui documente explicitement le transport SSH, avec sa propre configuration d'origine, de client et d'accès.

Quel que soit votre choix, le relais a toujours besoin d'une route VPN vers le backend, d'une connexion autorisée et d'un chemin de retour. La route n'est pas facultative parce que le côté public fonctionne.

Gardez l'identité SSH stable via un alias

Les utilisateurs veulent généralement continuer à taper leur nom d'hôte existant. Un alias de configuration SSH peut le faire sans rien changer au relais, et il garde la modification côté client, là où elle a sa place.

Host git.example.com
    HostName ssh-entry.example.net
    Port 2222
    User git
    HostKeyAlias git.example.com

Sans HostKeyAlias, OpenSSH cherche la clé d'hôte sous le nom auquel il se connecte, donc l'entrée se déplace et chaque utilisateur voit un avertissement de clé modifiée à la première utilisation. Définir l'alias garde les entrées known-hosts stables pendant que la connexion passe toujours par le relais.

Cet alias ne s'applique qu'aux dépôts distants basés sur SSH. Une URL Git en HTTPS ne le consulte pas, et l'URL distante typique doit correspondre au transport que vous avez réellement mis en place.

Ce que vous ne devez pas faire, c'est désactiver la vérification de la clé d'hôte pour faire disparaître l'avertissement. Cela retire une protection que la réparation du transfert n'a jamais demandé de toucher, et transforme une gêne cosmétique en exposition réelle.

Vérifiez chaque saut séparément

Testez l'écouteur public seul, puis la connexion du relais au backend depuis l'hôte du relais, et seulement ensuite le client de bout en bout. Chacun de ces trois tests écarte une couche différente. En sauter un signifie que les deux résultats restants ne peuvent pas être interprétés. Trois observations, trois endroits où une défaillance peut vivre.

Conservez l'adresse de destination, le port et les premiers octets de protocole avec chaque résultat. Ce trio distingue une erreur de bord d'un problème de routage VPN et d'un échec d'authentification SSH, et c'est la différence entre une réparation et une supposition.

Enfin, documentez l'exposition prévue. Un relais TCP qui transporte SSH vers un backend privé est une ouverture délibérée dans le périmètre public, et il mérite la même revue que tout autre service entrant.

Articles similaires