Named-bridge-network production Compose (not host networking, unlike dev) for rn-platform-01; egress-proxy + agents for rn-lab-host-01. Caddy reverse proxy, single public origin, Grafana restricted to management_cidr. iptables-restore firewall rulesets for all three hosts (Proxmox host, rn-platform-01, rn-lab-host-01) codifying the Platform/Lab-Host boundary: no student-to-student traffic, no direct Proxmox exposure, no direct Postgres/Provisioner exposure from the Lab Host. Mutual TLS for the Provisioner<->Lab Host Docker connection: certificate generation/verification scripts, dockerd TLS config, firewall-restricted port 2376. Application support already existed since Phase 1; this is purely the operational half. Validated: docker compose config, caddy validate, firewall structural lint, and a real openssl s_server/s_client mTLS handshake proving the cert chain and mutual-auth rejection (legitimate client verified, rogue client correctly rejected with verify code 19).
59 lines
3.4 KiB
Plaintext
59 lines
3.4 KiB
Plaintext
# 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
|