Drop packets with PREROUTING in iptables

13,981

I had a similar problem which was the need to "harden" network traffic even if somebody would deploy a container that was binding the application to any address: 0.0.0.0:port.

Docker provides a DOCKER-USER filter chain but it looks like all the magic happens in the DOCKER nat chain that is referenced in PREROUTING.

So no way around this nat happens before filtering and I don't want to touch too much at the docker rules.

I don't like the idea of having to change the packet once again so I came up with a scheme that returns everything by default and jumps to another chain in the PREROUTING before DOCKER gets called.

I then selectively jump back to DOCKER when I consider the traffic good.

Here's the code:

iptables -t nat -N DOCKER-BLOCK
iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j RETURN
iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j DOCKER-BLOCK

That's it!

The packet will jump to the DOCKER-BLOCK chain, and if that chain is empty, it'll go out the chain and continue on PREROUTING jumping to RETURN and it'll be blocked.

When you enable a port:

iptables -t nat -I DOCKER-BLOCK -p tcp -m tcp --dport 1234 -j DOCKER

It'll make the packet jump back to the DOCKER chain where it is managed by docker. Docker should handle the packet and the RETURN from PREROUTING should never be reached.

The nice way about it is that you never have to touch to the PREROUTING table anymore, if you want to flush, flush directly DOCKER-BLOCK.

Share:
13,981
Drakes
Author by

Drakes

Every person you meet is your teacher in some way.

Updated on September 18, 2022

Comments

  • Drakes
    Drakes over 1 year

    The filter table is best place to drop packets, agreed.

    But, out of the box, Docker bypasses INPUT filter rules with PREROUTING to its own FORWARD rules making Docker containers world-accessible. Inserting DOCKER-named filter INPUT/FORWARD rules fails because when Docker is restarted they are deleted then inserted (not appended).

    My best attempt is to insert another PREROUTING chain before Docker's and send unwanted packets from eth0 (WAN) to a black hole - 0.0.0.1 - because you cannot DROP/REJECT in a nat table anymore.

    # Route anything but TCP 80,443 and ICMP to an IPv4 black hole
    iptables -t nat -N BLACKHOLE
    iptables -t nat -A BLACKHOLE ! -i eth0 -j RETURN
    iptables -t nat -A BLACKHOLE -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
    iptables -t nat -A BLACKHOLE -p tcp --dport 80 -j RETURN
    iptables -t nat -A BLACKHOLE -p tcp --dport 443 -j RETURN
    iptables -t nat -A BLACKHOLE -p icmp -j RETURN
    iptables -t nat -A BLACKHOLE -p all -j DNAT --to 0.0.0.1
    iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j BLACKHOLE
    

    Here are what the NAT chains looks like with Docker and one container running:

    Nat table rules

    This seems to work well, though, is there a way to explicitly reject packets before reaching the other pre-routing rule?

    (Alpine Linux 3.6.2, Docker v17.05.0-ce)

  • Ram
    Ram almost 6 years
    Can you please explain on "That's all, by default everything coming from egress will end up in the filter table where I do have a catchall that drops everything."?
  • tehmoon
    tehmoon over 5 years
    @Ram Editing my answer, if you create the DOCKER-BLOCK chain and it is empty, all the packets will traverse that chain without being block. When they go back to the PREROUTING chain, they will jump to RETURN blocking them by default.