feat(firecracker): split orchestrator and gateway into separate VMs (PRD 0070)
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Successful in 19s
lint / lint (push) Successful in 53s
test / unit (pull_request) Failing after 1m46s
test / integration-firecracker (pull_request) Failing after 2m42s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped

Now that #469 got the DB off the data plane, the Firecracker infra runs as
two microVMs instead of one — mirroring the docker/macos plane split:

  * orchestrator VM (ORCH_IFACE) — control plane + buildah image builds; sole
    DB opener; host-seeded signing key. No gateway daemons.
  * gateway VM (new GW_IFACE) — egress / git-http / supervise data plane;
    mitmproxy CA + a host-minted `gateway` JWT (never the key). Reaches the
    orchestrator only over the one nft forward rule its link allows.

Both boot the SAME shared infra rootfs; a `bb_role=` kernel-cmdline arg
selects which plane a VM's PID-1 init starts, so there is still one published
artifact. The gateway learns the orchestrator's address via `bb_orch=` on the
cmdline (no IP baked into the artifact).

Isolation is nearly free: agents were already nft-dropped except the DNAT'd
gateway ports, so re-pointing that single DNAT rule at the gateway VM
(`dnat to gw_guest`) severs every agent's L3 route to the control plane. The
only added nft is the second infra link's mirror block (masquerade egress +
forward accept, which subsumes gateway->orchestrator) in the shared shell
script and the NixOS module.

netpool gains GW_IFACE + gw_slot() (the /31 above the orch link);
firecracker_vm.boot gains extra_boot_args for the role cmdline; infra_vm
ensure_running() boots + adopts the pair (orchestrator first, then the gateway
that resolves policy against it) and returns an InfraEndpoint mirroring the
docker/macos shape. Builds stay in the orchestrator (PRD 0070 v1); the gateway
is the slim unit.

Unit-tested (test_firecracker_infra_vm rewritten for two VMs; gw_slot helper
test added); the KVM boot / L3-isolation checks are validated on a Firecracker
host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 03:44:15 -04:00
parent a74894c6f6
commit 18d9b81add
11 changed files with 819 additions and 355 deletions
+16
View File
@@ -85,6 +85,22 @@ class TestNetpoolProbes(unittest.TestCase):
pool_guests = {sl.guest_ip for sl in netpool.all_slots()}
self.assertNotIn(s.guest_ip, pool_guests)
def test_gw_slot_is_second_link_above_orch(self):
# Dedicated gateway (data-plane) link: the /31 immediately above the
# orchestrator link. Must match the shell script's gw_host()/gw_guest()
# and be a distinct non-pool index that never collides with the
# orchestrator link or the agent pool.
with patch.dict("os.environ", {"BOT_BOTTLE_FC_IP_BASE": "10.243.0.0"}):
s = netpool.gw_slot()
orch = netpool.orch_slot()
pool_guests = {sl.guest_ip for sl in netpool.all_slots()}
self.assertEqual(netpool.GW_IFACE, s.iface)
self.assertEqual("10.243.255.2", s.host_ip)
self.assertEqual("10.243.255.3", s.guest_ip)
self.assertEqual(-2, s.index)
self.assertNotEqual(orch.guest_ip, s.guest_ip) # distinct from the orch link
self.assertNotIn(s.guest_ip, pool_guests) # distinct from the pool
class TestConsoleTail(unittest.TestCase):
def test_reads_tail(self):