Tor as a tool for remote access to a computer via SSH

Many of you know the problem – you run a Raspberry Pi under your desk, which closes your window blinds, takes care of your photos and monitors the humidity of your house. And then you walk away and remember you forgot to close the blinds and want to connect to it. Instead of complicated port forwarding (where the provider will often block the very port you’re forwarding), static IP address, or third-party forwarding services, you can use the open-source Tor project and the “Tor hidden service,” which is designed to create anonymous services whose location is hidden.

In this approach, we will not hide the location, but we will take advantage of the fact that a Tor connection doesn’t care what IP address you have, there is no need to forward ports, but you can access the service through the so-called Tor circuit. Let’s do some geeking out and command-line magic. I did this for Mac, but similar setup works for Linux.

The client will be a laptop (in my case with mac OS), the server can also be a Mac or Linux.

First we set up the laptop:

# install Tor
brew install tor && brew services start tor

# create config
mkdir -p /usr/local/var/lib/tor/onion_auth
cat > /usr/local/etc/tor/torrc << EOF
SocksPort 9050 # Default: Bind to localhost:9050 for local connections.
ClientOnionAuthDir /usr/local/var/lib/tor/onion_auth
EOF

# restart tor
brew services restart tor

We will generate a client authentication key. This isn’t technically necessary, SSH itself has key authentication, but if we’re going to add other services that don’t require a login or want to increase security and don’t want anyone trying to log in, it makes sense. Beware, forget about banning IPs, Tor won’t show you IPs from either side, so you can’t use the classic “if someone tries a wrong password 3 times, I’ll ban their IP” techniques. Let’s just make those keys to be safe…

wget 'https://raw.githubusercontent.com/pastly/python-snippits/master/src/tor/x25519-gen.py'
pip3 install pynacl
python3 ./x25519-gen.py

This command will generate two keys, private and public. Let’s keep both. Example output:

public:  VJC4DIDPPM3EQYMTIQWG4VJO6MLP3D56HJ2FH7C5HZSMFZZ3GJVA
private: BC3GVIGQWG35YDY23KSOU3BSJVCOF2Y7WXQAG7SFOQUJQ5XZBOVA

Let’s set up the server:

# Install Tor through homebrew
brew install tor && brew services start tor

# We will create a basic configuration
mkdir -p /usr/local/var/lib/tor
cat > /usr/local/etc/tor/torrc << EOF
SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections.
DataDirectory /usr/local/var/lib/tor
HiddenServiceDir /usr/local/var/lib/tor/ssh/
HiddenServicePort 22 127.0.0.1:22
EOF

# Restart Tor
brew services restart tor

# Here goes the public key (after "descriptor:x25519:")
echo 'descriptor:x25519:VJC4DIDPPM3EQYMTIQWG4VJO6MLP3D56HJ2FH7C5HZSMFZZ3GJVA' > /usr/local/var/lib/tor/ssh/authorized_clients/laptop.auth
brew services restart tor
echo -n 'Hidden service hostname: '
cat /usr/local/var/lib/tor/ssh/hostname
echo 'Now add key to client /usr/local/var/lib/tor/onion_auth/HOSTNICKNAME.auth_private:'
sed -e 's/.onion/:descriptor:x25519:PRIVATE_KEY/' < /usr/local/var/lib/tor/ssh/hostname
echo '(replace PRIVATE_KEY with your private key)'

We get a hostname that ends in .onion and tells us in what format to add the key to the client. We go back to the client and set up login to that particular server.

First, add the private key to /usr/local/var/lib/tor/onion_auth/HOSTNICKNAME.auth_private in the format as written above (HOSTNAME will be our hostname as we want to call it, it doesn’t have to be any hostname that works through DNS). It will start with the onion address (without the .onion at the end), followed by :descriptor:x25519:, followed by the private key (in our example, BC3GVIGQWG35YDY23KSOU3BSJVCOF2Y7WXQAG7SFOQUJQ5XZBOVA)

# Restart tor 
brew services restart tor

Add configuration to ~/.ssh/config:

Host HOSTNICKNAME
 Hostname ONIONADDRESS.onion
 User USERNAME
 Port 22
 CheckHostIP no
 ProxyCommand /usr/bin/nc -x localhost:9050 %h %p

We will replace the parts in capital letters: HOSTNICKNAME is the nickname we will use to call our computer, ONIONADDRESS will be the onion address of the Tor hidden service that the script has shown us, and optionally we can also give the parameter USERNAME, which will automatically set the user name if we don’t specify another one (e.g. “pi” for raspberry pi).

Then we just execute the command:

ssh HOSTNICKNAME

…and we are on the server (no matter where it is, what IP address it has, …).

If you’re doing this on Linux, the service is installed differently (e.g. using apt or yum), restarted differently (e.g. using service or systemctl, depending on the distribution), and it’s possible that Tor will have configuration files elsewhere (/etc/tor instead of /usr/local/etc/tor). Otherwise the principle remains the same.

The advantage of this approach is that you don’t have to register anywhere, create accounts and deal with various “free tier” restrictions like with ngrok, tunnelin and similar services. The downside is a slightly more complicated initial setup and that such a connection will be slower as it goes through several layers of anonymization.

Other options

A nice guide to SSH over Tor hidden service for Ubuntu. The only problem is that they don’t configure client authentication, which means that anyone can try to connect to ssh.

Possible alternative solutions for connecting to SSH on an internal network are:

  • ngrok. Public address changes or need a paid account, you need to run a custom client
  • tunnelin similar to ngrok, more permanent endpoint, they have a pretty cool free version
  • OpenVPN Cloud – free tier allows you to connect three “internal” networks without having to have one endpoint on the public network. So you can, for example, link two homes and one laptop that can automatically connect to both networks like if they were on LAN
  • ZeroTier – similar, but it works on a lower layer (SD-WAN, Ethernet). Free version has up to 50 devices, or they also have an open-source variant if you want to run your own server
  • frp – if you have at least one server with a public IP address, you can use this open-source tool in client-server mode to easily forward ports from different “clients” (who provide server services e.g. http, ssh). The nice thing is that one server on one public IP can serve multiple clients. The disadvantage is that if this server goes down, you cannot connect to other computers either.
  • autossh – if you have a server, you can also use autossh to create ssh connections to it, through which you make a reverse tunnel back to ssh. Autossh maintains these connections and eventually restarts them. You may also find this tutorial useful.

Bonus: If your client’s IP address changes and you still want to keep some ssh connections open, or survive a super slow connection, I suggest you take a look at mosh.