Homemade Ngrok

Oct 15, 2019 07:07 · 406 words · 2 minute read

Ngrok is a popular tool that promises secure and introspectable tunnels into your local machine. Considering I have a few hosts to myself, a few domain names that I own, a bit of curiosity and a pinch of free time, I decided to see what it would take to build a crappy equivalent of my own.

The Goal

I want to be able to point to a publicly-accessible URL in order to access content I expose from my local host.

I want that content to be only accessible when I am connected through a secure tunnel.

I want some alternate content to be displayed when I am not connected.

My Crude Weapons

  • ssh, both daemon and client
  • nginx

The Adventure

Really there’s nothing much to my crudest possible version minimum viable product. I first started out by the non-connected state, which is essentially saying “I have nginx wired up to some hostname.”

server {
  listen 80;
  server_name redacted.example.com;
  listen 443 ssl; # SSL termination managed by nginx
  # SSL stuff omitted for brevity.
  root /path/to/some/almost/empty/root;

  # Display a nice page when I am not connected
  error_page 502 /502.html;

  # This 
  location / {
    proxy_pass http://localhost:9999;
  }

  # nginx needs this or the 502 won't display.
  location /502.html{}
}

From the above, you’ll see what I’m doing is abusing directly depending on the fact that if there’s no backend connected, nginx will throw a Bad Gateway in the user’s face. I’m not changing out the code, which allows APIs to behave nicely; I’m just changing the message that’s thrown in the user’s face if it is 502’ing.

The rest of it is laughably simple:

ssh -R 9999:localhost:5678 redacted.example.com

What the above says is “open up port 9999 on the redacted.example.com server, and forward everything listening to that to port 5678 on my local host.”

That’s all. It works. I tried. I promise.

Next Things?

So far this serves my purposes well enough. If I wanted to add new features to this, like introspectability in the tunnelling, I’d do so by implementing a small program whose sole job would be to inspect a proxied connection, and chain it before my locally-exposed program, such that it would go:

Internet ---> nginx ---> introspecty proxy on localhost ---> what I'm exposing

And then, maybe, I could pull together a script that just wires up all of those things together. I don’t know. So far, it’s good enough as it is.