Let's say you have a computer in your office hosting an internal service to the company. But one day we would like to make that service available remotely because there's a pandemic and everybody is working from home. Unfortunately, the HTTP and HTTPS ports are not usable, perhaps the network configuration in the office or an existing service already using them.
A possible solution is a VPN. You could connect to your office network and reach your internal service directly. But this is not the solution I am going to treat here.
Another solution relies on an SSH tunnel with an external server.
Disclaimer We are going to exploit the fact that you can SSH to a remote server to set up TCP forwarding to your service internally. Open your service to the rest of the world. I would insist that you discuss with your IT department to assess the cybersecurity risks.
Okay, that's said, let's dig into the method.
First, you need to set up SSH between connectivity from your service to the external server. I suggest you create a local SSH key in the service server.
ssh-keygen -t rsa -C "$SERVICE_NAME"
Then add the public key into the authorized_keys
of the external server.
Now that you can confirm you can ssh into the external server from the service server, we need to update the ssh daemon configuration of the external server to allow port forwarding. Usually it sits in /etc/ssh/sshd_config
.
Add or edit the following line to
yes
to allow TCP forwarding.AllowTCPForwarding yes
If you want to do UDP forwarding, you need another solution. I advise you to look into Nginx, which can do it.
Add or edit also this line to
yes
GatewayPorts yes
It will allow your external server to act as a gateway. Meaning it will be able to receive communication in the name of the service server and forwarding it.
- Then restart the SSH daemon.
Note that it should not disconnect your current ssh session.sudo service ssh restart
Ok, now we are ready, so let's create the tunnel!
ssh -f -N $USER@$EXTERNAL_SERVER -R 443:127.0.0.1:443 -R 80:127.0.0.1:80 -C
$USER
refers to the user account used on the external.$EXTERNAL_SERVER
refers to the IP or hostname of the external.-f
is used to start ssh as a background process.-N
is used to block any input command into the ssh session.-C
is used to activate compression on SSH communication.-R 443:127.0.0.1:433
with that we forward the remote port (R as remote) 443 to the port 443 on the IP 127.0.0.1 (from the point of view of the service server that initialized the tunnel).-R 80:127.0.0.1:80
same as previous but from port 80 to port 80.
Give it a try, connect to your service through the domain name of the external server.
If you use AWS (not sure about other cloud providers), there's an extra step. There is another
GatewayPorts
-like flag to change in the AWS console directly. Log in to the console, select your instance, and deactivate theChange source / destination check
. Once done, you should be good to go.
Once you have a working tunnel that forwards your communication, stop it. And switch the command from ssh
to autossh
(You may need to install autossh first).
autossh -M 20000 -f -N $USER@$EXTERNAL_SERVER -R 443:127.0.0.1:443 -R 80:127.0.0.1:80 -C
-M 20000
is used to define the monitoring port. In short, autossh establish a ping between the two servers on that port to verify the connection is healthy or not. Whenever the ping fails, it will restart the ssh session.
And now we have a reliable tunnel! Give it a try and let me know in the comments if you encountered any issues.
Notes:
For some Linux distributions, the ports from 1 to 1024 are not allowed to be forwarded, except for the root user. It means you will need to establish an ssh tunnel with the root user. For cybersecurity, I strongly suggest you do not allow password login, change the SSH port to another non-standard one and, set up an IP whitelist to only allow connections from trusted servers.