Adds platform/lab-router/ — a narrow service that runs on the Lab Host, authenticates the Gateway via mTLS, and resolves an opaque routing token to a local container purely from Docker label state (no Postgres access, no callback to the Provisioner). Replaces the Gateway's env.internal_ip assumption (only valid when Gateway and Lab share a Docker daemon) with a data-driven direct/router decision per lab_host (lab_hosts.routing_mode/router_endpoint, migration 004), resolved in one place (platform/gateway/src/lab-endpoint.js's labTarget()) that every proxy/fetch call site in server.js now goes through. Extends the existing Docker TLS CA (infra/scripts/docker-tls/generate-docker-tls.sh) with a second mTLS leaf pair (router-server/gateway-client) for this new data-plane connection, deployed by the docker-tls Ansible role into a separate directory from the control-plane pair. Firewall (rn-lab-host-01.rules) replaces the old speculative wide port-range allowance with one narrow rule: platform_ip -> the Router's port only. dev-local stays routing_mode: direct — zero behavior change for local dev, confirmed by the full existing regression suite passing unmodified.
64 lines
3.8 KiB
Plaintext
64 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
|
|
|
|
# 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
|