From e36c6bb4318851f4be43b6ad9f44fbb2cd989059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 14 Aug 2026 09:51:22 +0000 Subject: [PATCH] allow port 22 through the docker-user allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the DOCKER-USER chain is the only thing gating docker-published ports from the internet — docker writes its own DNAT/FORWARD rules and bypasses ufw, so `ufw allow ` has no effect on a published container port. the allowlist permitted only 80 and 443, so a machine provisioned from this template dropped gitea ssh silently. the failure is hard to spot: the port looks open locally and docker ps shows it published, but external clients hang at TCP connect with no refusal. local tests pass because they arrive via lo and match the loopback RETURN before reaching the DROP. comments added so the next person recognises it faster. Co-Authored-By: Claude Opus 5 --- scripts/setup/machine-setup/ufw-docker-rules.conf | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/setup/machine-setup/ufw-docker-rules.conf b/scripts/setup/machine-setup/ufw-docker-rules.conf index a82ab97f..98f5fb44 100644 --- a/scripts/setup/machine-setup/ufw-docker-rules.conf +++ b/scripts/setup/machine-setup/ufw-docker-rules.conf @@ -2,8 +2,18 @@ # Append these to /etc/ufw/after.rules (after the existing COMMIT) # Blocks all external access to Docker-published ports except: # - Trusted IPs (add your own) -# - Explicitly allowed public ports (80, 443) +# - Explicitly allowed public ports (80, 443, 22) # - Docker internal and loopback traffic +# +# This chain is the ONLY thing gating Docker-published ports from the internet: +# Docker writes its own DNAT/FORWARD rules and bypasses ufw entirely, so a +# `ufw allow ` has no effect on a published container port. If a container +# port must be reachable publicly, it needs a RETURN line below. +# +# Failure mode to recognise: the port appears open locally and `docker ps` shows +# it published, but external clients hang at TCP connect with no refusal. Local +# tests pass because they arrive via `lo` and match the loopback RETURN above, +# never reaching the DROP. Test from another machine, not from the host itself. *filter :DOCKER-USER - [0:0] @@ -23,6 +33,9 @@ # Allow public ports -A DOCKER-USER -i eth0 -p tcp --dport 80 -j RETURN -A DOCKER-USER -i eth0 -p tcp --dport 443 -j RETURN +# gitea SSH (NPM stream -> gitea:22). Host sshd does not use :22 publicly on a +# box that serves gitea — it moves to :2022 plus the tailnet IP, see ssh.socket. +-A DOCKER-USER -i eth0 -p tcp --dport 22 -j RETURN # Drop everything else from external -A DOCKER-USER -i eth0 -j DROP