I just tried out Docker. It is awesome but seems not work nicely with ufw. By default, docker will manipulate the iptables a little bit. The outcome is not a bug but not wha
This problem has been around for a long time.
Disable iptables in Docker will take other problems.
If you have modified your server according to the current solution that we find on the internet, please rollback these changes first, including:
--iptables=false
, including configuration file /etc/docker/daemon.json
.DROP
instead of ACCEPT
./etc/ufw/after.rules
.This solution needs to modify only one UFW configuration file, all Docker configurations and options remain the default. Doesn't need to disable the docker iptables function.
Modify the UFW configuration file /etc/ufw/after.rules
and add the following rules at the end of the file:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-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 -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
Using the command sudo systemctl restart ufw
to restart UFW after changing the file. Now the public network can't access any published docker ports, the container and the private network can visit each other regularly, and the containers can also access the external network from inside.
If you want to allow public networks to access the services provided by the Docker container, for example, the service port of a container is 80
. Run the following command to allow the public networks to access this service:
ufw route allow proto tcp from any to any port 80
This command allows the public network to access all published ports whose container port is 80.
Note: If we publish a port by using option -p 8080:80
, we should use the container port 80
, not the host port 8080
.
If there are multiple containers with a service port of 80, but we only want the external network to access a particular container. For example, if the private address of the container is 172.17.0.2, use the following command:
ufw route allow proto tcp from any to 172.17.0.2 port 80
If the network protocol of service is UDP, for example, a DNS service, you can use the following command to allow the external network to access all published DNS services:
ufw route allow proto udp from any to any port 53
Similarly, if only for a specific container, such as IP address 172.17.0.2:
ufw route allow proto udp from any to 172.17.0.2 port 53
The following rules allow the private networks to be able to visit each other. Typically, private networks are more trusted than public networks.
-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
The following rules allow UFW to manage whether the public networks are allowed to visit the services provided by the Docker container. So that we can manage all firewall rules in one place.
-A DOCKER-USER -j ufw-user-forward
The following rules block connection requests initiated by all public networks, but allow internal networks to access external networks. For TCP protocol, it prevents from actively establishing a TCP connection from public networks. For UDP protocol, all accesses to ports which is less then 32767 are blocked. Why is this port? Since the UDP protocol is stateless, it is not possible to block the handshake signal that initiates the connection request as TCP does. For GNU/Linux we can find the local port range in the file /proc/sys/net/ipv4/ip_local_port_range
. The default range is 32768 60999
. When accessing a UDP protocol service from a running container, the local port will be randomly selected one from the port range, and the server will return the data to this random port. Therefore, we can assume that the listening port of the UDP protocol inside all containers are less then 32768. This is the reason that we don't want public networks to access the UDP ports that less then 32768.
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
https://github.com/chaifeng/ufw-docker
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
ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp
ufw-user-forward
, not ufw-user-input
ufw-user-input
Pro:
Easy to use and understand, supports older versions of Ubuntu.
For example, to allow the public to visit a published port whose container port is 8080
, use the command:
ufw allow 8080
Con:
It not only exposes ports of containers but also exposes ports of the host.
For example, if a service is running on the host, and the port is 8080
. The command ufw allow 8080
allows the public network to visit the service and all published ports whose containers' port is 8080
. But we just want to expose the service running on the host, or just the service running inside containers, not the both.
To avoid this problem, we may need to use a command similar to the following for all containers:
ufw allow proto tcp from any to 172.16.0.3 port 8080
ufw-user-forward
Pro:
Cannot expose services running on hosts and containers at the same time by the same command.
For example, if we want to publish the port 8080
of containers, use the following command:
ufw route allow 8080
The public network can access all published ports whose container ports are 8080
.
But the port 8080
of the host is still not be accessed by the public network. If we want to do so, execute the following command to allow the public access the port on the host separately:
ufw allow 8080
Con:
Doesn't support older versions of Ubuntu, and the command is a bit more complicated. But you can use my script https://github.com/chaifeng/ufw-docker.
If we are using an older version of Ubuntu, we can use ufw-user-input
chain. But be careful to avoid exposing services that should not be exposed.
If we are using a newer version of Ubuntu which is support ufw route
sub-command, we'd better use ufw-user-forward
chain, and use ufw route
command to manage firewall rules for containers.
The script ufw-docker supports Docker Swarm now. Please see the latest code for more, https://github.com/chaifeng/ufw-docker
Install for Docker Swarm modeWe can only use this script on manager nodes to manage firewall rules when using in Swarm mode.
after.rules
files on all nodes, including managers and workersRunning in Docker Swarm mode, this script will add a global service ufw-docker-agent
. The image chaifeng/ufw-docker-agent is also automatically built from this project.
I spent two hours trying out the proposals above and from other posts. The only solution that worked was from Tsuna's post in this Github thread:
Append the following at the end of
/etc/ufw/after.rules
(replace eth0 with your external facing interface):# Put Docker behind UFW *filter :DOCKER-USER - [0:0] :ufw-user-input - [0:0] -A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-USER -m conntrack --ctstate INVALID -j DROP -A DOCKER-USER -i eth0 -j ufw-user-input -A DOCKER-USER -i eth0 -j DROP COMMIT
And undo any and all of:
- Remove "iptables": "false" from /etc/docker/daemon.json
- Revert to DEFAULT_FORWARD_POLICY="DROP" in /etc/default/ufw
- Remove any docker related changes to /etc/ufw/before.rules
- Be sure to test that everything comes up fine after a reboot. I still believe Docker's out of the box behavior is dangerous and many more people will continue to unintentionally expose internal services to the outside world due to Docker punching holes in otherwise safe iptables configs.