L'SSH pubblico attraverso un relè fallisce su due confini non correlati, e correggerne uno di solito rivela l'altro. O il bordo restituisce una risposta HTTP invece di trasportare un flusso, o il relè antepone un'intestazione che il backend non ha mai accettato di leggere. Tre condizioni decidono quale dei due si vede: a cosa punta realmente il record pubblico, cosa invia il blocco di flusso verso l'origine e quale nome verifica il client SSH. Affrontale in quest'ordine.

Due confini, due sintomi

Un guasto al bordo somiglia a una pagina di stato HTTP che arriva dove dovrebbe esserci un banner SSH. La connessione si completa a livello TCP e i primi byte sono testo appartenente a un server web. È una discrepanza di protocollo, non un pacchetto perso. Dice anche che il bordo serve deliberatamente qualcos'altro per quel nome.

Un guasto all'origine appare diverso. La connessione raggiunge sshd, sshd legge byte che non sono lo scambio di identificazione atteso, e la sessione muore o si blocca senza mai offrire una chiave.

I due si confondono facilmente perché entrambi producono un client che si connette e poi fallisce. La prima risposta di protocollo che si riceve è ciò che li distingue, ed è per questo che appartiene al ticket.

L'intestazione PROXY è un messaggio, non un interruttore

Nel modulo di flusso, proxy_protocol on ordina a NGINX di scrivere un'intestazione di protocollo PROXY verso l'origine prima del carico utile. Non abilita il proxy TCP e non ha nulla a che vedere con la crittografia SSH. È una nota indirizzata al destinatario. Se il destinatario non riesce a leggerla, diventa detrito all'inizio del flusso.

Il servizio ricevente deve consumare quell'intestazione deliberatamente. Un normale sshd si aspetta lo scambio di identificazione SSH come primi byte della connessione, quindi un preambolo inatteso rompe l'handshake in un modo che sembra un server corrotto.

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

Quel frammento è l'intero relè funzionante per un backend raggiungibile attraverso la VPN. Nessuna direttiva PROXY compare perché il destinatario non parla il protocollo, e quell'assenza è una decisione, non una dimenticanza.

Separare anche le due direzioni. Impostare proxy_protocol sulla riga listen significa che NGINX si aspetta che il proprio client invii prima l'intestazione; abilitarlo per un client SSH ordinario produce esattamente l'opposto del problema che di solito si cerca di risolvere.

A cosa arriva realmente il nome host pubblico

Una pagina HTTP 400 in una sessione SSH è una prova sul bordo. Qualcosa nel percorso parla HTTP, e nessuna quantità di lavoro su chiave o account nel backend lo cambierà. Smetta di cambiare il backend finché il bordo non è risolto.

Il record con proxy ordinario di Cloudflare trasporta il suo traffico HTTP e HTTPS supportato su porte definite. Attivare il proxy per un nome host non crea un relè TCP generale, e spostare SSH sulla porta 443 non lo rende HTTPS.

Esistono due direzioni praticabili. Un record solo DNS può puntare al proprio relè TCP, il che mette la decisione sull'esposizione nelle proprie mani. In alternativa, usi un prodotto che documenti esplicitamente il trasporto SSH, con la propria configurazione di origine, client e accesso.

Qualunque scelta, il relè ha ancora bisogno di una rotta VPN verso il backend, di una connessione consentita e di un percorso di ritorno. La rotta non è facoltativa perché il lato pubblico funziona.

Mantenere stabile l'identità SSH tramite un alias

Gli utenti di solito vogliono continuare a digitare il proprio nome host esistente. Un alias nella configurazione SSH può farlo senza cambiare nulla del relè, e mantiene la modifica sul lato client, dove le compete.

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

Senza HostKeyAlias, OpenSSH cerca la chiave dell'host sotto il nome a cui si connette, quindi la voce si sposta e ogni utente vede un avviso di chiave modificata al primo utilizzo. Impostare l'alias mantiene stabili le voci di known-hosts mentre la connessione continua ad andare al relè.

Questo alias vale solo per i remoti basati su SSH. Un URL Git HTTPS non lo consulta, e l'URL tipico del remoto deve corrispondere al trasporto realmente predisposto.

Ciò che non si deve fare è disattivare la verifica della chiave dell'host per far sparire l'avviso. Questo rimuove una protezione che la riparazione dell'inoltro non ha mai chiesto di toccare, e trasforma un fastidio estetico in un'esposizione reale.

Verificare ogni salto separatamente

Provi l'ascoltatore pubblico da solo, poi la connessione dal relè al backend dall'host del relè, e solo dopo il client da un capo all'altro. Ciascuna di quelle tre prove esclude uno strato diverso. Saltarne una significa che i due risultati rimanenti non si possono interpretare. Tre osservazioni, tre luoghi dove un guasto può vivere.

Conservi l'indirizzo di destinazione, la porta e i primi byte di protocollo con ogni risultato. Quella terna distingue un errore di bordo da un problema di routing VPN e da un guasto di autenticazione SSH, ed è la differenza tra una riparazione e una congettura.

Infine, documenti l'esposizione prevista. Un relè TCP che trasporta SSH verso un backend privato è un'apertura deliberata nel perimetro pubblico, e merita la stessa revisione di qualsiasi altro servizio in ingresso.

Articoli correlati