Steps for limiting outside connections to docker container with iptables?

Solution 1:

Two things to bear in mind when working with docker's firewall rules:

  1. To avoid your rules being clobbered by docker, use the DOCKER-USER chain
  2. Docker does the port-mapping in the PREROUTING chain of the nat table. This happens before the filter rules, so --dest and --dport will see the internal IP and port of the container. To access the original destination, you can use -m conntrack --ctorigdstport.

For example:

iptables -A DOCKER-USER -i eth0 -s -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j ACCEPT
iptables -A DOCKER-USER -i eth0 -s -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j ACCEPT
iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j DROP

NOTE: Without --ctdir ORIGINAL, this would also match the reply packets coming back for a connection from the container to port 3306 on some other server, which is almost certainly not what you want! You don't strictly need this if like me your first rule is -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT, as that will deal with all the reply packets, but it would be safer to still use --ctdir ORIGINAL anyway.

Solution 2:

With Docker v.17.06 there is a new iptables chain called DOCKER-USER. This one is for your custom rules:

Unlike the chain DOCKER it is not reset on building/starting containers. So you could add these lines to your iptables config/script for provisioning the server even before installing docker and starting the containers:

-A DOCKER-USER -i eth0 -p tcp -m tcp --dport 3306 -j DROP

Now the port for MySQL is blocked from external access (eth0) even thought docker opens the port for the world. (These rules assume, your external interface is eth0.)

Eventually, you will have to clean up iptables restart the docker service first, if you messed it too much trying to lock down the port as I did.

Solution 3:

UPDATE: While this answer is still valid the answer by @SystemParadox using DOCKER-USER in combination with --ctorigdstport is better.

Here is a solution which persists well between restarts and allows you to affect the exposed port rather than the internal port.

iptables -t mangle -N DOCKER-mysql
iptables -t mangle -A DOCKER-mysql -s -j RETURN
iptables -t mangle -A DOCKER-mysql -s -j RETURN
iptables -t mangle -A DOCKER-mysql -j DROP
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 3306 -j DOCKER-mysql

I've built a Docker image that uses this method to automatically manage the iptables for you, using either environment variables or dynamically with etcd (or both):

Solution 4:

UPDATE: While valid in 2015, this solution is no longer the right way to do it.

The answer seems to be in Docker's documentation at

Docker’s forward rules permit all external source IPs by default. To allow only a specific IP or network to access the containers, insert a negated rule at the top of the DOCKER filter chain. For example, to restrict external access such that only source IP can access the containers, the following rule could be added: iptables -I DOCKER -i ext_if ! -s -j DROP

What I ended up doing was:

iptables -I DOCKER -i eth0 -s -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER -i eth0 -s -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER 3 -i eth0 -p tcp --dport 3306 -j DROP

I didn't touch the --iptables or --icc options.