# iptables-restore ruleset for rn-lab-host-01 (Lab Host VM). Deployed by # infra/ansible/roles/firewall/. See docs/PRODUCTION_NETWORK_SECURITY.md §6 for full rationale. # # This is the security-critical host — it runs privileged, disposable, student-controlled # containers. Its own INPUT/FORWARD policy is only half the story: the Provisioner's dynamic # per-Lab DOCKER-USER chains (platform/provisioner/src/egress.js, unchanged since Phase 1) # already enforce egress-proxy-only outbound for every student subnet and implicitly deny # Lab-to-Lab and Lab-to-anything-not-allowlisted traffic. This file is the BASE policy under # that dynamic layer — it must never be more permissive than what the dynamic chains assume, # and it's what protects the host itself (SSH, Docker TLS) rather than inter-container traffic. # # Policy summary: # - Docker TLS (2376) reachable ONLY from rn-platform-01 — this is the entire point of mTLS # plus a firewall restriction, defense in depth (a leaked client cert alone still isn't # enough without also being on this IP) # - SSH only from the management CIDR # - no port on this host is ever reachable from a student Lab's own Docker subnet — Labs only # ever reach the egress proxy and DNS resolver (already enforced by the Provisioner's # per-Lab chains); this file adds an explicit belt-and-suspenders DROP for the Lab subnet # range as a whole, so even a Lab whose per-environment chain failed to install can't reach # the host's own services # - nothing on this host is ever reachable from the public internet at all *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -A INPUT -m conntrack --ctstate INVALID -j DROP -A INPUT -p icmp --icmp-type echo-request -m conntrack --ctstate NEW -j ACCEPT -A INPUT -p tcp --dport 22 -s {{ management_cidr }} -m conntrack --ctstate NEW -j ACCEPT # Docker TLS — rn-platform-01 (the Provisioner) only. This is the ONLY control-plane flow # allowed into this host at all — see the "Platform Provisioner -> Lab Host Docker TLS # endpoint: ALLOW" example in the Phase 4 spec, encoded literally here. -A INPUT -p tcp --dport 2376 -s {{ platform_ip }} -m conntrack --ctstate NEW -j ACCEPT # Gateway -> per-Lab published port range, rn-platform-01 only. REQUIRED so the Gateway can # reach each student's Lab UI/terminal over the network once Labs run on a genuinely separate # host — see docs/PRODUCTION_NETWORK_SECURITY.md §"Gateway-to-Lab cross-VM reachability" for # why this exists and the small Provisioner/Gateway code change it depends on (NOT YET BUILT — # this firewall rule is the network-policy half; the port-publishing half is an explicit, # reviewed application-code follow-up, tracked there rather than silently assumed here). -A INPUT -p tcp --dport {{ lab_gateway_port_range | default('30000:32767') }} -s {{ platform_ip }} -m conntrack --ctstate NEW -j ACCEPT # Prometheus (rn-platform-01) scraping this host's node-exporter/cadvisor — metrics only, # read-only exposition endpoints, still restricted to the one legitimate scraper. -A INPUT -p tcp -m multiport --dports 9100,8080 -s {{ platform_ip }} -m conntrack --ctstate NEW -j ACCEPT # Belt-and-suspenders: explicit deny + log for any student Lab subnet reaching THIS HOST # (not another container — the host's own listening ports: SSH, Docker TLS, anything else). # The Provisioner's per-Lab DOCKER-USER chains already stop this at the container-egress layer; # this is what still protects the host if that layer is ever misconfigured or mid-install. -A INPUT -s {{ lab_subnet_range }} -j LOG --log-prefix "ROOTNODE-DENY-LAB-TO-HOST: " --log-level 4 -A INPUT -s {{ lab_subnet_range }} -j DROP -A INPUT -j LOG --log-prefix "ROOTNODE-DENY-LABHOST-DEFAULT: " --log-level 4 COMMIT