Base: SSH ControlMaster default on a writable socket path
Validate / docs-check (push) Successful in 9s
Validate / base-change-warning (push) Successful in 11s
Validate / validate-with-pi (push) Failing after 4m6s
Validate / validate-omos (push) Failing after 4m31s
Validate / validate-omos-with-pi (push) Failing after 4m52s
Validate / validate-base (push) Failing after 13m20s

Devboxes typically mount ~/.ssh from the host read-only (security: keys
readable, but agents can't tamper with config / known_hosts /
authorized_keys / plant a malicious ProxyCommand). OpenSSH's default
ControlPath is ~/.ssh/cm/... which is unwritable on such mounts, so
any attempt to multiplex fails with:

  unix_listener: cannot bind to path .../cm/...: Read-only file system
  kex_exchange_identification: Connection closed by remote host

The second line is downstream — when ControlMaster fails, ssh falls
back to fresh TCP connections, and on residential CGNAT (most European
ISPs) the per-(src,dst) concurrent-flow cap (~4) silently drops further
SYNs once exceeded, manifesting as banner-exchange timeouts that look
like a remote problem.

Fix: bake /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf in the
base image with Host * defaults — ControlPath rooted at /tmp/sshcm/
(per-container, always writable), ControlMaster auto, ControlPersist
10m, ServerAlive{Interval=30,CountMax=6}. Companion entrypoint-user.sh
creates /tmp/sshcm mode 700 on each container start (/tmp is
per-container so the dir can't be baked into a layer; mode 700 is
required by OpenSSH for ControlPath dirs). Debian's stock ssh_config
sources ssh_config.d/*.conf before its own Host * block, so user
~/.ssh/config overrides still win.

Two smoke assertions catch regressions: (a) the conf file exists, (b)
ssh -G reports a controlpath rooted at /tmp/sshcm/ — second one catches
the silent case where something later in the config chain shadows the
bake-in.

Discovered while running a recon shell from inside pi-devbox to a
Proxmox node — fresh ssh hit banner-exchange timeout, debug output
pointed at the read-only socket dir as the actual root cause.

Cascades to all variants and to pi-devbox automatically on next build
against base-latest. No size/threshold impact (~250-byte conf file).
This commit is contained in:
2026-05-24 19:51:38 +00:00
parent 3cbcb44cf5
commit 668592da0d
4 changed files with 74 additions and 0 deletions
+11
View File
@@ -1,6 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
# ── SSH ControlMaster socket dir ────────────────────────────────
# Companion to /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf in the
# base image — that file declares ControlPath=/tmp/sshcm/%r@%h:%p; this
# creates the directory with the right permissions on every container
# start. /tmp is per-container so the dir doesn't survive recreation;
# baking it into a Dockerfile layer would be wrong.
# Mode 700 is required — OpenSSH refuses to use a ControlPath dir that
# others can write to.
mkdir -p /tmp/sshcm
chmod 700 /tmp/sshcm
# ── Shell defaults: copy baked files from /etc/skel-devbox/ if absent
# Respects host bind-mounts and user customizations — existing files
# are never overwritten. To restore defaults: rm ~/.bash_aliases (or