We Are All Mole

Alright, so let’s say that you had a mental breakdown and as such, you’re required to fuck off for a bit so as to appease your temporary roommates that are severely lacking in their respective empathetic prowresses. But let’s also say that you’ve essentially turned the home that you yourself are solely paying for into a makeshift datacenter wherein you maintain a repository of the entirety of your electronic life and career. And lets say that for all your genius, your fundamental laziness has motivated you to merely forward a port in your router to your OpenSSH server—a server without a proper MFA workflow, and now you’re paranoid by the rando skript kiddies that are beating at the side of the thing. And so let’s say that you decide that you need a more robust solution in the event that you’re stupid enough to let people into your life that are going to take and never give, such that you feel unwelcome in your own home, when you need to access your computing machinery from a Motel 6 that’s the next state over.

All theoretically, of course.

Well, you’re in luck! I’m going to assume here that you have basic common sense (lol) and knowledge of networking, operating systems, DNS, blah blah blah etc., so that I can just ramble about the shenaniganery that I’ve done here. Um and, as with everything else that exists on this poor excuse of a swamp I call a brain dump, use this at your own risk etc., etc. Or don’t. Your life is your own, man.

Okay, so like you’re gonna need a few things. If you do it the way I did it, I mean. Um, this ain’t a comprehensive list. Extrapolate where necessary or whatever.

  • 1x FreeBSD machine. At the time of this writing, we’re at 15.0-RELEASE and so that’s what I’ve done this thing with. Connect it to the interwebz.
  • Router access. Duh. If you don’t have it, “obtain” it.
  • Grab yourself a delicious delicious domain name. Run it through Cloudflare or something.
  • Get yourself a shitty laptop that you can hit the streets with. On my escape from reality I found this one at a computer refurbishing place for just shy of $200 bucks. In this economy? A steal! Fucker had 16G RAM and Windoze Pro. Not that I care about MS or whatever, but like I’m lazy (as previously established) and I’m sure I’ll nuke it at some point, but the battery on this thing is niiiice…

So basically, you’re gonna do this thing in two-ish parts: (a) you gotta set up a shitty dynamic DNS thing or whatever to make sure that you don’t lock yourself out when your ISP decides to yeet your old IP, and (b) you gotta set up Wireguard. I mean, there are real services with real people that are trying to keep a roof over their heads that you can pay out the wazoo but assuming you’ve followed my (again, theorhetical) trajectory, that’s probably not an option for you at the moment or whatever.

So like, sneakily park your car right outside your apartment just in range of the most (in)convenient Wi-Fi cell, and pray that your Past Self(tm) remembered to open OpenSSH in the first place. And if you didn’t, friends, remember that you’re just gonna add sshd_enable="YES" and then sudo service sshd restart (and YES, I’m assuming you’ve already installed sudo or whatever, get with the program).

You’ll want to install Wireguard on your server and on any clients you’ll be using. For FreeBSD, I hear that they’ve added (or are about to add, or whatever) Wireguard to the kernel but the drama was super annoying so I’m not up to speed on that. (The forums indicate that it should be there.) But you’ll need some of the tooling so that you can be extra lazy. And this means you’ll want to install net/wireguard-tools so that you can get stuff like wg-quick. (The port notes say that bash is a runtime dependency, so if you aren’t already using that then, um, do so? I’m not going to be a part of the religion wars regarding shells, but I also believe folks shouldn’t be such purists.) So, install those shenanigans through pkg or via ports or whatever your particular poison is.

If you’re on Windoze, you can install it directly from the official site (or, if you’re a l33t haxor or something, just choco install wireguard). And if you’re using some other OS, idk just will it into existence or something. Or Google it.

For your super awesome FreeBSD server configs, you need a server-side Wireguard config. And for that, you need keys. So I tend to slap keys into /root/wg. Here I’ve chosen to use vpn0 as my virtual interface name. And I’m going to use bilbo as the name of my client computer (which will be helpful to differentiate individual pre-shared keys).

sudo -i
mkdir -p /root/wg
cd /root/wg
wg genkey | tee -a vpn0.pri | wg pubkey > vpn0.pub
wg genpsk > vpn0_bilbo.psk
chmod 400 *

