public-port allowlist must live in the repo and be audited externally #157
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Anchor
Sibling to the 2026-05-26 postmortem (filed alongside). Public SSH was reachable from the internet via router DMZ for an unknown stretch of time. Brute-force traffic from
60.163.139.198had been hammering sshd for at least hours, possibly days, before we noticed. Closing the hole was a one-toggle fix at the router. The bigger problem is that nothing in the repo or in any document said which public ports should be open, so a regression (DMZ flip, new router, UPnP punch, port-triggering rule) could re-expose the host without anyone noticing for a long time.What happened (short version)
60.163.139.198at one-attempt-per-~8-seconds for hours. No fail2ban, no ufw, just sshd handling it. No successful logins (key-only auth) but the noise displaced legitimate journal lines.80/tcpand443/tcpto 192.168.0.194, turned DMZ off, verified public HTTPS still works.Why the postmortem rates this high
Public exposure is the highest-blast-radius regression class on the homelab. A misconfigured port forward outranks every other operational fault by orders of magnitude. Bridge churn kills SSH for 5 minutes; a publicly reachable kube-apiserver could be a takeover. The closest thing we have to a defense is the memory of which ports are supposed to be open, and that memory currently lives only in this chat transcript.
Proposed allowlist (canonical)
These are the only ports that should be reachable from the public internet, forwarded to 192.168.0.194 (kai-server):
80/tcp- caddy HTTP, ACME challenges + redirect to HTTPS443/tcp- caddy HTTPS, serves all*.coilysiren.mevia reverse proxy3000/udp- Eco game traffic3001/tcp- Eco web UI3002/tcp- Eco game protocol3003/?- Eco (Kai mentioned 3000-3003 range; protocol unverified, document when confirmed)34197/udp- factorio default portExplicitly not allowlisted:
22/tcp- SSH is tailnet-only viakai@kai-serverover tailscale6443/tcp- kube-apiserver is tailnet-only via kubeconfig pinned tokai-server:644353096/tcp- tailscale PeerAPI, never public41641/udp- tailscale itself, NATs through cleanly with no port-forward neededWhere the canonical list should live
Two layers, both load-bearing:
coily exec audit-public-ports) plus a github actions or forgejo actions workflow that runs it from a cloud runner so the source IP is off-LAN. Output goes to vault inbox or Slack/Discord/wherever Kai actually reads.Regression prevention checklist
When ANY of these happen, re-audit:
/coilysiren/home/public-ipin SSM should track this)Audit = walk Virtual Servers, walk UPnP service list, walk Port Triggering, walk DMZ, walk DDNS, compare to allowlist.
What I didn't audit tonight
Each one is a potential bypass of the Virtual Servers allowlist.
Out of scope
How to apply
docs/public-ports.mdwith the allowlist above and a regression-prevention checklist.