Protect docker from internet while allowing LAN with iptables

So some of you might have noticed, that docker is setting up its own rules in iptables. However these rules reside mostly in the FORWARD chains and not in the INPUT.

Most users, that setup their small server or laptop use INPUT rules to secure it. This is allright, as normally a server and a laptop are standalone and no routers.

However with installing docker it becomes a router for the containers, hence docker uses the FORWARD chains. Knowing iptables means, that traffic gets sorted after PREROUTING into INPUT or FORWARD. Following the INPUT rules are not affecting the FORWARD chain.

Docker manages the FORWARD chain (and even the nat table) itself to control access to the containers. Basically it setups NAT and the FORWARDING to the containers as soon as you use the “–port” command or docker-compose “port:” setting.
In this docker does not differ between traffic from the internet or a local LAN. Everything that arrives at the FORWARD chain is used.

ISSUE: In the scenario of of having a laptop or small home server, which is usually connected via local LAN to the internet, this could lead to opening services that should only run in LAN, to the internet. Only an additional firewall outside could prevent this.

So what is the solution

To get back to a state where we can control opening services to the internet, you need four iptables rules. We will use the DOCKER-USER chain, which should be empty and is made by docker for user written rules within the docker setup. Therefore they will not be touched by docker.

Before starting you should make a list of your local networks, including the docker network (even the default network).

1. Stop docker from communicating

iptables -A DOCKER-USER -j DROP

This is adding a rule, that is just stopping any docker communication. After this all containers will be offline for the internet or locally.

2. Allow local networks to communicate

iptables -I DOCKER-USER -s <docker-network> -j RETURN
iptables -I DOCKER-USER -s <LAN> -j RETURN

The first rule allows the docker containers to communicate with each other, hence you need to add your docker network her or have multiple rules if you have multiple networks (e.g., 172.18.0.0/24).
The second rule allows the LAN to communicate again, hence it should be something along 192.168.1.0/24.
After this your docker services will be available from the local network, however not from the internet (IP spoofing would work, but you can only handle that with different interfaces). If you do not want to publish services to the internet, you can stop here.

3. Allow outside communication to reach containers

iptables -I DOCKER-USER -p tcp --dport <target-port> -j RETURN

Put here the target port of the service on the docker container to allow traffic.
IMPORTANT: This happens after nat, so if you have docker do some portmapping, e.g., 8080 -> 80 you need to put the container port 80 here.

With this you can have your docker services accessible on the LAN and decide which to publish to the internet.

Notes:
We use “-I” to add these rules before the DROP rule
“RETURN” is used as the chain “DOCKER-USER” is in front of all other docker rules in the filter table. Hence we need to return to those.

Dovecot learn ham/spam with rspamd via inet protocol for docker

I started rebuilding my mail server following Thomas Leisters Howto. However I decided to dockerize the whole setup. With that I needed to get rid of any socket communication and move to tcp based communication between different docker containers.

This was mainly easy, as most components already communicate via tcp. However the learn spam and ham mechanism still uses a socket.

So here are some details for my setup:

  • I used a user defined network via docker compose to connect the different containers. By that I have full control over the containers IPs
  • Each process is running in one container, so I have unbound, redis, rspamd, dovecot, postfix
  • Host system is a debian stretch
  • Docker containers are based on Alpine:latest

So what is the solution

BEWARE: I am basing my guide on Thomas config linked above.

1. First you need to change a few details in the ham/spam piping.

Within the dovecot.conf down at the plugin settings you need to set the “sieve_pipe_bin_dir” option to the location, where the pipe scripts (Step 3) will be stored. Beware to set the path as it will be in your docker image.

My setting:

sieve_pipe_bin_dir = /usr/local/sbin

2. Adapt the sieve scripts. These scripts trigger the learning as you can see in dovecot.conf. Ham on copying out of SPAM folder, Spam on copying into SPAM folder.

Do not forget to call “sievec” after placing them in the sieve folder.

learn-spam.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-pipe-spam";

learn-ham.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-pipe-ham";

3. Adapt the pipe scripts itself. These scripts will actually connect to rspamd to deliver the mail for learning.
During docker image creation you will need to copy the “rspamd-pipe-spam” and “rspam-pipe-ham” scripts into the “sieve_pipe_bin_dir” location (Step 1) and make them executable.

The script is connecting via the container name “rspamd” if you have a different one, you need to change or use the IP.

rspamd-pipe-spam

#!/bin/ash
cat $1 | /usr/bin/curl -s --data-binary @- http://rspamd:11334/learnspam
exit 0

rspamd-pipe-ham

#!/bin/ash
cat $1 | /usr/bin/curl -s --data-binary @- http://rspamd:11334/learnham
exit 0

4. To allow this scripts to call rspamd you need to allow the IP of dovecot for the worker controller.

worker-controller.inc

bind_socket = "rspamd container>:11334";
password = "<your pwd as described in the guide>";
secure_ip = "<dovecot container ip>";

This should enable ham/spam learning via sieve within a docker setup.

Addendum: To train existing mails, e.g. from an old server, you need to execute the following commands in the dovecot docker. Please make sure you adapt paths, if you changed them.
Learn HAM

find /var/vmail/mailboxes/*/*/mail/cur -type f -exec /usr/local/sbin/rspamd-pipe-ham {} \;

Learn SPAM

 

find /var/vmail/mailboxes/*/*/mail/Spam/cur -type f -exec /usr/local/sbin/rspamd-pipe-spam {} \;