Wireguard and docker: providing VPN access to arbitrary containers
Date
Tags
#vpn
#docker
#wireguard
Your container might benefit from VPN access
Some containers just aren't meant to be connected directly to the internet. After all, you wouldn't want your ISP knowing which Linux distribution you download and share.
If like me you have your BitTorrent client installed as a container on a homeserver to make sure it's always connected but you don't want to route your other containers through a VPN, you'll probably want to use a VPN-in-a-container and route your BitTorrent client through it.
I already had a similar solution using OpenVPN but it was time for an upgrade. Oh yes, it's Wireguard time.
As VPN provider, I use Mullvad.
The solution
Our situation is this: our homeserver (could be a Linux machine, a Raspberry Pi…) runs two docker containers, one which is fine to be directly connected to the internet and one which would benefit from VPN access.
One could install the Wireguard client straight on the machine and route both containers through the VPN, but for various reasons, that's now what we want here.
Our solution will be to add another container which connects to the VPN and route our sensitive container through the VPN container.
With some experimenting, I got it working 90%. The only issue was that while the BitTorrent client was perfectly shielded by the VPN, I could no longer access the client myself. Not great.
After two days of trying stuff out and searching the internet, I found the working solution on a blog post from 2021 which sadly already no longer exists. But thanks to the Web Archive, its wisdom is lost no more.
PostUp and PreDown
The reason I didn't get it working myself is because I knew the problem lay in the PostUp/PreDown
commands of the Wireguard configuration. And I don't know how to read or write those :/ Mullvad provides their own but they do not work in this situation.
I must therefore warn you that I sadly do not fully understand the solution. I probably could fiddle with it and get it working on a different system, but I don't understand it. I simply took my 90%-functional implementation, copy-pasted the PostUp/PreDown
commands from the linked blog post and voilà, success!
Not proud of it, and I hope I'll gain understanding of these commands in the near future, but that's the situation.
The implementation
You must have Wireguard installed on your system but it doesn't need to be running any connection.
docker-compose.yml
version: '2.3'
services:
wireguard:
image: linuxserver/wireguard
hostname: wireguard
container_name: wireguard
cap_add:
- net_admin
- sys_module
ports:
- 8112:8112
- 58846:58846
volumes:
- /lib/modules:/lib/modules
- ./data/wireguard:/config
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
deluge:
image: linuxserver/deluge
container_name: deluge
network_mode: service:wireguard
volumes:
- ./data/deluge:/config
- ./data/downloads:/downloads
In this docker-compose setup, we use the linuxserver/wireguard and linuxserver/deluge container images. Please have a look at their respective documentation for more information on their configuration.
A few interesting notes:
cap_add:
- net_admin
- sys_module
[…]
volumes:
- /lib/modules:/lib/modules
The linuxserver/wireguard image uses the system's Wireguard module and this configuration allows the container to access it.
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
This is important but sadly, I do not know what it does.
ports:
- 8112:8112
- 58846:58846
This is the interesting part. We assign those ports to the wireguard
container, but they are the ports exposed by the deluge
container! Indeed, since the deluge
container's network flows through the wireguard
container, we can only access the deluge
container through the wireguard
container's network.
By the way, port 8112
is used for the Deluge WebUI and port 58846
is used by Deluge Thin Clients. Your BitTorrent client of choice will most likely use different ports!
network_mode: service:wireguard
The trick that makes it all work: make sure that the deluge
container connects to the internet through the wireguard
container.
wg0.conf
This Wireguard configuration file is based on the one provided by Mullvad, but with the PostUp/PreDown
commands found in the blog post mentioned earlier.
[Interface]
PrivateKey = <private key>
Address = <ip address>
DNS = <ip address>
PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -j ACCEPT; iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route del $HOMENET3 via $DROUTE;ip route del $HOMENET2 via $DROUTE; ip route del $HOMENET via $DROUTE; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT
[Peer]
PublicKey = <public key>
AllowedIPs = 0.0.0.0/0
Endpoint = <ip address with port>
Verification
We need to make sure we are in fact connected safely to Mullvad! To do this, let's use Mullvad's https://am.i.mullvad.net/connected
API endpoint.
docker exec -t wireguard curl https://am.i.mullvad.net/connected
# You are connected to Mullvad (server XXYY-wireguard). Your IP address is XYZ.XYZ.XYZ.XYZ
Success! But wait, that's the wireguard
container, this just checks whether our config is working. What about the deluge
container?
docker exec -t deluge curl https://am.i.mullvad.net/connected
# You are connected to Mullvad (server XXYY-wireguard). Your IP address is XYZ.XYZ.XYZ.XYZ
Victory! Have fun sharing Linux distributions!