I set this up. The most complicated part was that–even if you’re root–you’ll still get “permission denied” errors when attempting to view or edit iptables rules inside of the default Discourse docker container.
root@24a1f9f4c038:/# iptables -L
# Warning: iptables-legacy tables present, use iptables-legacy to see them
iptables: Permission denied (you must be root).
root@24a1f9f4c038:/#
This is because–out of the box–the Discourse launcher
script that wraps the docker commands runs the Discourse docker container without the “NET_ADMIN” capability.
- capabilities(7) - Linux manual page
- | Docker Documentation
- Installing iptables in docker container based on alpinelinux - Stack Overflow
The most robust way to add the NET_ADMIN capability to the Discourse docker container is to update your container’s yaml file to include the necessary argument to the docker run ... /sbin/boot
command via the docker_args
yaml string:
docker_args: "--cap-add NET_ADMIN"
For example:
[root@osestaging1 discourse]# head -n15 containers/app.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed
docker_args: "--cap-add NET_ADMIN"
templates:
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
- "templates/web.template.yml"
[root@osestaging1 discourse]#
These will get added to the docker run ... /sbin/boot
command executed by launcher
via the $user_args
variable:
[root@osestaging1 discourse]# grep -A2 -E '^\s*\$docker_path run' launcher
$docker_path run --shm-size=512m $links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
-e DOCKER_HOST_IP="$docker_ip" --name $config -t "${ports[@]}" $volumes $mac_address $user_args \
$run_image $boot_command
[root@osestaging1 discourse]#
Because it’s super complex to make changes baked-into the discourse docker image after a docker pull
and before the docker run ... /sbin/boot
, I decided to have iptables installed & configured via a simple script executed by runit
when the container boots.
The following command will create the necessary template yaml file which will create the runit
script on the next ./launcher rebuild app
cat << EOF > /var/discourse/templates/iptables.template.yml
run:
- file:
path: /etc/runit/1.d/01-iptables
chmod: "+x"
contents: |
#!/bin/bash
################################################################################
# File: /etc/runit/1.d/01-iptables
# Version: 0.2
# Purpose: installs & locks-down iptables
# Author: Michael Altfield <michael@opensourceecology.org>
# Created: 2019-11-26
# Updated: 2019-12-17
################################################################################
sudo apt-get update
sudo apt-get install -y iptables
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -s 127.0.0.1/32 -j DROP
sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -j DROP
sudo iptables -A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -j ACCEPT
sudo iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
sudo iptables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
sudo iptables -A OUTPUT -j DROP
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A INPUT -s ::1/128 -j DROP
sudo ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -A INPUT -j DROP
sudo ip6tables -A OUTPUT -s ::1/128 -d ::1/128 -j ACCEPT
sudo ip6tables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
sudo ip6tables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
sudo ip6tables -A OUTPUT -j DROP
EOF
Note that this is a very basic iptables config. With the web.socketed.template.yml
file (which I use), the docker container technically doesn’t need Internet access at all since the nginx reverse proxy on the docker host communicates through to the Discourse nginx on the docker container via a unix domain socket. The main reason I permit Internet access is so that the docker container’s base OS can update itself with critical security patches via unattended-upgrades
. Hence opening for user 100
, which is the apt
user.
Finally, add the above templates/iptables.template.yml
file to your container’s app.yaml
file.
[root@osestaging1 discourse]# head -n15 /var/discourse/containers/discourse_ose.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed
docker_args: "--cap-add NET_ADMIN"
templates:
- "templates/iptables.template.yml"
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
[root@osestaging1 discourse]#
Now you should be able to
./launcher rebuild app
And after waiting about 10 minutes, your Discourse docker container will have iptables fully functioning