Twee DNS-namen, één OpenVPN-poort: wat echt selecteert
Twee hostnamen die naar één adres wijzen, bereiken toch één listener. Alleen een veld dat de client stuurt kan een backend kiezen, en native OpenVPN stuurt er geen.
Twee A-records, één adres, één daemon: welk naam een gebruiker ook typt, het pakket dat aankomt is identiek, en het proces dat aan poort 1194 is gebonden heeft niets om de twee te onderscheiden. Dat is geen verkeerde configuratie die op reparatie wacht; het is de grens van wat een naamopzoeking kan uitdrukken. Alles wat tussen twee OpenVPN-instanties kiest, moet een waarde zijn die de client in de verbinding stopt en die de frontend kan teruglezen, en een standaard OpenVPN-client stopt zo'n waarde niet in het openbaar. Wat volgt is waar dat je laat, en welke opstellingen het verkeer echt splitsen.
Begin met wat de resolver teruggeeft
Een client vraagt vpn-a.example op en krijgt een adres; dat adres plus een poort is alles wat hij kiest. De naam wordt lokaal verbruikt, tijdens de opzoeking, en wordt nooit een veld op de lijn, dus de listener aan de andere kant kan niet zien welk label is gebruikt. Wijs vpn-a.example en vpn-b.example naar één host en beide opzoekingen geven dezelfde bestemming, waardoor de daemon beide verbindingen in één instantie opneemt zonder een verschil te registreren. Adresgebaseerde profielen gedragen zich precies zoals naamgebaseerde, om dezelfde reden.
Dit sluit ook een familie van verleidelijke omwegen af: extra aliassen, per naam een certificaat kiezen op één socket, of een firewallregel die aan de opgevraagde naam hangt. Geen daarvan kan handelen op informatie die al was weggegooid voordat het eerste pakket de client verliet. Als twee servers gescheiden moeten blijven, moet de splitsing met de verbinding zelf meereizen, dat wil zeggen een adres, een poort of een payloadveld dat het protocol definieert.
De enige selector die telt, rijdt in het pakket mee
HTTP regelt dit met een Host-header, en TLS met een servernaam-extensie in de ClientHello, en zo bedient één adres vele sites achter één listener. Beide werken omdat het protocol op de lijn een verzoek voor een specifieke dienst draagt. Het relevante feit over OpenVPN is dat geen van die velden aanwezig is op de laag die een tussenliggende doos inspecteert.
In een standaardopstelling gebeurt de TLS-handshake die het controlekanaal beschermt nadat de buitenste sessie is opgezet, en wordt die erbinnen gedragen, dus een listener die die sessie niet heeft beëindigd ziet een ondoorzichtige stroom. Hij kan nog steeds zeggen dat dit OpenVPN is, omdat de opcode en de framing in de eerste bytes herkenbaar zijn, maar de servernaam, als die bestaat, zit in het versleutelde deel. Een ontwerp dat verwacht een virtuele host uit het buitenste pakket te lezen, leest een veld dat het protocol daar nooit schrijft. Dat komt door hoe de tunnel is gebouwd, niet door een gat dat een slimmere patroonvergelijking zou kunnen dichten.
Waar een protocolsplitter tekortschiet
Een multiplexer van het soort dat het sslh-project documenteert, luistert op één poort, inspecteert de eerste bytes en geeft de verbinding door aan de backend die het herkende protocol spreekt. Dat is echte waarde wanneer één poort SSH, HTTPS en OpenVPN tegelijk moet bedienen.
sslh houdt een aparte sonde per protocol, en zijn TLS-sonde kan een servernaam of een ALPN-waarde uit een echte ClientHello halen. Native OpenVPN herkennen is een andere classificatie dan een TLS-extensie lezen, en die twee componeren niet, omdat een client die nooit een ClientHello naar de luistersocket stuurt de sonde zonder iets te lezen achterlaat. Een regel die bij OpenVPN past, antwoordt daarom “dit is een OpenVPN-client” en gaat niet verder. Twee OpenVPN-backends achter die regel zijn niet te onderscheiden, de frontend heeft geen basis voor een keuze, en elke sessie landt op dezelfde plek. Multiplexing deelt één poort tussen verschillende diensten; het doet geen virtuele hosting binnen één dienst.
Opstellingen die twee servers echt scheiden
De goedkoopste eerlijke splitsing is een tweede openbaar adres, één per instantie, en bestaande profielen dragen al wat nodig is omdat ze naar verschillende adressen wijzen. De volgende is een andere poort op hetzelfde adres, wat niets kost behalve een getal in elk profiel. TCP en UDP houden gescheiden poorttabellen, dus 1194/tcp en 1194/udp zijn onverwante sockets en één van elk kan op één host naast elkaar bestaan; dat is transportopsplitsing en geen naamroutering, en het wordt daar vaak mee verward. Beide opties doorstaan een firewallbeoordeling, omdat de regel overeenkomt met iets dat het pakket echt bevat.
Als de grens die je nodig hebt tussen groepen gebruikers loopt in plaats van tussen twee implementaties, kan één instantie met verschillende clientconfiguraties, eigen certificaatbeheer en een expliciet toegangsbeleid dat dekken. Wat dat koopt is beleid binnen een server, geen isolatie tussen servers, en dat zijn verschillende eisen. Isolatie die onder compromittering stand moet houden, heeft nog steeds het adres of de firewallrand nodig.
Een TLS-terminator vóór OpenVPN kan een echte servernaam blootleggen aan een capabele frontend, maar alleen wanneer de clientzijde ook voor die extra laag is geconfigureerd. Bestaande profielen krijgen het gedrag niet door ze naar hetzelfde adres te wijzen.
Bewijs de selector voordat je bouwt
Noem het veld dat een backend gaat identificeren, en bevestig dan dat de client het echt stuurt; als het antwoord een adres is, houdt het plan stand, en als het een servernaam is in een handshake die de listener nooit bereikt, redt geen enkele hoeveelheid DNS-werk het. Drie commando's beslechten de luisterkant snel.
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
De openssl-regel geeft alleen een certificaat waar iets op dat adres TLS beëindigt; tegen een native OpenVPN-listener blijft hij hangen of faalt hij, en dat is op zichzelf het antwoord op wat de buitenste laag blootlegt. De journal-regel, gelezen op elke backend, laat zien welke instantie de sessie heeft geaccepteerd. Frontend-logs leggen vast wat is geclassificeerd en nooit wat de bedoeling was, dus de backend blijft de enige autoriteit over waar een gebruiker is geland.
Sluit af met twee profielen die bedoeld zijn om verschillende servers te bereiken, verbind ze één voor één en noteer de backend-log voor elk. Als beide sessies in hetzelfde journal verschijnen, ontbreekt de selector in plaats van verkeerd geconfigureerd te zijn, en de oplossing is een adres of een poort die de client kan richten. Het bewerken van de routeringsregel helpt niet, omdat de regel nooit iets heeft gekregen om op te routeren. Zodra de splitsing echt is, schrijf die naast de profielen, zodat de volgende persoon geen tweede alias toevoegt en verwacht dat die het werk doet.