dockeriptables

Docker ignores iptable rules when using "-p <port>:<port>"


Just realized a few days ago that Docker seems to bypass my iptable rules. I am not incredible experienced with Docker nor iptables. Tried a lot of different things the last days. Also saw that there was big change in recent docker versions with a special DOCKER-chain that should allow me to do that. However not sure what I am doing wrong but it never does what I expect it to do.

So what I want is quite simple. I want that it behaves like expected. That if I have an ACCEPT-Rule to go through and if not it gets blocked.

My iptable looked originally like that (so before my many unsuccessful attempts):

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -s 1.2.3.4 -p tcp -m tcp --dport 123 -j ACCEPT
-A INPUT -j DROP
COMMIT

Hoped that it does exactly what I want. Just allow access to ports 22 and 80 and also allow port 123 from the ip 1.2.3.4. However If I create a container with "-p 123:123" everybody can access it. Can anybody help me and tell me how I have to change the above file?

Thanks!

Docker-Version: 1.6.2

Edit:

Left initially my different tries out to not overcomplicate the question. However adding at least one of them could maybe be helpful.

*nat
:PREROUTING ACCEPT [319:17164]
:INPUT ACCEPT [8:436]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [16:960]
:DOCKER - [0:0]
COMMIT


*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER -s 1.2.3.4 -p tcp -m tcp --dport 123 -j ACCEPT
-A DOCKER -j DROP
-A INPUT -j DROP
COMMIT

The above kind of works. However get then a lot of other problems. For example do I get problems with container linking, DNS does not work anymore, and so on. So then end up adding a lot of additional rules to fix that issues but I get never to a state where it runs properly. So I guess there most be better and easier solution out there.

Solution:

Ended up doing more or less exactly what larsks said. Just did not add it to the FORWARD chain, I added it to the DOCKER chain instead. The problem with the FORWARD chain is that Docker adds its stuff in there when it restarts in first position. Which results in having my rules getting pushed down and not having any effect. However for the DOCKER chain it seems Docker appends only additional rules so mine stay in effect. So when I save my rules and then restart the server everything still works fine.

So now it looks more or less like that:

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]
# That I can access from IP 1.2.3.4
-A DOCKER -s 1.2.3.4/32 -p tcp -m tcp --dport 123 -j ACCEPT
# That I can access from other Docker containers
-A DOCKER -o docker0 -p tcp -m tcp --dport 123 -j ACCEPT
# Does not allow it for anything else
-A DOCKER -p tcp --dport 123 -j DROP

-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -j DROP
COMMIT

Solution

  • Your iptables configuration looks a little broken right now, as if you cleared it out at some point without restarting Docker. For example, you have a DOCKER chain available in both the filter and nat tables, but no rules that reference it, so rules placed in that chain will have no affect.

    In general, if you want to implement iptables rules that affect your Docker containers they need to go in the FORWARD chain of the filter table. Each container has it's own ip address, which means that your host is simply accepting packets and then FORWARDing them to the container address.

    Rules in the INPUT chain are only for packets with a final destination of an address on an interface in the host's global network namespace.

    However, I'm not sure that iptables is actually your problem.

    If you are trying to expose services in containers such that they are available to other systems, you need to publish those ports using the -p flag to docker run. You can read more about that in this section of the documentation.

    If you want to update your question with a specific example of what you are trying to accomplish I can provide a more targeted answer.

    Update

    It's true that when you publish a container port using -p it will generally be available to any source ip address. In order to restrict access to a published port you would need to add a new rule to your FORWARD chain. For example, if I start a web server:

    docker run --name web -p 80:8080 larsks/mini-httpd
    

    The web server in the container is now available on port 8080 on my host. If I want to block access to this port, I need to insert a rule into the FORWARD chain that blocks access to port 80 on the container ip. So first I need the container ip address:

    $ web_ip=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' web)
    $ echo $web_ip
    172.17.0.5
    

    The rule I create in the FORWARD chain needs to come before the rules that docker creates, so I will need to specify an explicit position:

    iptables -I FORWARD 1 -d $web_ip -p tcp --dport 80 \! -s 192.168.1.10 -j DROP
    

    This would block all traffic from hosts other than 192.168.1.10.

    If you want a rule to apply to all containers, rather than a specific container, you can bind it to the docker0 interface rather than a specific ip address:

    -A FORWARD -o docker0 -p tcp --dport 80 \! -s 192.168.1.10 -j DROP
    

    This would prohibit access to port 80 on any container.