ssh proxycommand, ssh -W, ssh proxyjump
Es war vor einigen Jahren, auf einem Treffen der SAGE Karlsruhe, wo im Rahmen eines Lightning Talks ssh proxycommand vorgestellt wurde. Ich hatte mich bisher immer mit einzelnen ssh-Aufrufen von Host zu Host weitergehangelt, und ssh proxycommand war für mich damals der erste Weg, direkt mit einem "natürlich" erscheindenen ssh-Aufruf trotz Sprunghost-Zwang auf dem Zielsystemen zu landen.
Das Verfahren wurde in neueren OpenSSH-Versionen noch zweimal vereinfacht, und in diesem Artikel möchte ich die Unterschiede herausstellen.
ssh ssh
Der ganz klassische Weg wäre
ssh mh@jumphost ssh mh@remotehost
Hierbei logge ich mich auf dem Jumphost ein und starte dann auf dem Jumphost einen eigenen ssh-Client, der sich schließlich auf dem Zielsystem einlogged. Dabei läuft der ssh-Client, mit dem ich mich auf dem Zielsystem authentifiziere, auf dem Jumphost, der dafür meine Credentials (Zugriff auf den Agent, einen ssh Private-Key oder ein Passwort) haben muss. Außerdem sieht der Client den gesamten Inhalt der ssh-Session im Klartext (oder kann ihn mit vorliegendem Keymaterial entschlüsseln). Zusammengefasst: Man muss dem Jumphost erhebliches Vertrauen entgegenbringen.
Außerdem muss man bei solchen Aktionen bei ssh-Tunnels gewaltig mitdenken und die beiden Tunnel auf dem Sprunghost noch irgendwie miteinander "vertüdeln".
ssh -o proxycommand ssh jumphost nc
Der klassische Weg zur Verwendung von Jumphosts mit ssh ist die Option "ProxyCommand".
ProxyCommand
Specifies the command to use to connect to the server. (…)
(…) The command can be basically anything, and
should read from its standard input and write to its standard
output. It should eventually connect an sshd(8) server running
on some machine, or execute sshd -i somewhere.
Die praktische Anwendung sieht so aus:
ssh -o proxycommand="ssh mh@torres.zugschlus.de nc %h %p" mh@q.bofh.de
Hierbei startet der lokale ssh-Client einen zweiten, der sich auf dem Jumphost einlogged und dort nc zum Aufbau einer TCP-Session zum eigentlichen Ziel verwendet. Der erste ssh-Client kann so "über Bande" direkt mit dem Zielsystem sprechen, sich dort authentifizieren und schließlich den gewünschten Job erledigen. Hierbei braucht der Jumphost weder Credentials, noch hat er das Schlüsselmaterial zum Mitlesen des Inhaltes der "eigentlichen" ssh-Session. Auch werden ssh-Tunnel etc trivial und ohne weiteres Nachdenken möglich, denn der vom Benutzer direkt gestartete ssh-Client auf dem Arbeitsplatzrechner spricht direkt mit der Gegenstelle. %h und %p werden in den Hostnamen und den Port des eigentlichen Zielsystems umgesetzt, was insbesondere im Zusammenhang mit .ssh/config die Konfiguration einfacher hinzuschreiben macht (siehe man ssh_config(5) Abschnitt TOKENS).
Auf dem Arbeitsplatzrechner laufen die folgenden Prozesse:
konsole,21984
├─bash,21987
└─ssh,23733 -o proxycommand=ssh mh@torres.zugschlus.de nc %h %p mh@q.bofh.de
└─ssh,23734 mh@torres.zugschlus.de nc q.bofh.de 22
Auf dem Jumphost sieht man diese Prozesse:
sshd,14036
└─nc,14037 q.bofh.de 22
Hierbei ist Prozess 23733 der vom Anwender aufgerufene ssh-Client, der direkt die Session zum Ziel hält. 23734 ist der zur Hilfe aufgerufene ssh-Client, der die Verbindung zum Jumphost (sshd 14036) aufbaut und auf dem Jumphost den nc-Prozess (14037) lostritt.
ssh -o proxycommand ssh -W
Etwas einfacher ist die noch nicht ganz so alte Option -W, bei der der ssh-Server auf dem Jumphost direkt die TCP-verbindung zum Ziel aufbaut, was ohne dedizierten netcat- oder socket-Prozess auskommt:
ssh -o proxycommand="ssh mh@torres.zugschlus.de -W %h:%p" mh@q.bofh.de
-W host:port
Requests that standard input and output on the client be forwarded to
host on port over the secure channel.
Prozess-Sammlung auf dem Arbeitsplatz
konsole,21984
├─bash,21987
└─ssh,23755 -o proxycommand=ssh mh@torres.zugschlus.de -W %h:%p mh@q.bofh.de
└─ssh,23756 -W q.bofh.de:22 torres.zugschlus.de
Auf dem Jumphost sieht man in diesem setup nur noch einen einzelnen sshd-Prozess, und in der Ausgabe von Netstat sieht man auch, dass es dieser Prozess ist, der die TCP-Session zum eigentlichen Ziel hält:
sshd,14293
tcp6 0 0 2a01:238:4071:3201::2:100:49626 2a01:238:4350:6101::100:100:22 ESTABLISHED 1001 922932 14293/sshd: mh
ssh -o proxyjump
Der letzte Schrei im ssh-Jumphost-Geschäft ist die Option ProxyJump, die hauptsächlich die Notation vereinfacht (aber auch Redundanz durch die Angabe mehrerer Jumphosts ermöglicht):
ssh -o proxyjump=mh@torres.zugschlus.de mh@q.bofh.de
ProxyJump
Specifies one or more jump proxies as [user@]host[:port]. Multiple proxies
may be separated by comma characters and will be visited sequentially. Setting
this option will cause ssh(1) to connect to the target host by first making a ssh(1)
connection to the specified ProxyJump host and then establishing a TCP
forwarding to the ultimate target from there.
Hierbei sieht man direkt, dass das nur Magie auf der Seite des ersten ssh-Clients ist; "unter der Haube" wird einfach ssh -W verwendet:
konsole,21984
├─bash,21987
└─ssh,23897 -o proxyjump=mh@torres.zugschlus.de mh@q.bofh.de
└─ssh,23898 -l mh -W [q.bofh.de]:22 torres.zugschlus.de
Und der Vollständigkeit halber hier die Jumphostseite
sshd,14320
tcp6 0 0 2a01:238:4071:3201::2:100:49628 2a01:238:4350:6101::100:100:22 ESTABLISHED 1001 922056 14320/sshd: mh
Viele Wege führen nach Rom
Im Endeffekt sind natürlich alle ProxyCommand- und ProxyJump-Varianten im Endeffekt dasselbe, die für Sprunghost, Authentifikation, Vertrauensbeziehungen etc keine unterschiedlichen Auswirkungen haben. ssh hat sich nur über die Jahre weiterentwickelt und bietet nun immer einfachere Notation für dieselben Aufgaben. Da man natürlich in der Praxis den ganzen Sermon nicht auf der Kommandozeile eintippen wird (ich hatte bei der Vorbereitung meine .ssh/config mit -F /dev/null deaktiviert und musste die Yubikey-Library mit -o und die richtige Identity mit -i jedes Mal manuell wieder anwählen, um ungewünschte Nebenwirkungen zu vermeiden), wird man sich das in seine .ssh/config hineinschreiben, die durch die neue Notation und die Möglichkeit, Wildcards zu verwenden, viel einfacher wird.
So kann man zum Beispiel alle Verbindungen zu Hosts mit Namen unterhalb von internal.example.com über jump.example.com schicken, indem man schreibt:
host *.internal.example.com
user meinuser
ProxyJump jumpuser@jump.example.com
Das funktioniert sogar, wenn man explizite Konfiguration für foo.internal.example.com hat, um z.B. einen ssh-Portforward zu aktivieren. Auf diese Weise erreicht man, dass man mit ssh foo.internal.example.com direkt auf dem Zielsystem landet, und zwar ohne zu sehen, wieviele ssh-Sessions, ssh-Clients, Portforwards und TCP-Sessions im Hintergrund verwendet werden müssen. Damit wir das Arbeiten auch in manchen Hochsicherheitsumgebungen wieder schmerzärmer.
Comments
Display comments as Linear | Threaded