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).
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
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...)
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.
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
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...
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
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...
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 does not come with systemd, but rather its own process manager called launchd.
<!-- 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>
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.
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!