Files
bot-bottle/Dockerfile.egress-proxy
T
didericis b9c70f7daa
test / unit (pull_request) Successful in 17s
test / integration (pull_request) Successful in 1m2s
fix(egress-proxy): build combined trust bundle (system + pipelock CA)
`--set ssl_verify_upstream_trusted_ca` REPLACES mitmproxy's default
trust store with the file we point it at. The earlier wiring
pointed it at just pipelock's CA, which broke for any host pipelock
passes through (api.anthropic.com is in DEFAULT_TLS_PASSTHROUGH):
pipelock CONNECT-tunnels the handshake to the real upstream,
egress-proxy sees the real public cert (signed by e.g. DigiCert),
and refuses to validate because pipelock's CA doesn't sign it.

Fix in Dockerfile entrypoint: when EGRESS_PROXY_UPSTREAM_CA is
set, concatenate /etc/ssl/certs/ca-certificates.crt + the pipelock
CA into /home/mitmproxy/.mitmproxy/combined-trust.pem, and pass
that as ssl_verify_upstream_trusted_ca. Covers both legs:

  - pipelock-MITM'd hosts → leaf cert signed by pipelock CA →
    validates against the pipelock half of the bundle.
  - pipelock-passthrough hosts (api.anthropic.com et al.) → real
    upstream cert → validates against the system half.

Standalone runs of the image (no EGRESS_PROXY_UPSTREAM_CA) skip
the concat and use mitmproxy's default trust store.

Reproduces against today's main: agent gets "Unable to connect to
API: SSL certificate verification failed" on api.anthropic.com,
egress-proxy logs "Server TLS handshake failed. Certificate verify
failed: unable to get local issuer certificate". After this patch
the trust bundle includes the real upstream root + pipelock's CA
and both validation paths succeed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 15:52:08 -04:00

64 lines
3.3 KiB
Docker

# Per-bottle egress-proxy sidecar image (PRD 0017).
#
# Replaces cred-proxy (PRD 0010). Sits on the agent's HTTP_PROXY /
# HTTPS_PROXY path (wiring lands in chunk 2) and owns three jobs:
# 1. MITM HTTPS using the per-bottle CA (chunk 2 moves the CA
# generation from pipelock).
# 2. Enforce manifest-declared path_allowlist per route.
# 3. Inject Authorization headers for routes that declare an auth
# block.
#
# Chunk 1 of PRD 0017 ships this image and the addon. Wiring it
# into the bottle launch (and the per-bottle CA + the pipelock
# upstream proxy) is chunk 2.
# mitmproxy base image. mitmdump + addon API are already there; we
# only need to drop our addon in. TODO: pin by digest.
FROM mitmproxy/mitmproxy:11.1.3
USER root
# The addon ships as two files. `_core.py` is pure-logic, importable
# both inside the container and from the host's tests; `_addon.py` is
# the mitmproxy hook wrapper. Both land flat in /app/ so mitmdump's
# loader finds them as top-level sibling modules.
COPY claude_bottle/egress_proxy_addon_core.py /app/egress_proxy_addon_core.py
COPY claude_bottle/egress_proxy_addon.py /app/egress_proxy_addon.py
# Pre-create the runtime directories the backend's start step will
# `docker cp` into. docker cp does not create intermediate dirs, so
# the mkdir must be baked into the image.
# /etc/egress-proxy routes.yaml lands here
# ~/.mitmproxy mitmproxy CA (cert+key concat) + the
# pipelock CA (cert only, for upstream
# trust on the HTTPS_PROXY=pipelock leg)
# Ownership lets the unprivileged mitmproxy user read the files.
RUN mkdir -p /etc/egress-proxy /home/mitmproxy/.mitmproxy \
&& chown -R mitmproxy:mitmproxy /etc/egress-proxy /home/mitmproxy/.mitmproxy /app
USER mitmproxy
# Listening port. Agents dial egress-proxy on this port via their
# HTTP_PROXY env. Surfaced as EXPOSE for documentation; not required
# for the internal network to route to it.
EXPOSE 9099
# Entrypoint:
# - Build a combined upstream-trust bundle when
# EGRESS_PROXY_UPSTREAM_CA is set (the backend's start step
# sets it to the in-container pipelock-CA path).
# `--set ssl_verify_upstream_trusted_ca` REPLACES mitmproxy's
# default trust store with the file we point it at; if we just
# pointed it at pipelock's CA, mitmproxy would refuse any host
# pipelock passes through (api.anthropic.com etc.) because
# pipelock's CA doesn't sign the real upstream certs. So
# concatenate the system bundle + pipelock CA into one PEM and
# point mitmproxy at that — covers both pipelock-MITM'd and
# pipelock-passthrough hosts.
# - --mode regular@9099 → standard HTTP/HTTPS forward proxy.
# - -s /app/egress_proxy_addon.py → loads our addon, reads
# /etc/egress-proxy/routes.yaml.
# Standalone runs (no EGRESS_PROXY_UPSTREAM_CA) skip the bundle
# build and use mitmproxy's default trust store.
ENTRYPOINT ["sh", "-c", "if [ -n \"$EGRESS_PROXY_UPSTREAM_CA\" ] && [ -f \"$EGRESS_PROXY_UPSTREAM_CA\" ]; then COMBINED=/home/mitmproxy/.mitmproxy/combined-trust.pem; cat /etc/ssl/certs/ca-certificates.crt \"$EGRESS_PROXY_UPSTREAM_CA\" > \"$COMBINED\"; exec mitmdump --mode regular@9099 --set ssl_verify_upstream_trusted_ca=\"$COMBINED\" -s /app/egress_proxy_addon.py; else exec mitmdump --mode regular@9099 -s /app/egress_proxy_addon.py; fi"]