# iptables-restore ruleset for rn-platform-01 (Platform VM). Deployed by # infra/ansible/roles/firewall/. Variables ({{ }}) are Ansible Jinja2, rendered before this # file reaches the host — see infra/ansible/group_vars/production.yml. # # Policy summary (full rationale: docs/PRODUCTION_NETWORK_SECURITY.md §6): # - only the reverse proxy's 443/80 are reachable from the internet # - SSH only from the management CIDR (your home/office/VPN, never 0.0.0.0/0) # - Docker TLS (2376) is a rn-lab-host-01 concern, not this host's — nothing to open here # - Postgres, Provisioner's internal API, and the Platform API/Gateway's raw ports are never # exposed on this VM's external interface at all — the reverse proxy is the only public # listener; everything else binds to the docker-compose internal network only, so there is # no host-level rule needed to "hide" them, only to make sure nothing ever publishes them # by accident (this ruleset's default-deny INPUT catches that class of mistake too) # - rn-lab-host-01 may reach exactly one port here: Loki (3100), for its own log shipper — # the Provisioner otherwise calls OUT to rn-lab-host-01, never the other way around — and # may NEVER reach Postgres or the Provisioner's internal API *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Loopback and established/related — standard baseline -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -A INPUT -m conntrack --ctstate INVALID -j DROP # ICMP — keep basic reachability diagnostics working -A INPUT -p icmp --icmp-type echo-request -m conntrack --ctstate NEW -j ACCEPT # SSH — management CIDR only, never open to the internet -A INPUT -p tcp --dport 22 -s {{ management_cidr }} -m conntrack --ctstate NEW -j ACCEPT # Public HTTPS/HTTP — the reverse proxy (Caddy), the only public listener on this host -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT # Loki log ingestion — rn-lab-host-01's promtail only (that host's own log shipper, pushing its # container/journald logs here for centralized viewing in Grafana). Same "one specific source # IP, one specific port" pattern as Docker TLS on the other host. -A INPUT -p tcp --dport 3100 -s {{ lab_host_ip }} -m conntrack --ctstate NEW -j ACCEPT # Explicit deny + log for anything from rn-lab-host-01 hitting Postgres or the Provisioner's # internal port — defense in depth on top of "these ports were never published," and the one # rule in this file that most directly encodes the Lab->Platform boundary the whole platform # depends on. Logged (not silently dropped) so an attempted breach attempt is visible in # infra/monitoring/ alerts, not just absorbed by the default DROP. -A INPUT -s {{ lab_host_ip }} -p tcp --dport 5432 -j LOG --log-prefix "ROOTNODE-DENY-LABHOST-PG: " --log-level 4 -A INPUT -s {{ lab_host_ip }} -p tcp --dport 5432 -j DROP -A INPUT -s {{ lab_host_ip }} -p tcp --dport 4100 -j LOG --log-prefix "ROOTNODE-DENY-LABHOST-PROVISIONER: " --log-level 4 -A INPUT -s {{ lab_host_ip }} -p tcp --dport 4100 -j DROP # Default: everything else already falls to the chain policy (DROP), nothing further needed — # but an explicit final logged catch-all makes "what got denied and why" visible in logs rather # than silent. -A INPUT -j LOG --log-prefix "ROOTNODE-DENY-PLATFORM-DEFAULT: " --log-level 4 COMMIT