ufw-docker

How to use UFW firewall with Docker containers?

Motivation

You are using uncomplicated firewall (ufw) and you realised that your docker web app is accessible via the internet regardless of the status of ufw, then this article is for you. I try here to answer two questions:

  • Why does not ufw work for Docker containers?
  • How to get ufw to work with Docker?

Why does not ufw work for Docker containers?

The answer is very simple, both ufw and docker modify the same iptables configurations, leading to misconfigurations that exposes containers regardless of the status of ufw.

As you may already know, ufw is a simple command line tool that modifies the configurations of iptables, the actual firewall infrastructure of Linux. Docker edits the same tables that ufw configures, which often lead to a situation causing containers to bypass rules set by ufw and adhering to rules set by docker.

How to get ufw to work with Docker?

There are several ways to get ufw to work with Docker. You can find a nice discussion about the potential solutions on stackoverflow and elsewhere. Here are some useful links that are accessible at the time of publishing this post:

iptables=false in /etc/docker/daemon.json and manually setting up ufw rules

In this solution, we start with setting up our Docker to the mode where it doesn’t modify iptables rules:

$ echo "{
\"iptables\": false
}" > /etc/docker/daemon.json

Let’s restart the docker server:

$ sudo systemctl restart docker

After than we need to tweak the default ufw forward policy from dropping connection to accepting them, then force ufw to reload the rules, this allows the connections from external interfaces to pass the internal one: 

$ sudo sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ sudo ufw reload

Finally we need to add a rule to the iptable that will pass the connection to the docker network. Given that the docker network is on 172.17.0.0/16 space:

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

This means that connections coming to the network interface will be passed via ufw to the docker network 172.17.0.0/16, and now ufw can control the traffic to that network. A lot of manual work

using ufw-docker

Since this is a fairly known and common issue, someone took the time to develop a tool to fix the issue for everyone else Chai Feng. The tool can be found here on github: https://github.com/chaifeng/ufw-docker. The installation process is well documented, as well as the usage. Nonetheless, I will describe the necessary steps needed for installation and discuss the usage of ufw-docker in little details.

Before installing ufw-docker it is necessary to check that the Docker iptables feature is enabled. So in case you have disabled it by adding --iptables=false to the /etc/docker/daemon.json, remove that line from the file and restart the docker service.

Ok let’s proceed with the installation:

Copy the ufw-docker script to the /usr/local/bin/ufw-docker. It can be found online under: https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker. Make sure it is executable and that’s it.

$ sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
$ chmod +x /usr/local/bin/ufw-docker

Now let’s use the tool to modify the /etc/ufw/after.rules and add all necessary after rule to fix the ufw with docker problem

$ ufw-docker install

This command adds the following rules at the end of the /etc/ufw/after.rules:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN

-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP

COMMIT
# END UFW AND DOCKER

After that you need to restart the ufw service or force ufw to reload the rules:

$ sudo systemctl restart ufw

or

$ sudo ufw reload</span>

With these rules added to the end of the files it is possible to use ufw and ufw-docker to filter network traffic.

For example to allow http and https traffic to a docker container running on the local ip 172.17.0.2 use the following commands:

$ ufw route allow proto tcp from any to 172.17.0.2 port 80
$ ufw route allow proto tcp from any to 172.17.0.2 port 443

It is also possible to use the ufw-docker command to expose ports of a specific running docker container such as nginx using the following command:

$ ufw-docker allow nginx 80
$ ufw-docker allow nginx 443

Conversely, to delete the rules use:

$ ufw-docker delete allow nginx 80
$ ufw-docker delete allow nginx 443

Use firewalls provided by your cloud provider

This is an advise rather than a solution to the question that is the title of this blog post.

With that said, I would recommend using a firewall that is outside the host that is running docker. Cloud providers usually provide firewalls as part of the configurable infrastructure. Using such a solution will reduce docker host configuration time and complexity and will eliminate sources of errors. You can also reuse the same firewalls to protect multiple hosts.