on
Configuring and securing SSH jumphosts
Introduction
SSH
natively provides the ability to forward sockets, though in the following we focus on network sockets. Using SSH
, users can pull a port from a remote host (ssh -L ..
; local port forwarding) or push a port to a remote host (ssh -R ..
; remote port forwarding).
SSH
also allows for using bastion hosts
(also referred to as — in my opinion more suitably — jumphosts
), i.e. systems that the connection muss pass through in order to reach the target system.
The combination of these two features allows for arbitrarily complex port forwarding through various networks, all the while keeping the strong authentication and encryption properties of SSH
.
Accessing a system behind NAT
via SSH
Similarly to how a VPN
would connect various systems at OSI
-Layer 3, we can connect devices at Layer 4
(with port granularity) using TCP
-Sockets and a publicly reachable jumphost
through the use of SSH
.
System 1
The system we want to expose can push its SSH
port to the jumphost
(port 2222
).
ssh -R 22:127.0.0.1:2222 jumphost@<IP-OR-HOSTNAME>
System 2
Now we can access System 1
from System 2
.
ssh -L 127.0.0.1:2222:127.0.0.1:2222 jumphost@<IP-OR-HOSTNAME> # Pull the port to our system
ssh username@127.0.0.1 # Access the system
It turns out that this pattern can be simplified, by specifying the jumphost
-System as, well, a jumphost
.
ssh -J jumphost@<IP-OR-HOSTNAME> 127.0.0.1:2222
Essentially, by providing the J
-flag, we can think of the following SSH
-access as happening from the jumphost
itself. And since System 1
pushed its SSH
-socket onto the jumphost
, we can access it at port 2222
.
A template for secure SSH
jumphosts
This is great already (and — for all intents and purposes — a simple alternative to what many people use ngrok
for), but also quite verbose. Plus, do you really want to allow System 1
and System 2
full access to your jumphost
?
The sane answer is no and thus I tend to stick to the following pattern for securing (and sharing!) the jumphost
.
Configuration of the jumphost
The following configuration must happen on the jumphost
.
Creating a jumphost-<SERVICE-NAME>
user
By giving the user an explanatory name and comment, it’ll be easier to identify its purpose.
useradd -g jumphosts -s /bin/false -m -c "Jumphost user to publish and access the SSH-socket of system01" jumphost-ssh-system01
Restricting port forwarding
Inside /etc/ssh/sshd_config
we add the following lines:
Match User jumphost-ssh-system01
PermitOpen 127.0.0.1:2222
X11Forwarding no
AllowAgentForwarding no
ForceCommand /bin/false
Now, the following limitations are enforced for the new user:
- The user cannot use a shell (
-s /bin/false
) - The user’s ability for port forwarding is restricted to a single
host:port
combination (Match User..
+PermitOpen 127.0.0.1:2222
) - No
AgentForwarding
orX11Forwarding
can take place
Configuring access using public keys
It is a good idea to only ever use pubkey
-authentication (or, in enterprise environments, certificate-based authentication). Thus, the ~/.ssh/authorized_keys
-file of userjumphost-ssh-system01
must be populated with the publickeys
of all systems that are to be connected via SSH
and via the jumphost
.
The publishing system can now push its socket onto the jumphost
(authorized port only), and subscribing systems may pull the port onto their system, or configure the jumphost
accordingly (CLI
or in their ~/.ssh/config
).
Configuration using .ssh/config
Instead of pulling ports manually, or specifying the J
-flag, this verbosity can be outsourced into the .ssh/config
-file.
Host jumphost
HostName <IP-OR-HOSTNAME>
User jumphost-ssh-system01
Host system01
HostName <IP-OR-HOSTNAME>
User <USERNAME>
ProxyJump jumphost
If system01
is only accessible via another jumphost
, this can easily be achieved by adjusting the ProxyJump
-option: ProxyJump jumphost,username@<IP-OR-HOSTNAME>:PORT
. This pattern allows for arbitrarily deep nesting, to jump
through various networks. If downstream jumphosts
are to be access
-controlled, this requires further care though.