Securing Docker with Wireguard

Mark Percival
Apr 23 · 3 min read

I typically develop on a remote machine. That means I’m SSH’d (actually ‘mosh’) into a remote VPS, and doing all my development remotely via the terminal.

At some point, I added Docker to the mix. Docker improved my developer experience by allowing me to quickly spin up an environment on a remote machine and not have to worry about installing a bunch of stuff globally (Postgres, Redis, etc). But also learned that Docker alters your iptable rules in order to work and has the side effect of possibly exposing ports to the outside world if you’re not careful.

The goal of this article is to describe how I both secure Docker and access my remote instances via Wireguard.

Wireguard to the rescue

Setting up a VPN used to be a somewhat involved process, but Wireguard has changed that. Wireguard is remarkably fast, lightweight, and easy to set up on both the client an server.

There are plenty of articles on installing Wireguard, instead, I’ll focus on my setup for accessing remote services from your local machine while denying any public internet connections.

The summary of my setup is this:

  • The server is available via the Wireguard interface on a private IP address, for example, 172.22.22.1
  • I map 172.22.22.1 in /etc/hosts to a hostname I want to use, let’s say demo.mdp.im
  • I block inbound eth0 (external) access to Docker via iptable rules, as recommended by the Docker docs.
  • Now I access demo.mdp.im like I normally would and have access to any running Docker instance that’s exposing a port.
  • Bonus: I use LetsEncrypt with DNS validation to create a valid certificate for demo.mdp.im. This connection is already encrypted via Wireguard, but the certificate allows me to hit the development webserver with my local browser and be under valid HTTPS.

The nitty gritty details

Iptable Rules

It’s just 3 rules in the DOCKER-USER group for iptables. (Modifying the DOCKER-USER iptable chain is the Docker-preferred way to modify iptable rules that co-exist with Docker’s modifications)

sudo iptables -A DOCKER-USER -o eth0 -j RETURN
sudo iptables -A DOCKER-USER -i eth0 -m state --state ESTABLISHED,RELATED -j RETURN
sudo iptables -A DOCKER-USER -i eth0 -j DROP

Let’s break it down:

sudo iptables -A DOCKER-USER -o eth0 -j RETURN
sudo iptables -A DOCKER-USER -i eth0 -m state --state ESTABLISHED,RELATED -j RETURN

The first 2 rules allow for outbound traffic on `eth0`, and inbound return traffic from established and related connections. Basically allowing docker containers to freely talk to the outside world for connections it initiates.

sudo iptables -A DOCKER-USER -i eth0 -j DROP

The final instruction is what secures the docker instances, dropping any packets from eth0 that don’t match the above rules.

That’s it. Wireguard is amazing. I can open my laptop and not wait for the VPN to connect. I just get immediate access to my development box via a secure connection, and no more port forwarding/tunnels wrangling.

My Wireguard config

Client side

[Interface]
Address = 17.22.22.2/32
PrivateKey = {PrivateKey}
ListenPort = 21814[Peer]
PublicKey = {ServerPublicKey}
Endpoint = 123.45.67.89:995
AllowedIPs = 172.22.22.0/24

Server Side

[Interface]
Address = 172.22.22.1/24
SaveConfig = true
ListenPort = 995
PrivateKey = {PrivateKey}[Peer]
PublicKey = {ClientPubKey}
AllowedIPs = 172.22.22.2/32

Caveats

This isn’t a perfect solution. You need to make sure you set up your iptable rules to persist after a reboot. You should also still practice good security habits. For example, don’t run your Postgres instance with no password just because it’s behind a firewall. Wireguard is a great VPN solution, but like all tools, it’s possible to misconfigure something and expose your remote system if you’re not careful. Layer your security accordingly.

Addendum

Guide to installing Wireguard

Mark Percival

Written by

Former ATLien living in SF