#!/bin/bash # edge-firewall.sh — restrict the edge ports to Cloudflare's ranges only. # Mode file ./firewall.mode: "CF" (default, CF ranges only) or "OPEN" (anyone — # grey mode: visitors connect directly, nginx rejects unknown SNI). # Idempotent; safe to re-run (rebuilds the DOCKER-USER rules from scratch). set -euo pipefail cd "$(dirname "$0")" MODE="CF" [[ -f firewall.mode ]] && MODE=$(cat firewall.mode) CF4_URL="https://www.cloudflare.com/ips-v4" CF6_URL="https://www.cloudflare.com/ips-v6" # Reset our rules, keep anything else in DOCKER-USER. iptables -F DOCKER-USER ip6tables -F DOCKER-USER 2>/dev/null || true # DOCKER-USER sees forwarded traffic in BOTH directions. Exempt everything # originating from a docker bridge (container egress) BEFORE any port rules — # otherwise the CF-range catch-all below also drops containers' outbound 443 # (Matrix/R2/platform calls). Inbound filtering by source is unaffected. for br in $(ls /sys/class/net/ | grep -E '^(br-|docker0)'); do iptables -I DOCKER-USER 1 -i "$br" -j RETURN done # The ipfs3 ingress relay arrives over tailscale0 and terminates on the edge # nginx; exempt it from the CF-ranges-only filter (trusted tailnet source). iptables -C DOCKER-USER -i tailscale0 -j RETURN 2>/dev/null || \ iptables -I DOCKER-USER 1 -i tailscale0 -j RETURN # Transit hop for occ-files (CT 320): CF-bound traffic from it rides this CT so # the exit-node failover below covers oCIS blob writes too. Forward + NAT only; # occ-files adds the CF-range routes toward this CT's LAN IP. if [ "$(hostname -I 2>/dev/null | awk '{print $1}')" = "192.168.13.221" ]; then iptables -C FORWARD -s 192.168.13.210 -d 192.168.13.0/24 -j ACCEPT 2>/dev/null || \ iptables -I FORWARD 1 -s 192.168.13.210 -d 192.168.13.0/24 -j ACCEPT iptables -C FORWARD -s 192.168.13.210 -j ACCEPT 2>/dev/null || \ iptables -I FORWARD 1 -s 192.168.13.210 -j ACCEPT iptables -t nat -C POSTROUTING -s 192.168.13.210 -d 192.168.13.0/24 -j RETURN 2>/dev/null || \ iptables -t nat -A POSTROUTING -s 192.168.13.210 -d 192.168.13.0/24 -j RETURN iptables -t nat -C POSTROUTING -s 192.168.13.210 -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -s 192.168.13.210 -j MASQUERADE fi if [[ $MODE == OPEN ]]; then echo "edge firewall: OPEN (any source allowed on 80/443; nginx SNI gate applies)" exit 0 fi mapfile -t CF4 < <(curl -s -m 15 "$CF4_URL" | grep -v '^$') mapfile -t CF6 < <(curl -s -m 15 "$CF6_URL" | grep -v '^$') ((${#CF4[@]} > 5)) || { echo "cf ips-v4 fetch failed" >&2; exit 1; } for net in "${CF4[@]}"; do iptables -A DOCKER-USER -p tcp -m multiport --dports 80,443 -s "$net" -j RETURN done iptables -A DOCKER-USER -p tcp -m multiport --dports 80,443 -j DROP if ((${#CF6[@]} > 0)); then for net in "${CF6[@]}"; do ip6tables -A DOCKER-USER -p tcp -m multiport --dports 80,443 -s "$net" -j RETURN 2>/dev/null || true done ip6tables -A DOCKER-USER -p tcp -m multiport --dports 80,443 -j DROP 2>/dev/null || true fi echo "edge firewall: ${#CF4[@]} v4 + ${#CF6[@]} v6 CF ranges allowed on 80/443"