Zwei DNS-Namen, ein OpenVPN-Port: Was tatsächlich auswählt
Zwei Hostnamen, die auf eine Adresse zeigen, erreichen dennoch einen einzigen Listener. Nur ein Feld, das der Client sendet, kann ein Backend auswählen, und natives OpenVPN sendet keines.
Zwei A-Records, eine Adresse, ein Daemon: Welchen Namen ein Nutzer auch eintippt, das ankommende Paket ist identisch, und der an Port 1194 gebundene Prozess hat nichts, womit er die beiden unterscheiden könnte. Das ist keine Fehlkonfiguration, die auf Reparatur wartet; es ist die Grenze dessen, was eine Namensauflösung ausdrücken kann. Alles, was zwischen zwei OpenVPN-Instanzen wählt, muss ein Wert sein, den der Client in die Verbindung legt und den das Frontend zurücklesen kann, und ein Standard-OpenVPN-Client legt keinen solchen Wert im Klartext ab. Was folgt, ist, wo das hinführt und welche Anordnungen den Verkehr wirklich trennen.
Beginne mit dem, was der Resolver zurückgibt
Ein Client fragt vpn-a.example an und erhält eine Adresse; diese Adresse plus ein Port ist alles, was er anwählt. Der Name wird lokal während der Auflösung verbraucht und wird nie zu einem Feld auf der Leitung, sodass der Listener am anderen Ende nicht sehen kann, welches Label verwendet wurde. Richte vpn-a.example und vpn-b.example auf denselben Host, und beide Abfragen ergeben dasselbe Ziel, weshalb der Daemon beide Verbindungen in einer Instanz aufnimmt, ohne einen Unterschied zu registrieren. Adressbasierte Profile verhalten sich genau wie namensbasierte, aus demselben Grund.
Das schließt auch eine Familie verlockender Auswege aus: zusätzliche Aliase, ein Zertifikat pro Name an einem Socket oder eine Firewall-Regel, die am abgefragten Namen hängt. Keine davon kann auf Informationen reagieren, die bereits verworfen wurden, bevor das erste Paket den Client verließ. Wenn zwei Server getrennt bleiben müssen, muss die Trennung mit der Verbindung selbst reisen, also eine Adresse, ein Port oder ein Nutzlastfeld, das das Protokoll definiert.
Der einzige Selektor, der zählt, fährt im Paket mit
HTTP löst das mit einem Host-Header, TLS mit einer Server-Name-Erweiterung im ClientHello, und so bedient eine Adresse viele Sites hinter einem Listener. Beides funktioniert, weil das Protokoll auf der Leitung eine Anfrage für einen bestimmten Dienst trägt. Die relevante Tatsache zu OpenVPN ist, dass keines der beiden Felder auf der Schicht vorhanden ist, die eine Zwischenbox inspiziert.
In einer Standardanordnung findet der TLS-Handshake, der den Steuerkanal schützt, statt, nachdem die äußere Sitzung aufgebaut ist, und wird in ihr transportiert, sodass ein Listener, der diese Sitzung nicht beendet hat, einen undurchsichtigen Strom sieht. Er kann noch erkennen, dass es OpenVPN ist, weil Opcode und Rahmung in den ersten Bytes erkennbar sind, aber der Servername, falls einer existiert, liegt im verschlüsselten Teil. Ein Design, das einen virtuellen Host aus dem äußeren Paket lesen will, liest ein Feld, das das Protokoll dort nie hinschreibt. Das kommt daher, wie der Tunnel gebaut ist, nicht von einer Lücke, die ein klügerer Musterabgleich schließen könnte.
Wo ein Protokoll-Splitter zu kurz greift
Ein Multiplexer der Art, die das sslh-Projekt dokumentiert, lauscht auf einem Port, prüft die ersten Bytes und übergibt die Verbindung an das Backend, das das erkannte Protokoll spricht. Das ist echter Nutzen, wenn ein einziger Port SSH, HTTPS und OpenVPN zugleich bedienen muss.
sslh unterhält eine eigene Sonde pro Protokoll, und seine TLS-Sonde kann einen Servernamen oder einen ALPN-Wert aus einem echten ClientHello ziehen. Natives OpenVPN zu erkennen ist eine andere Klassifikation als eine TLS-Erweiterung zu lesen, und die beiden lassen sich nicht kombinieren, weil ein Client, der nie ein ClientHello an den lauschenden Socket sendet, die Sonde ohne etwas Lesbares zurücklässt. Eine Regel, die auf OpenVPN passt, antwortet daher „das ist ein OpenVPN-Client“ und geht nicht weiter. Zwei OpenVPN-Backends hinter dieser Regel sind ununterscheidbar, das Frontend hat keine Grundlage für eine Wahl, und jede Sitzung landet am selben Ort. Multiplexing teilt einen Port zwischen verschiedenen Diensten; es betreibt kein virtuelles Hosting innerhalb eines Dienstes.
Anordnungen, die zwei Server wirklich trennen
Die billigste ehrliche Trennung ist eine zweite öffentliche Adresse, eine pro Instanz, und bestehende Profile tragen bereits das Nötige, weil sie auf verschiedene Adressen zeigen. Die nächste ist ein anderer Port auf derselben Adresse, was nichts außer einer Zahl in jedem Profil kostet. TCP und UDP führen getrennte Porttabellen, sodass 1194/tcp und 1194/udp unverbundene Sockets sind und je einer von beiden auf einem Host koexistieren kann; das ist Transporttrennung statt Namensrouting und wird oft damit verwechselt. Beide Optionen überstehen eine Firewall-Prüfung, weil die Regel auf etwas passt, das das Paket tatsächlich enthält.
Wenn die Grenze, die du brauchst, zwischen Nutzergruppen statt zwischen zwei Implementierungen verläuft, kann eine Instanz mit unterschiedlichen Client-Konfigurationen, eigener Zertifikatsbehandlung und ausdrücklicher Zugriffsrichtlinie das abdecken. Das kauft Richtlinie innerhalb eines Servers, nicht Isolation zwischen Servern, und das sind verschiedene Anforderungen. Isolation, die auch unter Kompromittierung halten muss, braucht weiterhin die Adresse oder die Firewall-Kante.
Ein TLS-Terminator vor OpenVPN kann einem fähigen Frontend einen echten Servernamen offenlegen, aber nur, wenn auch die Clientseite für diese zusätzliche Schicht konfiguriert ist. Bestehende Profile erwerben das Verhalten nicht dadurch, dass man sie auf dieselbe Adresse zeigt.
Beweise den Selektor, bevor du baust
Benenne das Feld, das ein Backend identifizieren soll, und bestätige dann, dass der Client es tatsächlich sendet; ist die Antwort eine Adresse, hält der Plan, und ist es ein Servername in einem Handshake, der den Listener nie erreicht, rettet ihn keine noch so große DNS-Arbeit. Drei Befehle klären die Lauscherseite schnell.
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
Die openssl-Zeile liefert nur dort ein Zertifikat, wo etwas an dieser Adresse TLS beendet; gegen einen nativen OpenVPN-Listener stockt sie oder schlägt fehl, und das ist selbst die Antwort darauf, was die äußere Schicht preisgibt. Die journal-Zeile zeigt, auf jedem Backend gelesen, welche Instanz die Sitzung angenommen hat. Frontend-Logs halten fest, was klassifiziert wurde, und nie, was beabsichtigt war, deshalb bleibt das Backend die einzige Instanz, die weiß, wo ein Nutzer gelandet ist.
Schließe mit zwei Profilen ab, die verschiedene Server erreichen sollen, verbinde sie nacheinander und notiere das Backend-Log für jedes. Tauchen beide Sitzungen im selben Journal auf, fehlt der Selektor, statt falsch konfiguriert zu sein, und die Lösung ist eine Adresse oder ein Port, den der Client anzielen kann. Die Routing-Regel zu bearbeiten hilft nicht, weil der Regel nie etwas zum Routen übergeben wurde. Sobald die Trennung real ist, schreibe sie neben die Profile, damit die nächste Person nicht einen zweiten Alias hinzufügt und erwartet, dass er die Arbeit tut.