docker will bypass your firewall
Docker has networking-related behaviour that in my opinion is at best surprising and at worst dangerous.
Here is a minimal but innocent-looking docker run
line that exposes the external port 8080
to the internal port 80
of the container defined by the nginx
image.
docker run -p 8080:80 nginx
The problem is the implicit meaning of 8080:80
which, when put more explicitly, can be written as 0.0.0.0:8080:80
1.
The 0.0.0.0
prefix is the bind address, i.e., all addresses on all interfaces. In other words, you are completely exposed.
#But I'm firewalled!
You might be thinking this is an acceptable and common default, because even if the container is only intended to be accessed locally, the system in question is firewalled with iptables or an iptables-based firewall like ufw.
It would be reasonable to think so, but this is where Docker knows better: it has configured iptables to give itself priority over all your other rules, and it will allow external incoming connections anyway.
In fairness the documentation does state this clearly, but most people don't read documentation unless they have to.
Docker installs two custom iptables chains named DOCKER-USER and DOCKER, and it ensures that incoming packets are always checked by these two chains first.
#docker-compose is affected, too
docker-compose is used to declaratively run containers with a specific parameters, like exposed ports and volume paths. Rather than passing these values as arguments to the docker
command they can be encoded as YAML in compose.yml
and then started using the simple command docker-compose up
.
This minimal compose.yml
is functionally equivalent to the docker run
line above.
version: '3'
services:
app:
image: nginx
ports:
- 8080:80
Because docker-compose
is just calling the underlying Docker engine the result would be exactly the same here too.
#Solution
The simplest solution is to make it a habit to always specify the bind address (e.g., 127.0.0.1
) and never leave a port definition bare. This is a good habit to get into even when the intended address is 0.0.0.0
as it forces you to think about what you actually want.
A more robust general-purpose solution that entirely avoids this class of problem is to not use a local firewall at all. Use a hardware firewall or the one available from most cloud providers.
The lesson here is check your assumptions and always try to hack yourself first. Running regular port scans on your own infrastructure can be an invaluable tool to help you find unintentionally exposed services before someone else does.
-
This isn't 100% equivalent. A lack of bind address will default to IPv4 and IPv6 if available, but using
0.0.0.0
limits it to IPv4. ↩