SSH, via a Jump Host, with a dynamic port number

sshssh-tunnel

I've got 3 hosts:

  1. My computer.
  2. A Jump Host (aka bastion) with a static IP.
  3. A server with a dynamic IP; behind a NAT router/firewall, with no inbound ports open.

The server currently connects to the Jump Host, and establishes an SSH tunnel via -R "0:localhost:22", so the port is dynamically allocated by the Jump Host (this has been very reliable so far).

With some socket magic, I have the dynamically allocated port number recorded in a file on the Jump Host.

With this, I can SSH to the Jump Host, and run ssh -p $(cat /path/to/port-file) localhost

But is it possible to skip this extra step?


This would be useful for Ansible, where my inventory.yml needs to have the port number updated:

server:
  # /usr/bin/ssh jh cat /path/to/port-file
  ansible_port: "34625"
  ansible_host: "localhost"
  ansible_ssh_common_args: "-o ProxyCommand='ssh -q -W %h:%p jh' -o StrictHostKeyChecking=no"

It might be possible to use ProxyCommand:

It can (sort-of) use command substitution on my computers ~/.ssh/config file:

Host server
  ProxyCommand ssh -q -W localhost:$(echo "34625") jh

This works (even without echo -n) where the substitution seems to happen on my local computer.

And despite the inefficiency of using SSH to get the port number first, this doesn't work:

Host server
  ProxyCommand ssh -q -W localhost:$(ssh jh cat /path/to/port-file) jh

It results in:

Bad packet length 1349676916.
ssh_dispatch_run_fatal: Connection to UNKNOWN port 65535: message authentication code incorrect

And oddly, the server (at the end of the chain), does note this in its auth logs:

sshd[5558]: Bad protocol version identification '' from ::1 port 36048

Which implies that the port number is being returned. But no idea why it breaks at this point.

And ssh -vvv shows that it identifies the hostkey in my ~/.ssh/known_hosts.


I've also tried creating a custom "ssh_config" file on the Jump Host, using ssh -F /path/to/ssh_config, with the contents:

Host tunnel.server
  HostName localhost
  Port 34625

But I don't think I can use this with the ProxyCommand.


I also suspect StrictHostKeyChecking=no might be need to be introduced at some point, when the port number changes.

Best Answer

Second partial solution, inspired by @anx...

Create a socket file

ssh -R '/path/to/socket-file:localhost:22' tunnel@jh

Then, to use this socket (from the Jump Host), I can use socat:

ssh -o "ProxyCommand socat - UNIX-CLIENT:/path/to/socket-file" localhost

The use of socat seems like an unnecessary step, where I'm sure there must be a way to get the ssh command to use the socket file directly, but I can't find it yet.

I've also not found how to use this socket file from my computer (as ProxyCommand is run on localhost, not on the JumpHost).

I should also note; as the tunnel account (on the Jump Host) is very restricted (it's only there to establish these tunnel connections), I need to set StreamLocalBindMask=0111 so my account on the Jump Host can use this socket file. Likewise, the old socket file should be removed if a new connection is established, via StreamLocalBindUnlink=yes.

Both of these options need to be set on the Jump Host, in "/etc/ssh/sshd_config":

Match User tunnel
  StreamLocalBindMask 0111
  StreamLocalBindUnlink yes

Unfortunately Match rules are ignored in "/etc/ssh/sshd_config.d/tunnel.conf" before OpenSSH 8.4, released September 27 2020 (bug report), and this isn't currently available on Ubuntu 20.04.1 LTS.

Related Topic