In unserem vorherigen Blogpost „Grundlagen im Tunnelbau mit OpenSSH” haben wir erläutert, wie man OpenSSH1 Portweiterleitungen einrichten kann, um beispielsweise bequem auf eine PostgreSQL-Datenbank in der Testumgebung zugreifen zu können, obwohl diese nicht direkt aus dem lokalen Netzwerk erreichbar ist.

In den Beispielen haben wir gesehen, dass eine SSH Verbindung zum eigentlichen Zielsystem häufig über einen „Jump-Host“ bzw. „Jump-Server“ erfolgen muss:

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

In solchen Fällen kann man die Verbindung zur Datenbank mit dem Befehl

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

aufbauen.

In diesem Artikel zeigen wir, wie man bequem mithilfe der Konfigurationsdatei ~/.ssh/config Portweiterleitungen über mehr als einen Jump-Host aufbauen kann. Zudem sehen wir, dass die SSH Konfiguration dabei hilft sich Schreibarbeit beim SSH Befehl zu sparen. Schließlich stellen wir unsere Open-Source-Bibliothek ssh-proxy vor, mit der Portweiterleitungen mit mehreren Jump-Hosts auch programmatisch aufgebaut werden können.

Portweiterleitungen mit mehreren Jump-Hosts

Im Gegensatz zum einfachen Szenario aus der Einleitung muss in manchen Fällen sogar die Verbindung zu einem Jump-Host über einen anderen Jump-Host erfolgen.

Beispiel für Local-Port-Forwarding mit zwei Jump-Hosts

Im abgebildeten Beispiel ist eine SSH Verbindung von client zu jumphost1 möglich. Außerdem kann man sich von jumphost1 zu jumphost2 verbinden und schließlich von jumphost2 auf das Zielsystem, der Datenbank. Zu beachten ist, dass in diesem Beispiel weder eine direkte Verbindung von client zu jumphost2 noch von jumphost1 zum Datenbank-Zielsystem möglich ist.

Würde man versuchen eine Portweiterleitung zur Datenbank mit dem Befehl

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

herzustellen, dann würde der SSH Befehl scheitern, weil die Verbindung zu jumphost2 nicht hergestellt werden kann.

OpenSSH Konfigurationsdateien

OpenSSH bietet über Konfigurationsdateien einen eleganten Weg, um deklarativ zu konfigurieren, wie eine Verbindung zum entsprechenden System hergestellt werden kann.

Eine ausführliche Dokumentation der OpenSSH Konfigurationsdateien findet man auf der ssh_config man page.

Für unser Beispiel konfigurieren wir OpenSSH so, dass eine Verbindung zum jumphost2 über jumphost1 erfolgt. Dazu fügen wir in ~/.ssh/config den Eintrag

Host jumphost2.example.com
    ProxyJump jumphost1.example.com

hinzu. Gegebenenfalls muss die Datei ~/.ssh/config zuvor angelegt werden.

Damit würde die Portweiterleitung zur Datenbank mit dem Befehl

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

funktionieren. OpenSSH weiß durch den Eintrag in ~/.ssh/config, dass jumphost2 über jumphost1 erreicht werden kann und baut die dafür notwendige zusätzliche SSH Verbindung völlig transparent im Hintergrund auf.

Um statt ssh jumphost2.example.com auch kurz ssh jumphost2 schreiben können, konfigurieren wir sowohl für jumphost1 also auch jumphost2 einen Alias:

Host jumphost1
    HostName jumphost1.example.com

Host jumphost2
    HostName jumphost2.example.com
    ProxyJump jumphost1

Der Befehl zur Portweiterleitung verkürzt sich damit auf

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

Damit der Verbindungsaufbau tatsächlich völlig bequem und vor allem ohne mehrfache Eingabe des Passworts erfolgen kann, sollte die Authentifizierung möglichst über ein generiertes öffentlich-/privates-Schlüsselpaar erfolgen. Wir möchten an dieser Stelle an eine externe Anleitung zum Umgang mit SSH Schlüsseln verweisen.

Beliebig viele Jump-Hosts

In unserem Beispiel haben wir gesehen, wie wir eine Portweiterleitung konfigurieren können, wenn zwei Sprünge notwendig sind. Dadurch, dass die Konfiguration deklarativ erfolgt kann das Beispiel auch einfach auf noch komplexere Szenarien übertragen werden, bei denen noch mehr Sprünge notwendig sind.

Allgemein betrachtet würde der Verbindungsaufbau über n Jump-Hosts erfolgen:

client → jumphost 1 → jumphost 2 → […] → jumphost n → target

In der Konfiguration hinterlegen wir dazu lediglich, wie man von Jump-Host n-1 zu Jump-Host n kommt:

Host jumphost1
    HostName jumphost1.example.com

Host jumphost2
    HostName jumphost2.example.com
    ProxyJump jumphost1

Host jumphost3
    HostName jumphost3.example.com
    ProxyJump jumphost2

[…]

Host jumphost_N
    HostName jumphost_N.example.com
    ProxyJump jumphost_N-1

Für weitere Beispiele und Erläuterungen zur Konfiguration von Jump-Hosts möchten wir an dieser Stelle auf den Artikel OpenSSH/Cookbook/Proxies and Jump Hosts verweisen.

Java Bibliothek „ssh-proxy“

Um Portweiterleitungen programmatisch aufzubauen, stellt die cronn GmbH die Java Bibliothek ssh-proxy unter einer Open-Source-Lizenz frei zur Verfügung. Die Bibliothek basiert auf der verbreiteten Java SSH Implementierung JSch. Diese SSH Implementierung wird von ssh-proxy um eine bessere Unterstützung für SSH Konfigurationsdateien erweitert. Direktiven wir ProxyJump werden dabei verstanden und beim Verbindungsaufbau berücksichtigt, so wie es OpenSSH selbst tun würde.

Beispiel zur Verwendung:

try (SshProxy sshProxy = new SshProxy()) {
    int targetPort = 5432;
    int randomLocalPort = sshProxy.connect("jumphost2", "db.example.com", targetPort);
    // Connect to the database via localhost:randomLocalPort or inject it is JDBC URL in the Spring context
}
// Note: All port forwardings get closed when the sshProxy instance is closed

Die Bibliothek kann beispielsweise in einem integrativen Test verwendet werden, um vollautomatisch eine (temporäre) Portweiterleitung zur Testdatenbank oder einem Service in der Testumgebung herzustellen, sodass eine Entwicklerin oder ein Tester den Test bequem lokal auf dem Laptop ausführen bzw. debuggen kann.

Ausblick

In diesem Artikel haben wir gesehen, wie man Portweiterleitungen mithilfe der SSH Konfigurationsdatei auch in komplexeren Szenarien einrichten kann.

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

  1. Wegen der kürzeren Schreibweise und des prägnanteren Namens 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.