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 must 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
Similar to a Layer-3 Tunneling protocol, we can connect devices at Layer 4 using TCP-Sockets and a publicly reachable jumphost using nothing but 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 it is also quite verbose. Plus, do you really want to allow System 1 and System 2 full access to your jumphost?
The 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 will 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:portcombination (Match User..+PermitOpen 127.0.0.1:2222) - No
AgentForwardingorX11Forwardingcan 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 user jumphost-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.