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:
@@ -31,9 +31,9 @@ def _recorder(calls: list[list[str]]):
|
||||
def _plan(*upstreams: GitGateUpstream) -> GitGatePlan:
|
||||
return GitGatePlan(
|
||||
slug="demo",
|
||||
entrypoint_script=Path(),
|
||||
hook_script=Path(),
|
||||
access_hook_script=Path(),
|
||||
entrypoint_script=Path("/stage/entrypoint.sh"),
|
||||
hook_script=Path("/stage/pre-receive"),
|
||||
access_hook_script=Path("/stage/access-hook"),
|
||||
upstreams=tuple(upstreams),
|
||||
)
|
||||
|
||||
@@ -61,6 +61,8 @@ class TestProvisionGitGate(unittest.TestCase):
|
||||
self.assertIn(
|
||||
["docker", "cp", "/host/kh", "gw:/git-gate/creds/bottle1/foo-known_hosts"], cps,
|
||||
)
|
||||
# The shared (bottle-agnostic) hooks are installed into the gateway.
|
||||
self.assertIn(["docker", "cp", "/stage/pre-receive", "gw:/etc/git-gate/pre-receive"], cps)
|
||||
# The init script runs in the gateway, namespaced under the bottle id.
|
||||
exec_scripts = [c for c in calls if c[:3] == ["docker", "exec", "gw"] and c[3] == "sh"]
|
||||
self.assertEqual(1, len(exec_scripts))
|
||||
@@ -70,9 +72,9 @@ class TestProvisionGitGate(unittest.TestCase):
|
||||
calls: list[list[str]] = []
|
||||
with patch(_RUN, side_effect=_recorder(calls)):
|
||||
provision_git_gate("gw", "b1", _plan(_up("foo"))) # no known_hosts
|
||||
cps = [c for c in calls if c[:2] == ["docker", "cp"]]
|
||||
self.assertEqual(1, len(cps)) # only the key, not known_hosts
|
||||
self.assertTrue(cps[0][3].endswith("/foo-key"))
|
||||
creds_cps = [c for c in calls if c[:2] == ["docker", "cp"] and "/git-gate/creds/" in c[3]]
|
||||
self.assertEqual(1, len(creds_cps)) # only the key, not known_hosts
|
||||
self.assertTrue(creds_cps[0][3].endswith("/foo-key"))
|
||||
|
||||
def test_no_upstreams_is_noop(self) -> None:
|
||||
with patch(_RUN) as m:
|
||||
|
||||
Reference in New Issue
Block a user