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).
63 lines
3.8 KiB
Plaintext
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
|