Docker's initial IPv6 support is based on allocating public IPv6 to containers. This approach has many issues: * The server may not get a block of IPv6 assigned to it * It's complicated to allocate a block of IPv6 to cloudron server on home setups * It's unclear how dynamic IPv6 is. If it's dynamic, then should containers be recreated? * DNS setup is complicated * Not a issue for Cloudron itself, but with -P, it just exposed the full container into the world Given these issues, IPv6 NAT is being considered. Even though NAT is not a security mechanism as such, it does offer benefits that we care about: * We can allocate some private IPv6 to containers * Have docker NAT66 the exposed ports * Works similar to IPv4 Currently, the IPv6 ports are always mapped and exposed. The "Enable IPv6" config option is only whether to automate AAAA records or not. This way, user can enable it and 'sync' dns and we don't need to re-create containers etc. There is no inherent benefit is not exposing IPv6 at all everywhere unless we find it unstable. Fixes #264
148 lines
6.5 KiB
Bash
Executable File
148 lines
6.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -eu -o pipefail
|
|
|
|
echo "==> Setting up firewall"
|
|
|
|
####### IPv4
|
|
|
|
iptables -t filter -N CLOUDRON || true
|
|
iptables -t filter -F CLOUDRON # empty any existing rules
|
|
|
|
# first setup any user IP block lists
|
|
ipset create cloudron_blocklist hash:net || true
|
|
/home/yellowtent/box/src/scripts/setblocklist.sh
|
|
|
|
iptables -t filter -A CLOUDRON -m set --match-set cloudron_blocklist src -j DROP
|
|
# the DOCKER-USER chain is not cleared on docker restart
|
|
if ! iptables -t filter -C DOCKER-USER -m set --match-set cloudron_blocklist src -j DROP; then
|
|
iptables -t filter -I DOCKER-USER 1 -m set --match-set cloudron_blocklist src -j DROP
|
|
fi
|
|
|
|
# allow related and establisted connections
|
|
iptables -t filter -A CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -p tcp -m tcp -m multiport --dports 22,80,202,443 -j ACCEPT # 202 is the alternate ssh port
|
|
|
|
# whitelist any user ports. we used to use --dports but it has a 15 port limit (XT_MULTI_PORTS)
|
|
ports_json="/home/yellowtent/platformdata/firewall/ports.json"
|
|
if allowed_tcp_ports=$(node -e "console.log(JSON.parse(fs.readFileSync('${ports_json}', 'utf8')).allowed_tcp_ports.join(','))" 2>/dev/null); then
|
|
IFS=',' arr=(${allowed_tcp_ports})
|
|
for p in "${arr[@]}"; do
|
|
iptables -A CLOUDRON -p tcp -m tcp --dport "${p}" -j ACCEPT
|
|
done
|
|
fi
|
|
|
|
if allowed_udp_ports=$(node -e "console.log(JSON.parse(fs.readFileSync('${ports_json}', 'utf8')).allowed_udp_ports.join(','))" 2>/dev/null); then
|
|
IFS=',' arr=(${allowed_udp_ports})
|
|
for p in "${arr[@]}"; do
|
|
iptables -A CLOUDRON -p udp -m udp --dport "${p}" -j ACCEPT
|
|
done
|
|
fi
|
|
|
|
ipset create cloudron_ldap_allowlist hash:net || true
|
|
ipset flush cloudron_ldap_allowlist
|
|
|
|
ldap_allowlist_json="/home/yellowtent/platformdata/firewall/ldap_allowlist.txt"
|
|
if [[ -f "${ldap_allowlist_json}" ]]; then
|
|
|
|
# without the -n block, any last line without a new line won't be read it!
|
|
while read -r line || [[ -n "$line" ]]; do
|
|
[[ -z "${line}" ]] && continue # ignore empty lines
|
|
[[ "$line" =~ ^#.*$ ]] && continue # ignore lines starting with #
|
|
ipset add -! cloudron_ldap_allowlist "${line}" # the -! ignore duplicates
|
|
done < "${ldap_allowlist_json}"
|
|
|
|
# ldap server we expose 3004 and also redirect from standard ldaps port 636
|
|
iptables -t nat -I PREROUTING -p tcp --dport 636 -j REDIRECT --to-ports 3004
|
|
iptables -t filter -A CLOUDRON -m set --match-set cloudron_ldap_allowlist src -p tcp --dport 3004 -j ACCEPT
|
|
fi
|
|
|
|
# turn and stun service
|
|
iptables -t filter -A CLOUDRON -p tcp -m multiport --dports 3478,5349 -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -p udp -m multiport --dports 3478,5349 -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -p udp -m multiport --dports 50000:51000 -j ACCEPT
|
|
|
|
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-request -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-reply -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -p udp --sport 53 -j ACCEPT
|
|
iptables -t filter -A CLOUDRON -s 172.18.0.0/16 -j ACCEPT # required to accept any connections from apps to our IP:<public port>
|
|
iptables -t filter -A CLOUDRON -i lo -j ACCEPT # required for localhost connections (mysql)
|
|
|
|
# log dropped incoming. keep this at the end of all the rules
|
|
iptables -t filter -A CLOUDRON -m limit --limit 2/min -j LOG --log-prefix "IPTables Packet Dropped: " --log-level 7
|
|
iptables -t filter -A CLOUDRON -j DROP
|
|
|
|
if ! iptables -t filter -C INPUT -j CLOUDRON 2>/dev/null; then
|
|
iptables -t filter -I INPUT -j CLOUDRON
|
|
fi
|
|
|
|
# Setup rate limit chain (the recent info is at /proc/net/xt_recent)
|
|
iptables -t filter -N CLOUDRON_RATELIMIT || true
|
|
iptables -t filter -F CLOUDRON_RATELIMIT # empty any existing rules
|
|
|
|
# log dropped incoming. keep this at the end of all the rules
|
|
iptables -t filter -N CLOUDRON_RATELIMIT_LOG || true
|
|
iptables -t filter -F CLOUDRON_RATELIMIT_LOG # empty any existing rules
|
|
iptables -t filter -A CLOUDRON_RATELIMIT_LOG -m limit --limit 2/min -j LOG --log-prefix "IPTables RateLimit: " --log-level 7
|
|
iptables -t filter -A CLOUDRON_RATELIMIT_LOG -j DROP
|
|
|
|
# http https
|
|
for port in 80 443; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --syn --dport ${port} -m connlimit --connlimit-above 5000 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
# ssh
|
|
for port in 22 202; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --dport ${port} -m state --state NEW -m recent --set --name "public-${port}"
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --dport ${port} -m state --state NEW -m recent --update --name "public-${port}" --seconds 10 --hitcount 5 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
# ldaps
|
|
for port in 636 3004; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --syn --dport ${port} -m connlimit --connlimit-above 5000 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
# docker translates (dnat) 25, 587, 993, 4190 in the PREROUTING step
|
|
for port in 2525 4190 9993; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --syn ! -s 172.18.0.0/16 -d 172.18.0.0/16 --dport ${port} -m connlimit --connlimit-above 50 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
# msa, ldap, imap, sieve, pop3
|
|
for port in 2525 3002 4190 9993 9995; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --syn -s 172.18.0.0/16 -d 172.18.0.0/16 --dport ${port} -m connlimit --connlimit-above 500 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
# cloudron docker network: mysql postgresql redis mongodb
|
|
for port in 3306 5432 6379 27017; do
|
|
iptables -A CLOUDRON_RATELIMIT -p tcp --syn -s 172.18.0.0/16 -d 172.18.0.0/16 --dport ${port} -m connlimit --connlimit-above 5000 -j CLOUDRON_RATELIMIT_LOG
|
|
done
|
|
|
|
if ! iptables -t filter -C INPUT -j CLOUDRON_RATELIMIT 2>/dev/null; then
|
|
iptables -t filter -I INPUT 1 -j CLOUDRON_RATELIMIT
|
|
fi
|
|
|
|
# Workaround issue where Docker insists on adding itself first in FORWARD table
|
|
iptables -D FORWARD -j CLOUDRON_RATELIMIT || true
|
|
iptables -I FORWARD 1 -j CLOUDRON_RATELIMIT
|
|
|
|
####### IPv6
|
|
|
|
# if file exists and has some content (despite being 0 size)
|
|
if cat /proc/net/if_inet6 >/dev/null 2>&1; then
|
|
echo "==> Setting up IPv6 firewall"
|
|
ip6tables -t filter -N CLOUDRON || true
|
|
ip6tables -t filter -F CLOUDRON # empty any existing rules
|
|
|
|
ip6tables -t filter -A CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT
|
|
ip6tables -t filter -A CLOUDRON -p tcp -m tcp -m multiport --dports 22,80,202,443 -j ACCEPT # 202 is the alternate ssh port
|
|
|
|
ip6tables -t filter -A CLOUDRON -m limit --limit 2/min -j LOG --log-prefix "IP6Tables Packet Dropped: " --log-level 7
|
|
# ip6tables -t filter -A CLOUDRON -j DROP
|
|
|
|
if ! ip6tables -t filter -C INPUT -j CLOUDRON 2>/dev/null; then
|
|
ip6tables -t filter -I INPUT -j CLOUDRON
|
|
fi
|
|
fi
|
|
|
|
echo "==> Setting up firewall done"
|