Running (and debugging) iptables inside a Docker container

Running (and debugging) iptables inside a Docker container

Sometimes there's a need to run iptables inside a Docker container. The most common scenario is probably when the container is attached not to a standard Docker bridge (which provides network connectivity using iptables) but to a network configured using macvlan or ipvlan driver. In that scenario the container is directly exposed to the network.

In most cases the container has only one application running, for which one or more ports should be exposed. Exposing that port means exposing it to everyone and relying on the application itself to be able to protect itself. That might not always be desirable. And that's where iptables (and ip6tables if IPv6 is needed) come into the picture. They can be used for example to restrict access to the app to only a set of IP addresses.

Prerequisites

By default a Docker container is quite restricted in what it can do with the network (after all the network function is provided using the shared host kernel). So in order to be able to modify the in-kernel firewall NET_ADMIN capability has to be added:

docker run --cap-add NET_ADMIN my_container

The other necessary thing is to have iptables actually installed inside the container, for example for Alpine Linux (in the Dockerfile):

RUN apk add --no-cache iptables ip6tables

Running iptables

Logging

Running iptables inside the container is not really different from running it on a standalone host. There's one key difference - the -j LOG target doesn't result in any logging (as that would have to happen via the host kernel), which could make troubleshooting of complex rules a bit of a problem. Luckily there's another way of logging from iptables - using a userspace daemon. The actual logging target is called NFLOG and can be invoked like this:

iptables -A INPUT -j NFLOG --nflog-prefix "[default-drop]:" --nflog-group 1

The daemon itself is called ulogd and in Alpine Linux it comes in ulogd package:

apk add ulogd

Only fairly minimal configuration is required (by default its stored in /etc/ulogd.conf:

[global]

logfile="/var/log/ulogd.log"

stack=log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,emu1:LOGEMU

[log1]
group=1

[emu1]
file="/var/log/ulogd_syslogemu.log"
sync=1

The group referred in the log1 section is the same one as used in the iptables command above.

Once the config is done the ulogd can be started:

ulogd -d

And the log of dropped packets appears in /var/log/ulogd_syslogemu.log

Tracking state of flows

In most iptables setups packets returning are automatically accepted:

iptables -A INPUT -i eth0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

This works well, but what's worth considering that each connection tracked consumes some memory, so if the app only connects to a know list of external hosts - it might be better to whitelist those.

IPv6 considerations

If the container needs IPv6 connectivity there are some further things to consider (particularly when using macvlan or ipvlan drivers). IPv6 needs ICMPv6 for neighbour discovery (ARP in IPv4 world), so a rule that allows it is necessary:

ip6tables -A INPUT -i eth0 -p icmpv6 -j ACCEPT

Credits

header photo by unsplash-logochris panas
Show Comments