feat(backend): slice 13d — cut docker launch over to the consolidated gateway (e2e green)
Rewire DockerBottleBackend.launch to the consolidated model, replacing the
per-bottle sidecar bundle. VALIDATED END-TO-END on real docker:
test_sandbox_escape passes all 5 attacks (egress DLP + git-gate gitleaks)
through the shared gateway.
launch now:
- mints git-gate dynamic keys (if any), then launch_consolidated() to
register the bottle + provision its git-gate state into the gateway and
get the agent's attach context (pinned source IP, gateway address);
- installs the SHARED gateway CA (gateway.ca_cert_pem) into the agent;
- renders the agent-only consolidated compose on the gateway network at the
pinned IP, proxied through the gateway;
- points the agent's git-gate insteadOf (http://<gw>:9420) and supervise
MCP (http://<gw>:9100) at the gateway (DockerBottlePlan.agent_git_gate_url
/ agent_supervise_url);
- teardown = compose down + teardown_consolidated (dereg + deprovision).
Dropped the per-bottle networks, per-bottle egress CA, and bundle service.
Fixes surfaced by the real e2e (couldn't be caught by unit mocks):
- provision_git_gate installs the bottle-agnostic pre-receive/access hooks
into the gateway (were cp'd per-bundle before);
- source-IP allocation reads the *actual* container IPs on the network
(gateway + orchestrator + agents), not just gateway + registry — the
orchestrator container sits on the network and was colliding.
Unit tests for the old bundle launch rewritten against the new collaborators.
NOTE for reviewers: the gateway/bundle image (bot-bottle-sidecars) must be
rebuilt when its flat sources change — `ensure_built` only builds-if-missing,
so a stale image silently runs the OLD single-tenant daemons. A content-hash
/ --rebuild path is a follow-up.
pyright 0 errors; pylint 9.83/10; unit suite green (1760 tests; the 13
test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise);
test_sandbox_escape green on real docker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -76,15 +76,21 @@ def _container_ip(name: str, network: str) -> str:
|
||||
return ip
|
||||
|
||||
|
||||
def _taken_ips(client: OrchestratorClient, gateway_ip: str) -> list[str]:
|
||||
"""Every address already in use on the gateway network: the gateway
|
||||
container plus every live bottle the registry knows."""
|
||||
taken = [gateway_ip]
|
||||
for rec in client.list_bottles():
|
||||
src = rec.get("source_ip")
|
||||
if isinstance(src, str) and src:
|
||||
taken.append(src)
|
||||
return taken
|
||||
def _network_container_ips(network: str) -> list[str]:
|
||||
"""Every address currently assigned on the gateway network — the ground
|
||||
truth for "in use": the gateway + orchestrator infrastructure containers
|
||||
and every live agent. Read from the network so a new bottle can't collide
|
||||
with anything actually attached (a registry-only view would miss the
|
||||
orchestrator/gateway containers)."""
|
||||
proc = run_docker([
|
||||
"docker", "network", "inspect", "--format",
|
||||
"{{range .Containers}}{{.IPv4Address}} {{end}}", network,
|
||||
])
|
||||
ips: list[str] = []
|
||||
for entry in proc.stdout.split():
|
||||
# entries look like "172.20.0.2/16" — keep the address.
|
||||
ips.append(entry.split("/", 1)[0])
|
||||
return ips
|
||||
|
||||
|
||||
def launch_consolidated(
|
||||
@@ -106,7 +112,7 @@ def launch_consolidated(
|
||||
|
||||
cidr = _network_cidr(network)
|
||||
gateway_ip = _container_ip(gateway_name, network)
|
||||
source_ip = next_free_ip(cidr, _taken_ips(client, gateway_ip))
|
||||
source_ip = next_free_ip(cidr, _network_container_ips(network))
|
||||
|
||||
inputs = registration_inputs(egress_plan)
|
||||
reg = client.register_bottle(
|
||||
|
||||
Reference in New Issue
Block a user