Egress enforcement is a firewall-layer control, not an application-layer convention (correction B). HTTP_PROXY/HTTPS_PROXY configure containerd for a clean pull path; they enforce nothing on their own. - src/egress.js — per-environment dedicated iptables chain (ROOTNODE-ENV-<short-id>), built with -A (append, order-preserving) rather than the -I (insert-at-head) pattern an earlier design draft used, which would have installed rules in reverse order and let the default-deny shadow the proxy/DNS accepts. Required infrastructure (egress proxy, DNS) is explicitly permitted first, then RFC1918 ranges are dropped unconditionally, then a final default-deny catches the public internet. One jump rule per environment hooks its subnet into its chain from DOCKER-USER (the chain Docker guarantees it won't silently rewrite). Destroy is deterministic: unhook, flush, delete — no comment-grep matching. The ordering fix is empirically verified, not just reasoned about: the egress proxy's own address is itself an RFC1918 address (it's Platform-side infrastructure), and testing confirms it's reachable while the broader RFC1918 range is still blocked — exactly the scenario the original ordering bug would have broken. - platform/egress-proxy/ — shared Squid instance (one per deployment, not per-student), destination-allowlisted (dstdomain ACL against a plain-text allowlist file), starting set covers docker.io/ghcr.io/gcr.io/quay.io/k8s.io. Proxy performs domain allowlisting; the firewall chain above is what makes it unbypassable, not the other way around. The egress proxy's address is resolved per-student-network (each network's Docker gateway IP, since the proxy runs with host networking and is present on every bridge gateway) rather than a fixed hostname — a static address wouldn't be reachable from more than one isolated student network at a time. Found while testing with two simultaneous environments. Verified: student-to-student isolation (A cannot reach B's internal IP), RFC1918 blocking (A cannot reach a simulated trusted-infra address), and the proxy allowlist itself (CONNECT tunnel to an allowlisted host succeeds, non-allowlisted gets a clean 403 from Squid). Co-Authored-By: Claude Sonnet 5 <[email protected]>
23 lines
841 B
SquidConf
23 lines
841 B
SquidConf
# RootNode Academy shared egress proxy — one instance, not per-student.
|
|
# Destination allowlisting only; the firewall (egress.js) is the actual enforcement boundary
|
|
# that makes this proxy unbypassable, not the other way around
|
|
# (docs/PHASE1_PLATFORM_FOUNDATION.md §5/§6, correction B).
|
|
|
|
http_port 3128
|
|
|
|
acl allowed_dst dstdomain "/etc/squid/allowlist.txt"
|
|
acl SSL_ports port 443
|
|
acl Safe_ports port 80 443
|
|
acl CONNECT method CONNECT
|
|
|
|
http_access deny !Safe_ports
|
|
http_access deny CONNECT !SSL_ports
|
|
http_access allow allowed_dst
|
|
http_access deny all
|
|
|
|
# Audit/troubleshooting log — requirement #7. Squid's default access log format already
|
|
# includes timestamp, client IP, method, URL, and result code, which is what "why did this
|
|
# scenario's image pull fail" support needs.
|
|
access_log /var/log/squid/access.log squid
|
|
cache deny all
|