A simple solution for routing specific docker containers through a WireGuard VPN using only two simple systemd-networkd files, no cumbersome wg
or ip
calls.
Thanks for the article, very simple and works well.
Per your warning, Iāve added the following to the .network file for my ethernet interface to handle container traffic routing if the WireGuard interface is somehow destroyed:
[Route]
Destination=0.0.0.0/0
Type=blackhole
Metric=1
Table=242
This seems to do the trick after a test using ip link set down dev wg1. I can no longer curl nor ping from within the container until the wg1 device and its route are restored.
Iām glad that the article was helpful!
And thank you for the config tip. Using metrics looks very clean and simple. Iāll test it on my machine when I get around to it and add it to the article
Sorry that it took so long. I finally tested your solution, it seems to work for me as well! I added it to the article. Thanks again!
This is exactly what Iāve been looking for where there exists a kill-switch which is active regardless of the state of the Wireguard connection. Usually most other tutorials setup a kill-switch in the Wireguard-config which is only active when there is a connection. Changing the private key, hostname (IP, or address) exposes all traffic sent through.
Speaking of the solution, would it be possible to make a shell script of all the steps which users can easily run to set up everything? And perhaps make a git of it as well? Moreover, port forwarding from a service, such as a BitTorrent container, works out-of-the-box without doing anything? Letās say my VPN provider gives me port 42891, I simply only have to setup that port in the BitTorrent client and port forwarding should work?
Creating a script that generates the two systemd-networkd files from an existing wg-quick config is an interesting idea that should definitely be possible. If I get around to it, Iāll be sure to put it here!
Port forwarding through the VPN works almost out of the box, youāll still have to tell docker to publish the corresponding ports as you normally would, e.g. using -p 1234:80
in a Docker call or
ports:
- "1234:80"
in a Docker Compose file would make port 80 inside the container available as port 1234 through the VPN.
Both can be the same, so if your VPN provider gives you port 42891, run the container with port 42891 and use 42891:42891
as port option.
I added a explaination for that to the article. Thanks for the feedback!
Thank you very much for the additional information concerning port forwarding Iāll look more into this in the coming week. Whatās your take on implementing a firewall with the setup? Is it really needed given if you have a firewall setup for the system outside of the Docker containers?
To harden the security, Iām thinking about encrypting the private key. However, I see that PostUp is not possible to run with systemd-networkd given error: " Unknown key name āPostUpā in section āWireGuardā, ignoring." (inspiration from archlinux wiki). So please let me know if you can think of any solution to accomplish this with pass.
Reading from your article: " Note that published ports of tunneled containers are not reachable on localhost
, only through the VPN. Sadly, I havenāt yet found a possibility to fix that."
This is actually possible with multi-host-networking. The easiest way is to create two networks and add them both to the second container which wants to access ports from the first container.
Fictive example:
networks: default: tunneled0: ipam: config: - subnet: 10.123.0.0/16 services: container1: image: .... networks: tunneled0: container2: image: ... networks: - default - tunneled0
Here is a real-world example which I use where Iāve added three networks to get an inter-container network. ruTorrent is running in the front network (tunneled0) and expose port 80 and 5000 to inter network and then back network which NGINX access to get the reverse proxy work.
Real world example
version: "3.8" networks: tunneled0: ipam: config: - subnet: 10.123.0.0/16 inter: name: inter driver: bridge internal: true driver_opts: com.docker.network.bridge.name: dockerinter back: name: back driver: bridge driver_opts: com.docker.network.bridge.name: dockerback rutorrent: image: linuxserver/rutorrent expose: - "80" #(ruTorrent web) - "5000" #(ruTorrent SCGI) networks: - tunneled0 - inter ... swag: image: linuxserver/swag cap_add: - NET_ADMIN environment: ... networks: - back - inter ports: - "443:443" ...
Whatās your take on implementing a firewall with the setup? Is it really needed given if you have a firewall setup for the system outside of the Docker containers?
Iām no expert on that, you should have a firewall somewhere in front of Docker, e.g. I have Docker running on my home server and my router is doing the firewalling.
So please let me know if you can think of any solution to accomplish this with pass.
I havenāt tried it, but you should be able to not set the private key in the .netdev file and then use a custom oneshot systemd unit that runs the command used in PostUp from the ArchWiki on startup to enter the key.
This is actually possible with multi-host-networking . The easiest way is to create two networks and add them both to the second container which wants to access ports from the first container.
Thank you very much! That solutions seems to work. Iāll add it to the blog post when I get around to it.
I have a firewall setup in my router and have setup iptables for my server outside docker. I was thinking about if it is needed to add iptables rules inside the docker setup as well. But I guess it isnāt and the reason for why most vpn-clients have it is probably to use it as a kill-switch.
Hello! first of all, thanks for the guide. It is exactly the kind of approach I was looking for when rerouting docker containers. I have a problem though: even though I can $ sudo curl -4 --interface wg0 icanhazip.com
and it gives me the VPN IP, when I add it to a docker network and attach it to a container (via portainer, which is how I am managing containers) and enter it, it has no connection. Using $ networkctl status wg0
shows itās status as āroutable (configured)ā. I can connect to the same peer in the same local network using another user from the same (self-hosted) VPN peer. Oh, also, the peer of access is using Algo (https://github.com/trailofbits/algo/issues) to manage VPN profiles (which adds a layer of complexity to the equation, as it uses PresharedKeys along with Public and Private ones).
Do you have any idea where I might be falling short?
Once again, thank you.
@rodrigorodrigo, it sounds like a routing issue. Can you show me your 85-wg0.network
file and your Algo-generated config file without the pub/priv key and endpoint?
Sure thing @Maren, thanks for taking the time to help. Here are both files that compose the networkd config, as well as the Algo-generated config file:
user@server:/etc/systemd/network$ cat /etc/systemd/network/85-wg0.network
[Match]
Name=wg0
[Network]
Address = IPv4, IPv6
[RoutingPolicyRule]
From = 10.123.0.0/16
Table = 242
[Route]
Gateway = IPv4, IPv6 (same as Network / Address)
Table = 242
[Route]
Destination = 0.0.0.0/0
Type = blackhole
Metric = 1
Table = 242
user@server:/etc/systemd/network$ cat /etc/systemd/network/80-wg0.netdev
[NetDev]
Name = wg0
Kind = wireguard
Description = WireGuard VPN
[WireGuard]
PrivateKey = privkey
[WireGuardPeer]
PublicKey = publicKey
PresharedKey = PresharedKey
AllowedIPs = 0.0.0.0/0
Endpoint= VPNIP:PORT
user@VpnPeer: cat ~/algo/configs/config/wireguard/nas.conf
[Interface]
PrivateKey = PrivKey
Address = IPV4, IPV6
DNS = DNSIPv4, DNSIPv6
[Peer]
PublicKey = PublicKey
PresharedKey = PresharedKey
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = IP:port
I believe everything is right, I have double (tripled, quadrupled) checked everything according to the guide.
EDIT: I have just noticed that I have a DNS parameter that is not on the guide. It was included in the Algo profile so I just inserted it there.
UPDATE::
I think DietPi has a bugged version of networkd that seems to be floating around. It seemed to restart successfully but never establish. I tried the same approach on Ubuntu Mate and at least got further (Connection established, handshake, basic tests).
I did run into issue on my Ubuntu Mate build though. wg0 can curl a request properly but my docker tunneled requests all fail to resolve. Not sure if I am missing something about DNS but literally nothing can escape the docker if I set its network to tunneled.
Any thoughts appreciated but I am stopping working on this for the day ⦠ha
OLD Info
Iāve been banging my head on this one. Trying to set this up on DietPi - ran their client type install for wireguard. I think the type is only more or less changes for config.
Feel free to take a look if you want: https://dietpi.com/phpbb/viewtopic.php?p=16308#p16308
I canāt seem to establish a connection through my wg0.
Config values populated from auto-genned mullvad wireguard file
80-wg0.netdev
[NetDev]
Name = wg0
Kind = wireguard
Description = WireGuard VPN
[WireGuard]
PrivateKey = <PVKey>
[WireGuardPeer]
PublicKey = <PubKey>
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint= <EPIP:PORT>
85-wg0.network
[Match]
Name=wg0
[Network]
Address = IPV4/CIDR
Address = IPV6/CIDR
[RoutingPolicyRule]
From = 10.123.0.0/16
Table = 242
[Route]
Gateway = IPV4/CIDR -- Same as Network
Gatewau = IPV6/CIDR -- Same as Network
Table = 242
[Route]
Destination = 0.0.0.0/0
Type = blackhole
Metric = 1
Table = 242
networkctl
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 eth0 ether off unmanaged
3 wlan0 wlan routable unmanaged
4 wg0 wireguard off unmanaged
5 br-dbc8c1c558d9 bridge no-carrier unmanaged
6 docker0 bridge no-carrier unmanaged
Testing
sudo curl -4 --interface wg0 icanhazip.com
curl: (7) Couldn't connect to server
sudo curl -4 --interface wlan0 icanhazip.com
MY.IP.SERVER.IP.NON.VPN
I dunno if I missed something here - should I have to set any config directly with wireguard? I think networkd should have just picked it up from files.
Anyways - thoughts?
Unsure how to get any logs - I wonder if its related to resolvconf and networkd
In 85-wg0.network, there is a typo in the second Gateway (Gatewau)⦠Furthermore, try to use one Address and Gateway first. In my case, I had to use a minor change between them where address have /16 at the end, while Gateway does not (only IP as example 10.100.100.100).
I appreciate the reply. The typo was only on here (I did some edits on my post and the remote and tried to keep them in sync). I will update it here but you were right that its a typo on the website.
That said - the wg0 network interface is working perfectly as described by this file.
It is only docker that is failing to make it to the internet.
Here is a cleaned snapshot direct from the computer of info I find relevant - also, I know there are keys and IPs, I scrambled them.
Testing out current setup
feeder@ubuntu-mate:~$ sudo curl -4 --interface wg0 https://icanhazip.com
69.135.55.1 //fake vpn ip
feeder@ubuntu-mate:~$ sudo curl -4 https://icanhazip.com
70.124.178.211 // fake public ip
feeder@ubuntu-mate:~$ sudo docker exec -it 7d87c09e1c9a /bin/bash
root@7d87c09e1c9a:/# curl -4 https://icanhazip.com --verbose
* Could not resolve host: icanhazip.com
* Closing connection 0
curl: (6) Could not resolve host: icanhazip.com
Checking IP inside Docker Container
root@7d87c09e1c9a:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.123.0.2 7d87c09e1c9a
root@7d87c09e1c9a:/# exit
exit
docker-compose.yml
feeder@ubuntu-mate:~$ cat compose/feeder/docker-compose.yml
version: "3.7"
networks:
tunneled0:
ipam:
config:
- subnet: 10.123.0.0/16
inter:
driver: bridge
internal: true
driver_opts:
com.docker.network.bridge.name: dockerinter
back:
driver: bridge
driver_opts:
com.docker.network.bridge.name: dockerback
services:
something:
image: linuxserver/something
container_name: something
dns: "8.8.8.8" /// I tried it with and without dns
environment:
- PUID=1000
- PGID=1000
- TZ=America/Chicago
volumes:
- ./config:/config
ports:
- 6789:6789
networks:
- tunneled0
restart: unless-stopped
sudo wg
feeder@ubuntu-mate:~$ sudo wg
interface: wg0
public key: 6uE0bdqHNZpgvt75qGaXYJxfSJ6ACiWg8zElTpogcls= // scrambled key - matches public key that pairs to the private key of my wg-quick
private key: (hidden)
listening port: 43750
peer: Z67ACpoiW0YHNGabJ6tgE5JxfSXpgv8zFlTdqqugcls= // scrambled key - matches WQ Quick and 80-wg0.netdev
endpoint: 69.211.21.69:51820 // fake VPN endpoint (scrambled mullvad), matches 80-wg0.netdev
allowed ips: ::/0, 0.0.0.0/0
latest handshake: 2 minutes, 48 seconds ago
transfer: 22.71 KiB received, 9.68 KiB sent
networkctl
feeder@ubuntu-mate:~$ networkctl
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 eth0 ether routable unmanaged
3 wlan0 wlan no-carrier unmanaged
4 wg0 wireguard routable configured
5 docker0 bridge no-carrier unmanaged
6 br-5d292cc9a7f9 bridge no-carrier unmanaged
7 br-e0c2770b5c6a bridge routable unmanaged
9 veth36fae70 ether degraded unmanaged
cat 80-wg0.netdev
feeder@ubuntu-mate:~$ cat /etc/systemd/network/80-wg0.netdev
[NetDev]
Name = wg0
Kind = wireguard
Description = WireGuard VPN
[WireGuard]
PrivateKey = 94gsq5SN2JGYC2hzVs/u1hVPNBJFXLMt4ZgZDFleOnY= // scrambled mullvad key straight from wg quick - is the private key pair to public key output in wg command
[WireGuardPeer]
PublicKey = Z67ACpoiW0YHNGabJ6tgE5JxfSXpgv8zFlTdqqugcls= // scrambled mullvad key straight from wg quick - matches output of wg
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint= 69.211.21.69:51820 // scrambled vpn endpoint straight from wg-quick matches wg output
cat 85-wg0.network
feeder@ubuntu-mate:~$ cat /etc/systemd/network/85-wg0.network
[Match]
Name=wg0
[Network]
Address = 10.22.33.44/32 // straight from wg-quick, scrambled
Address = fde0:ccfc:cfcc:ff01::2:1a17/128 // straight from wg-quick, scrambled
[RoutingPolicyRule]
From = 10.123.0.0/16
Table = 242
[Route]
Gateway = 10.22.33.44/32 // straight from wg-quick, scrambled
Gateway = fde0:ccfc:cfcc:ff01::2:1a17/128 // straight from wg-quick, scrambled
Table = 242
[Route]
Destination = 0.0.0.0/0
Type = blackhole
Metric = 1
Table = 242
I still think this is a routing issue. Could you please try to do what I recommended in the last post? Remove /32
and /128
from Gateway so you get the following in your cat 85-wg0.network file:
"85-wg0.network
[Match]
Name = wg0
[Network]
Address = 10.22.33.44/32
Address = fde0:ccfc:cfcc:ff01::2:1a17/128
[RoutingPolicyRule]
From = 10.123.0.0/16
Table = 242
[Route]
Gateway = 10.22.33.44
Gateway = fde0:ccfc:cfcc:ff01::2:1a17
Table = 242
[Route]
Destination = 0.0.0.0/0
Type = blackhole
Metric = 1
Table = 242
Went ahead and tried that out - I assumed since the connection is working, just not from docker it wouldnāt have an impact.
Switched, restarted networkd and even rebooted - docker still can not connect to icanhazip.com
Thanks for the idea though.
I faced the same issue as you where my wg0 connection was up and running, but my docker didnāt have any internet connection. Doing what I told you solved the issue for me. Unfortunately that didnāt solve it for you. I guess you have not added any kernel parameters to /etc/sysctl.conf, changed iptables rules etc., correct?