diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aeb942..66a718e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,30 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). --- +## Unreleased + +### Fixed + +- **`~/.config/devbox-shell/ssh-lan.conf` is Included even if it is created + after the container started.** `setup-lan-access.sh` wrote the + `Include ~/.config/devbox-shell/ssh-lan.conf` line only if the file was + readable at container start. A file created afterwards was visible in the + container and silently ignored, so ssh fell through to `~/.ssh/config`. Hit on + EMB-3HXGNXJ0 on 2026-10-18: its `tor-ms22` override (container key, no + passphrase) was skipped. ssh then used the Mac's passphrase-protected key, and + `BatchMode` failed with `Permission denied (publickey,…)`. Rerunning the script + inside the container fixed it. The line is now written whether or not the file + exists. With this image's OpenSSH 10.0p2, an `Include` whose file (or parent + directory) is missing is skipped. A file created later is read on the next + connection, and a directory at that path is ignored. A file that **exists but + is unreadable** makes ssh abort every connection (`Can't open user config + file`), so that one case is still left out, now with a warning on stderr. + Output is byte-identical to v1.12.0 when the file exists at start. + `scripts/smoke-test.sh` gains two checks: Included when created after start, + and left out when unreadable. Against the v1.12.0 script the first fails and + the second passes. README *Naming LAN peers* no longer says that creating the + file needs a restart. + ## v1.12.0 — 2026-10-18 Minor release. Headlines: **pi 1.3.0**, **mempalace 3.11.0**, **pi-atelier diff --git a/README.md b/README.md index a5121ea..98e31d6 100644 --- a/README.md +++ b/README.md @@ -1174,12 +1174,19 @@ published image (they are a fact about your LAN, not the image). Alternatively, set `DEVBOX_LAN_AUTOJUMP_PRIVATE=1` to ProxyJump *any* RFC1918 address through the host without naming peers (see `.env.example`). -Once the file exists it is re-read on every connection, so *edits* take effect -immediately — no container or session restart. **Creating it for the first time -does need one restart**, because `setup-lan-access.sh` only emits the -`Include ~/.config/devbox-shell/ssh-lan.conf` line when the file is already -readable at container start (`if [ -r "$SSH_LAN_CONF" ]`). Until then ssh never -looks at it — which reads exactly like "my override is being ignored". +ssh reads the file on every connection, so creating it and editing it both take +effect immediately — no container or session restart. `setup-lan-access.sh` +writes the `Include ~/.config/devbox-shell/ssh-lan.conf` line whether or not the +file exists yet (ssh skips an `Include` whose file is missing). Images up to and +including v1.12.0 wrote that line only if the file was readable at container +start. There, a file created afterwards is ignored until a restart, or until you +run `bash /usr/local/lib/pi-devbox/setup-lan-access.sh` in the container. +That looks exactly like "my override is being ignored". One case still needs +action: a file that exists but the container cannot read is left out, because +ssh would otherwise refuse every connection. The script then prints a warning at +start. Two other permission traps apply to this file as to any ssh config. +ssh rejects it if it is world-writable (`Bad owner or permissions`). And on native +Linux, the host user's uid must match the container's `developer` (1000). #### Giving the container its own key for a peer diff --git a/rootfs/usr/local/lib/pi-devbox/setup-lan-access.sh b/rootfs/usr/local/lib/pi-devbox/setup-lan-access.sh index ae847c7..5a1362f 100755 --- a/rootfs/usr/local/lib/pi-devbox/setup-lan-access.sh +++ b/rootfs/usr/local/lib/pi-devbox/setup-lan-access.sh @@ -51,8 +51,8 @@ # HOST-OWNED PEER POLICY (portable; keeps this image generic) # Named LAN peers are facts about a *specific* host's network, not about the # image — a roaming laptop sees different LANs. So we never bake peer names -# here. Instead, if the host bind-mounts ~/.config/devbox-shell/ssh-lan.conf -# (the same devbox-shell bridge dir used for shared aliases), we Include it +# here. Instead we Include ~/.config/devbox-shell/ssh-lan.conf (the host +# bind-mounts the devbox-shell bridge dir, also used for shared aliases) # *before* ~/.ssh/config. That file holds the host's own jump overrides, e.g. # Host pve pve-2 pbs-vm # ProxyJump host @@ -151,8 +151,22 @@ EOF # Optional host-owned named-peer jump overrides (portable: lives on the host, # not in the image). Included BEFORE ~/.ssh/config so its ProxyJump wins. + # + # Included even when the file does not exist yet. ssh skips an Include whose + # file is missing, and reads it on the next connection once it appears. + # Through v1.12.0 the Include was written only if the file was readable at + # container start. A file created later was then visible in the container but + # ignored, until a restart (EMB-3HXGNXJ0, 2026-10-18: its tor-ms22 override + # was skipped, so ssh fell back to a passphrase key and BatchMode failed). + # Only a file that EXISTS but cannot be read is left out. ssh aborts every + # connection on one ("Can't open user config file"), which would break all + # ssh, git included. A directory at that path is harmless (ssh ignores it). + # Measured with this image's OpenSSH 10.0p2; scripts/smoke-test.sh checks + # both cases. SSH_LAN_CONF="${HOME}/.config/devbox-shell/ssh-lan.conf" - if [ -r "$SSH_LAN_CONF" ]; then + if [ -e "$SSH_LAN_CONF" ] && [ ! -r "$SSH_LAN_CONF" ]; then + echo "setup-lan-access: $SSH_LAN_CONF exists but is not readable, so it is not Included (ssh would refuse every connection). Fix its permissions, then rerun this script or restart the container." >&2 + else LAN_CONF_BLOCK=$(cat <<'EOF' # Host-owned named-peer jump overrides (bind-mounted; edit on the host). diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 7f57e58..2346af1 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -696,6 +696,29 @@ exec_test "git core.sshCommand matches sidecar presence" ' else [ -z "$cmd" ] || exit 1; echo "no sidecar, correctly unset" fi' +# ssh-lan.conf is Included whether or not it exists at container start. Until +# v1.12.0 the Include was written only if the file was readable at start, so a +# file created later was silently ignored (EMB-3HXGNXJ0, 2026-10-18). ssh skips +# an Include whose file is missing, and picks the file up once it appears, with no +# restart. The exception is a file that exists but cannot be read: ssh then +# aborts every connection, so that case is still left out. 644 is set +# explicitly because ssh rejects a world-writable config file. +exec_test "ssh-lan.conf Included even if created after start" ' + T=$(mktemp -d) && mkdir -p $T/.config/devbox-shell && + HOME=$T DEVBOX_LAN_ACCESS=jump bash /usr/local/lib/pi-devbox/setup-lan-access.sh >/dev/null 2>&1 && + grep -qx "Include ~/.config/devbox-shell/ssh-lan.conf" $T/.ssh-local/config && + HOME=$T ssh -F $T/.ssh-local/config -G lanpeer 2>/dev/null | grep -qx "hostname lanpeer" && + printf "Host lanpeer\n HostName 10.9.9.9\n" > $T/.config/devbox-shell/ssh-lan.conf && + chmod 644 $T/.config/devbox-shell/ssh-lan.conf && + HOME=$T ssh -F $T/.ssh-local/config -G lanpeer 2>/dev/null | grep -qx "hostname 10.9.9.9" + r=$?; rm -rf $T; exit $r' +exec_test "unreadable ssh-lan.conf left out (ssh would abort)" ' + T=$(mktemp -d) && mkdir -p $T/.config/devbox-shell && : > $T/.config/devbox-shell/ssh-lan.conf && + chmod 000 $T/.config/devbox-shell/ssh-lan.conf && + HOME=$T DEVBOX_LAN_ACCESS=jump bash /usr/local/lib/pi-devbox/setup-lan-access.sh >/dev/null 2>&1 && + ! grep -q "ssh-lan.conf$" $T/.ssh-local/config && + HOME=$T ssh -F $T/.ssh-local/config -G lanpeer >/dev/null 2>&1 + r=$?; rm -rf $T; exit $r' # The vendored mempalace snapshot is refreshed MANUALLY per release (see # rootfs/usr/local/share/pi-devbox/skills/VENDORED.md). Through v1.8.4 it also # silently SHADOWED the live skillset copy, so staleness was invisible — and the