diff options
| author | rtk0c <[email protected]> | 2025-10-24 21:11:33 -0700 |
|---|---|---|
| committer | rtk0c <[email protected]> | 2025-10-24 21:11:33 -0700 |
| commit | 46944e9b1f93dde6418c6684621a56dc17ccf0f1 (patch) | |
| tree | 9888ec87135334f317b213013628ee10ba1a938e | |
| parent | e324f4feaaafa9b71c20e8eda1e59ca618040679 (diff) | |
Finish hand-rolled-ngrok article for publishing
Add HTTP & redirects section, remove Wireguard section because I haven't
done it, and probably won't because tailscale is just so much eaiser.
Some editorial direction to not "shits and giggles".
| -rw-r--r-- | content/blog/hand-rolled-ngrok-over-protonvpn.md | 150 |
1 files changed, 146 insertions, 4 deletions
diff --git a/content/blog/hand-rolled-ngrok-over-protonvpn.md b/content/blog/hand-rolled-ngrok-over-protonvpn.md index 55168bd..fa1e88c 100644 --- a/content/blog/hand-rolled-ngrok-over-protonvpn.md +++ b/content/blog/hand-rolled-ngrok-over-protonvpn.md @@ -1,11 +1,12 @@ --- title: "I paid for the whole vpn, so I'm damn well going to use the whole vpn" +summary: "Or: hand roll a ngrok with protonvpn port forwarding for shenanigans" +date: 2025-10-24T21:00:00-07:00 tags: ["sysadmin"] categories: ["Life of a sysadmin"] -draft: true --- -_Or: hand roll a ngrok with protonvpn port forwarding, for shits and giggles_ +_Or: hand roll a ngrok with protonvpn port forwarding for shenanigans_ # Dumbness: port forward `sshd` @@ -92,6 +93,147 @@ Enable and immediately start both services: Read the public address under `/run/protonvpn`, now you should be able to ssh, from anywhere on the internet, to this machine. -# Sillinessw: port forward wireguard +# Silliness? port forward HTTP -TODO +It's basically the same process as for sshd. +Make your HTTP server of choice, nginx or whatever, listen on 10.2.0.2:80 (or some other port of your choice). Maybe even setup DNS and a certificate for it. And then you can just access it on the public internet like any other server. That's it. + +Now, please don't do any nefarious things with this... +That's how we lost port forwarding from mullvad. The curious reader who has not yet caught up on this matter may perform an internet search on their own behalf. + +# Automatically redirect to current port allocation + +Remembering that magic port number is pretty painful. +Keeping it in links you save or send to friends is also pretty painful. +Wouldn't it be nice to have some kind of short url service, that just redirects you to the current IP:port assigned, as of accessing?. + +For example, you can go to [https://sh.example.com/foo/bar], and be redirected to [https://funny-business.example.com:12345/foo/bar]. And when + +The python server that generates the redirects, and exposes an API for updating the mappings for our natpmp script to work with. + +```python +#!/usr/bin/python + +# example config file +""" +server-1 i.example.com +server-2 ii.example.com +this-is-a-joke foo.example.com:6969 +""" + +import sys +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlparse, parse_qs + +config = {} +config_path = sys.argv[1] + +PARENT_DOMAIN = "example.com" + +with open(config_path) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + prefix, port = line.split(maxsplit=1) + config[prefix] = port + +def update_config(prefix, port): + config[prefix] = port + with open(config_path, 'w') as f: + for prefix, port in config.items(): + f.write(f"{prefix} {port}\n") + +class RedirectHandler(BaseHTTPRequestHandler): + def do_POST(self): + p = urlparse(self.path) + if p.path != "/-/config": + return + q = parse_qs(p.query) + update_config(q['prefix'][0], int(q['port'][0])) + + self.send_response(200) + self.end_headers() + + def do_GET(self): + # self.path[0] is always '/', start at idnex 1 to ignore that + split_pt = self.path.find('/', 1) + if split_pt == -1: + split_pt = len(self.path) + prefix = self.path[1:split_pt] + rest = self.path[split_pt:] + + if port := config[prefix]: + self.send_response(302) + self.send_header('Location', f"https://{prefix}.{PARENT_DOMAIN}:{port}{rest}") + self.end_headers() + return + + self.send_response(404) + self.end_headers() + +HTTPServer(('127.0.233.80', 8046), RedirectHandler).serve_forever() +``` + +Run it somehow. Perhaps as a `Type=simple` systemd service. + +And nginx config fragment, which should be dropped inside the `http{}` block. +A very standard reverse proxy setup. +Unrestricted GET, and basic_auth protected POST (and everything else) for updating the redirect mappings. + +```conf +upstream sh { + server 127.0.233.80:8046; + keepalive 2; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name sh.example.com; + + location / { + limit_except GET { + auth_basic "POST restricted"; + auth_basic_user_file /etc/nginx/sh.example.com-htpasswd; + } + + proxy_pass http://sh; + } +} +``` + + +And update our port mapping script to send the newly obtained port to our redirects server. + +```diff +--- a/protonvpn-update-port-mapping.sh ++++ b/protonvpn-update-port-mapping.sh +@@ -8,6 +8,9 @@ + ip6=$(curl -s --interface=protonvpn -6 icanhazip.com) + echo "$ip6" > "$RUNTIME_DIRECTORY/public-ip6" + ++SERVICE_NAME=funny-business ++ ++old_port=0 + while true; do + natpmpc -a 1 22 udp 60 -g 10.2.0.1 > /dev/null \ + && natpmpc -a 1 22 tcp 60 -g 10.2.0.1 > $TMPFILE \ +@@ -21,5 +24,14 @@ + + echo "$port" > "$RUNTIME_DIRECTORY/public-port" + ++ if [[ $port != $old_port ]]; then ++ echo "Port changed from $old_port to $port" ++ # Valid DNS names and digits don't need any URL encoding to be standard compliant, so I'm using -d to be short ++ curl -X POST -u 'YOUR_USER:YOUR_PASSWORD' https://sh.example.com/-/config \ ++ -d "prefix=$SERVICE_NAME" \ ++ -d "port=$port" ++ old_port=$port ++ fi ++ + sleep 45 + done +``` + +Start everything, and now you can visit [https://sh.example.com/funny-business] for fun and profit. |
