How docker can break your ufw rules
TLDR: Docker can break your ufw rules if you are not careful. And you might not even notice it until it is too late.
I was in shock recently when I tested my personal servers ip-address for open ports with nmap -sV -p- <your-server-ip>. I had 5 open ports but it should have only been 3. I ssh'ed into my server to check the firewall rules with sudo ufw status and it looked like everything was fine. SSH and HTTP were allowed but nothing else. I double checked and tried to connect to these other ports and to my surprise they worked.
After some digging I found that using docker's -p flag to map ports to the host machine uses iptables and doesn't respect the ufw rules. This is a known quirk of docker and there are some workarounds to fix it.
Identify the problematic containers
First we need to understand if docker uses ports or not. Run docker ps --format "{{.ID}}\t{{.Ports}}\t{{.Names}}" and look for the containers that have ports mapped to the host machine looking something like this 0.0.0.0:8080->8080/tcp. If you see any of these, you need to fix them as described in the fixes below.
Here this 0.0.0.0:5000->8080/tcp means that the containers 8080 port is mapped to the host machine's 5000 port. 0.0.0.0 means that the port is mapped to all ip-addresses, that is the problematic part. If it was 127.0.0.1:8080->8080/tcp it would only be mapped to the localhost ip-address and not the public ip-address.
1. Solution: ufw-docker (recommended)
There is a solution is what Chai Feng came up with in his repo https://github.com/chaifeng/ufw-docker. There are several ways to apply it but the easiest is to download the script and run it.
# downloads the script
sudo wget -O /usr/local/bin/ufw-docker \
https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker
# applies the script
ufw-docker install
ufw-docker install-service
# after doing this every exposed port will be removed and we need to re-expose them
# apply the new rules
ufw-docker allow <docker-container> 443/tcp
ufw-docker allow <docker-container> 80/tcp
ufw-docker reload
ufw-docker status
This is the recommended fix as it is the simplest and most effective solution.
2. Solution: port mapping to localhost (for simple cases)
Use the -p flag with the ip-adress of localhost instead of the port alone.
docker run -p 127.0.0.1:8080:8080
docker run -p 8080:8080 //badThis will map the port to the localhost ip-address instead of the public ip-address. It also works with docker compose.
ports:
- "127.0.0.1:8080:8080"
# not - "8080:8080"
3. Solution: Disable iptables for the docker daemon (not recommended)
Warning: This will impact docker container networking and you might not be able to connect containers to each other.
This is more invasive but if you really want to be sure, you can disable iptables for the docker daemon.
sudo nano /etc/docker/daemon.json
Add the following to the file to the Object:
{
... // other settings
"iptables": false
}
Save and restart the docker daemon.
sudo systemctl restart docker
This will disable iptables for the docker daemon and all containers will be able to use the ports you want.
Debugging
If you are still having issues, you can try to debug the issue by running the following commands:
sudo docker ps --format "{{.ID}}\t{{.Ports}}\t{{.Names}}"
sudo docker inspect <container-id>
sudo iptables -L -n
This will show you the iptables rules and the docker containers and their networks.
Wrap Up
Don't just trust your setup, try to break it at times. Start with nmap and basic hacking practices to test your setup. Maybe there is a forgotten port or a subdomain that is open to the public.