Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 389a2c10ce |
@@ -31,6 +31,14 @@ GATEWAY_LABEL = "bot-bottle-orch-gateway=1"
|
|||||||
# the source IP the gateway attributes by is the address on this network.
|
# the source IP the gateway attributes by is the address on this network.
|
||||||
GATEWAY_NETWORK = "bot-bottle-gateway"
|
GATEWAY_NETWORK = "bot-bottle-gateway"
|
||||||
|
|
||||||
|
# mitmproxy's CA dir in the bundle. A persistent named volume here keeps the
|
||||||
|
# gateway's self-generated CA STABLE across container recreation — every agent
|
||||||
|
# installs this one CA to trust the shared gateway's TLS interception, so it
|
||||||
|
# must not rotate when the gateway restarts.
|
||||||
|
MITMPROXY_HOME = "/home/mitmproxy/.mitmproxy"
|
||||||
|
GATEWAY_CA_VOLUME = "bot-bottle-gateway-mitmproxy"
|
||||||
|
GATEWAY_CA_CERT = f"{MITMPROXY_HOME}/mitmproxy-ca-cert.pem"
|
||||||
|
|
||||||
# The real sidecar-bundle image + its Dockerfile. Kept as a local constant
|
# The real sidecar-bundle image + its Dockerfile. Kept as a local constant
|
||||||
# rather than imported from backend.docker.sidecar_bundle, which would drag
|
# rather than imported from backend.docker.sidecar_bundle, which would drag
|
||||||
# the whole backend layer into the lean orchestrator (see #359); unify when
|
# the whole backend layer into the lean orchestrator (see #359); unify when
|
||||||
@@ -143,11 +151,26 @@ class DockerGateway(Gateway):
|
|||||||
"--name", self.name,
|
"--name", self.name,
|
||||||
"--label", GATEWAY_LABEL,
|
"--label", GATEWAY_LABEL,
|
||||||
"--network", self.network,
|
"--network", self.network,
|
||||||
|
# Persist the self-generated CA so it survives restarts (agents
|
||||||
|
# trust it) — see GATEWAY_CA_VOLUME.
|
||||||
|
"--volume", f"{GATEWAY_CA_VOLUME}:{MITMPROXY_HOME}",
|
||||||
self.image_ref,
|
self.image_ref,
|
||||||
])
|
])
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
raise GatewayError(f"gateway failed to start: {proc.stderr.strip()}")
|
raise GatewayError(f"gateway failed to start: {proc.stderr.strip()}")
|
||||||
|
|
||||||
|
def ca_cert_pem(self) -> str:
|
||||||
|
"""The gateway's CA certificate (PEM) that agents install to trust its
|
||||||
|
TLS interception. Read from the running container; raises if the
|
||||||
|
gateway hasn't generated it yet (it's created on first mitmproxy
|
||||||
|
start)."""
|
||||||
|
proc = run_docker(["docker", "exec", self.name, "cat", GATEWAY_CA_CERT])
|
||||||
|
if proc.returncode != 0 or not proc.stdout.strip():
|
||||||
|
raise GatewayError(
|
||||||
|
f"gateway CA cert not available: {proc.stderr.strip() or 'empty'}"
|
||||||
|
)
|
||||||
|
return proc.stdout
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
proc = run_docker(["docker", "rm", "--force", self.name])
|
proc = run_docker(["docker", "rm", "--force", self.name])
|
||||||
if proc.returncode != 0 and "No such container" not in proc.stderr:
|
if proc.returncode != 0 and "No such container" not in proc.stderr:
|
||||||
@@ -157,4 +180,5 @@ class DockerGateway(Gateway):
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"Gateway", "DockerGateway", "GatewayError",
|
"Gateway", "DockerGateway", "GatewayError",
|
||||||
"GATEWAY_NAME", "GATEWAY_LABEL", "GATEWAY_IMAGE", "GATEWAY_NETWORK",
|
"GATEWAY_NAME", "GATEWAY_LABEL", "GATEWAY_IMAGE", "GATEWAY_NETWORK",
|
||||||
|
"GATEWAY_CA_VOLUME", "GATEWAY_CA_CERT",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import unittest
|
|||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from bot_bottle.orchestrator.gateway import (
|
from bot_bottle.orchestrator.gateway import (
|
||||||
|
GATEWAY_CA_CERT,
|
||||||
GATEWAY_NAME,
|
GATEWAY_NAME,
|
||||||
DockerGateway,
|
DockerGateway,
|
||||||
GatewayError,
|
GatewayError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_CA_PEM = "-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----\n"
|
||||||
|
|
||||||
_RUN_DOCKER = "bot_bottle.orchestrator.gateway.run_docker"
|
_RUN_DOCKER = "bot_bottle.orchestrator.gateway.run_docker"
|
||||||
|
|
||||||
|
|
||||||
@@ -53,6 +57,8 @@ class TestDockerGateway(unittest.TestCase):
|
|||||||
self.assertIn("bot-bottle-sidecars:latest", runs[0])
|
self.assertIn("bot-bottle-sidecars:latest", runs[0])
|
||||||
# Runs on the shared gateway network so agents can reach it by IP.
|
# Runs on the shared gateway network so agents can reach it by IP.
|
||||||
self.assertEqual(self.sc.network, runs[0][runs[0].index("--network") + 1])
|
self.assertEqual(self.sc.network, runs[0][runs[0].index("--network") + 1])
|
||||||
|
# Persists its CA on a named volume so agents keep trusting it.
|
||||||
|
self.assertTrue(any("mitmproxy" in a for a in runs[0]))
|
||||||
|
|
||||||
def test_ensure_running_creates_network_when_missing(self) -> None:
|
def test_ensure_running_creates_network_when_missing(self) -> None:
|
||||||
calls: list[list[str]] = []
|
calls: list[list[str]] = []
|
||||||
@@ -68,6 +74,17 @@ class TestDockerGateway(unittest.TestCase):
|
|||||||
creates = [c for c in calls if c[:3] == ["docker", "network", "create"]]
|
creates = [c for c in calls if c[:3] == ["docker", "network", "create"]]
|
||||||
self.assertEqual([["docker", "network", "create", self.sc.network]], creates)
|
self.assertEqual([["docker", "network", "create", self.sc.network]], creates)
|
||||||
|
|
||||||
|
def test_ca_cert_pem_reads_from_container(self) -> None:
|
||||||
|
with patch(_RUN_DOCKER, return_value=_proc(stdout=_CA_PEM)) as m:
|
||||||
|
self.assertEqual(_CA_PEM, self.sc.ca_cert_pem())
|
||||||
|
argv = m.call_args.args[0]
|
||||||
|
self.assertEqual(["docker", "exec", self.sc.name, "cat", GATEWAY_CA_CERT], argv)
|
||||||
|
|
||||||
|
def test_ca_cert_pem_raises_when_absent(self) -> None:
|
||||||
|
with patch(_RUN_DOCKER, return_value=_proc(returncode=1, stderr="No such file")):
|
||||||
|
with self.assertRaises(GatewayError):
|
||||||
|
self.sc.ca_cert_pem()
|
||||||
|
|
||||||
def test_ensure_running_reuses_existing_network(self) -> None:
|
def test_ensure_running_reuses_existing_network(self) -> None:
|
||||||
calls: list[list[str]] = []
|
calls: list[list[str]] = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user