mkdir -p /usr/local/etc/wireguard
cat ./vpn0.pri > /usr/local/etc/wireguard/vpn0.conf
cat ./vpn0_bilbo.psk >> /usr/local/etc/wireguard/vpn0.conf

Neat. Okay, so that gives you a keypair for the server and a PSK. But you still need a keypair for the client. So go onto your client, create a new tunnel, and make note of the public key. Then, go edit /usr/local/etc/wireguard/vpn0.conf on the server, and you’ll find the two keys you added earlier — the first one being the server’s private key, and the second one being the pre-shared key. Work around those lines, replacing it with the following template:

# server configuration
[Interface]
PrivateKey = <SERVER-PRIVATE-KEY>
Address = 172.16.2.0/32     # server virtual IP address
ListenPort = 51820          # feel free to change this

# client configuration
[Peer]
PublicKey = <CLIENT-PUBLIC-KEY>
AllowedIPs = 172.16.2.4/32  # client virtual IP address
PreSharedKey = <PRE-SHARED-KEY>

Um, so you’ll notice those friendly little Class B IPs there—honestly, choose your own IP blocks, they’re really not important in this context (so long as you make sure everything lines up). On my end I seem to play IP Conflict Bingo fairly frequently so that’s just how I laid mine out. Get your own blocks, ya mooch!

So now, you probably need a firewall. I use IPFW personally, for Reasons(tm). If you use PF, that’s okay, nobody’s perfect. If you’re using IPF, please reach out to me because I’d like to buy some of your life-extending elixir please. Now, good news is that IPFW is already something that’s available in the GENERIC kernel. So you don’t need to go about compiling a custom one or anything. How do I know? Well, I may or may not have forgotten to compile my custom kernel after the recent upgrade and lo and behold the thing still did the thing. Now I have (foolishly) had the firewall type set to “open” and really what I wanted was to keep the same ruleset AND allow the NAT to do its thing. Sooooo there’s a couple of things that I did. First, create a ruleset for ipfw. It can look something like this:

# put this at /usr/local/etc/ipfw.rules

ADD="ipfw -q add"
PIF="re0"   # this is the name of your public interface, use `ifconfig` to find this
VIF="vpn0"  # this is the name of your private interface

ipfw -q -f flush
ipfw -q nat 1 config if $PIF

$ADD 00010 allow ip from any to any via lo0
$ADD 00020 deny ip from any to 127.0.0.0/8
$ADD 00021 deny ip from 127.0.0.0/8 to any
$ADD 00030 deny ip from any to ::1
$ADD 00031 deny ip from ::1 to any
$ADD 00040 allow ipv6-icmp from :: to ff02::/16
$ADD 00041 allow ipv6-icmp from fe80::/10 to fe80::/10
$ADD 00042 allow ipv6-icmp from fe80::/10 to ff02::/16
$ADD 00043 allow ipv6-icmp from any to any icmp6types 1
$ADD 00044 allow ipv6-icmp from any to any icmp6types 2,135,136

$ADD 00100 nat 1 ip from any to any in via $PIF
$ADD 00101 nat 1 ip from 172.16.2.0/23 to any out via $PIF

$ADD 65000 allow ip from any to any

Make sure your system will actually let you forward traffic. Run the following:

sysctl net.inet.ip.forwarding

If you see a response such as net.inet.ip.forwarding: 1 then you’re good. If you see anything else, e.g. net.inet.ip.forwarding: 0 then run the following:

sysctl net.inet.ip.forwarding=1

And then in /etc/rc.conf make sure you have the following lines:

gateway_enable="YES"
firewall_enable="YES"
firewall_script="/usr/local/etc/ipfw.rules"
firewall_logging="YES"
wireguard_enable="YES"
wireguard_interfaces="vpn0"

Then, run sudo service firewall restart. If you’re super cool, definitely do this while you’re remote without any backup plan in place. Otherwise, be at the machine physically or something so you can unbork your impending fuckup. And if that seems to work, run sudo service wireguard restart. If you run ifconfig you ought to be able to see your new interface.

For the public side of things, you need to configure port forwarding on your router (use whatever port you slapped into ListenPort on your server’s Wireguard instance) and then go add a subdomain to your freshly-purchased domain. If you want to figure out what your IP address is, you can just curl -s https://icanhazip.com. I’m gonna assume that you’re using Cloudflare for this — and if you’re not, improvise. (Remember that if you’re using Cloudflare’s free plan, you might not be able to stick your home’s IP behind their proxy — that’s a different problem for a different time.) I wrote this script and stuck it at /usr/local/bin/ipsdsync:

