From 8a3047b06bdbec3ff644f004a190c4a2ba0a2696 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Thu, 27 Aug 2026 04:11:16 +0100 Subject: [PATCH] fix_ssh_permissions never checked the home directory itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asked to look at user provisioning and .ssh dir creation — the recurring pain point was "the .ssh permissions are never right even when I do everything correctly", and tracing it: fix_ssh_permissions() only ever touched ~/.ssh and its contents. sshd's StrictModes (on by default; nothing here turns it off) also refuses authorized_keys if the HOME DIRECTORY is group- or world-writable — not just .ssh. An account created by this script's own adduser call gets a safe 0755 home by default, but one that already existed some other way (cloud-init, a prior useradd, LDAP, or simply an account that was already there) might not, and the script never checked or reported on it. From the user's side that looks exactly like the complaint: .ssh is 0700, the key is present and correctly formatted, and login still fails, with nothing in the script's own output pointing at why. Fixed by extending fix_ssh_permissions() — called on every path that adds or verifies a key — to check the home directory's mode first and clear only the group/other write bits if set (go-w), reported with a warn rather than done silently, since it's genuinely surprising when it fires. Deliberately not forced to a fixed mode: 0755 vs 0750 is a real choice some setups make on purpose, and this only removes the specific bits sshd refuses to see. Verified with two simulations: a 775 home directory gets warned about and corrected to 755 (.ssh and authorized_keys already-wrong modes fixed too, as before); a 750 home directory is left untouched with no warning. The stat call tries GNU's -c first (this step only runs on Linux) with a BSD -f fallback, same pattern officer-setup's layout.sh already uses for the same reason — not a live concern here since MACOS_SKIP keeps this step off macOS entirely, but consistent rather than a call that would silently do nothing if that ever changed. Co-Authored-By: Claude Opus 5 --- scripts/setup/machine-setup/lib/ssh.sh | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/scripts/setup/machine-setup/lib/ssh.sh b/scripts/setup/machine-setup/lib/ssh.sh index 6b72706a..b38f1728 100644 --- a/scripts/setup/machine-setup/lib/ssh.sh +++ b/scripts/setup/machine-setup/lib/ssh.sh @@ -59,12 +59,35 @@ authorized_key_count() { has_authorized_key() { (($(authorized_key_count) > 0)); } -# Everything about ~/.ssh that has to be true for sshd to use it at all. sshd -# ignores an authorized_keys file that is group- or world-writable, and does so -# silently from the client's point of view — the login just fails. +# Everything that has to be true for sshd to use ~/.ssh at all. sshd's StrictModes +# (on by default, and nothing here turns it off) checks the HOME DIRECTORY too, not +# just .ssh — this used to check only .ssh, which is the shape of bug that is hardest +# to notice: an account whose home came from somewhere other than THIS script's own +# adduser call (cloud-init, a prior useradd, LDAP, or simply an account that already +# existed) can have a group- or world-writable home directory, and sshd refuses +# authorized_keys under it silently. The .ssh directory and the key both look +# completely correct — because they are — and nothing here used to say why the +# login still failed. fix_ssh_permissions() { - local dir + local dir home mode dir="$(user_ssh_dir)" + home="$USER_HOME" + + if [[ -d "$home" ]]; then + # GNU stat's -c, tried first since this step only ever runs on Linux (MACOS_SKIP) + # — with a BSD fallback anyway rather than a call that silently returns nothing on + # whatever this is next tested against, the same trap officer-setup's layout.sh + # file_owner() hit for the same reason. + mode="$(stat -c %a "$home" 2>/dev/null || stat -f %Lp "$home" 2>/dev/null)" + # 0022 is the group-write and other-write bits together. Cleared rather than + # forced to a fixed mode — 0755 vs 0750 is a real choice some setups make on + # purpose, and this only removes the bits sshd actually refuses to see. + if [[ -n "$mode" ]] && ((8#$mode & 0022)); then + chmod go-w "$home" + warn "${home} was group/world-writable (${mode}) — sshd's StrictModes refuses authorized_keys under a writable home regardless of .ssh's own permissions; cleared" + fi + fi + [[ -d "$dir" ]] || install -d -m 0700 -o "$USERNAME" -g "$(user_group)" "$dir" chmod 700 "$dir" [[ -f "$dir/authorized_keys" ]] && chmod 600 "$dir/authorized_keys"