Using reverse ssh tunnels for efficient remote working

Having in place a setup that allows remote access to your development machine – which most probably is located at your work place -, has many advantages. Not only does it prevent duplicating equipment (e.g., having a decent set-top box at home), but it enables the possibility to get work done when travelling. However, some times it is desirable that such development machine is only available from a local network. It is in these cases that reverse ssh tunnels come handy.

Reverse SSH tunnelling is a technique that allows to open a ssh connection to a machine that is not available from the outside (e.g., because it sits behind NAT). The basics is that machine A (no external access) creates the tunnel to machine B (external access). From that moment machine A is ssh-able from machine B. This allow machine C (anywhere with access to the Internet) to access machine A through B.

Let us use a practical example (Note that IP addresses are intentionally fabricated to not respond to real ones):

Machine A
   user: user1
   address: 192.168.1.25
  
Machine B
   user: user2
   address: 137.55.10.30

Machine C
   user user3
   address 133.123.1.21

From machine A create the SSH tunnel to machine B (the port 69696 can be replace by any unused port)

$ ssh -R 69696:localhost:22 user2@137.55.10.30

Now from machine B it is possible to ssh machine A

$ ssh localhost -p 69696

While this works, the problem is that if the ssh connection is dropped and you do not have access to machine A (which cannot be reached from the outside) then machine A is not reachable. In order to avoid this, we can use autossh – a monitor that restarts ssh connections if they are dropped. In our case, from machine A

$ autossh -M 10984 -o "PubkeyAuthentication=yes" -o "PasswordAuthentication=no" -i ~/.ssh/ssh/tunnel -R 69696:localhost:22 user2@137.55.10.30

Note that in this case we are using ssh keys to create the tunnel. Not using passwords is usually better from a security perspective. In our case, it will also allow us to automate the tunnel creation and survive a reboot in machine A (see below). Consult autossh’s manual for exploring more options (man autossh).

For machine B how the tunnel is connected an maintained is not relevant. Therefore

$ ssh localhost -p 69696

Since autossh is in the end a process, it cannot survive a reboot automatically. In order to do so, we create a simple bash script and add it to the init sequence

$ mkdir /etc/tunnel
$ vi tunnel.sh
mkdir /etc/tunnel
#!/bin/sh -e

sleep 20
sudo -u user2 autossh -M 10984 -N -f -o "PubkeyAuthentication=yes" -o "PasswordAuthentication=no" -i ~/.ssh/ssh/tunnel -R 69696:localhost:22 user2@137.55.10.30

Note that sleeping (sleep 20) is necessary to ensure that the network card is available. Also, two options are added to the command.

-N: Do not execute a command on the middleman machine
-f: drop in the background

Now, autossh will maintain the ssh tunnel alive, even after a reboot.

A minor issue is that It might result in a lot of overhead to ssh 2 machines every time you need a new window. To avoid this, it is possible to concatenate two ssh connections in one command. Following our example, from machine C

$ ssh -A -t user2@137.55.10.30 ssh -i $HOME/.ssh/ssh/tunnel localhost -p 69696
-A: Disables forwarding of the authentication agent connection.
-t: Force pseudo-tty allocation. This is useful to get all symbols correctly interpreted in the server machine (machine A).

If you use this setup on a daily basis, yo might find convenient to create an alias for the ssh over ssh tunnel command and put it on you Xrc file (e.g., bashrc, zshrc)

$ alias mytunnel='ssh -A -t user2@137.55.10.30 ssh -i $HOME/.ssh/ssh/tunnel localhost -p 69696'

Enjoy! 🙂

Javier

Leave a comment