SSH Tunneling

Per molti utenti, SSH è semplicemente quel comando che permette di collegarsi ad un server remoto:
ssh user@server
In realtà SSH è uno degli strumenti più potenti che abbiamo a disposizione su GNU/Linux. Non serve solo ad aprire una shell remota: può diventare un tunnel cifrato per database, pannelli web, API interne e servizi che non dovrebbero mai essere esposti direttamente su Internet.
Negli ultimi anni ho visto troppe installazioni con PostgreSQL aperto sul mondo, dashboard Kubernetes pubbliche o pannelli di management raggiungibili senza alcun controllo. Nella maggior parte dei casi, bastava usare un tunnel SSH.
Vediamo quindi come sfruttarlo nel modo corretto, con un occhio molto attento alla sicurezza.
Cos’è SSH Tunneling
SSH Tunneling (o port forwarding) permette di trasportare traffico TCP attraverso una connessione SSH cifrata.
In pratica:
- il servizio rimane privato
- non serve aprire nuove porte sul firewall
- il traffico viaggia cifrato
- si riduce enormemente la superficie di attacco
È una soluzione perfetta ad esempio per:
- database PostgreSQL/MySQL
- dashboard interne
- pannelli Proxmox
- Grafana
- servizi self-hosted
- ambienti di sviluppo
- …
Local Port Forwarding (-L)
Questo è il caso più comune.
Immaginiamo un database PostgreSQL raggiungibile solo dal server remoto:
ssh -N -L 5432:localhost:5432 user@server-remoto
Vediamo cosa significa:
-L 5432:localhost:5432
dice a SSH:
“Ascolta sulla porta 5432 locale e inoltra tutto verso localhost:5432 del server remoto”
A questo punto il database remoto sarà raggiungibile localmente:
psql -h localhost -U postgres
senza esporre PostgreSQL su internet.
Ed è esattamente così che dovrebbe essere configurato un database pubblico: accessibile solo via tunnel o rete privata.
Sicurezza: bind solo su localhost
Una configurazione corretta lato database dovrebbe essere:
listen_addresses = 'localhost'
oppure:
127.0.0.1
Non:
0.0.0.0
a meno che non ci sia una reale necessità.
Lo stesso vale per:
- Redis
- Prometheus
- Grafana
- pgAdmin
- pannelli web vari
Se un servizio non deve essere pubblico, non deve ascoltare pubblicamente.
Eseguire il tunnel in background
Un tunnel spesso serve per ore.
Possiamo mandarlo in background:
ssh -f -N -L 5432:localhost:5432 user@server-remoto
dove:
- -N evita l’apertura della shell remota
- -f manda il processo in background
Per verificare che il tunnel sia attivo:
lsof -i :5432
oppure:
ss -lntp
Remote Port Forwarding (-R)
Il forwarding remoto fa l’opposto.
Espone un servizio locale attraverso un server remoto.
Esempio:
ssh -N -R 9000:localhost:3000 user@server-remoto
In questo caso:
- il server remoto espone la porta 9000
- il traffico viene inoltrato verso il nostro PC locale sulla porta 3000
Molto utile per:
- demo rapide
- sviluppo remoto
- webhook temporanei
Attenzione ai rischi del forwarding remoto
Qui bisogna fare molta attenzione.
Per default OpenSSH limita l’accesso al tunnel al solo localhost remoto. Ed è una scelta molto sensata.
Se abilitate:
GatewayPorts yes
nel file:
/etc/ssh/sshd_config
state potenzialmente esponendo il vostro servizio locale al mondo intero.
Prima di farlo chiedetevi:
- serve davvero?
- chi può raggiungere quella porta?
- il servizio ha autenticazione?
- c’è un firewall?
In molti casi è meglio usare:
GatewayPorts clientspecified
oppure limitare tutto tramite firewall.
Dynamic Port Forwarding (-D)
Questa modalità trasforma SSH in un proxy SOCKS5.
ssh -N -D 1080 user@server-remoto
A quel punto possiamo configurare il browser usando:
SOCKS5 localhost:1080
Il traffico verrà instradato attraverso il server remoto.
Può essere utile:
- su Wi-Fi pubblici
- per accedere a reti aziendali
- per test geografici
- per cifrare traffico non sicuro
Bastion Host e Jump Server
Nelle infrastrutture moderne è sempre più comune usare un bastion host.
L’idea è semplice:
- un solo server esposto
- tutti gli altri restano privati
Connessione classica:
ssh -J user@bastion user@internal-server
Oppure con tunnel database:
ssh -L 5432:localhost:5432 -J user@bastion user@db-server
È una soluzione estremamente più sicura rispetto ad aprire SSH ovunque.
~/.ssh/config: la svolta
Quando iniziano ad esserci molti tunnel, conviene usare:
~/.ssh/config
Esempio:
Host db-prod
HostName bastion.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519
LocalForward 5432 db.internal:5432
ServerAliveInterval 60
ServerAliveCountMax 3
ExitOnForwardFailure yes
A quel punto basta:
ssh -fN db-prod
Molto più leggibile e gestibile.
AutoSSH: tunnel persistenti
Le connessioni cadono.
Specialmente:
- Wi-Fi instabili
- VPS economiche
- reti mobili
- NAT aggressivi
Qui entra in gioco autossh.
Installazione:
apt install autossh
Esempio:
autossh -M 0 -f -N \
-L 5432:db.internal:5432 \
deploy@bastion
Con:
ServerAliveInterval 60
ServerAliveCountMax 3
il tunnel verrà ricreato automaticamente.
systemd: tunnel permanenti
Se il tunnel deve restare sempre attivo, meglio usare systemd.
Esempio:
[Unit]
Description=Tunnel PostgreSQL
After=network.target
[Service]
User=deploy
ExecStart=/usr/bin/autossh -M 0 -N \
-o "ServerAliveInterval=60" \
-o "ServerAliveCountMax=3" \
-L 5432:db.internal:5432 \
[email protected]
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Abilitazione:
systemctl enable --now tunnel-postgres
Best practice fondamentali
Ci sono alcune regole che considero obbligatorie.
1. Disabilitare login password
Usare solo chiavi SSH:
PasswordAuthentication no
2. Disabilitare root login
PermitRootLogin no
3. Usare chiavi moderne
Preferire:
ed25519
Creazione:
ssh-keygen -t ed25519
4. Limitare gli utenti autorizzati
AllowUsers deploy backup
5. Non usare tunnel inutili
Chiudere sempre ciò che non serve.
Un tunnel dimenticato è una superficie di attacco dimenticata.
6. Monitorare i log SSH
Sempre.
journalctl -u ssh
oppure:
/var/log/auth.log
7. Proteggere il bastion host
È il punto più critico dell’infrastruttura.
Consiglio almeno:
- Fail2ban
- MFA
- firewall restrittivo
- accesso IP-based
- aggiornamenti costanti
Una nota importante: SSH Forwarding supporta solo TCP
Questa è una limitazione spesso dimenticata: SSH forwarding funziona solo con TCP.
I servizi UDP non possono essere inoltrati direttamente tramite SSH.
Per quelli serve:
- VPN
- WireGuard
- Tailscale
- soluzioni dedicate
Conclusioni
SSH Tunneling è una di quelle funzionalità che esistono da decenni ma che ancora oggi vengono sottovalutate.
Eppure permette di:
- evitare porte pubbliche inutili
- proteggere database
- accedere a servizi interni
- semplificare l’accesso remoto
- ridurre drasticamente l’esposizione
Spesso vedo setup complicatissimi risolvibili con una singola riga:
ssh -L
E sinceramente continuo a pensare che uno dei principi migliori in ambito sysadmin sia sempre lo stesso:
meno porte aperte, meno problemi.
Le opinioni in quanto tali sono opinabili e nulla ti vieta di approfondire l’argomento.
Risorse: