Openbare SSH via een relay mislukt op twee ongerelateerde grenzen, en het repareren van de ene onthult meestal de andere. Ofwel stuurt de rand een HTTP-antwoord terug in plaats van een stream te vervoeren, ofwel zet de relay een header vooraan die de backend nooit heeft ingestemd te lezen. Drie voorwaarden bepalen welke u ziet: waar de openbare record werkelijk naar wijst, wat het streamblok upstream stuurt, en welke naam de SSH-client verifieert. Werk ze in die volgorde af.

Twee grenzen, twee symptomen

Een fout aan de rand ziet eruit als een HTTP-statuspagina die aankomt waar een SSH-banner zou moeten staan. De verbinding komt op TCP-niveau tot stand en de eerste bytes zijn tekst die bij een webserver hoort. Dat is een protocolverschil, geen verloren pakket. Het vertelt u ook dat de rand voor die naam bewust iets anders levert.

Een fout aan de upstream ziet er anders uit. De verbinding bereikt sshd, sshd leest bytes die niet de verwachte identificatie-uitwisseling zijn, en de sessie sterft of hangt zonder ooit een sleutel aan te bieden.

De twee zijn makkelijk te verwarren omdat beide een client opleveren die verbindt en dan faalt. De eerste protocolrespons die u ontvangt, is wat ze onderscheidt, en daarom hoort die in het ticket.

De PROXY-header is een bericht, geen schakelaar

In de streammodule draagt proxy_protocol on NGINX op om vóór de payload een PROXY-protocolheader richting upstream te schrijven. Het schakelt geen TCP-proxy in, en het heeft niets met SSH-versleuteling te maken. Het is een notitie aan de ontvanger. Als de ontvanger hem niet kan lezen, wordt het afval aan het begin van de stream.

De ontvangende dienst moet die header bewust consumeren. Een normale sshd verwacht de SSH-identificatie-uitwisseling als eerste bytes van de verbinding, dus een onverwachte inleiding breekt de handshake op een manier die op een beschadigde server lijkt.

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

Dat fragment is het geheel van een werkende relay voor een backend die via de VPN bereikbaar is. Er verschijnt geen PROXY-richtlijn omdat de ontvanger het protocol niet spreekt, en dat ontbreken is een beslissing, geen weglating.

Scheid ook de twee richtingen. proxy_protocol op de listen-regel zetten betekent dat NGINX verwacht dat zijn eigen client de header eerst stuurt; het inschakelen voor een gewone SSH-client levert precies het omgekeerde op van het probleem dat mensen meestal proberen op te lossen.

Wat de openbare hostnaam werkelijk bereikt

Een HTTP 400-pagina in een SSH-sessie is bewijs over de rand. Iets in het pad spreekt HTTP, en geen enkele sleutel- of accountwerkzaamheid op de backend zal dat veranderen. Stop met de backend te wijzigen tot de rand is opgelost.

De gewone geproxiede record van Cloudflare vervoert zijn ondersteunde HTTP- en HTTPS-verkeer op vaste poorten. De proxy aanzetten voor een hostnaam maakt geen algemene TCP-relay, en SSH naar poort 443 verplaatsen maakt het geen HTTPS.

Er bestaan twee werkbare richtingen. Een DNS-only record kan naar uw eigen TCP-relay wijzen, wat de beslissing over blootstelling in uw handen legt. Anders gebruikt u een product dat SSH-transport uitdrukkelijk documenteert, met zijn eigen origin-, client- en toegangsconfiguratie.

Wat u ook kiest, de relay heeft nog steeds een VPN-route naar de backend, een toegestane verbinding en een terugweg nodig. De route is niet optioneel omdat de openbare kant werkt.

Houd de SSH-identiteit stabiel via een alias

Gebruikers willen meestal hun bestaande hostnaam blijven typen. Een alias in de SSH-configuratie kan dat doen zonder iets aan de relay te veranderen, en het houdt de wijziging aan de clientkant waar die hoort.

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

Zonder HostKeyAlias zoekt OpenSSH de hostsleutel op onder de naam waarmee het verbindt, dus de vermelding verschuift en elke gebruiker ziet bij eerste gebruik een waarschuwing over een gewijzigde sleutel. Het alias instellen houdt known-hosts-vermeldingen stabiel terwijl de verbinding nog steeds naar de relay gaat.

Dit alias geldt alleen voor op SSH gebaseerde remote-repositories. Een HTTPS-Git-URL raadpleegt het niet, en de gebruikelijke remote-URL moet overeenkomen met het transport dat u daadwerkelijk hebt ingericht.

Wat u niet moet doen, is de hostsleutelverificatie uitschakelen om de waarschuwing te laten verdwijnen. Dat verwijdert een bescherming die de doorstuurrepair nooit gevraagd heeft aan te raken, en het maakt van een cosmetische ergernis een echte blootstelling.

Verifieer elke hop apart

Test de openbare listener alleen, dan de relay-naar-backend-verbinding vanaf de relayhost, en pas daarna de client van begin tot eind. Elk van die drie tests sluit een andere laag uit. Er één overslaan betekent dat de resterende twee resultaten niet te interpreteren zijn. Drie waarnemingen, drie plaatsen waar een fout kan leven.

Bewaar het bestemmingsadres, de poort en de eerste protocolbytes bij elk resultaat. Dat drietal onderscheidt een randfout van een VPN-routeringsprobleem en van een SSH-authenticatiefout, en dat is het verschil tussen een reparatie en een gok.

Documenteer ten slotte de beoogde blootstelling. Een TCP-relay die SSH naar een private backend brengt, is een bewuste opening in de openbare perimeter, en verdient dezelfde beoordeling als elke andere inkomende dienst.

Gerelateerde artikelen