Categories
Technology

Bootstrapping Docker Swarm Part 6: Setting up a Firewall

This is part of a multi-part series on getting Docker Swarm up and running. You might want to start with the original post called Bootstrapping Docker Swarm.

Probably the most confusing part of setting up Docker Swarm, for me, was setting up the firewall. There are lots of guides out there that I did not find helpful. Here’s what I did find helpful. There are three main points to know:

  1. Docker will mess with your ipables rules and you can’t do anything about it.
  2. Your containers connect to the outside world through the FORWARD chain and not the INPUT chain so your carefully constructed firewall rules that are all tied to INPUT will do nothing as Docker just bypasses them.
  3. When restarting the firewall to, say, make changes, be careful to not overwrite Docker’s rules or you will have to restart Docker, too, as there is no other way to reset the Docker firewall rules.

Because of the second rule, the best way to protect your host is to not use the INPUT chain but to make a new chain and then tie it to both INPUT and FORWARD. Then when you want to make tiny rule changes you can simply rebuild the FORWARD chain and ignore everything else. So let’s show you how to do that.

If you’re simply using iptables and you’re keeping your rules in /etc/iptables/rules.v4 then your firewall rules probably look very simply like this:

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -s 127.0.0.0/8 ! -i lo -j DROP
-A INPUT -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 80 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 443 -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -j REJECT
-A FORWARD -j REJECT
-A OUTPUT -j ACCEPT
COMMIT

This very simple configuration simply allows connections on port 22, 80, and 443 and rejects everything else. If you make changes to it you might apply them by running this command:

systemctl restart netfilter-persistent

We’re not going to do that anymore. First, there are specific ports that we need to allow to get Docker Swarm running:

  • TCP 2377 (controller communication)
  • TCP/UDP 7946 (node communication)
  • UDP 4789 – for overlay network traffic (unencrypted)
  • ESP 50 – for overlay network traffic (encrypted)

So here is how we will rewrite the above rules to work with Docker Swarm.

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]

# flush existing rules
-F INPUT
-F DOCKER-USER
-F FILTERS

# all rules for INPUT will go through the chain called FILTERS
-A INPUT -j FILTERS

# filter stuff coming toward docker
-A DOCKER-USER -i eth0 -j FILTERS
-A DOCKER-USER -i eth1 -j FILTERS

# basic rules: accept related, deny invalid, deny "localhost" from off host, accept pings
-A FILTERS -i lo -j ACCEPT
-A FILTERS -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FILTERS -m conntrack --ctstate INVALID -j DROP
-A FILTERS -s 127.0.0.0/8 ! -i lo -j DROP
-A FILTERS -p icmp -j ACCEPT

# allow SSH, HTTP and HTTPS
-A FILTERS -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 22 -j ACCEPT
-A FILTERS -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 80 -j ACCEPT
-A FILTERS -p tcp -m state --state NEW -m tcp --src 0.0.0.0/0 --dport 443 -j ACCEPT

# docker swarm cluster management communications
-A FILTERS -p tcp -m state --state NEW -m tcp --src 10.0.0.0/24  --dport 2377 -j ACCEPT

# node communication
-A FILTERS -p udp -m state --state NEW -m udp --src 10.0.0.0/24  --dport 7946 -j ACCEPT
-A FILTERS -p tcp -m state --state NEW -m tcp --src 10.0.0.0/24  --dport 7946 -j ACCEPT

# overlay network traffic (unencrypted)
-A FILTERS -p udp -m state --state NEW -m udp --src 10.0.0.0/24  --dport 4789 -j ACCEPT

# overlay network traffic (encrypted)
-A FILTERS -p 50 --src 10.0.0.0/24 -j ACCEPT

# fallback defaults
-A FILTERS -j REJECT

# defaults for all rules
-A INPUT -j REJECT
-A FORWARD -j ACCEPT
-A OUTPUT -j ACCEPT

COMMIT

What we’ve done here is create a new chain called FILTERS that gets applied to both INPUT and FORWARD. If we make changes to this filter we should reload it by running this command instead of the one above:

iptables-restore -n < /etc/iptables/rules.v4

This will avoid removing the rules that exist and just load the rules that we’ve got in the rules file. But that’s ok because the first thing that our rules file does is flush the rules that we want to flush.

You can start with these rules and adjust them as appropriate for all of the hosts that we’ve built. For example, some of your hosts only have one interface so you don’t need to put eth0 and eth1, you only need eth0. Only your load balancers need to allow access from 80 and 443.

But remember, Docker, by default, puts your containers directly on the network and completely bypasses your firewall. Good luck!

Next Steps

In this part we set up the your firewall to protect your host. Now you have a working Docker Swarm cluster and you can start to build new images that you can deploy to it and will be served by your HAProxy instance. Good luck!

There are still a lot more steps! Follow on to read the rest of the steps.