Score:1

Ensure Docker traffic for node in Swarm exclusively passes through a VPN connection

in flag

I have two nodes in a Docker Swarm cluster. One of those nodes has an OpenVPN client connection to a VPN provider on interface tun0. My goals are,

  • Any services assigned to this node exclusively use the VPN connection
  • No leaks (i.e., DNS or other traffic)
  • If the VPN disconnects, all traffic gets dropped
  • Allow service discovery and connections to other containers in the Swarm

For DNS, I have added a dns entry to /etc/docker/daemon.json that uses the VPN provider's DNS servers that are only accessible through the VPN.

Here are the iptable rules I have come up with:

iptables -I DOCKER-USER 1 -o tun0 -j ACCEPT
iptables -I DOCKER-USER 2 -i tun0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I DOCKER-USER 3 -j DROP

The resulting DOCKER-USER chain looks like this:

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     all  --  *      tun0    0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  tun0   *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0

From basic tests like running nslookup and curl with the VPN connection on and off these rules seem to work, but I have very little experience with iptables. Is this the correct way of doing this?

Score:1
fr flag

IPtables is linear and it reads the rules from top to bottom until it reaches a ACCEPT, REJECT, or DROP target, and then it stops reading the rules. In your case, you want to have iptables -I DOCKER-USER -j DROP as the very last rule in your chain, otherwise every packet will be dropped. Also, there is no need for the RETURN rule at the end, because IPtables will stop reading rules once it reaches the DROP rule right above it. Those IPtables rules with tun0 look fine, but makes sure you have these rules as well:

iptables -t filter -I INPUT 1 -i tun0 -j DOCKER-USER
iptables -t filter -I OUTPUT 2 -o tun0 -j DOCKER-USER

For good practice, make sure you accept all loopback traffic, which will never reach the internet nor will it leave the machine:

iptables -t filter -I INPUT 3 -i tun0 -j ACCEPT

Let's go through your requirements one-by-one:

  1. Any services assigned to this node exclusively use the VPN connection

You would not use IPtables to do this. Run these commnds on the servers:

ip route add default via ${LOCAL_VPN_IP} 

I think OpenVPN typically uses 10.8.0.0/16, so the default gateway would probably be 10.8.0.1 or something like that. IProute2 (ip command) is built into the kernel, just like IPtables.

  1. No leaks (i.e., DNS or other traffic)

You should first redirect all traffic through the VPN by putting this in your OpenVPN's server config:

push "redirect-gateway autolocal"

This makes the clients put all of their traffic through the VPN, even DNS and such. If the OpenVPN server goes down, the internet will stop working on the clients.

  1. If the VPN disconnects, all traffic gets dropped

See step 2

  1. Allow service discovery and connections to other containers in the Swarm

I believe OpenVPN does this by default. I am able to ping/arp from one client to other clients that are on the OpenVPN server. You should definitely be able to access services that are on other clients.

I hope this answers your questions!

in flag
Thank you! I have edited my question to be more clear. Here are some follow-up questions... 1. The `INPUT 1` and `OUTPUT 2` rules route traffic through the DOCKER-USER subchain. Are those required as Docker automatically manages rules under the FORWARD chain? 2. For my original rules, should I use RETURN to allow it to go through the other FORWARD subchains, or ACCEPT? 3. Is the `ip route` command redundant? Won't all traffic will be forced over the `tun0` interface regardless based on the iptables rules?
fr flag
1. Yes, you could also add `iptables -t filter -A FORWARD -j DOCKER-USER`. `INPUT` is for traffic destined for the server, `OUTPUT` is for traffic originating at the server, and `FORWARD` is for traffic that neither originates nor terminates at the server. In other words, `FORWARD` is what your router does when your computer connects to google.com. 2. `RETURN` isn't useful unless you are in a subchain and want to return to the chain up a level. 3. `ip route` is not redundant. `iptables` allows or disallows packets, but it does not tell your server where to send packets. `ip route` does.
in flag
1. So you are saying I can use `iptables -t filter -A FORWARD -j DOCKER-USER` instead of adding the `INPUT` and `OUTPUT` rules you mentioned? 2. `DOCKER-USER` is Docker managed sub-chain of `FORWARD`, so then `RETURN` should be used to return up to the `FORWARD` chain? 3. Makes sense, I will need to look more into this as my current default route goes through my local IP.
fr flag
1. `INPUT`, `OUTPUT`, and `FORWARD` all serve different functions, so you need to include all 3 if you want all of your traffic to be managed by the VPN. 2. If `INPUT`, `OUTPUT`, and `FORWARD` are all sent to `DOCKER-USER`, the `DOCKER-USER` chain manages all of the traffic for all 3 chains, meaning you do not need to `RETURN` anything to the `FORWARD` chain, as the `FORWARD` chain will be dealt with by the rules in `DOCKER-USER`.
in flag
By sub-chaining `DOCKER-USER` into `INPUT` and `OUTPUT`, would that not apply those rules to all traffic, including for the host? It seems that would drop everything to my physical interface `eth0`, including the connection to the VPN itself, as it would not fit the ACCEPT rules.
in flag
Here are the docs for Docker iptables as a reference. https://docs.docker.com/network/iptables/ The line of interest is "Docker installs two custom iptables chains named DOCKER-USER and DOCKER, and it ensures that incoming packets are always checked by these two chains first."
fr flag
Sorry for the late reply, I got busy. So, I did not know that Docker created those chains. That being said, you are right. `DOCKER-USER` seems to only check "incoming packets" from my understanding. So, if you wanted outgoing packets to only go through the VPN, you would do something like `iptables -t filter -A OUTPUT -o tun0 -j ACCEPT` followed by `iptables -t filtler -A OUTPUT ! -o tun0 -j DROP` or you could also just have `iptables -t filter -A OUTPUT -j DROP` after the ACCEPT rule.
in flag
Thanks for the follow-up! The chains all make sense now, I will implement those. As for the ip route, it looks like OpenVPN does that automatically during startup with these `route add 0.0.0.0/1 via REMOVED` and `128.0.0.0/1 via REMOVED`. Unfortunately, I could not a way to confirm if the OpenVPN server has `redirect-gateway autolocal` set. Do you know of a way to confirm this?
fr flag
You would put `push "redirect-gateway autolocal"` in your OpenVPN server config file. That way, it would push that setting to the clients to re-route all of their traffic. But it looks like OpenVPN must have some setting like that enabled by default. Those route commands should redirect all traffic through the VPN.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.