In der Software-Entwicklung oder System-Administration möchte man manchmal auf Dienste zugreifen, die nicht direkt aus dem lokalen Netzwerk erreichbar sind.

In diesem Artikel zeigen wir die drei üblichsten Varianten, wie man mit OpenSSH1 Tunnels baut, um eine Verbindung zwischen Systemen herzustellen, die eigentlich nicht direkt miteinander sprechen können.

Local Port Forwarding

Ein häufiger Anwendungsfall in der Software-Entwicklung bei der cronn GmbH ist der Zugriff auf eine Datenbank oder ein Web-Service in einer Testumgebung.

In der Regel sind die Dienste in der Testumgebung nicht direkt vom eigenen Computer erreichbar, sondern erfordern den Zugriff über einen Server in der Testumgebung, auf den man sich per SSH anmelden kann.

In unserem Beispiel wollen wir eine SQL-Abfrage auf einer PostgreSQL-Datenbank in der Testumgebung ausführen. Man könnte sich dazu per SSH auf dem Datenbank-Server anmelden und in der Kommandozeile mit psql die SQL-Abfrage laufen lassen. Dieses Vorgehen ist in der Praxis oft zu unhandlich und man möchte viel lieber mit einem Datenbank-Client seiner Wahl (z.B. DBeaver oder DataGrip) arbeiten.

OpenSSH bietet mit „Local Port Forwarding“ ein nützliches Mittel, um genau das zu erreichen. Einige Datenbank-Clients bieten bereits eine grafische Konfigurationsmöglichkeit, um die Verbindung über eine Portweiterleitung (auch SSH-Tunnel genannt) herzustellen. In diesem Artikel wollen wir jedoch beleuchten, was eigentlich dahinter steckt und wie man die Verbindung auch dann herstellen kann, wenn z.B. der Datenbank-Client diese Möglichkeit nicht anbietet.

Einfaches Beispiel für Local-Port-Forwarding mit SSH

Angenommen die Datenbank lauscht auf Port 5432 auf dem Datenbank-Server in unserer Testumgebung.

Mit dem Befehl

ssh -L 5432:localhost:5432 db.example.com

kann eine Weiterleitung des Ports 5432 auf den eigenen Computer eingerichtet werden. Wenn die Verbindung steht, dann kann man sich mit dem Datenbank-Client seiner Wahl ganz bequem auf den lokalen Port 5432 verbinden und die SQL-Abfragen ausführen.

Wenn auf dem eigenen Computer bereits eine lokale Datenbank auf Port 5432 läuft, dann würde der ssh-Befehl mit dem Fehler

bind [127.0.0.1]:5432: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 5432
Could not request local forwarding.

fehlschlagen.

In der Praxis sieht man lokale Portweiterleitungen daher häufig mit einem anderen, freien, lokalen Port wie z.B. 54321:

ssh -L 54321:localhost:5432 db.example.com

Wer den Befehl ausführt, wird bemerken, dass OpenSSH neben der Portweiterleitung auch eine interaktive Sitzung inklusive Shell startet. In der Regel möchte man allerdings nur den Port weiterleiten. Das Aufbauen der interaktiven Sitzung kann mit der Option -N abgeschaltet werden:

ssh -N -L 54321:localhost:5432 db.example.com

In manchen Fällen ist der Datenbank-Server allerdings selbst nicht per SSH erreichbar oder es fehlen einem die Berechtigungen. Wenn es einen anderen Server in der Testumgebung gibt, auf den man sich per SSH anmelden kann und der sich zur Datenbank verbinden kann, dann können wir die Portweiterleitung über diesen Server herstellen. Wird dieser Server hauptsächlich dazu verwendet, um zu anderen Zielen zu „springen“, dann bezeichnet man ihn üblicherweise als „Jump-Host“ oder „Jump-Server“.

Erweitertes Beispiel für Local-Port-Forwarding mit SSH

In diesem Fall baut man die Verbindung zur Datenbank mit dem Befehl

ssh -L 54321:db.example.com:5432 jump-host.example.com

auf.

Möchte man die Verbindung zu zwei Datenbanken herstellen (z.B. einer primary und secondary Instanz), dann könnte man ssh zweimal starten mit unterschiedlichen lokalen Ports:

ssh -N -L 54321:primary-db.example.com:5432 jump-host.example.com
ssh -N -L 54322:secondary-db.example.com:5432 jump-host.example.com

