From 8bdf4c0a07a5f2252504d714ea740099e16fd070 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 01:51:04 +0000 Subject: [PATCH] =?UTF-8?q?Add=20explicit=20UFW=20rules=20for=20Caddy's=20?= =?UTF-8?q?80/443=20=E2=80=94=20Docker=20was=20silently=20bypassing=20UFW?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit services/caddy.sh never called ufw allow for any of its published ports. Confirmed live: ufw status showed no rule for 80 or 443 on a box with UFW active (default deny incoming), yet HTTPS sites were reachable fine — Docker manipulates iptables directly for published container ports (the ports: mapping in Caddy's own compose file), which bypasses UFW's filtering entirely regardless of what ufw status reports. This wasn't an actual exposure gap — 80/443 are supposed to be open to everyone, that's the point of a reverse proxy — but it means ufw status was actively misrepresenting this box's real firewall state on its two most externally-facing ports, which is exactly the kind of thing that looks like a problem (and did, when investigating an unrelated Let's-Encrypt failure) even though nothing was actually unprotected. Add explicit ufw allow rules for 80/tcp, 443/tcp, and 443/udp (HTTP/3) so ufw status reflects reality, matching every other service in this repo managing its own firewall rules instead of relying on undocumented Docker/iptables interaction. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn --- services/caddy.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/services/caddy.sh b/services/caddy.sh index 1037ba2..7ba0e83 100644 --- a/services/caddy.sh +++ b/services/caddy.sh @@ -232,6 +232,7 @@ install_caddy() { echo "[DRY-RUN] Would write $CADDY_DIR/docker-compose.yml (ports 80/443 + HTTP/3)" echo "[DRY-RUN] Would write a starter $CADDY_DIR/Caddyfile (if none exists)" echo "[DRY-RUN] Would write $CADDY_DIR/README.md" + echo "[DRY-RUN] Would open 80/tcp, 443/tcp, 443/udp in UFW (Docker's own iptables rules let this traffic through either way, but ufw status should actually reflect it)" echo "[DRY-RUN] Would optionally start Caddy (docker compose up -d)" return 0 fi @@ -384,6 +385,24 @@ CADDYFILE chown -R "$ACTUAL_USER:$ACTUAL_USER" "$CADDY_DIR" echo " ✓ Caddy configured at $CADDY_DIR" + # Every other service in this repo opens its own UFW rule; this file + # never did — Docker manipulates iptables directly for published + # container ports (the ports: mapping above), which bypasses UFW's own + # filtering entirely for 80/tcp, 443/tcp, and 443/udp regardless of + # what `ufw status` shows. Confirmed live: sites were reachable over + # HTTPS with zero matching UFW rule for either port. That's not a + # meaningful protection gap on its own — 80/443 are supposed to be open + # to everyone, that's the whole point of a reverse proxy — but it means + # `ufw status` actively misrepresents this box's real exposure on its + # two most externally-facing ports, which is confusing and worth fixing + # even though nothing was actually unprotected as a result. + if command -v ufw &>/dev/null; then + ufw allow 80/tcp comment "Caddy HTTP" >/dev/null 2>&1 + ufw allow 443/tcp comment "Caddy HTTPS" >/dev/null 2>&1 + ufw allow 443/udp comment "Caddy HTTP/3" >/dev/null 2>&1 + declare -F ensure_ufw_enabled >/dev/null 2>&1 && ensure_ufw_enabled + fi + write_readme "$CADDY_DIR" << 'CADDY_README' # Caddy — reverse proxy + automatic HTTPS