J'ai cherché une solution à ce problème de manière intermittente au cours des 16 derniers mois. Mais à chaque fois que je regarde, il semble impossible de faire cela avec le protocole SSH tel que spécifié dans les RFC pertinents et mis en œuvre par les principales implémentations.
Cependant, si vous êtes prêt à utiliser un client SSH légèrement modifié et que vous êtes prêt à utiliser les protocoles d'une manière qui n'était pas exactement prévue lors de leur conception, alors c'est possible d'y parvenir. Plus de détails ci-dessous.
Pourquoi ce n'est pas possible
Le client n'envoie pas le nom d'hôte dans le cadre du protocole SSH.
Il pourrait envoyer le nom d'hôte dans le cadre d'une recherche DNS, mais cela pourrait être mis en cache, et le chemin du client à travers les résolveurs vers les serveurs d'autorité pourrait ne pas traverser le proxy, et même s'il le faisait, il n'y a aucun moyen robuste d'associer des recherches DNS spécifiques à des clients SSH spécifiques.
Il n'y a rien de spécial que vous puissiez faire avec le protocole SSH lui-même non plus. Vous devez choisir un serveur sans même avoir vu la bannière de version SSH du client. Vous devez envoyer une bannière au client avant qu'il n'envoie quoi que ce soit au proxy. Les bannières des serveurs pourraient être différentes, et vous n'avez aucune chance de deviner laquelle est la bonne à utiliser.
Même si cette bannière est envoyée de manière non cryptée, vous ne pouvez pas la modifier. Chaque bit de cette bannière sera vérifié lors de l'établissement de la connexion, donc vous provoqueriez un échec de connexion un peu plus loin.
La conclusion pour moi est assez claire, il faut changer quelque chose du côté client pour que cette connectivité fonctionne.
La plupart des solutions de contournement consistent à encapsuler le trafic SSH dans un autre protocole. On pourrait également envisager une extension du protocole SSH lui-même, dans laquelle la bannière de version envoyée par le client inclut le nom d'hôte. Cela peut rester compatible avec les serveurs existants, puisqu'une partie de la bannière est actuellement spécifiée comme un champ d'identification en forme libre, et bien que les clients attendent généralement la bannière de version du serveur avant d'envoyer la leur, le protocole permet au client d'envoyer sa bannière en premier. Certaines versions récentes du client SSH (par exemple celui sur Ubuntu 14.04) envoient la bannière sans attendre la bannière du serveur.
Je ne connais aucun client qui a pris des mesures pour inclure le nom d'hôte du serveur dans cette bannière. J'ai envoyé un correctif à la liste de diffusion OpenSSH pour ajouter une telle fonctionnalité. Mais il a été rejeté en raison du désir de ne pas révéler le nom d'hôte à quiconque espionne le trafic SSH. Étant donné qu'un nom d'hôte secret est fondamentalement incompatible avec le fonctionnement d'un proxy basé sur le nom, ne vous attendez pas à voir une extension SNI officielle pour le protocole SSH de sitôt.
Une vraie solution
La solution qui a le mieux fonctionné pour moi a en fait été d'utiliser IPv6.
Avec IPv6, je peux attribuer une adresse IP séparée à chaque serveur, de sorte que la passerelle puisse utiliser l'adresse IP de destination pour savoir vers quel serveur envoyer le paquet. Il arrive parfois que les clients SSH fonctionnent sur des réseaux où le seul moyen d'obtenir une adresse IPv6 serait d'utiliser Teredo. Teredo est connu pour être peu fiable, mais seulement lorsque l'extrémité IPv6 native de la connexion utilise un relais Teredo public. On peut simplement mettre un relais Teredo sur la passerelle, où vous exécuteriez le proxy. Miredo peut être installé et configuré comme relais en moins de cinq minutes.
Un contournement
Vous pouvez utiliser un saut hôte/hôte bastion. Cette approche est destinée aux cas où vous ne souhaitez pas exposer le port SSH des serveurs individuels directement à l'internet public. Cela a aussi l'avantage de réduire le nombre d'adresses IP exposées dont vous avez besoin pour SSH, c'est pourquoi il est utilisable dans ce scénario. Le fait que ce soit une solution destinée à ajouter une autre couche de protection pour des raisons de sécurité ne vous empêche pas de l'utiliser lorsque vous n'avez pas besoin de cette sécurité supplémentaire.
ssh -o ProxyCommand='ssh -W %h:%p user1@bastion' user2@target
Un hack sale pour faire fonctionner si la vraie solution (IPv6) est hors de portée
Le hack que je m'apprête à décrire ne doit être utilisé qu'en dernier recours absolu. Avant même de penser à utiliser ce hack, je recommande fortement d'obtenir une adresse IPv6 pour chacun des serveurs auxquels vous souhaitez accéder de manière externe via SSH. Utilisez IPv6 comme méthode principale pour accéder à vos serveurs SSH et n'utilisez ce hack que lorsque vous devez exécuter un client SSH à partir d'un réseau IPv4 où vous n'avez aucune influence sur le déploiement d'IPv6.
L'idée est que le trafic entre le client et le serveur doit être un trafic SSH parfaitement valide. Mais le proxy doit uniquement comprendre suffisamment le flux de paquets pour identifier le nom d'hôte. Comme SSH ne définit pas de moyen d'envoyer un nom d'hôte, vous pouvez envisager d'autres protocoles qui offrent une telle possibilité.
HTTP et HTTPS permettent tous deux au client d'envoyer un nom d'hôte avant que le serveur n'envoie des données. La question maintenant est de savoir s'il est possible de construire un flux d'octets qui est simultanément valide comme trafic SSH et comme HTTP ou HTTPS. HTTPS est quasiment exclu, mais HTTP est possible (pour des définitions suffisamment libérales d'HTTP).
SSH-2.0-OpenSSH_6.6.1 / HTTP/1.1
$:
Host: example.com
Cela ressemble-t-il à du SSH ou à de l'HTTP pour vous ? C'est du SSH et totalement conforme aux RFC (à l'exception de certains caractères binaires un peu modifiés par le rendu SF).
La chaîne de version SSH inclut un champ de commentaire, qui dans l'exemple ci-dessus a pour valeur / HTTP/1.1
. Après le saut de ligne, SSH a des données de paquet binaire. Le premier paquet est un message MSG_SSH_IGNORE
envoyé par le client et ignoré par le serveur. La charge à ignorer est :
:
Host: example.com
Si un proxy HTTP est suffisamment libéral dans ce qu'il accepte, alors la même séquence d'octets serait interprétée comme une méthode HTTP appelée SSH-2.0-OpenSSH_6.6.1
et les données binaires au début du message à ignorer seraient interprétées comme un nom d'en-tête HTTP.
Le proxy ne comprendrait ni la méthode HTTP ni le premier en-tête. Mais il pourrait comprendre l'en-tête Host
, ce qui est tout ce dont il a besoin pour trouver l'arrière-plan.
Pour que cela fonctionne, le proxy devrait être conçu sur le principe qu'il n'a besoin de comprendre suffisamment d'HTTP que pour trouver l'arrière-plan, et une fois que l'arrière-plan est trouvé, le proxy transmettra simplement le flux de bytes brut et laissera la véritable terminaison de la connexion HTTP à l'arrière-plan.
Cela peut sembler un peu tiré par les cheveux de faire autant d'hypothèses sur le proxy HTTP. Mais si vous étiez prêt à installer un nouveau logiciel développé dans le but de prendre en charge SSH, alors les exigences pour le proxy HTTP ne semblent pas trop contraignantes.
Dans mon propre cas, j'ai constaté que cette méthode fonctionnait sur un proxy déjà installé sans aucun changement de code, de configuration ou autre. Et cela pour un proxy écrit uniquement avec HTTP en tête, pas SSH.
Preuve de concept client et proxy. (Avis que le proxy est un service exploité par moi-même. N'hésitez pas à remplacer le lien une fois qu'un autre proxy aura été confirmé pour prendre en charge cette utilisation.)
Inconvénients de ce hack
- Ne l'utilisez pas. Il vaut mieux utiliser la vraie solution, qui est IPv6.
- Si le proxy tente de comprendre le trafic HTTP, il est sûr de se casser.
- Compter sur un client SSH modifié n'est pas agréable.