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: 18.104.22.168 Machine C user user3 address 22.214.171.124
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 email@example.com
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 firstname.lastname@example.org
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 email@example.com
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 firstname.lastname@example.org 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 email@example.com ssh -i $HOME/.ssh/ssh/tunnel localhost -p 69696'