fix(network): create user-defined egress bridge for pipelock sidecar
Docker's legacy `bridge` network has no embedded DNS resolver — only user-defined bridges do — so attaching the pipelock sidecar to `bridge` made it unable to resolve `api.anthropic.com` and dead-ended Claude Code traffic. Add `network_create_egress`, refactored around a shared `_network_create_with_prefix` helper, and wire it through `pipelock_start` and `cli.sh` so the sidecar straddles the agent's --internal network and a per-agent user-defined egress bridge instead. The agent container itself still attaches to the internal network only. Assisted-by: Claude Code
This commit is contained in:
+24
-14
@@ -6,10 +6,13 @@
|
||||
# forward proxy with hostname allowlisting + DLP scanning + URL-entropy
|
||||
# checks. We run one sidecar container per agent, attached to the
|
||||
# agent's --internal network (created by lib/network.sh) and to a
|
||||
# default-bridge network for upstream egress. The agent's HTTPS_PROXY /
|
||||
# HTTP_PROXY env vars point at the sidecar's service name on the
|
||||
# internal network; combined with --internal (which omits the default
|
||||
# gateway), pipelock is the only egress route the agent has.
|
||||
# per-agent user-defined bridge network for upstream egress (also
|
||||
# created by lib/network.sh — see the comment in network_create_egress
|
||||
# for why we don't use Docker's legacy `bridge` network). The agent's
|
||||
# HTTPS_PROXY / HTTP_PROXY env vars point at the sidecar's service
|
||||
# name on the internal network; combined with --internal (which omits
|
||||
# the default gateway), pipelock is the only egress route the agent
|
||||
# has.
|
||||
#
|
||||
# Image pin: ghcr.io/luckypipewrench/pipelock@sha256:<digest>. The
|
||||
# digest is resolved by hand against ghcr.io for tag 2.3.0 (the
|
||||
@@ -261,7 +264,7 @@ pipelock_write_yaml() {
|
||||
|
||||
# --- Sidecar lifecycle -----------------------------------------------------
|
||||
|
||||
# pipelock_start <slug> <internal_network> <yaml_dir> <yaml_filename>
|
||||
# pipelock_start <slug> <internal_network> <egress_network> <yaml_dir> <yaml_filename>
|
||||
#
|
||||
# Boots the pipelock sidecar:
|
||||
# 1. `docker run -d` on the internal network with the canonical
|
||||
@@ -284,6 +287,9 @@ pipelock_write_yaml() {
|
||||
# Args:
|
||||
# <slug> — agent slug; sidecar name will be claude-bottle-pipelock-<slug>
|
||||
# <internal_network> — name of the agent's internal docker network
|
||||
# <egress_network> — name of the agent's user-defined egress
|
||||
# network; the sidecar joins this so it can
|
||||
# reach upstream hostnames with working DNS
|
||||
# <yaml_dir> — host directory containing the YAML
|
||||
# <yaml_filename> — filename within yaml_dir
|
||||
#
|
||||
@@ -291,8 +297,9 @@ pipelock_write_yaml() {
|
||||
pipelock_start() {
|
||||
local slug="${1:?pipelock_start: missing slug}"
|
||||
local internal_network="${2:?pipelock_start: missing internal network}"
|
||||
local yaml_dir="${3:?pipelock_start: missing yaml dir}"
|
||||
local yaml_filename="${4:?pipelock_start: missing yaml filename}"
|
||||
local egress_network="${3:?pipelock_start: missing egress network}"
|
||||
local yaml_dir="${4:?pipelock_start: missing yaml dir}"
|
||||
local yaml_filename="${5:?pipelock_start: missing yaml filename}"
|
||||
|
||||
local name
|
||||
name="$(pipelock_container_name "$slug")"
|
||||
@@ -340,14 +347,17 @@ pipelock_start() {
|
||||
die "failed to copy pipelock yaml into ${name}"
|
||||
fi
|
||||
|
||||
# Attach to a default-bridge network for upstream egress (the
|
||||
# internal network has no gateway by definition, so without a second
|
||||
# network the sidecar can't reach the public internet either).
|
||||
# Using the well-known `bridge` network is the simplest way to give
|
||||
# it a default route; we do not create a per-agent egress network.
|
||||
if ! docker network connect bridge "$name" >/dev/null 2>&1; then
|
||||
# Attach to a per-agent user-defined bridge network for upstream
|
||||
# egress. The internal network has no gateway by definition, so
|
||||
# without a second network the sidecar can't reach the public
|
||||
# internet at all. We deliberately do NOT use Docker's legacy
|
||||
# `bridge` network: only user-defined bridges run Docker's embedded
|
||||
# DNS resolver, which pipelock needs to resolve `api.anthropic.com`
|
||||
# and similar upstream hostnames. The egress network is created by
|
||||
# network_create_egress in lib/network.sh.
|
||||
if ! docker network connect "$egress_network" "$name" >/dev/null 2>&1; then
|
||||
docker rm -f "$name" >/dev/null 2>&1 || true
|
||||
die "failed to attach pipelock sidecar ${name} to bridge network for upstream egress"
|
||||
die "failed to attach pipelock sidecar ${name} to egress network ${egress_network}"
|
||||
fi
|
||||
|
||||
if ! docker start "$name" >/dev/null 2>&1; then
|
||||
|
||||
Reference in New Issue
Block a user