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:
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.
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.
-
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. ↩