Skip to content

Commit bb47c3e

Browse files
committed
fix(ufw): scope DOCKER-USER rules to Caddy container IP
Unscoped --dport 443 DROP rules matched any packet with dst port 443, including outbound HTTPS from containers. App's tldextract init hung fetching publicsuffix.org → unhealthy → spoo.me unreachable. Rules now match -d 172.30.0.20 (Caddy IP, pinned in compose) so only inbound-to-Caddy traffic is filtered. Container outbound is unaffected.
1 parent 62055cc commit bb47c3e

1 file changed

Lines changed: 23 additions & 11 deletions

File tree

infrastructure/ufw-cloudflare.sh

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,45 +68,57 @@ done <<< "$v6"
6868

6969
# ── Step 5: mirror the allowlist into DOCKER-USER ──────────────────────
7070
# Docker bypasses UFW filter; DOCKER-USER is the documented hook.
71-
# Marker-delimited block in after.rules so re-runs replace cleanly.
71+
# Rules are scoped to dst = Caddy container IPs so they only affect
72+
# inbound traffic, not outbound HTTPS from any container (which would
73+
# otherwise also match a naked `--dport 443 DROP`).
74+
# Caddy container IPs are pinned in docker-compose.prod.yml.
75+
76+
CADDY_IPV4=172.30.0.20
77+
CADDY_IPV6="" # spoonet is v4-only today; left empty so v6 block no-ops
7278

7379
BEGIN_MARKER='# BEGIN cf-docker-user (managed by ufw-cloudflare.sh)'
7480
END_MARKER='# END cf-docker-user'
7581

7682
write_docker_user_block() {
7783
local rules_file="$1"
7884
local ip_list="$2"
85+
local dst_ip="$3" # Caddy container IP for this family; "" skips
7986

8087
if grep -qF "$BEGIN_MARKER" "$rules_file"; then
8188
echo "[ufw-cloudflare] removing prior managed block from $rules_file"
8289
sed -i "/^$BEGIN_MARKER\$/,/^$END_MARKER\$/d" "$rules_file"
8390
fi
8491

92+
# Skip the file if no container IP for this family.
93+
if [[ -z "$dst_ip" ]]; then
94+
echo "[ufw-cloudflare] no container IP for $rules_file family; skipping"
95+
return
96+
fi
97+
8598
echo "[ufw-cloudflare] writing managed block to $rules_file"
8699
{
87100
echo ""
88101
echo "$BEGIN_MARKER"
89102
echo "*filter"
90-
# `:CHAIN - [0:0]` flushes existing rules so re-runs replace.
91103
echo ":DOCKER-USER - [0:0]"
92104
while IFS= read -r ip; do
93105
[[ -z "$ip" ]] && continue
94-
echo "-A DOCKER-USER -p tcp -s $ip --dport 443 -j RETURN"
95-
echo "-A DOCKER-USER -p udp -s $ip --dport 443 -j RETURN"
96-
echo "-A DOCKER-USER -p tcp -s $ip --dport 80 -j RETURN"
106+
echo "-A DOCKER-USER -p tcp -s $ip -d $dst_ip --dport 443 -j RETURN"
107+
echo "-A DOCKER-USER -p udp -s $ip -d $dst_ip --dport 443 -j RETURN"
108+
echo "-A DOCKER-USER -p tcp -s $ip -d $dst_ip --dport 80 -j RETURN"
97109
done <<< "$ip_list"
98-
echo "-A DOCKER-USER -p tcp --dport 443 -j DROP"
99-
echo "-A DOCKER-USER -p udp --dport 443 -j DROP"
100-
echo "-A DOCKER-USER -p tcp --dport 80 -j DROP"
101-
# Default Docker fall-through for traffic not on those ports.
110+
echo "-A DOCKER-USER -p tcp -d $dst_ip --dport 443 -j DROP"
111+
echo "-A DOCKER-USER -p udp -d $dst_ip --dport 443 -j DROP"
112+
echo "-A DOCKER-USER -p tcp -d $dst_ip --dport 80 -j DROP"
113+
# Default Docker fall-through.
102114
echo "-A DOCKER-USER -j RETURN"
103115
echo "COMMIT"
104116
echo "$END_MARKER"
105117
} >> "$rules_file"
106118
}
107119

108-
write_docker_user_block /etc/ufw/after.rules "$v4"
109-
write_docker_user_block /etc/ufw/after6.rules "$v6"
120+
write_docker_user_block /etc/ufw/after.rules "$v4" "$CADDY_IPV4"
121+
write_docker_user_block /etc/ufw/after6.rules "$v6" "$CADDY_IPV6"
110122

111123
# ── Step 6: enable + reload ────────────────────────────────────────────
112124
echo "[ufw-cloudflare] enabling UFW…"

0 commit comments

Comments
 (0)