feat(macos): give nested containers working egress
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 17s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m32s
test / coverage (pull_request) Successful in 23s
test / publish-infra (pull_request) Has been skipped

Answers the last open question in #392: DNS from inside a nested
container was not a phantom, but it was also not a DNS problem. Public
resolution fails there by design — everything egresses through the
proxy — and the actual breakage was two missing pieces:

- The proxy URL names `bot-bottle-gateway`, which resolves only through
  the bottle's own /etc/hosts. Containers got their own hosts file, so
  they failed at "Could not resolve proxy", which reads like broken DNS.
  base_hosts_file propagates the bottle's entries.
- The gateway TLS-intercepts, so a container that does not trust the
  bottle's CA bundle fails with "unable to get local issuer
  certificate". The bundle is now mounted read-only and the usual env
  vars point at it, which covers curl, wget, python, and node without
  distro-specific trust commands.

Verified on the macOS host by reproducing each failure and confirming
that these three conditions applied by hand produced HTTP 200 from
inside a nested container. podman already forwards the proxy env, so
that needed nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 19:37:39 -04:00
parent 41959fe2a2
commit f9f27788db
2 changed files with 65 additions and 1 deletions
@@ -151,6 +151,39 @@ class TestNestedContainersImage(unittest.TestCase):
))
class TestInitScript(unittest.TestCase):
"""The bootstrap runs inside the bottle, so its wiring is only checkable
here or on a live macOS host."""
def setUp(self) -> None:
self.script = (
Path(nested_containers.__file__).with_name("nested-containers-init.sh")
.read_text(encoding="utf-8")
)
def test_ca_bundle_path_matches_the_backend_constant(self) -> None:
from bot_bottle.backend.util import AGENT_CA_BUNDLE
self.assertIn(f'CA_BUNDLE="{AGENT_CA_BUNDLE}"', self.script)
def test_propagates_the_bottle_hosts_file_into_containers(self) -> None:
"""The proxy URL names bot-bottle-gateway, which resolves only via the
bottle's /etc/hosts. Without this a nested container dies at "Could
not resolve proxy" — which reads as broken DNS but is a missing hosts
entry; public DNS is meant to fail."""
self.assertIn('base_hosts_file="/etc/hosts"', self.script)
def test_mounts_and_trusts_the_gateway_ca(self) -> None:
"""The gateway TLS-intercepts, so without the bundle every HTTPS call
from a nested container fails with "unable to get local issuer
certificate"."""
self.assertIn("volumes=[", self.script)
for var in (
"SSL_CERT_FILE", "CURL_CA_BUNDLE", "REQUESTS_CA_BUNDLE",
"NODE_EXTRA_CA_CERTS",
):
self.assertIn(f'"{var}=${{CA_BUNDLE}}"', self.script)
class TestGuestEnvironment(unittest.TestCase):
def test_disabled_bottle_gets_no_docker_environment(self) -> None:
self.assertEqual({}, nested_containers.guest_env(False))