fix(macos): persist gateway CA on host
test / integration-docker (push) Successful in 19s
Update Quality Badges / update-badges (push) Failing after 42s
lint / lint (push) Successful in 52s
test / unit (push) Successful in 1m43s
test / integration-firecracker (push) Successful in 4m56s
test / coverage (push) Successful in 17s
test / publish-infra (push) Successful in 1m49s
test / integration-docker (push) Successful in 19s
Update Quality Badges / update-badges (push) Failing after 42s
lint / lint (push) Successful in 52s
test / unit (push) Successful in 1m43s
test / integration-firecracker (push) Successful in 4m56s
test / coverage (push) Successful in 17s
test / publish-infra (push) Successful in 1m49s
This commit was merged in pull request #454.
This commit is contained in:
@@ -41,7 +41,7 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ... import log
|
from ... import log
|
||||||
from ...orchestrator.gateway import GATEWAY_CA_CERT
|
from ...orchestrator.gateway import GATEWAY_CA_CERT, MITMPROXY_HOME
|
||||||
from ...orchestrator.lifecycle import (
|
from ...orchestrator.lifecycle import (
|
||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
||||||
@@ -52,6 +52,7 @@ from ...paths import (
|
|||||||
CONTROL_PLANE_TOKEN_ENV,
|
CONTROL_PLANE_TOKEN_ENV,
|
||||||
HOST_DB_FILENAME,
|
HOST_DB_FILENAME,
|
||||||
host_control_plane_token,
|
host_control_plane_token,
|
||||||
|
host_gateway_ca_dir,
|
||||||
)
|
)
|
||||||
from .. import util as backend_util
|
from .. import util as backend_util
|
||||||
from . import util as container_mod
|
from . import util as container_mod
|
||||||
@@ -218,6 +219,14 @@ class MacosInfraService:
|
|||||||
# Container-only DB volume: one kernel writes bot-bottle.db, never
|
# Container-only DB volume: one kernel writes bot-bottle.db, never
|
||||||
# shared with the host or another guest.
|
# shared with the host or another guest.
|
||||||
"--volume", f"{self._db_volume}:{_DB_ROOT_IN_CONTAINER}",
|
"--volume", f"{self._db_volume}:{_DB_ROOT_IN_CONTAINER}",
|
||||||
|
# The DB needs a container-only ext4 volume for coherent SQLite
|
||||||
|
# locking, but the CA has no such constraint. Keep it in the host
|
||||||
|
# app-data root so infra-container recreation and Apple Container
|
||||||
|
# volume pruning cannot silently rotate every bottle's trust
|
||||||
|
# anchor (issue #450).
|
||||||
|
"--mount",
|
||||||
|
container_mod.bind_mount_spec(
|
||||||
|
str(host_gateway_ca_dir()), MITMPROXY_HOME),
|
||||||
# Bind-mount the control-plane source (read-only); a code change
|
# Bind-mount the control-plane source (read-only); a code change
|
||||||
# takes effect on relaunch with no image rebuild.
|
# takes effect on relaunch with no image rebuild.
|
||||||
"--mount",
|
"--mount",
|
||||||
@@ -261,9 +270,9 @@ class MacosInfraService:
|
|||||||
|
|
||||||
def ca_cert_pem(self, *, timeout: float = DEFAULT_CA_TIMEOUT_SECONDS) -> str:
|
def ca_cert_pem(self, *, timeout: float = DEFAULT_CA_TIMEOUT_SECONDS) -> str:
|
||||||
"""The gateway's mitmproxy CA (PEM) agents install to trust its TLS
|
"""The gateway's mitmproxy CA (PEM) agents install to trust its TLS
|
||||||
interception. Read out of the container (the CA lives on a
|
interception. Read through the container path backed by the persistent
|
||||||
container-internal path, not a host mount); polls because mitmproxy
|
host CA directory; polls because mitmproxy writes it a beat after
|
||||||
writes it a beat after start."""
|
start."""
|
||||||
def _fetch() -> str | None:
|
def _fetch() -> str | None:
|
||||||
result = container_mod.run_container_argv(
|
result = container_mod.run_container_argv(
|
||||||
["container", "exec", self._name, "cat", GATEWAY_CA_CERT])
|
["container", "exec", self._name, "cat", GATEWAY_CA_CERT])
|
||||||
|
|||||||
@@ -324,15 +324,24 @@ re-attachment blocker distinct from #443/#445).
|
|||||||
|
|
||||||
The CA lives on the **host filesystem** at `bot_bottle_root()/gateway-ca`
|
The CA lives on the **host filesystem** at `bot_bottle_root()/gateway-ca`
|
||||||
(`host_gateway_ca_dir()`), bind-mounted into the container at mitmproxy's
|
(`host_gateway_ca_dir()`), bind-mounted into the container at mitmproxy's
|
||||||
confdir. This is deliberately a host bind-mount, **not a Docker named volume**:
|
confdir. This is deliberately a host bind-mount, **not a container-runtime
|
||||||
a named volume survives `docker rm` but is silently wiped by
|
named volume**: a named volume survives ordinary container removal but can be
|
||||||
`docker volume prune` / `docker system prune --volumes` during routine host
|
silently wiped by Docker's or Apple Container's volume-prune commands during
|
||||||
maintenance, which is exactly how the ephemeral-CA symptom shows up in
|
routine host maintenance, which is exactly how the ephemeral-CA symptom shows
|
||||||
practice. A path under the app-data root is never pruned by docker, and stays
|
up in practice. A path under the app-data root is not managed or pruned by the
|
||||||
directly inspectable and rotatable from the host. mitmproxy reuses an existing
|
container runtime, and stays directly inspectable and rotatable from the host.
|
||||||
CA and generates one only on first run, so the bind-mount alone gives
|
mitmproxy reuses an existing CA and generates one only on first run, so the
|
||||||
|
bind-mount alone gives
|
||||||
"adopt-existing, generate-on-first-run" for free.
|
"adopt-existing, generate-on-first-run" for free.
|
||||||
|
|
||||||
|
The macOS backend uses the same host-resident CA directory and bind-mounts it
|
||||||
|
into the consolidated Apple infra container. Its `bot-bottle-mac-db` named
|
||||||
|
volume remains container-only because that prevents incoherent cross-kernel
|
||||||
|
SQLite locking, but the CA is deliberately not stored there: Apple Container
|
||||||
|
also has a `container volume prune` operation, and the named volume is
|
||||||
|
temporarily unreferenced while the infra container is recreated. Keeping the
|
||||||
|
CA on the host makes both ordinary recreation and volume pruning safe.
|
||||||
|
|
||||||
**Deliberate rollover** is the explicit inverse: `rotate_gateway_ca()` removes
|
**Deliberate rollover** is the explicit inverse: `rotate_gateway_ca()` removes
|
||||||
the persisted CA material so the next start remints it, and the
|
the persisted CA material so the next start remints it, and the
|
||||||
`python -m bot_bottle.orchestrator.rotate_ca` one-shot wires that together with
|
`python -m bot_bottle.orchestrator.rotate_ca` one-shot wires that together with
|
||||||
|
|||||||
@@ -61,6 +61,20 @@ class TestInfraRun(unittest.TestCase):
|
|||||||
mounts = [argv[i + 1] for i, a in enumerate(argv) if a == "--mount"]
|
mounts = [argv[i + 1] for i, a in enumerate(argv) if a == "--mount"]
|
||||||
self.assertTrue(all("bot-bottle.db" not in m for m in mounts))
|
self.assertTrue(all("bot-bottle.db" not in m for m in mounts))
|
||||||
|
|
||||||
|
def test_ca_is_persisted_on_the_host_not_the_container_volume(self) -> None:
|
||||||
|
"""The CA survives infra recreation and cannot be removed by Apple
|
||||||
|
Container's volume-prune command."""
|
||||||
|
argv = self._run_container(MacosInfraService(repo_root=Path("/r")))
|
||||||
|
mounts = [argv[i + 1] for i, a in enumerate(argv) if a == "--mount"]
|
||||||
|
ca_mounts = [
|
||||||
|
m for m in mounts
|
||||||
|
if "target=/home/mitmproxy/.mitmproxy" in m
|
||||||
|
]
|
||||||
|
self.assertEqual(1, len(ca_mounts))
|
||||||
|
self.assertIn("source=", ca_mounts[0])
|
||||||
|
self.assertIn("/gateway-ca", ca_mounts[0])
|
||||||
|
self.assertNotIn(",readonly", ca_mounts[0])
|
||||||
|
|
||||||
def test_nat_network_precedes_the_host_only_network(self) -> None:
|
def test_nat_network_precedes_the_host_only_network(self) -> None:
|
||||||
argv = self._run_container(MacosInfraService(repo_root=Path("/r")))
|
argv = self._run_container(MacosInfraService(repo_root=Path("/r")))
|
||||||
nets = [argv[i + 1] for i, a in enumerate(argv) if a == "--network"]
|
nets = [argv[i + 1] for i, a in enumerate(argv) if a == "--network"]
|
||||||
|
|||||||
Reference in New Issue
Block a user