Come raggiungere un database che ascolta solo su localhost?
Un’istanza Postgres su un server remoto è collegata a 127.0.0.1 di proposito. Nessuno sulla rete può raggiungerla, solo i processi in esecuzione su quella stessa macchina. Il tuo laptop non è quella macchina, quindi psql -h remote-server -p 5432 fallisce prima ancora di arrivare all’autenticazione. Aprire la porta al mondo vanifica il motivo per cui era stata collegata al loopback.
Quello che ti serve è prendere in prestito, per qualche minuto e dal tuo terminale, la vista che la macchina remota ha di 127.0.0.1. Quando fai login, SSH ha già una connessione cifrata e autenticata aperta tra le due macchine. Il port forwarding riutilizza quella connessione per trasportare traffico diverso dalla tua shell.
Come funziona il port forwarding SSH sotto il cofano?
Una sessione SSH normale sembra un singolo tubo: i tasti premuti entrano, l’output del terminale torna indietro. In realtà, il protocollo multiplexa diversi canali indipendenti su quell’unica connessione cifrata. La shell è un canale. Una connessione TCP forwardata è un altro.
Chiedi a ssh di forwardare una porta: apre un nuovo canale per ogni connessione fatta a quella porta, trasporta i byte grezzi attraverso la sessione esistente e li consegna dall’altra parte. Nessun nuovo certificato, nessun secondo login. Il tunnel eredita qualsiasi accesso abbia già il login SSH.
Tienilo a mente prima di guardare i flag. -L, -R e -D non sono tre meccanismi diversi. Sono lo stesso meccanismo di channel-forwarding, orientato in direzioni diverse.
Come si usa il port forwarding locale SSH (-L)?
| |
Leggi l’argomento di -L come local_port:destination_host:destination_port. Questo apre la porta 5432 sul tuo laptop. Qualsiasi cosa si connetta a quella porta viene forwardata attraverso il tunnel verso localhost:5432 così come lo vede db-host, che è l’istanza Postgres collegata al proprio loopback. In un secondo terminale:
| |
psql pensa di parlare con un database locale. In realtà parla con l’interfaccia di loopback di db-host attraverso SSH.
L’host di destinazione non deve per forza essere il server SSH stesso:
| |
Questo forwarda tramite jumpbox verso una terza macchina che jumpbox può raggiungere e tu no. È la forma a cui la maggior parte delle persone pensa quando dice “tunnel SSH”: una macchina con l’accesso che a te manca, che forwarda verso una rete che altrimenti non potresti toccare. Se hai già Postgres in esecuzione in locale, la porta 5432 sul tuo laptop è occupata; scegli un’altra porta locale come 15432 e passa -p 15432 a psql.
Come esporre una porta locale con il remote forwarding SSH (-R)?
-R fa la stessa cosa al contrario. La sintassi è remote_port:destination_host:destination_port, ma il listener si apre sul lato remoto.
| |
Ora public-host ascolta sulla porta 8080, e qualsiasi connessione a quella porta torna indietro attraverso il tunnel verso localhost:3000 sulla tua macchina: il tuo server di sviluppo locale. Ottieni un indirizzo raggiungibile per un provider di webhook o un collega senza aprire una porta sul tuo firewall, prendendo in prestito una macchina che ne ha già una.
C’è un comportamento predefinito che frega tutti. sshd collega le porte remote-forwarded all’interfaccia di loopback dell’host remoto, quindi curl public-host:8080 da una terza macchina fallisce anche se il forward è attivo. Solo i processi su public-host stesso possono raggiungerla. Cambiare questo richiede GatewayPorts yes nello sshd_config di quella macchina, sotto il controllo del suo amministratore. Nessun flag sul tuo comando ssh lo sovrascrive.
Come trasformare un tunnel SSH in un proxy SOCKS (-D)?
-L e -R forwardano entrambi verso una destinazione fissa, decisa quando lanci il comando. -D non sceglie nessuna destinazione:
| |
Questo apre un proxy SOCKS su localhost:1080. Qualsiasi applicazione puntata su quella porta sceglie la destinazione per ogni connessione, e ssh la instrada come se a connettersi fosse remote-host stesso:
| |
La parte -hostname conta. Con il semplice --socks5, curl risolve internal-service.corp sul tuo laptop, dove quel nome non esiste, e fallisce prima ancora di usare il proxy. --socks5-hostname manda il nome attraverso il tunnel così che sia remote-host a risolverlo.
I browser funzionano allo stesso modo una volta impostato il proxy nelle loro impostazioni di rete (e abilitato “proxy DNS when using SOCKS v5” in Firefox). A quel punto, ogni sito vede l’IP di remote-host ed è soggetto alle sue regole di firewall. È così che raggiungi un’intera rete interna passando da un solo jump box, o verifichi cosa mostra un sito visto da un’altra regione, senza scrivere un forward separato per ogni destinazione.
Quale tipo di forwarding SSH scegliere: -L, -R o -D?
-L locale | -R remoto | -D dinamico | |
|---|---|---|---|
| Il listener si apre su | La tua macchina | L’host remoto | La tua macchina |
| Destinazione | Fissa, impostata da riga di comando | Fissa, impostata da riga di comando | Scelta per connessione dall’app |
| Uso tipico | Raggiungere un servizio che solo l’host remoto vede | Esporre il tuo servizio locale al lato remoto | Instradare traffico arbitrario attraverso la rete dell’host remoto |
| Serve dall’altra parte | Niente oltre a un normale login SSH | GatewayPorts se altri devono raggiungerlo | Niente oltre a un normale login SSH |
Come tenere aperto un tunnel SSH in background?
Due problemi si presentano subito: dover digitare il comando completo ogni volta, e il tunnel che muore quando chiudi il terminale. ~/.ssh/config risolve il primo:
| |
Ora ssh db-tunnel apre entrambi i forward insieme. LocalForward, RemoteForward e DynamicForward corrispondono a -L, -R e -D, una riga per ogni forward, quante ne servono in un blocco Host.
Per un forward che deve restare attivo senza supervisione, aggiungi -N (non eseguire nessun comando remoto, mantieni solo il tunnel) e -f (vai in background dopo l’autenticazione), più un keepalive così che una connessione caduta in silenzio venga notata invece di restare appesa:
| |
Questo però non si riconnette quando la rete cade. ssh termina e il forward sparisce. autossh avvolge lo stesso comando e lo riavvia quando la connessione muore:
| |
-M 0 disattiva la porta di monitoraggio propria di autossh e lascia che sia il keepalive ServerAlive* a rilevare una connessione morta. Con l’OpenSSH moderno, è questa la configurazione da usare.
Quando è meglio non usare i tunnel SSH?
Una porta forwardata eredita l’accesso del login SSH che l’ha aperta. Comodo quando sei tu a tenere la chiave, ma un’esposizione reale se la chiave o la sessione vengono compromesse, perché il tunnel regala tutto ciò che quel login può raggiungere. AllowTcpForwarding in sshd_config è l’interruttore lato server (default yes). Impostarlo a no chiude questa strada, ma chiunque abbia accesso shell sulla macchina può comunque lanciare il proprio forwarder, quindi aiuta solo se limiti anche l’accesso shell.
I tunnel smettono anche di scalare oltre una manciata di persone o servizi. Ognuno è un processo legato a un terminale o a un’istanza di autossh. Non c’è un posto centrale dove vedere chi ha un tunnel aperto, revocare l’accesso senza toccare ogni configurazione client, o applicare una sola policy su tutti gli utenti. A quel punto una VPN, o un identity-aware proxy davanti al servizio, dovrebbe sostituire il mucchio di tunnel personali.
Non sono nemmeno lo stesso strumento di un reverse proxy o di Cloudflare Tunnel, anche se tutti e tre rendono raggiungibile qualcosa che prima non lo era. Un reverse proxy sta permanentemente davanti a un servizio ed è esposto direttamente a internet. Cloudflare Tunnel è più vicino a -R: una connessione in uscita dal tuo lato che diventa raggiungibile dall’esterno, ma attraverso l’edge di Cloudflare con un hostname stabile e nessuna porta server da gestire. Un reverse proxy o Cloudflare Tunnel si basano invece su certificati, come descritto in come funziona l’handshake TLS; un tunnel SSH non ne ha bisogno. La cifratura è la sessione SSH stessa, autenticata dalla tua chiave invece che da un certificato di cui un browser si fiderebbe.
Cosa configurare per primo?
Prendi il tunnel che digiti più spesso e trasformalo in un blocco Host in ~/.ssh/config oggi stesso. Raggiungere qualcosa che solo un host remoto vede è -L. Esporre un servizio locale attraverso una macchina con un indirizzo pubblico è -R. Instradare il traffico di un’applicazione attraverso una rete remota senza scegliere le destinazioni in anticipo è -D. Una volta nel config, ssh -N -f db-tunnel è tutto quello che devi digitare, e smetti di sbagliare numeri di porta alle 11 di sera.