Skip to content

Instantly share code, notes, and snippets.

@blockpane
Last active August 30, 2023 16:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save blockpane/600b988e3d0d79a1bf668a763d8379c9 to your computer and use it in GitHub Desktop.
Save blockpane/600b988e3d0d79a1bf668a763d8379c9 to your computer and use it in GitHub Desktop.
Example of how to setup iptables to correctly filter Docker services
#!/bin/bash
### This is an example script that sets up IP tables with Docker. Many admins are not aware that UFW
### and Docker do not play well together, and accidently expose RPC ports and vulnerable services to
### the network. This replaces UFW with Netfilter-persistent and adds rules to the DOCKER-USER chain
### which is the correct way to filter incoming traffic with docker. This specific example is for a
### Tendermint node running via docker, and wireguard / ssh running directly on the host.
# CHANGE THIS TO ACTUAL PUBLIC INTERFACE!!!!!!!!!!
EXT_IF=eth0
ip a show ${EXT_IF} || exit 1 # Since we change input to drop at the end, be absolutely sure the interface exists!
[ $(id -u) -eq 0 ] || { echo "must be root"; kill 0; }
# Get rid of UFW
ufw disable
systemctl disable ufw
systemctl stop ufw
apt-get -y remove ufw
# Ensure rules are saved and restored on restart:
DEBIAN_FRONTEND=noninteractive
apt-get -y install iptables-persistent netfilter-persistent
systemctl enable netfilter-persistent
# Ensure we don't get dropped while loading new rules:
iptables -P INPUT ACCEPT
iptables -F INPUT
# Always allow VPN or loopback, and keep state:
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
### allow local communication with docker nets:
iptables -A INPUT -s 172.16.0.0/12 -j ACCEPT # very important!
# anything running directly on the host is added here:
# Wireguard
iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT
# SSH, highly recommend adding '-s x.x.x.x/x' with trusted IP addresses, and/or installing fail2ban
iptables -A INPUT -p udp -m udp --dport 22 -j ACCEPT
### To filter docker ports we have to use the DOCKER-USER chain, RETURN allows the connection.
### we use the contrack modules original destination port attribute instead of tcp/udp module's dport
iptables -F DOCKER-USER
iptables -A DOCKER-USER -i ${EXT_IF} -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
### Public docker services, default is to DROP:
# Need to use the conntrack original destination port:
iptables -A DOCKER-USER -i ${EXT_IF} -p tcp -m tcp -m conntrack --ctorigdstport 26656 -j RETURN
# Block the rest
iptables -A DOCKER-USER -i ${EXT_IF} -j DROP # Drop all from external interface
iptables -A DOCKER-USER -j RETURN # allow all other interfaces
iptables -P INPUT DROP
# clean up any old ufw tables:
iptables-save| grep -v ufw |iptables-restore
### now ipv6 - docker containers accessed via ipv6 don't use the USER chain so INPUT chain handles it fine, not sure why :/
ip6tables -P INPUT ACCEPT
ip6tables -F INPUT
ip6tables -A INPUT -i lo -j ACCEPT
# ipv6 needs ICMP or it will not work.
ip6tables -A INPUT -p icmpv6 -j ACCEPT
ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# public services
ip6tables -A INPUT -p tcp --dport 26656 -j ACCEPT
ip6tables -A INPUT -p udp --dport 51820 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
ip6tables -P INPUT DROP
ip6tables-save| grep -v ufw |ip6tables-restore
# save
netfilter-persistent save
@martin-mueller-cemas
Copy link

Thanks for the example! There seems to be a small error in line 48 for allowing SSH, because it uses TCP (instead of UDP) - maybe this is a copy & paste error from the line above ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment