feat(mitmproxy): wire the sidecar into the bottle launch lifecycle
Second step of PRD 0005. The mitmproxy sidecar from the previous commit now actually runs alongside pipelock when a bottle launches. - BottleBackend gains a non-abstract provision_ca with a default no-op so non-Docker backends aren't forced to implement TLS interception. provision() orchestrates ca → prompt → skills → ssh → git; CA goes first so trust is set up before anything else runs inside the agent. - DockerBottlePlan gains `mitmproxy_plan: MitmproxyProxyPlan`. The prepare step builds it alongside the existing pipelock plan; no new manifest schema or host-side scratch files. - DockerBottleBackend grows self._mitm, threads it through prepare and launch. Mirror of the existing self._proxy pattern. - launch.py brings the mitmproxy sidecar up between pipelock and the agent container, passing pipelock's service-name URL via env. ExitStack callback handles teardown in reverse order. - The agent's HTTPS_PROXY / HTTP_PROXY now point at mitmproxy (not pipelock directly). Three new -e flags inject the CA trust trio (NODE_EXTRA_CA_CERTS / SSL_CERT_FILE / REQUESTS_CA_BUNDLE) at docker run time; Docker propagates those into docker exec so the claude process sees them without per-exec threading. - New provisioner backend/docker/provision/ca.py extracts the CA cert from the running mitmproxy sidecar, copies it into the agent at /usr/local/share/ca-certificates/claude-bottle-mitm.crt, runs update-ca-certificates, and emits a stderr line with the SHA-256 fingerprint (stdlib ssl + hashlib; no subprocess). Cleanup needs no change — `docker ps --filter name=^claude-bottle-` already catches the new claude-bottle-mitm-<slug> containers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""Extract mitmproxy's CA cert and install it into the agent
|
||||
container's trust store.
|
||||
|
||||
mitmproxy generates a fresh CA on first launch inside its sidecar.
|
||||
This provisioner pulls the public cert through a host stage dir,
|
||||
drops it into the agent at `/usr/local/share/ca-certificates/...`,
|
||||
runs `update-ca-certificates` to rebuild the system bundle, and
|
||||
emits a single stderr log line with the SHA-256 fingerprint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import ssl
|
||||
import subprocess
|
||||
|
||||
from ....log import info
|
||||
from ..bottle_plan import DockerBottlePlan
|
||||
from ..launch import AGENT_CA_PATH
|
||||
from ..mitmproxy import DockerMitmproxyProxy, mitmproxy_container_name
|
||||
|
||||
|
||||
def provision_ca(plan: DockerBottlePlan, target: str) -> None:
|
||||
"""Pull mitmproxy's CA cert, install in the agent, log fingerprint.
|
||||
Called from BottleBackend.provision after the agent container is
|
||||
up. The mitmproxy sidecar is already running (started during
|
||||
`launch`)."""
|
||||
sidecar = mitmproxy_container_name(plan.mitmproxy_plan.slug)
|
||||
stage_cert = plan.stage_dir / "mitm-ca.crt"
|
||||
|
||||
DockerMitmproxyProxy().extract_ca_cert(sidecar, stage_cert)
|
||||
|
||||
container = target
|
||||
subprocess.run(
|
||||
["docker", "cp", str(stage_cert), f"{container}:{AGENT_CA_PATH}"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["docker", "exec", "-u", "0", container, "chmod", "644", AGENT_CA_PATH],
|
||||
stdout=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["docker", "exec", "-u", "0", container, "update-ca-certificates"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
|
||||
# SHA-256 of the cert's DER bytes — the standard fingerprint
|
||||
# form. stdlib only; never the private key (which stays in the
|
||||
# sidecar). Logged once at launch as an audit signal.
|
||||
pem = stage_cert.read_text()
|
||||
der = ssl.PEM_cert_to_DER_cert(pem)
|
||||
fingerprint = hashlib.sha256(der).hexdigest()
|
||||
info(f"mitm ca fingerprint: sha256:{fingerprint[:32]}...")
|
||||
Reference in New Issue
Block a user