Poor man's ngrok with tcp proxy and ssh reverse tunnel
Kamal Mustafa Nov 18 '17
We have a private development server where we do most our work. But like all modern web application these days, it need to talk (or being talked to) with other applications as well. One is through webhook, where your app receive kind of notifications from other application through http request.
To receive a webhook, your application need to be accessible from the remote application. As mentioned above, our dev server is on private network. The only we can access it from our local computer is by doing ssh tunnel. For example, I'll start my day with:-
ssh dev-server -L 8000:localhost:8000
and then when I run such as
python manage.py runserver on the remote dev server, I can access that django application from my laptop as http://localhost:8000/. But of course we can't receive a webhook such as from github this way.
One common solution to this problem is by using service such as ngrok, where I can run command on the remote dev server:-
ngrok http 8000
ngrok will give us random subdomain such as https://xxyymmdd.ngrok.com/, and when accessed over Internet, will forward back the request to our django development server where the ngrok command above was running. It's perfect and I use it most of the time for my personal projects.
But for work, I don't feel easy using third party services that we can't control much. One solution is to setup a proxy on a small vps that fully exposed to Internet. We can use Caddy or nginx for this. But this is a permanent setup. I want something ad-hoc similar to ngrok above, where the tunnel will just die after I closed connection to the remote dev server.
Second option will be ssh reverse tunnel with GatewayPorts enabled. So I can run command like this on the dev-server:-
ssh -R :8000:localhost:8000 proxy-server-ip
That allow connection to port 8000 on proxy-server being forwarded to the host where the command above was running. But one problem with this is you can't get TLS connection to port 8000 of the proxy-server.
So to level this up a bit, we need help from a second tools - a tcp proxy. There's a lot actually if you search this up, so I just settled down with one that seem to provide what I need. I picked tcpproxy, a little tool written in Golang. It has all that I need. So my command, when I want to open up a proxy is this (run on the remote dev-server):-
ssh -t -R 9000:localhost:8000 scarif.planet.rocks -l username "tcpproxy -laddr 0.0.0.0:8000 -raddr localhost:9000 -lcert /etc/letsencrypt/live/scarif.planet.rocks/fullchain.pem -lkey /etc/letsencrypt/live/scarif.planet.rocks/privkey.pem -ltls" Proxying from 0.0.0.0:8000 to 127.0.0.1:9000
This then provide public access to https://scarif.planet.rocks:8000/ which then get proxied to our django development server running on port 8000. Ok, it look pretty confusing at first. But it require me to only understand this command instead lengthy docs to know what it's doing. Let's look this step by step:-
- Open a reverse tunnel from port 9000 on proxy server to port 8000 at local machine (local here mean where the command executed).
- Run tcpproxy on proxy server, listen on port 8000 at all network interfaces. Here we also enable tls and use cert provided by letsencrypt.
- Forward connection to that port 8000 to port 9000 on local interface.
- Forward connection on port 9000 back to our local machine through the ssh reverse tunnel.
And here not so nice diagram courtesy of asciiflow:-
XXXXXXX XX XX +--------+ +------------+ X | | | | XX X | <--------------+ | <-------------+X Internet X | | | | X XX +--------+ +------------+ XX X 8000 9000 8000 XX XXX local proxy-server X XXX X XX
One thing still missing with this approach is the nice request inspector that ngrok has.
- Terminate remote tcpproxy process if the ssh connection die. Using
ssh -tseem to work if I just
Ctrl+Cthe connection, but if it died, for example due to network issue, tcpproxy on the proxy server will left running and you have to ssh into it and manually kill the process. This question on stackexchange suggest few workaround but I haven't try yet.
Q: Do you know ssh GatewayPorts ?
A: Yes, but as mentioned above, it doesn't give us TLS connection from the public.
Q: Have you try pagekite ?
A: Yes, but I have a hard time trying to understand how it work if I want to host my own server.