SSH Reverse Tunnel

Phone Home

I want to always be able to securely connect to freyja from anywhere in the world.

The following guide will enable me to connect to the laptop if I leave it at home, leave it at work, or in the unfortunate event that someone steals it. This phone home technique will work as long as the laptop can create an outgoing ssh connection. It will work behind NAT routers, but probably not behind strong corporate firewalls that only allow web proxy traffic out (may look into corkscrew at some point).

I shall use macOS launchd to create and maintain connections automatically, not by forking the ssh client, but using the launchd KeepAlive feature.

Fun stuff

Setup The Server

First step is to setup an authorized_keys file to allow logins for a private key, run the following on the client machine (macOS X laptop in my case). If Elliptic Curve DSA (ECSDA) is available and supported on both ends, it can be used by adding ‘-t ecdsa’ to the ssh-keygen command.

Example default dsa key generation:

ssh-keygen -f ~/.ssh/reverse

Now a private key has been generated, read the public key on the client and copy it:

cat ~/.ssh/reverse.pub

On the server paste the public key on it’s own line in ~/.ssh/authorized_keys or use ‘ssh-copy-id’ for easy installation if available. Prefix the public key with ‘command=””,no-pty’ to prevent any commands from being executed using this private key and to prevent wasting resources for a pty (not a security feature). Optionally add a comment to the end so you can keep track of the purpose of this installed public key. The result should look something like the following:

cat ~/.ssh/authorized_keys
command="",no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDP7XDRB+10Ruc+0H0xxWbLVA21hPxvfI8EFe31v6exVUJNnNyezIUjUw+PymmI0Jv/RhCRzXkXAj1UzQOzGNs4bzK2sF7CDGwpqltap4ZvrqHmdw0j2ernqd8lSM61995dO48q+JuWmJ255DeyDnrwvNcln8+8+4cYOkTATtcOdf9w7ZJO0U4NL+2famNpvd35gBQyzCJruW+IcFr4itd3lI0F1US7c3uxs4orCShi2v4MC3apeP/qfstXokoz1nrTjSNB86BQnZ5p8vYUIHi1/fJz1Qkurw+rFYikZT8S1cQ8gaabBAfHgcqgDhyVWNOgTJzjttaLgfLpJm+VlBKX [email protected]

Now you’ve allowed any remote machine to login to the user machine on the server with the private key generated above ‘~/.ssh/reverse’. The login won’t be able to run any commands and won’t be able to allocate a pty (which probably is pointless after no command execution, better safe then sorry).

Additionally, if it’s possible to modify the ‘.ssh/sshd_config’ file on the server, it should be modified to send ssh alive packets (similar to TCP keep alive) packets to the client. On an Ubuntu server this can be accomplished with:

echo "ClientAliveInterval 20" | sudo tee -a /etc/ssh/sshd_config
sudo restart ssh

The above modification will ping the client every 20 seconds the connection is idle as defined the ”ClientAliveInterval” which is disabled by default. If ”ClientAliveCountMax” (defaults to 3) number of pings go unanswered, the server will drop the connection. This is critical to detecting the remote client has disappeared and freeing up the port defined below for a reconnect from the client when it comes back online. It isn’t strictly necessary as the server will drop the connection after a while on its own, but significantly speeds up reconnects.

=== Test the Server Setup from the Client ===

Verify that the server is correctly setup by running the ssh command manually. This is important for two reasons:

* The first time the ssh client connects to the server, it by default needs the user to manually accept the host’s ssh key. This will never succeed in the automated launchd task described below and must be done ahead of time.
* Verify nothing is broken.

To test the configuration, run the following on your local machine (obviously replace ”njoror.squashedfly.eu” with your server):

ssh -i ~/.ssh/reverse -NT -R 12345:localhost:22 njoror.squashedfly.eu

The result should be that the command blocks and appears to hang. At the same time, verify that port 12345 is now listening on the server. If port 22 on the client is in fact the ssh server this can be quickly tested by reading some data over the connection such as the SSH server’s version using netcat on the server:

netcat localhost 12345

After testing is complete, use CTRL-c to break both the ssh and netcat command. If something didn’t work, double check the steps above for errors before proceeding.

==== Setup The Client on macOS ====

