VPN

This post is sponsored by… NordVPN!

Okay no not really, nobody is sponsoring me :(

Hence, instead of buying a VPN service, I’m hosting my own. Not only can we do it for free by using Fly.io’s generous free allowance, but we also have more transparency, where our traffic is passing through. So the goal of this post is to get a self-hosted VPN service with WireGuard running using Fly.io. As a neat side-effect, we’re also creating our own virtual, private network, I guess similar to Tailscale.

First, we need something something that can build a Docker image. Well, it’s not going to result in a Docker image but rather a Firecracker VM. We could write our own Dockerfile by following WireGuards instructions on how to set it up, or we could just use some pre-built ones. There’s the excellent image from linuxserver.io, linuxserver/wireguard. However, the process(es) are started using init which is not so friendly with Fly.io that wants to be PID 1 - maybe I missed something, but I couldn’t get it running (at least not on that afternoon).

Looking further, I came across the wg-easy Dockerfile: Not only does it not use init, it also provides a GUI to configure peerings!

We start off with a Dockerfile that basically emulates wg-easys Dockerfile, but with the setting to enable IP forwarding at the Dockerfile level as opposed to when starting the container (for which I don’t really understand the motivation why this isn’t set in the first place):

FROM weejewel/wg-easy

CMD sysctl net.ipv4.ip_forward=1 && /usr/bin/dumb-init node server.js

Using flyctl launch, I create a new app in the nearest datacenter, upon which a shiny new fly.toml will be created. I modify it to take the WireGuard port into account and to notify it about its hostname. It eventually boils down to the following:

app = "free-vpn"

[env]
  WG_HOST = "free-vpn.fly.dev"

[[services]]
  internal_port = "51820"
  protocol = "udp"

  [[services.port]]
    port = "51820"

[[services]]
  internal_port = "51821"
  protocol = "tcp"

  [[services.ports]]
    handlers = ["http"]
    port = 80
    force_https = true

  [[services.ports]]
    handlers = ["tls"]
    port = 443

The image also wishes for a password, used for the admin interface. We could set it in this file as well in the environment section, but alternatively we can also make use of the secrets feature: flyctl secrets set PASSWORD=super_secret_password. This will expose the secret value to the container as an environment variable.

Finally, we can launch our enchillada using flyctl deploy! After less than a minute, fly tells us our app is ready:

Fly.io app interface

Indeed, following the URL of the app we encounter the admin interface! After singing in with out super_secret_password, we’re greeted with a… mostly empty screen. From here, we can add new clients (that end up as peers to our VM). The setup is pretty cool, we get the option to use a QR code that we can scan e.g. from the WireGuard app in our phone, which properly configures it!

WireGuard dashboard

Immediately, the phone connects to the VM, forwarding all traffic! This is also visible in the GUI using some numbers showing the current in-/outgoing traffic as well as some subtle diagrams.

Persistent Volume

At this point, every time the VM restarts, the configuration is lost, hence, requiring setting up the clients again. This is due to the ephemeral nature of flys VMs. To persist the configuration between restarts and across multiple VMs, we can use volumes: First, we attach a volume to the app: fly volumes create configuration. Finally, we mount the volumne on the machine on WireGuard’s configuration path by adding the following lines to fly.toml:

[mounts]
  source="configuration"
  destination="/etc/wireguard"

Fly.io features

Being run on top of Fly.io we should be able to get some pretty cool features out of the box (that I haven’t tested yet):

  • Autoscaling depending on the number of concurrent connections
  • Multiple geographic locations

Conclusion

With around 20 lines of code (or rather, configuration), we’ve started a self-hosted VPN service with private virtual networking.


Project

655 Words

2022-11-13 22:12 +0000