Alternativ dazu kann man beide Portweiterleitungen mit nur einem Befehl erzeugen:

ssh -L 54321:primary-db.example.com:5432 \
    -L 54322:secondary-db.example.com:5432 \
    -N jump-host.example.com

Alle Details und Optionen zum Kommandozeilenprogramm ssh können auf der ssh man page nachgelesen werden.

Dynamic Port Forwarding

Möchte man sich in der Zielumgebung auf eine Vielzahl von Diensten verbinden, dann kann das Erzeugen von lokalen Portweiterleitungen schnell unübersichtlich werden. OpenSSH bietet für diesen Anwendungsfall die Möglichkeit der dynamischen Portweiterleitung. Anstatt eine Liste von Zielsystemen inklusive Ports zu definieren, gibt man stattdessen einen freien lokalen Port an (z.B. 2000), auf dem OpenSSH einen SOCKS-Proxy öffnet:

ssh -N -D 2000 jump-host.example.com

Über diesen lokalen SOCKS-Proxy können alle Systeme erreicht werden, als ob man sich selbst auf dem Jump-Host befinden würde. Eine Voraussetzung ist allerdings, dass der Client den Verbindungsaufbau über einen SOCKS-Proxy explizit unterstützt. Alle gängigen Browser unterstützen SOCKS-Proxies, sodass eine dynamische Port-Weiterleitung sehr praktisch sein kann, wenn man sich beispielsweise auf eine Vielzahl verschiedener Webapplikationen verbinden möchte, auf die man nur über einen Jump-Host zugreifen kann.

Remote Port Forwarding

In etwas selteneren Fällen möchte man die Portweiterleitungen in der umgekehrten Richtung einrichten.

Beispiel für Remote-Port-Forwarding mit SSH

Ein Anwendungsfall wäre beispielsweise die Portweiterleitung eines Service mit REST-API, den man bei einem regelmäßigen Integrationstest in einer Continuous-Integration-Umgebung verwenden möchte. Für das Beispiel nehmen wir an, dass man aus Sicherheitsgründen keinen direkten Zugriff ausgehend vom Continuous-Integration-Server in die Testumgebung erlauben möchte. Stattdessen ist allerdings der SSH-Zugriff ausgehend von einem vertrauenswürdigen Server (hier: gateway.cloud.com) aus der Testumgebung auf den Continuous-Integration-Server erlaubt.

In diesem Fall kann eine umgekehrte Portweiterleitung („Remote Port Forwarding“) eingerichtet werden, indem man die Weiterleitung auf gateway.cloud.com mit dem Befehl

ssh -N -R 8080:service.cloud.com:8080 continuous-integration.example.com

erzeugt.

Unsere Software, die auf dem Continuous-Integration-Server getestet wird, kann in diesem Fall über http://localhost:8080 auf den REST-Service zugreifen, obwohl dieser entfernt in der isolierten Testumgebung betrieben wird.

Ausblick

In einigen Fällen ist der Jump-Host, über den man eine Port-Weiterleitung einrichten möchte, selbst nicht direkt per SSH erreichbar sondern nur mit einem „Sprung“ über einen anderen Server. Mit der ProxyJump oder ProxyCommand Option in der lokalen SSH-Konfiguration (~/.ssh/config) können auch komplexere Szenarien elegant eingerichtet werden. In einem zukünftigen Artikel werden wir diese Konfigurationsmöglichkeit genauer beleuchten. Dabei stellen wir unsere Java-Bibliothek ssh-proxy vor, mit der Portweiterleitungen einfach in Java-Applikationen eingerichtet werden können, z.B. in Integrations-Tests.

In einem weiteren zukünftigen Artikel werden wir zeigen, wie man auf Linux mit Systemd User Services und autossh Portweiterleitungen einrichten kann, die sich automatisch wieder aufbauen, wenn die Netzwerkverbindung unterbrochen wurde.

  1. Wegen der kürzeren Schreibeweise und dem prägnanteren Namen verwenden wir in diesem Artikel „SSH” und „OpenSSH” synonym, obwohl streng genommen SSH für das Konzept für verschlüsselte Netzverbindungen steht und OpenSSH eine Implementierung dessen ist. Alle Beispiele im Artikel sind OpenSSH-spezifisch.