Apple uses launchd to launch system services. The purpose of launchd is very similar to Ubuntu’s upststart and Freedesktop’s systemd in that it’s goal is to start services and manage them.

In a nutshell the primary features needed for this phone home script are:

* Run at start-up without user intervention
* Run as another user
* Restart a process when it dies

First, we need to setup a plist file for OS X, create the following file and modify it as necessary for items such as the user and host name, place the following in ”/Library/LaunchDaemons/server.name.client.name.home.plist”:

<code xml /Library/LaunchDaemons/server.njoror.client.freyja.home.plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>server.njoror.client.freyja.home</string>
<key>ProgramArguments</key>
<array>
<string>ssh</string>
<string>-NTC</string>
<string>-o ServerAliveInterval=20</string>
<string>-o ExitOnForwardFailure=yes</string>
<string>-i</string>
<string>/Users/skippy/.ssh/reverse</string>
<string>-R 12345:localhost:22</string>
<string>[email protected]</string>
</array>
<key>UserName</key>
<string>skippy</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
</code>

This will switch to Username user on the Mac OS X client and then attempt to run the ssh command described by ProgramArguments. SSH command description:

  • -N – Don’t run a remote command. Attempting to run a remote command will fail due to the restrictions imposed by the authorized_keys file.
  • -T – Disable pty allocation. There is no need for this when only port-forwarding is desired.
  • -C – Request compression. This is optional, typically my processors out pace my network speed, especially when on 4G/LTE networks. This is optional.
  • -o ServerAliveInterval=20 – The client will attempt to send pings to the server ever 20 seconds. After 3 failed pings (Default ServerAliveCountMax is 3), the client will drop the connection and ssh with return.
  • -o ExitOnForwardFailure=yes – If port forwarding fails to get setup due to something like another process (or old ssh process) being bound to the hardcoded port, fail and return.
  • -i /Users/<username>/.ssh/servername-home-fwd – Use the specified ssh private key (generated above) for this connection. This must be the the private key for the public key in the authorized_keys file on the server.
  • -R 12345:localhost:22 – Remotely forward the localhost port 22 (sshd) to the server’s port 12345. This allows the server to connect to the client’s ssh port.
  • [email protected] – Connect to ssh servername with user remoteuser.

When the ssh tunnel dies due to a change in network connection or fails to setup the initial port forwarding as requested, the launchd manager will restart it in 10 seconds due to the KeepAlive key. The default restart time is 10 seconds and should work just fine for this task.

The RunAtLoad does as the name suggests and runs this launchd task at load and boot time.

If all goes according to plan, the launchd plist can be loaded and it will connect to the server:

sudo launchctl load /Library/LaunchDaemons/server.name.client.name.home.plist

So mine would be ”sudo launchctl load /Library/LaunchDaemons/server.njoror.client.freyja.home.plist”

Launchd will start up ssh as directed by the plist and connect to the remote server. The netcat command described above can be run on the remote server to verify it’s working. If it’s not working, check the logs on the client and server under ”/var/log” for hints as to what went wrong. After modifying the plist file, be sure to unload it and then reload it by changing ”load” to ”unload” in the command described above followed again by ”load”.

If it is working, your Mac OS X will automagically open a reverse tunnel to the server described above. You can then login to the client Mac OS X machine by using ”ssh -p12345 [email protected]” on the server. Note that the host will always be localhost due to port forwarding, and the user is the user on the macOS client, mine is ”ssh -p12345 [email protected]”.

=== Password-less login ===

At the moment when you connect from the server to your laptop using ”ssh -p12345 [email protected]” it will request the password for the Laptop user, if you want to get round this you can make a set of SSH keys for the server (using ”ssh-keygen”) and copying the contents of the servers ”~/.ssh/id_rsa.pub” to your ”~/.ssh/authorized_keys” on your mac.

==== Doing Even More ====

I’m looking only to ssh back in to my laptop, but with a few modifications to the launchd plist, it’s possible to use this to setup ssh vpn tunnels using tun interfaces. Refer to the ssh man page for the the ”-w” option. You’ll need to setup routes and what not to fully use it. Things get complicated quick and many times OpenVPN is a better solution.

Related Articles

%d bloggers like this: