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 chris panas