fix(ssh): tunnel ssh through pipelock so agents on --internal can reach git remotes
The agent container is on an --internal Docker network with no default route — only the pipelock sidecar is reachable. HTTPS_PROXY routes HTTP through pipelock, but raw TCP (e.g. SSH on port 30009) had no egress path, so `git fetch` against any bottle.ssh entry failed with "Network is unreachable". Fix: tunnel SSH through pipelock's HTTP CONNECT proxy. - lib/ssh.sh injects `ProxyCommand socat - PROXY:<pipelock>:%h:%p,proxyport=<n>` into each Host block in the in-container ~/.ssh/config. socat is already in the image (apt-installed for the ssh-agent forwarder). - lib/pipelock.sh auto-adds each bottle.ssh[].Hostname to the effective allowlist so pipelock permits the CONNECT. - cli.sh threads the pipelock host:port into ssh_setup. Note: works for SSH hosts pipelock's SSRF layer doesn't block. CGNAT (100.64.0.0/10) and other non-RFC1918 ranges should pass; if a future host gets blocked, expose pipelock's trusted_domains as a follow-up. Assisted-by: Claude Code
This commit is contained in:
+18
-3
@@ -89,7 +89,12 @@ ssh_validate_entries() {
|
||||
ssh_setup() {
|
||||
local container="${1:?ssh_setup: missing container}"
|
||||
local stage_dir="${2:?ssh_setup: missing stage dir}"
|
||||
shift 2
|
||||
# proxy_host_port is the pipelock sidecar as <host>:<port> (no scheme).
|
||||
# Used as socat's PROXY: argument so the agent can reach SSH hosts
|
||||
# over the agent's --internal network — the only egress route is the
|
||||
# pipelock CONNECT proxy. Required.
|
||||
local proxy_host_port="${3:?ssh_setup: missing proxy_host_port}"
|
||||
shift 3
|
||||
|
||||
local container_home="${CLAUDE_BOTTLE_CONTAINER_HOME:-/home/node}"
|
||||
local container_ssh="${container_home}/.ssh"
|
||||
@@ -140,8 +145,18 @@ ssh_setup() {
|
||||
# No IdentityFile — IdentityAgent points SSH at the public (forwarded)
|
||||
# socket. Pointing at the real agent socket directly would be rejected
|
||||
# by ssh-agent's UID-match check (see file header).
|
||||
printf 'Host %s\n HostName %s\n User %s\n Port %s\n IdentityAgent %s\n\n' \
|
||||
"$name" "$hostname" "$user" "$port" "$public_socket" >> "$config_file"
|
||||
#
|
||||
# ProxyCommand tunnels the SSH connection through pipelock via HTTP
|
||||
# CONNECT. The agent container has no default route (--internal
|
||||
# network); pipelock is the only path to anywhere. socat's PROXY:
|
||||
# mode does CONNECT host:port to the proxy. %h / %p expand to this
|
||||
# block's HostName / Port. The SSH host must also appear in
|
||||
# pipelock's allowlist — pipelock_effective_allowlist auto-includes
|
||||
# bottle.ssh[].Hostname entries so this just works for declared
|
||||
# hosts.
|
||||
printf 'Host %s\n HostName %s\n User %s\n Port %s\n IdentityAgent %s\n ProxyCommand socat - PROXY:%s:%%h:%%p,proxyport=%s\n\n' \
|
||||
"$name" "$hostname" "$user" "$port" "$public_socket" \
|
||||
"${proxy_host_port%:*}" "${proxy_host_port##*:}" >> "$config_file"
|
||||
|
||||
if [ -n "$known_host_key" ]; then
|
||||
# Write under both the Host alias and the Hostname so SSH finds the key
|
||||
|
||||
Reference in New Issue
Block a user