PostgreSQL
Lassen Sie in einem Terminal den Tunnel laufen:
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:5433:127.0.0.1:5432 user@server
In einem anderen:
psql -h 127.0.0.1 -p 5433 -U app appdb
Port 5433 ist ein beliebiger freier Port auf Ihrem Rechner. Ein anderer Port als 5432 vermeidet einen Konflikt mit einem lokalen PostgreSQL. Ein Connection-String funktioniert genauso, in jedem Tool und jeder Bibliothek:
postgresql://app@127.0.0.1:5433/appdb
In einem grafischen Datenbank-Tool tragen Sie Host 127.0.0.1 und Port 5433 mit dem Datenbank-Login ein und lassen die SSH-Option des Tools aus, denn diese Aufgabe übernimmt schon der Tunnel.
Auf dem Server sieht es so aus, als käme die Verbindung vom Server selbst. In unserem Test lieferte inet_client_addr() den Wert 127.0.0.1. Daher braucht pg_hba.conf eine Zeile dafür, die die meisten Standarddateien bereits enthalten:
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
Schreiben Sie 127.0.0.1 und nicht localhost. Der MySQL-Client versteht localhost als Aufforderung, den lokalen Unix-Socket zu nutzen, ignoriert den Port und scheitert:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
Auch --protocol=TCP behebt das, falls Sie unbedingt localhost tippen wollen.
Das Konto muss von der Adresse aus erlaubt sein, die der Server sieht. Ein als 'app'@'localhost' angelegter Benutzer kann über TCP scheitern. Beim offiziellen MySQL-Image, in dem skip_name_resolve aktiv ist, kam Access denied for user 'app'@'127.0.0.1'. Legen Sie das Konto für diese Adresse an:
CREATE USER 'app'@'127.0.0.1' IDENTIFIED BY '...';
GRANT ALL ON appdb.* TO 'app'@'127.0.0.1';
Welche Adresse wohin gehört
-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
- Lassen Sie die erste Adresse bei
127.0.0.1, damit niemand sonst in Ihrem Netzwerk Ihren Tunnel nutzen kann. - Die Datenbank kann ausschließlich auf Loopback lauschen:
listen_addresses = 'localhost'in PostgreSQL,bind-address = 127.0.0.1in MySQL. Beides funktionierte in unserem Test, und der Tunnel braucht keine weitere Öffnung. - Bevorzugen Sie auch als Ziel
127.0.0.1stattlocalhost. Der SSH-Server löst den Namen auf und probiert womöglich zuerst IPv6.
Remote- und dynamische Forwards folgen demselben Muster, siehe SSH-Tunnel: lokal, remote und dynamisch.
Liegt die Datenbank auf einem anderen Rechner, zum Beispiel als Managed Service, nennen Sie sie als Ziel. Die Datenbank sieht dann die private Adresse des SSH-Servers, und genau die gehört in ihre Zugriffsliste:
ssh -N -L 127.0.0.1:5433:db.internal.example.com:5432 user@server
TLS bleibt wichtig
SSH verschlüsselt von Ihrem Rechner bis zum SSH-Server. Liegt die Datenbank auf einem anderen Rechner, ist die letzte Teilstrecke nicht mehr von dieser Verschlüsselung abgedeckt, nutzen Sie daher zusätzlich TLS auf der Datenbankverbindung.
Die Zertifikatsprüfung braucht besondere Sorgfalt, denn Ihr Client glaubt, er verbinde sich mit 127.0.0.1, und das steht nicht im Zertifikat:
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"
Setzen Sie in PostgreSQL host auf den Namen im Zertifikat und hostaddr auf den Tunnel. Das bestand in unserem Test mit vollständiger Prüfung:
psql "host=db.example.com hostaddr=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
MySQL hat keine entsprechende Einstellung. --ssl-mode=VERIFY_IDENTITY prüft den Namen, den Sie getippt haben, Sie bräuchten also ein Zertifikat, das 127.0.0.1 enthält, oder einen Namen in Ihrer hosts-Datei, der darauf zeigt. Einfaches REQUIRED verschlüsselt, ohne zu prüfen, wer antwortet.
Ein Benutzer, der nur diesen Tunnel öffnen kann
Eine Person oder App, die nur die Datenbank braucht, sollte keine Shell auf dem Server bekommen. Legen Sie einen Benutzer ohne Shell an:
sudo useradd -m -s /usr/sbin/nologin dbtunnel
Installieren Sie dessen öffentlichen Schlüssel in /home/dbtunnel/.ssh/authorized_keys. Fügen Sie dann eine Drop-in-Datei hinzu, zum Beispiel /etc/ssh/sshd_config.d/10-dbtunnel.conf. Match endet am Ende dieser Datei und wirkt sich daher nicht auf die Einstellungen danach aus:
Match User dbtunnel
AllowTcpForwarding local
PermitOpen 127.0.0.1:5432
X11Forwarding no
AllowAgentForwarding no
PermitTTY no
ForceCommand /usr/sbin/nologin
Führen Sie sudo sshd -t aus, laden Sie neu und verbinden Sie sich:
ssh -N -L 127.0.0.1:5433:127.0.0.1:5432 dbtunnel@server
Das haben wir mit dieser Konfiguration beobachtet:
- Der obige Tunnel funktioniert, und
psqlverbindet sich darüber. - Port 3306 oder jedes andere Ziel wird abgelehnt.
-Rscheitert mitremote port forwarding failed for listen port.- Eine normale Anmeldung gibt
This account is currently not available.aus. - Auch
-L 5433:localhost:5432wird abgelehnt.PermitOpenvergleicht Text, ohne Namensauflösung, der Client muss also genau127.0.0.1anfordern.
Um stattdessen einen einzelnen Schlüssel zu beschränken, können Sie Optionen in authorized_keys verwenden, in einer Zeile:
restrict,port-forwarding,permitopen="127.0.0.1:5432" ssh-ed25519 AAAA... dbtunnel
Das blockiert ebenfalls eine Shell und andere Ziele. In unserem Test erlaubte es dennoch -R, das Öffnen eines Ports auf dem Server bleibt also möglich. Der Match-Block mit AllowTcpForwarding local schließt das, und wir bevorzugen ihn aus diesem Grund.
Dieser Benutzer kommt nur bis zur Anmeldung der Datenbank. Das Datenbankkonto sollte trotzdem ein eigenes Passwort oder Zertifikat haben und nur die nötigen Rechte.
Fragen
Warum lokal Port 5433 statt 5432?
Jeder freie Port geht. Ein anderer vermeidet den Konflikt mit einem PostgreSQL, das schon auf Ihrem Rechner läuft.
Warum ignoriert mysql den Port, wenn ich localhost schreibe?
Der MySQL-Client versteht localhost als lokalen Unix-Socket. Verwenden Sie 127.0.0.1 oder ergänzen Sie --protocol=TCP.
Ist der Datenbankverkehr im SSH-Tunnel verschlüsselt?
Von Ihrem Rechner bis zum SSH-Server ja. Läuft die Datenbank auf einem anderen Rechner, liegt die letzte Strecke außerhalb des Tunnels. Nutzen Sie dann zusätzlich TLS.
Quellen
- ssh(1), OpenBSD-Handbuch
- sshd_config(5), OpenBSD-Handbuch
- sshd(8), Format von authorized_keys, OpenBSD-Handbuch
- PostgreSQL, sichere TCP/IP-Verbindungen mit SSH-Tunneln
- PostgreSQL, Verbindungsparameter von libpq
- MySQL 8.0, Verbindung zum Server