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:
- https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu.
- https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/
- https://github.com/chaifeng/ufw-docker
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.