Deux noms DNS, un port OpenVPN : ce qui sélectionne vraiment
Deux noms d'hôte pointant vers une même adresse atteignent malgré tout un seul listener. Seul un champ que le client envoie peut choisir un backend, et l'OpenVPN natif n'en envoie aucun.
Deux enregistrements A, une adresse, un démon : quel que soit le nom que saisit l'utilisateur, le paquet qui arrive est identique, et le processus lié au port 1194 n'a rien pour distinguer les deux. Ce n'est pas une mauvaise configuration en attente de réparation ; c'est la limite de ce qu'une résolution de nom peut exprimer. Tout ce qui choisit entre deux instances OpenVPN doit être une valeur que le client place dans la connexion et que le frontend peut relire, et un client OpenVPN standard n'en place aucune en clair. Ce qui suit montre où cela vous laisse, et quelles dispositions séparent réellement le trafic.
Partez de ce que le résolveur renvoie
Un client demande vpn-a.example et reçoit une adresse ; cette adresse plus un port est tout ce qu'il compose. Le nom est consommé localement, pendant la résolution, et il ne devient jamais un champ sur le fil, si bien que le listener à l'autre bout ne peut pas voir quelle étiquette a été utilisée. Pointez vpn-a.example et vpn-b.example vers un même hôte et les deux résolutions donnent la même destination, c'est pourquoi le démon absorbe les deux connexions dans une seule instance sans enregistrer de différence. Les profils fondés sur l'adresse se comportent exactement comme ceux fondés sur le nom, pour la même raison.
Cela ferme aussi une famille de contournements tentants : alias supplémentaires, choix d'un certificat par nom sur une seule socket, ou règle de pare-feu indexée sur le nom interrogé. Aucun d'eux ne peut agir sur une information déjà jetée avant que le premier paquet ait quitté le client. Si deux serveurs doivent rester séparés, la séparation doit voyager avec la connexion elle-même, c'est-à-dire une adresse, un port ou un champ de charge utile que le protocole définit.
Le seul sélecteur qui compte voyage dans le paquet
HTTP y parvient avec un en-tête Host, et TLS avec une extension de nom de serveur dans le ClientHello, et c'est ainsi qu'une adresse sert de nombreux sites derrière un seul listener. Les deux fonctionnent parce que le protocole sur le fil porte une demande pour un service précis. Le fait pertinent à propos d'OpenVPN est qu'aucun de ces champs n'est présent à la couche qu'inspecte une boîte intermédiaire.
Dans une disposition standard, la poignée de main TLS qui protège le canal de contrôle a lieu une fois la session externe établie et est transportée à l'intérieur de celle-ci, si bien qu'un listener qui n'a pas terminé cette session voit un flux opaque. Il peut encore dire qu'il s'agit d'OpenVPN, car l'opcode et le tramage des premiers octets sont reconnaissables, mais le nom de serveur, s'il existe, se trouve dans la partie chiffrée. Une conception qui espère lire un hôte virtuel depuis le paquet externe lit un champ que le protocole n'y écrit jamais. Cela vient de la façon dont le tunnel est construit, non d'un vide qu'une correspondance de motifs plus fine pourrait combler.
Où un séparateur de protocoles s'arrête
Un multiplexeur du genre que documente le projet sslh écoute sur un port, inspecte les premiers octets et remet la connexion au backend qui parle le protocole reconnu. C'est une vraie valeur quand un seul port doit servir SSH, HTTPS et OpenVPN ensemble.
sslh garde une sonde distincte par protocole, et sa sonde TLS peut extraire un nom de serveur ou une valeur ALPN d'un véritable ClientHello. Reconnaître l'OpenVPN natif est une classification différente de la lecture d'une extension TLS, et les deux ne se composent pas, car un client qui n'envoie jamais de ClientHello à la socket d'écoute laisse la sonde sans rien à lire. Une règle qui correspond à OpenVPN répond donc « c'est un client OpenVPN » et ne va pas plus loin. Deux backends OpenVPN derrière cette règle sont indiscernables, le frontend n'a aucune base pour choisir, et chaque session atterrit au même endroit. Le multiplexage partage un port entre services différents ; il ne fait pas d'hébergement virtuel à l'intérieur d'un service.
Des dispositions qui séparent vraiment deux serveurs
La séparation honnête la moins chère est une seconde adresse publique, une par instance, et les profils existants portent déjà le nécessaire puisqu'ils pointent vers des adresses différentes. La suivante est un port différent sur la même adresse, ce qui ne coûte rien de plus qu'un nombre dans chaque profil. TCP et UDP tiennent des tables de ports distinctes, si bien que 1194/tcp et 1194/udp sont des sockets sans rapport et qu'un de chaque peut coexister sur un même hôte ; c'est une séparation de transport et non un routage par nom, avec lequel on la confond souvent. Les deux options survivent à une revue de pare-feu, parce que la règle correspond à quelque chose que le paquet contient réellement.
Si la frontière dont vous avez besoin passe entre des groupes d'utilisateurs plutôt qu'entre deux implémentations, une instance avec des configurations client distinctes, sa propre gestion des certificats et une politique d'accès explicite peut la couvrir. Ce que cela achète, c'est de la politique à l'intérieur d'un serveur, non de l'isolation entre serveurs, et ce sont deux exigences différentes. Une isolation qui doit tenir sous compromission a toujours besoin de l'adresse ou du bord du pare-feu.
Un terminateur TLS placé devant OpenVPN peut exposer un vrai nom de serveur à un frontend capable, mais seulement quand le côté client est aussi configuré pour cette couche supplémentaire. Les profils existants n'acquièrent pas ce comportement du fait qu'on les pointe vers la même adresse.
Prouvez le sélecteur avant de construire
Nommez le champ qui identifiera un backend, puis confirmez que le client l'envoie réellement ; si la réponse est une adresse, le plan tient, et si c'est un nom de serveur à l'intérieur d'une poignée de main qui n'atteint jamais le listener, aucun travail DNS ne le sauvera. Trois commandes règlent vite le côté écoute.
ss -lntup | grep -E ':443|:1194'
openssl s_client -connect vpn-a.example:443 -servername vpn-a.example </dev/null | head -n 20
journalctl -u openvpn-server@server-a -n 20 --no-pager
La ligne openssl ne donne un certificat que là où quelque chose à cette adresse termine TLS ; face à un listener OpenVPN natif, elle cale ou échoue, ce qui est en soi la réponse sur ce que la couche externe expose. La ligne journal, lue sur chaque backend, montre quelle instance a accepté la session. Les journaux du frontend notent ce qui a été classé et jamais ce qui était visé, si bien que le backend reste la seule autorité sur l'endroit où un utilisateur a atterri.
Terminez avec deux profils destinés à joindre des serveurs différents, connectez-les un par un et notez le journal du backend pour chacun. Si les deux sessions apparaissent dans le même journal, le sélecteur manque plutôt qu'il n'est mal configuré, et le correctif est une adresse ou un port que le client peut viser. Modifier la règle de routage n'aidera pas, car la règle n'a jamais reçu quoi que ce soit sur quoi router. Une fois la séparation réelle, écrivez-la à côté des profils pour que la personne suivante n'ajoute pas un second alias en espérant qu'il fasse le travail.