TGLMAN

tunnel home hosted service to public with ssh

SSH is a really powerful tool and has a lot of funtionalites built in that can solve quite complex use case, one of this is tunnelling, you can easily create a tunnel with a command ssh user@host -L 127.0.0.1:4000:127.0.0.1:3000, this for example allow you to open a local port (4000) that map a service listening on "127.0.0.1" (3000) on the "host". A more interesting use case is actually the opposite ssh user@host -R 127.0.0.1:4000:127.0.0.1:3000 in this case you can expose a local only service (3000) as a service on the "host" on port 4000, you could do also *:4000 to accept any incoming connections on the "host" but may need some configuration on the ssh server.

So ssh can be used for expose a local service not reachable on the wider internet on the internet using a public host with just an ssh server running, so let's go through how do that, but i will do it using as well a http server on the public host to have better/safer control of what go through and for ssl.

Let's do it in few steps:

  • configure a user on the public host to be used by the tunnel
  • configure a service in the private host to establish the ssh tunnel
  • configure a http server to expose publicly the private service

Configure the user

first thing create the user on the public facing host for handle the tunnel, better to do a specific user to limit risks.

sudo adduser user_for_tunnel

after creating the user let's make sure that do not support login by password, for do that let's edit /etc/ssh/ssh_config and add at the bottom:

Match User user_for_tunnel 
        PasswordAuthentication no 

Done this we can generate a new ssh key with ssh-keygen and copy the public key to our public host.

scp new_key.pub user@host:~/

Done this we can put the public key in the ~/.ssh/authorized_key of the user_for_tunnel.

# make sure ssh folder exist
sudo mkdir /home/user_for_tunnel/.ssh/
# copy the key
cat new_key.pub | sudo tee -a /home/user_for_tunnel/.ssh/authorized_keys
# make sure the files have the right permission
sudo chmod 600 /home/user_for_tunnel/.ssh/authorized_keys 
# make sure the files have the right owner
sudo chown -R user_for_tunnel:user_for_tunnel  /home/user_for_tunnel/.ssh/

test the connection with

ssh -i new_key user_for_tunnel@tglman.com

Configure the tunnel

Let's configure a service to handle the tunnel in the private host, so at first let's create a configuration directory for the keys and copy the private key.

sudo mkdir /etc/remote_tunnel/
sudo cp new_key /etc/remote_tunnel/
# make sure that only the user of the service can read it (I'm going for root)
sudo chmod 400 /etc/remote_tunnel/new_key

Then let's create a service that use ssh to tunnel, I will put it in /etc/systemd/system/ssh-tunnel.service this will map the local port to the server port

[Unit]
Description=SSH tunnel to tglman.com
After=network.target

[Service]
Type=simple
	ExecStart=/usr/bin/ssh -N user_for_tunnel@tglman.com \
	  -o StrictHostKeyChecking=accept-new -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 \
	  -R 127.0.0.1:4000:127.0.0.1:4000 -i /etc/remote_tunnel/new_key
RestartSec=10
Restart=on-success
RestartForceExitStatus=255

[Install]
WantedBy=multi-user.target

To notice the option: -o StrictHostKeyChecking=accept-new this is necessary to accept the ssh key of your public host, maybe you may want to avoid this option to do that you need to login once by hand with the same user or just copy the key to knwon_hosts by hand.

Done this let's just enable and start the service

sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel
sudo systemctl start ssh-tunnel

Configure the proxy

Finally, we can edit hour http service to make our service public, in my case I'm using my own server vacuna, so I add a subdomain and a proxy to connect to the service using localhost

   host {
        name "homeservice.tglman.com"
        proxy "/" {
            url "http://127.0.0.1:4000"
        }
   }

As a reference I got inspiration for the systemd tunnel service from here: https://gist.github.com/olbat/0e4879825b01239d8d6064d239e0723b

That's all!