Persistent Reverse SSH Tunnel with SSH Keys

How to use a reverese SSH tunnel to access a machine behind NAT.

Have you ever wanted to access a machine behind NAT? Lets say you have a machine that blocks all incoming traffic (DESTINATION), and you would like to be able to access it from your home machine (SOURCE). Since the destination machine allows for outgoing connections, we can use Reverse SSH tunneling and a simple script to keep this connection up from the destination to source. As long as this connection stays up, we will be able to access the destination machine behind NAT from our home machine.

The steps described here will create an unprivileged user named tunnel on each machine. That user will then be used to create the tunnel and run a script via cron to ensure that it remains up.

Create a tunnel user on DESTINATION machine (machine behind NAT):

1
2
3
useradd tunnel
passwd tunnel # Set a strong password
su - tunnel # Become the user 'tunnel'

Now create a public/private key pair.

If you don’t have a public/private key pair, you can create one by following the OpenSSH Key Generation guide.

Now create a tunnel user on SOURCE (home machine) and save the public key for [tunnel@DESTINATION] in the authorized_keys file:

1
2
3
4
useradd tunnel
passwd tunnel # Set a strong password
su - tunnel
mkdir .ssh

Copy the content of id_rsa.pub file from the DESTINATION above into the ~/.ssh/authorized_keys file of the SOURCE.

1
[tunnel@SOURCE ~]# vi .ssh/authorized_keys

Now paste in the public key for tunnel@DESTINATION

1
[tunnel@SOURCE ~]# cat id_rsa.pub >> .ssh/authorized_keys

At this point you should be able to ssh from tunnel@DESTINATION to tunnel@SOURCE without using a password.

Now we will create a script on the DESTINATION (call it persistent_reverse_ssh.sh) which will be testing the connection and port forwarding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
createTunnel()
{
    /usr/bin/ssh -f -N -L8822:DESTINATION:22 -R7000:localhost:22 tunnel@SOURCE
    if [[ $? -eq 0 ]]
    then
        echo "Tunnel to 192.168.248.201 created successfully"
    else
        echo "An error occurred creating a tunnel to 192.168.248.201 RC was $?"
    fi

# Run the 'ls' command remotely.  If it returns non-zero, then create a new connection
/usr/bin/ssh -p 8822 localhost ls
if [[ $? -ne 0 ]]
then
    echo Creating new tunnel connection
    createTunnel
fi

Save the script above and make it executable:

1
chmod 755 ~/persistent_reverse_ssh.sh

This script does two things:

  • First whenever the user on DESTINATION machine tries to ssh to localhost on port 8822, this script will route that connection to port 22 on SOURCE machine.
  • Second, every time the user on SOURCE machine tries to ssh to port 7000 of itself, script will route that connection to port 22 of the DESTINATION machine.

This script will attempt to ssh to localhost port 8822 and run the ls command. If that fails, it will attempt to create a ssh tunnel. The shh command will create a tunnel for local port 8822 to port 22 on SOURCE which the script uses to execute the ls command remotely and test the connection.

Now to make this connection persistent, we need to make sure the connection is continiously checked. To do this we will add the persistent_reverse_ssh.sh to the user tunnel’s crontab on DESTINATION machine. Edit tunnel’s crontab:

1
[tunnel@DESTINATION ~]# crontab -e

Make the script run every minute by adding the line below to your crontab:

1
1 * * * * /home/tunnel/persistent_reverse_ssh.sh

Save the file and exit. For more information on how crontab works, click here.

Now the script will run every minute and check the connection, if the connection is broken, it will reconnect.