Restricting External Container Access with Iptables

Hi,

The Docker networking documentation show’s how to easily restrict external container access to a single IP using Iptables.

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 8.8.8.8 can access the containers, 
the following rule could be added:

$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP

What’s the best approach for allowing, say, two external IP addresses?

Adding another rule negating another IP won’t work as the 1st negation would have already matched and returned from the table.

My initial thoughts are a PRE_DOCKER table with a final return of DROP before the DOCKER table.

Any thoughts or suggestions would be greatly appreciated.

Cheers.

Hi,

Here’s my 1st iteration on a solution for this.

So far it seems to work just as required, but feedback would be appreciated.

Goal: Restrict access by IP to docker container listening on the host port 5000 (Docker Private Resgistry).

Senario: Currently, by default, all traffic is allowed access port 5000

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:5000

Solution:

a) Create a PRE_DOCKER table with a default rule of REJECT, insert this as the 1st table on the FORWARD chain.

sudo iptables -N PRE_DOCKER
sudo iptables -I FORWARD -o docker0 -j PRE_DOCKER
sudo iptables -A PRE_DOCKER -i docker0 ! -o docker0 -j ACCEPT
sudo iptables -A PRE_DOCKER -i docker0 -o docker0 -j ACCEPT
sudo iptables -A PRE_DOCKER -j REJECT

b) Insert rules before the default REJECT to allow IP addresses.

One IP from the USA and one IP from Asia:

sudo iptables -I PRE_DOCKER -s 192.184.41.144 -j ACCEPT
sudo iptables -I PRE_DOCKER -s 120.29.76.14 -j ACCEPT

c) End result:

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 PRE_DOCKER  all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:5000

Chain PRE_DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       120.29.76.14        0.0.0.0/0           
    0     0 ACCEPT     all  --  *      *       192.184.41.144       0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

It’s a Sunday afternoon here so I’m just kind of tinkering, but my tests so far work OK.

Seems reasonable to you too?

I could be totally wrong, iptables can be tricky and I’m no guru with it.

Cheers.

Hi,

2nd iteration… posting here for posterity, feedback welcome.

This seems to work as I require now.

Access to Docker containers via Public and Private interfaces are filtered.

# Create a PRE_DOCKER table
iptables -N PRE_DOCKER

# Insert this as the first table on the FORWARD chain.
iptables -I FORWARD -o docker0 -j PRE_DOCKER

# Access is specifically ordered

# Docker internal use
iptables -A PRE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A PRE_DOCKER -i docker0 ! -o docker0 -j ACCEPT
iptables -A PRE_DOCKER -m state --state RELATED -j ACCEPT
iptables -A PRE_DOCKER -i docker0 -o docker0 -j ACCEPT

# Docker Containers Restricted LAN Access
iptables -A PRE_DOCKER -i eth1 -s 192.168.33.0/24 -j ACCEPT

# Docker Containers Public Access (http is open for all)
iptables -A PRE_DOCKER -i eth0 -p tcp --dport 80  -j ACCEPT
iptables -A PRE_DOCKER -i eth0 -p tcp --dport 443 -j ACCEPT

# All other Docker Containers Public Admin access (my IP)
sudo iptables -I PRE_DOCKER -s 192.184.41.144 -j ACCEPT

# Set the default action to REJECT
iptables -A PRE_DOCKER -j REJECT

3rd iteration on my solution for this, posting here for posterity and to complete this thread.

I’ve made this into a full bash script and will provide a link to a blog post which has a more complete intro and description.

#!/usr/bin/env bash

# Usage:
# timeout 10 docker_iptables.sh
#
# Use the builtin shell timeout utility to prevent infinite loop (see below)

if [ ! -x /usr/bin/docker ]; then
    exit
fi

# Create a PRE_DOCKER table
iptables -N PRE_DOCKER

# Default action
iptables -I PRE_DOCKER -j DROP

# Docker Containers Public Admin access (insert your IPs here)
iptables -I PRE_DOCKER -i eth0 -s 192.184.41.144 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -s 120.29.76.14 -j ACCEPT

# Docker Containers Restricted LAN Access (insert your LAN IP range or multiple IPs here)
iptables -I PRE_DOCKER -i eth1 -s 192.168.1.101 -j ACCEPT
iptables -I PRE_DOCKER -i eth1 -s 192.168.1.102 -j ACCEPT

# Docker internal use
iptables -I PRE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 ! -o docker0 -j ACCEPT
iptables -I PRE_DOCKER -m state --state RELATED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 -o docker0 -j ACCEPT

# Docker container named www-nginx public access policy
WWW_IP_CMD="/usr/bin/docker inspect --format='{{.NetworkSettings.IPAddress}}' www-nginx"
WWW_IP=$($WWW_IP_CMD)

# Double check, wait for docker socket (upstart docker.conf already does this)
while [ ! -e "/var/run/docker.sock" ]; do echo "Waiting for /var/run/docker.sock..."; sleep 1; done

# Wait for docker web server container IP
while [ -z "$WWW_IP" ]; do echo "Waiting for www-nginx IP..."; WWW_IP=$($WWW_IP_CMD); done

# Insert web server container filter rules
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 80  -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 443 -j ACCEPT

# Finally insert the PRE_DOCKER table before the DOCKER table in the FORWARD chain.
iptables -I FORWARD -o docker0 -j PRE_DOCKER

Blog post is at http://rudijs.github.io/2015-07/docker-restricting-container-access-with-iptables/

Hi Rudi and thanks for this excellent script. I seem to have all outgoing traffic blocked (apart from the admin IPs) when I apply these rules. I need my containers to send outgoing traffic, ideally without any restrictions.

I’ve tried different additional rules without success, do you have any idea how this could be achieved?

Thanks,
Pedro.

Hi, @rudijs, I’ve followed your instructions and put my rules into UFW configuration in before.rules, and all works like a charm!
Just replaced PRE_DOCKER with DOCKER-USER.