# 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 # Lab Host Router (8443) — rn-platform-01 (the Gateway) only. This is the DATA-plane # equivalent of the Docker TLS rule above: the Gateway never reaches a Lab's Docker-internal # address directly (no per-Lab port publishing, no wide port range needed) — it reaches this # one mTLS-authenticated Router instead, which resolves the request to a local container on # ITS OWN host. See docs/PHASE4_1_PREPRODUCTION_HARDENING.md §1-3 and # docs/PRODUCTION_NETWORK_SECURITY.md — this closes the "Gateway-to-Lab cross-VM reachability" # gap Phase 4 flagged as a required follow-up; it is no longer open. -A INPUT -p tcp --dport {{ lab_router_port | default('8443') }} -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