#!/usr/local/bin/bash

TOKEN=<CLOUDFLARE-TOKEN>
ZONE=<CLOUDFLARE-ZONE>
SUBDOMAIN=<YOUR-SUBDOMAIN>
TTL=900

ip=$(curl -s https://icanhazip.com)

printf 'start sync: %s\n' "$(date)"

if ! [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  printf 'bad ip\n' >&2
  exit 1
fi

printf 'current ip is %s\n' "${ip}"

res=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records?type=A&name=$SUBDOMAIN" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN")

success=$(echo $res | jq -r '.success')
record_id=$(echo $res | jq -r '.result[0].id // empty')
record_contents=$(echo $res | jq -r '.result[0].content //empty')

if [ "$success" != "true" ]; then
  printf 'record retrieval error\n' >&2
  echo $res | jq
  exit 1

elif [ -z "$record_id" ]; then
  printf 'missing record; creating it now\n'

  res=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $TOKEN" \
      --data "{
          \"type\": \"A\",
          \"name\": \"$SUBDOMAIN\",
          \"content\": \"$ip\",
          \"ttl\": $TTL,
          \"proxied\": false
        }")

elif [ "${record_contents}" != "${ip}" ]; then
  printf 'found stale record (%s); updating it now\n' "${record_contents}"

  res=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records/$record_id" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $TOKEN" \
      --data "{
          \"type\": \"A\",
          \"name\": \"$SUBDOMAIN\",
          \"content\": \"$ip\",
          \"ttl\": $TTL,
          \"proxied\": false
        }")

elif [ "${record_contents}" != "${ip}" ]; then
  printf 'found stale record (%s); updating it now\n' "${record_contents}"

  res=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records/$record_id" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $TOKEN" \
      --data "{
          \"type\": \"A\",
          \"name\": \"$SUBDOMAIN\",
          \"content\": \"$ip\",
          \"ttl\": $TTL,
          \"proxied\": false
        }")

else
  printf 'record found; no update needed\n'

fi

exit 0

If you want to figure out where your domain’s zone ID is in Cloudflare, follow this post. And if you want to create an API key, follow this one. You’ll need both of those if you want the above script to work. Make sure to test your shit first. :) Don’t forget to sudo chmod +x /usr/local/bin/ipsdsync so that it can actually run.

Then, go add that script to the crontab. (e.g. crontab -e) —

*/15 * * * * /usr/local/bin/ipsdsync

^— so I have that running every 15 minutes. And cron dumps things into mail so you’ll have a log.

When you’re done with all that, return to the shell on the server. Then cat out the public key (the one at /root/wg/vpn0.pub if you’re a normie who stuck to the script). Take that Base64 representation and use replace it where required in the next step, which would be configuring your client’s Wireguard config. And that should look like the following:

[Interface]
PrivateKey = <CLIENT-PRIVATE-KEY>
Address = 172.16.2.4/32
DNS = 1.1.1.1

[Peer]
PublicKey = <SERVER-PUBLIC-KEY>
PresharedKey = <PRE-SHARED-KEY>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <YOUR-SUBDOMAIN>:<LISTEN-PORT>

If you’re on Windoze, you might see an option labeled to the effect of “Block untunneled traffic (kill-switch)” and you should probably enable that if you want this to be truly full-tunnel, so that your machine doesn’t leak traffic around the VPN. When you’re done with that, activate the tunnel.

You can test this thing in a number of ways. On FreeBSD, you should be able to run sudo wg show and get stats regarding traffic flow on individual endpoints. The Wireguard UI (on Windows, for example) should also have similar stats. But you can also go to DuckDuckGo and search “what is my IP address?” repeatedly with the tunnel on or off and see the IP address switch. And of course you ought to be able to access various things on your server’s network.

And that’s really it—so next time you find the need to flee, but you also want to check on your 3D printer’s status, monitor the thermostat, or pretend that you’re a ghost flickering the lights… slap this piece together and go to town. Figuratively and literally. Feel like a mole? Embrace the tunnel. Embrace mole.

lordinateur.xyz