Using TOR like a VPN to make SSH available

Back in the day, the machines I used to maintain either had public IP addresses or were local to my home network; that has changed. I still want to regularly log in to devices "on the road" by means of SSH.

The approaches I considered were:

"Use the (modern) internet": Only consider IPv6 connections where there is no NAT any more, have the devices announce themselves via some kind of dyndns (TSIG'd AXFRs seem to be the way to go)), and tell their firewalls to let SSH traffic pass (UPNP might be the way to go there). I did not follow thisapproach because there are still backwards internet providers that don't provide IPv6, and because especially mobile providers have a tendency not to implement port opening protocols.

"Use a VPN": Set up a VPN server, assign addresses based on host certificates. Would have worked, but I dreaded the configuration effort and running an own central server.

"Use TOR hidden services": Run TOR on all boxes and announce a hidden service that runs SSH. A helper script and custom DNS data allow using this without any per-server configuration on the clients. That's the approach I'm describing here.

Warning Even though this may sound secure due to the use of TOR hidden services, it does not attempt to be. I would have been perfectly OK with just opening the SSH port to the network (relying on SSH to be secure and not needing any more authentication from the client) and announcing that address, and that's the only security level I expect. Do not follow the steps shown here without understanding what they mean and the implications on your device's security.

Server setup On Debian, making SSH available via TOR is really straightforward: Install openssh-server and tor. Configure SSH ( /etc/ssh/sshd_config ) in a way you want to have public-facing. All I changed from the default config was setting PasswordAuthentication no to avoid pwnage from insecure guest / testing accounts.

Start a TOR hidden service. In /etc/tor/torrc , there are already commented out examples for that. In line with them, I added: HiddenServiceDir /var/lib/tor/hidden_service/ HiddenServicePort 22 127.0.0.1:22 After restarting SSH and TOR, the .onion name for that host can be read from /var/lib/tor/hidden_service/hostnme ; I'll be using abc1234567890.onion as an example name here. Note It's not a fully hidden service The service run here should not be mistaken for having any privacy properties in the sense of hiding the server. The service uses the same host key on TOR and in the LAN, so everyone who knows your host key will know who runs this servicE. toR is used only for discovery here. TOR can probably be configured for the hidden service to only use one hop, this could increase performance and decrease the load on the TOR network; I did not get around to trying this yet.

Client setup I For a first test, I set up my SSH client manually in ~/.ssh/config : Host myhost.example.com ProxyCommand nc -X 5 -x localhost:9050 abc1234567890.onion 22 After that, ssh myhost.example.com always went through TOR. For some setups, that may be sufficient; I chose to take it a little further.

DNS setup As I prefer not to have explicit configuration about every server on every client machine (as I would need to with the above), I decided to publish the .onion names of the hosts; the most logical choice seems to be in DNS. To avoid confusing other services, I went for a mechanism that looks a bit like DNS-SD (but is not, as .onion addresses are not "real" host names, and SSH does not use DNS-SD anyway). In my zone file for example.com, it looks like this: _ssh._onion.myhost.example.com IN TXT abc1234567890.onion Those I publish with knot DNS, but any DNS server should be able to take those as static records.

Client setup II I now only keep a single stanza in my SSH config entry: Host * ProxyCommand ~/.config/maybe-tunnel %h %p This makes all connections go through an (executable) helper script of the following content: #!/bin/sh set -e if getent hosts $1 >/dev/null then exec nc $1 $2 else torhost="`dig +short TXT _ssh._onion."$1" | grep \\.onion | sed 's/"//g'`" if [ x = x"$torhost" ] then echo "Host $1 could not be resolved, and has no _ssh._onion TXT record either." >&2 exit 1 fi exec nc -X 5 -x localhost:9050 "$torhost" $2 fi This script looks up the configured host name. If it resolves, it uses nc to open a direct connection, emulating what SSH does by default. (If SSH accepted dynamic configuration, I would just not set a ProxyCommand at all in this case). This covers both the case of "regular" servers that have public IPs, and also makes local connections work, because as long as I am inside the example.com LAN and the other machine is too, the internal dnsmasq server will answer the request with the local address. If the address does not resolve, the script looks for an _ssh._onion record that would tell the address of a hidden service announced as per the above. If one is found, a connection is established using the local TOR instance (running on port 9050 by default). Note SSH host keys As I use this setup right now, I either establish the hosts' identity once-per-client when I first use them in the LAN (so their public host keys get stored in my user's known_hosts file under their .example.com name), and/or I store their public keys in the known_hosts file I deploy to my boxes. If one uses DNSSEC, it should be feasible to announce SSHFP records for myhost.example.com (even though there's no A/AAAA record for that host) and trust those.

Outlook This does not interact well when both client and server are in the same public WiFi and could discover each other locally (they'd still go via TOR). It would be interesting to extend this to proper DNS-SD discovery with multicast -- essentially, once a host has a known key (from TOFU or SSHFP records), whatever has no A/AAAA record would be up for local discovery, falling back to TOR based discovery. TOR was what worked easy and fast, but its onion routing properties are not really being used here. Other schemes that "just" provide a TCP turnover point might be more convenient here. Obviously, if anything were to be usable without per-client customization, that' be great, but as SSH does not really support virtual hosting, that might be tricky.