Exposing Services from Your Devices and Making Them Accessible from Anywhere Using SSH
Tags: ssh, networking, expose, home
Published: 2021-02-28 05:32:31

# Introduction and Goal

There are many use cases where you want to forward traffic from a Gateway server to some other computer.  Perhaps one of the most famous examples of this are developer tools that "publish" local ports to publicly accessible endpoints (viz. Ngrok, localtunnel, etc). What many forget to mention, and thus what took me an embarrassingly long amount to time to rediscover, is that, for certain use cases, there already exists a tool built-in to UNIX that can do this out of the box (with one small configuration change on the Gateway server).

# SSH: Background

While SSH is generally thought of as a way to log-in to a remote server and get a shell to execute commands, it also supports many, many more less-mentioned features. The one we are most interested in in the context of this post is _port forwarding_.  SSH supports two modes of port forwarding: local (controlled with the `-L` switch), and remote (`-R`). Which one you use depends on which way you want traffic to flow:

* local: forwards local traffic to the remote host over the SSH tunnel
* remote: forwards remote traffic to the local host over the SSH tunnel

## Local Port-Forwarding

In a contrived scenario where your friend has given you SSH access to a server on their LAN, and you want to log into their routers admin page, you can execute:

```
ssh -L 8081:192.168.1.1:80 theserveryouhaveaccessto.com
```

...and then you can open a browser on your local machine, point it at http://localhost:8081, and all traffic will be piped through the SSH tunnel to their router's admin page. (Hopefully your friend has changed the default password to their router to prevent any nefarious subsequent action...)

## Remote port-forwarding.

Now let's make traffic flow in the opposite direction: let's say we have another contrived scenario where you are developing a website, and you access it at the local port `3000` (http://localhost:3000). Now say you want to show your same friend how the website is looking in your current stage of development.  You have a problem: how do you "publish" your local port to a public place? Well, in this case we can execute:

```
ssh -R 3000:127.0.0.1:3000 theserveryouhaveaccessto.com
```

Now tell your friend to type this into the address bar of their preferred browser: http://theserveryouhaveaccessto.com:3000.  Voiala!  They will be able to access the web-server running on your local machine.

# Setting up the Server

Since remote port forwarding is (potentially) undesired default behavior, you _do_ have to make a configuration change to support it on the SSH server. Make sure you have this option enabled in `/etc/sshd/config`:

```
GatewayPorts yes
```

Restart `sshd`, and you're all set!

Now, all of this is well and good, except that it is a manual process to setup forwarded ports, and following a reboot of your computer, you have to remember a lengthy command to run in order to publish ports on your gateway. To complicate matters further, if your network connection between your machine and the gateway gets interrupted, you have to restart the command manually.  Suppose you want to automate this process and remove the manual steps: how would one accomplish such a task?

I'm glad you asked...

## AutoSSH

From autossh's man page:

> autossh is a program to start a copy of ssh and monitor it, restarting it as necessary should it die or stop passing traffic.

This sounds perfect: it is exactly what we were looking for.  In order to automatically start publishing ports to a gateway involves basically replacing `ssh` with `autossh`, plus a few extra switches.

Here is an example that I found to be "low-maintenance", yet may have debatably insecure practices. Use at your own risk.

```
autossh -N -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -R 22223:localhost:22 theserverihaveaccessto.com
```

Now all we have to do is get the command to run whenever the system powers on.  There are a few ways to do this, depending on what OS you are running...

## Linux: `systemd` Sample Unit File

If you are running systemd (Linux), this is the section that applies to you.

Put this in `/etc/systemd/system/my-ssh-tunnel.service` and enable with `systemctl enable my-ssh-tunnel.service`. Don't forget to start it after enabling it (`systemctl start my-ssh-tunnel.service`).

```
[Unit]
Description=my-ssh-tunnel
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=YOUR_USER_NAME_HERE
PIDFile=/tmp/my-ssh-tunnel.pid
Environment=AUTOSSH_PIDFILE=/tmp/my-ssh-tunnel.pid
Environment=AUTOSSH_DEBUG=1
ExecStart=/usr/bin/autossh -N -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -R 22223:localhost:22 theserverihaveaccessto.com
ExecStop=/usr/bin/pkill -F /tmp/my-ssh-tunnel.pid
Restart=always
RestartSec=60

[Install]
WantedBy=multi-user.target
```

## MacOS: `launchd` Sample `plist`

MacOS does not come with systemd, but rather its own process manager called [launchd](https://www.launchd.info/).

```xml
<!-- put in /Library/LaunchDaemons/my-ssh-tunnel.plist -->
<!-- to enable it: launchctl load /Library/LaunchDaemons/my-ssh-tunnel.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>my-ssh-tunnel</string>
  <key>UserName</key>
  <string>YOUR_USER_NAME_HERE</string>
  <key>KeepAlive</key>
  <true/>
  <key>ThrottleInterval</key>
  <integer>30</integer>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/autossh</string>
    <string>-N</string>
    <string>-o</string>
    <string>UserKnownHostsFile=/dev/null</string>
    <string>-o</string>
    <string>StrictHostKeyChecking=no</string>
    <string>-R</string>
    <string>22222:localhost:22</string>
    <string>theserverihaveaccessto.com</string>
  </array>
</dict>
</plist>
```

# Drawbacks

SSH is an extremely versatile tool, that supports more features than meets the eye upon first inspection.  For a short time, I used the methodology described in this post to create my own poor-man's VPN: I exposed ports from several of my personal computers on a gateway so that I could access them from anywhere.

The major downside to this approach is that once a port is exposed on a gateway, anybody can access the service on that port.  Please utilize this approach judiciously.

# Further Research

In an upcoming post I will discuss how to easily set up a VPN amongst your personal devices so that you can access them from anywhere, without any tricky port mapping!