Files
rootnode-academy/infra/firewall/rn-lab-host-01.rules
T
ced4568 d2280d0f0d feat(security): production Compose topology, firewall rulesets, Docker mTLS
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).
2026-08-18 17:13:09 -05:00

63 lines
3.8 KiB
Plaintext

# 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