PostgreSQL
In un terminale, lascia il tunnel in esecuzione:
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:5433:127.0.0.1:5432 user@server
In un altro:
psql -h 127.0.0.1 -p 5433 -U app appdb
La porta 5433 è una qualsiasi porta libera sul tuo computer. Usarne una diversa da 5432 evita il conflitto con un PostgreSQL locale. Una stringa di connessione funziona allo stesso modo, in qualsiasi strumento o libreria:
postgresql://app@127.0.0.1:5433/appdb
In uno strumento grafico per database, inserisci host 127.0.0.1 e porta 5433 con le credenziali del database, e lascia spenta l'opzione SSH dello strumento, perché quel lavoro lo fa già il tunnel.
Sul server la connessione sembra arrivare dal server stesso. Nel nostro test inet_client_addr() ha restituito 127.0.0.1. Quindi pg_hba.conf ha bisogno di una riga per quell'indirizzo, che la maggior parte dei file predefiniti ha già:
host appdb app 127.0.0.1/32 scram-sha-256
MySQL
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:3307:127.0.0.1:3306 user@server
mysql -h 127.0.0.1 -P 3307 -u app -p appdb
Scrivi 127.0.0.1 e non localhost. Il client MySQL interpreta localhost come richiesta del socket Unix locale, ignora la porta e fallisce:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
Anche --protocol=TCP risolve, se vuoi proprio scrivere localhost.
L'account deve essere autorizzato dall'indirizzo che vede il server. Un utente creato come 'app'@'localhost' può fallire via TCP. Sull'immagine ufficiale di MySQL, che ha skip_name_resolve attivo, è fallito con Access denied for user 'app'@'127.0.0.1'. Crea l'account per quell'indirizzo:
CREATE USER 'app'@'127.0.0.1' IDENTIFIED BY '...';
GRANT ALL ON appdb.* TO 'app'@'127.0.0.1';
Quale indirizzo va dove
-L 127.0.0.1:5433:127.0.0.1:5432
| | | |
| | | +- port of the database
| | +- database address, as seen by the SSH server
| +- port on your machine
+- address to listen on, on your machine
- Tieni il primo indirizzo a
127.0.0.1, così nessun altro nella tua rete può usare il tuo tunnel. - Il database può restare in ascolto solo sul loopback:
listen_addresses = 'localhost'in PostgreSQL,bind-address = 127.0.0.1in MySQL. Entrambi hanno funzionato nel nostro test, e il tunnel non richiede di aprirli. - Come destinazione preferisci
127.0.0.1alocalhost. Il nome lo risolve il server SSH, e potrebbe provare prima IPv6.
Gli inoltri remoti e dinamici seguono lo stesso schema, vedi tunnel SSH locali, remoti e dinamici.
Se il database è su un'altra macchina, per esempio un servizio gestito, indicalo come destinazione. Il database vedrà allora l'indirizzo privato del server SSH, ed è quello che deve comparire nella sua lista di accessi consentiti:
ssh -N -L 127.0.0.1:5433:db.internal.example.com:5432 user@server
Il TLS conta ancora
SSH cifra dal tuo computer al server SSH. Se il database è su un'altra macchina, l'ultimo tratto è fuori da quella cifratura, quindi usa il TLS anche sulla connessione al database.
Il controllo del certificato richiede attenzione, perché il tuo client crede di collegarsi a 127.0.0.1, che non è nel certificato:
psql "host=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
... server certificate for "db.example.com" does not match host name "127.0.0.1"
In PostgreSQL, imposta host sul nome del certificato e hostaddr sul tunnel. Nel nostro test ha funzionato con la verifica completa:
psql "host=db.example.com hostaddr=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
MySQL non ha un'impostazione equivalente. --ssl-mode=VERIFY_IDENTITY controlla il nome che hai scritto, quindi servirebbe un certificato che elenchi 127.0.0.1, oppure un nome nel file hosts che punti a quell'indirizzo. Il semplice REQUIRED cifra senza controllare chi ha risposto.
Un utente che può solo aprire questo tunnel
Una persona o un'app che ha bisogno solo del database non dovrebbe ottenere una shell sul server. Crea un utente senza shell:
sudo useradd -m -s /usr/sbin/nologin dbtunnel
Installa la sua chiave pubblica in /home/dbtunnel/.ssh/authorized_keys. Poi aggiungi un file drop-in, per esempio /etc/ssh/sshd_config.d/10-dbtunnel.conf. Match si ferma alla fine di quel file, quindi non influisce sulle impostazioni che seguono:
Match User dbtunnel
AllowTcpForwarding local
PermitOpen 127.0.0.1:5432
X11Forwarding no
AllowAgentForwarding no
PermitTTY no
ForceCommand /usr/sbin/nologin
Esegui sudo sshd -t, ricarica e collegati:
ssh -N -L 127.0.0.1:5433:127.0.0.1:5432 dbtunnel@server
Cosa abbiamo visto con questa configurazione:
- Il tunnel qui sopra funziona e
psqlsi collega attraverso di esso. - La porta 3306 o qualsiasi altra destinazione viene rifiutata.
-Rfallisce conremote port forwarding failed for listen port.- Un login normale stampa
This account is currently not available. - Viene rifiutato anche
-L 5433:localhost:5432.PermitOpenconfronta il testo, senza risolvere nomi, quindi il client deve chiedere esattamente127.0.0.1.
Per limitare una sola chiave puoi invece usare le opzioni in authorized_keys, su una sola riga:
restrict,port-forwarding,permitopen="127.0.0.1:5432" ssh-ed25519 AAAA... dbtunnel
Anche questo blocca la shell e le altre destinazioni. Nel nostro test permetteva ancora -R, quindi aprire una porta sul server resta possibile. Il blocco Match con AllowTcpForwarding local lo chiude, e per questo lo preferiamo.
Questo utente può raggiungere solo il login del database. L'account del database deve comunque avere la propria password o il proprio certificato, e solo i permessi che gli servono.
Domande
Perché usare la porta 5433 sul mio lato invece di 5432?
Va bene qualsiasi porta libera. Una diversa evita il conflitto con un PostgreSQL già in esecuzione sulla tua macchina.
Perché mysql ignora la porta se scrivo localhost?
Il client MySQL tratta localhost come il socket Unix locale. Usa 127.0.0.1, oppure aggiungi --protocol=TCP.
Il traffico del database è cifrato dentro un tunnel SSH?
Dalla tua macchina al server SSH sì. Se il database gira su un'altra macchina, l'ultimo tratto è fuori dal tunnel, quindi usa anche TLS.
Fonti
- ssh(1), manuale OpenBSD
- sshd_config(5), manuale OpenBSD
- sshd(8), formato di authorized_keys, manuale OpenBSD
- PostgreSQL, connessioni TCP/IP sicure con tunnel SSH
- PostgreSQL, parametri di connessione di libpq
- MySQL 8.0, connessione al server