Keep the sshd hardening migration from locking users out

Validate authorized_keys line by line with the question sshd actually
asks: ssh-keygen -lf on the whole file also fingerprints a private key
copied there by mistake, which sshd cannot use, so the migration would
have disabled the only working login path.

Tighten ~/.ssh and authorized_keys the way omarchy-setup-security-sshd
does, and back off from a group-writable home directory: StrictModes
makes sshd ignore the key either way, with the same lockout.

Complete with a notice instead of failing on conditions the migration
cannot repair (a broken or pre-Include sshd_config, an overriding admin
rule, a failed reload of a valid config), so those machines keep passwords
as they were without blocking every migration queued behind this one.
Only missing privileges stay pending, since a terminal rerun fixes that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ryan Hughes
2026-08-30 18:36:26 -04:00
co-authored by Claude Fable 5
parent ca4f596a14
commit 986962bb64
2 changed files with 121 additions and 27 deletions
+68 -13
View File
@@ -38,6 +38,9 @@ STUB
cat >"$stub_bin/sudo" <<'STUB'
#!/bin/bash
printf 'sudo %s\n' "$*" >>"${CALL_LOG:?}"
if [[ ${SUDO_ALLOWED:-1} != 1 ]]; then
exit 1
fi
exec "$@"
STUB
@@ -53,11 +56,19 @@ run_migration() {
local config="$root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf"
mkdir -p "$home/.ssh" "${config%/*}"
chmod "${HOME_MODE:-755}" "$home"
: >"$test_dir/$scenario.calls"
if [[ ${AUTHORIZED_KEY_STATE:-valid} == "valid" ]]; then
printf '%s\n' "$public_key" >"$home/.ssh/authorized_keys"
elif [[ $AUTHORIZED_KEY_STATE == "invalid" ]]; then
printf 'not a public key\n' >"$home/.ssh/authorized_keys"
case "${AUTHORIZED_KEY_STATE:-valid}" in
valid) printf '%s\n' "$public_key" >"$home/.ssh/authorized_keys" ;;
invalid) printf 'not a public key\n' >"$home/.ssh/authorized_keys" ;;
private) cat "$test_dir/key" >"$home/.ssh/authorized_keys" ;;
esac
if [[ ${LOOSE_SSH_PERMS:-0} == 1 ]]; then
chmod 755 "$home/.ssh"
chmod 644 "$home/.ssh/authorized_keys"
fi
if [[ ${ALREADY_HARDENED:-0} == 1 ]]; then
printf 'PasswordAuthentication no\n' >"$config"
fi
# Keep the privileged production destination fixed in the shipped migration.
@@ -70,6 +81,7 @@ run_migration() {
SSHD_PASSWORD_AUTH="${SSHD_PASSWORD_AUTH:-no}" \
SSHD_KBD_AUTH="${SSHD_KBD_AUTH:-no}" \
SSHD_RELOAD_VALID="${SSHD_RELOAD_VALID:-1}" \
SUDO_ALLOWED="${SUDO_ALLOWED:-1}" \
bash -euo pipefail
}
@@ -79,6 +91,12 @@ SSHD_ENABLED=0 SSHD_ACTIVE=0 run_migration disabled
! grep -q '^sudo ' "$test_dir/disabled.calls" || fail "disabled SSH does not prompt for privileges"
pass "SSH migration no-ops when sshd is not enabled or active"
ALREADY_HARDENED=1 SSHD_ENABLED=1 SSHD_ACTIVE=1 run_migration hardened >/dev/null
[[ ! -s $test_dir/hardened.calls ]] || fail "an already-hardened machine must not touch sshd or prompt"
grep -qxF "PasswordAuthentication no" "$test_dir/hardened/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf" ||
fail "the existing hardening config is left alone"
pass "SSH migration no-ops when the hardening config already exists"
AUTHORIZED_KEY_STATE=missing SSHD_ENABLED=1 run_migration no-key >/dev/null
[[ ! -e $test_dir/no-key/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "SSH migration must not disable passwords without an authorized key"
@@ -89,10 +107,30 @@ AUTHORIZED_KEY_STATE=invalid SSHD_ENABLED=1 run_migration invalid-key >/dev/null
fail "SSH migration must not trust a malformed authorized_keys file"
pass "SSH migration requires a usable authorized key before disabling passwords"
SSHD_ENABLED=1 SSHD_ACTIVE=1 run_migration active >/dev/null
# ssh-keygen -lf accepts a whole private-key file, so only a per-line check
# catches the classic `cp id_ed25519 authorized_keys` slip that sshd cannot use.
AUTHORIZED_KEY_STATE=private SSHD_ENABLED=1 run_migration private-key >/dev/null
[[ ! -e $test_dir/private-key/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "SSH migration must not treat a private key as an authorized key"
! grep -q '^sudo ' "$test_dir/private-key.calls" || fail "a private-key authorized_keys does not prompt for privileges"
pass "SSH migration rejects an authorized_keys holding a private key"
# StrictModes makes sshd ignore authorized_keys under a group-writable home,
# so the key that validated would be unusable and passwords the only way in.
HOME_MODE=775 SSHD_ENABLED=1 run_migration loose-home >/dev/null
[[ ! -e $test_dir/loose-home/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "SSH migration must not disable passwords when sshd would ignore the key"
! grep -q '^sudo ' "$test_dir/loose-home.calls" || fail "a group-writable home does not prompt for privileges"
pass "SSH migration leaves a group-writable home directory alone"
LOOSE_SSH_PERMS=1 SSHD_ENABLED=1 SSHD_ACTIVE=1 run_migration active >/dev/null
config="$test_dir/active/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf"
grep -qxF "PasswordAuthentication no" "$config" || fail "SSH migration disables password authentication"
grep -qxF "KbdInteractiveAuthentication no" "$config" || fail "SSH migration disables keyboard-interactive authentication"
[[ $(stat -c '%a' "$test_dir/active/home/.ssh") == "700" ]] ||
fail "SSH migration tightens ~/.ssh so StrictModes accepts the key"
[[ $(stat -c '%a' "$test_dir/active/home/.ssh/authorized_keys") == "600" ]] ||
fail "SSH migration tightens authorized_keys so StrictModes accepts the key"
grep -qxF "sudo sshd -t" "$test_dir/active.calls" || fail "SSH migration validates sshd syntax"
grep -qxF "sudo sshd -T" "$test_dir/active.calls" || fail "SSH migration validates effective sshd settings"
grep -qxF "sudo systemctl reload sshd.service" "$test_dir/active.calls" || fail "SSH migration reloads an active daemon"
@@ -104,18 +142,35 @@ SSHD_ENABLED=1 SSHD_ACTIVE=0 run_migration stopped >/dev/null
! grep -qF 'reload sshd.service' "$test_dir/stopped.calls" || fail "SSH migration must not start or reload a stopped daemon"
pass "SSH migration hardens an enabled daemon without starting it"
if SSHD_ENABLED=1 SSHD_ACTIVE=1 SSHD_PASSWORD_AUTH=yes run_migration ineffective >"$test_dir/ineffective.output" 2>&1; then
fail "SSH migration must fail when password authentication remains effective"
fi
# Conditions the migration cannot repair complete with a notice — leaving the
# machine as it was — so they never block the migrations queued behind this one.
SSHD_ENABLED=1 SSHD_ACTIVE=1 SSHD_PASSWORD_AUTH=yes run_migration ineffective >"$test_dir/ineffective.output" 2>&1 ||
fail "an ineffective drop-in must complete without blocking later migrations"
[[ ! -e $test_dir/ineffective/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "SSH migration removes an ineffective config"
! grep -qF 'reload sshd.service' "$test_dir/ineffective.calls" || fail "SSH migration must not reload ineffective hardening"
pass "SSH migration stays pending when another rule keeps password authentication enabled"
pass "SSH migration backs off when another rule keeps password authentication enabled"
if SSHD_ENABLED=1 SSHD_ACTIVE=1 SSHD_SYNTAX_VALID=0 run_migration invalid-config >"$test_dir/invalid-config.output" 2>&1; then
fail "SSH migration must fail when sshd rejects its config"
fi
SSHD_ENABLED=1 SSHD_ACTIVE=1 SSHD_SYNTAX_VALID=0 run_migration invalid-config >"$test_dir/invalid-config.output" 2>&1 ||
fail "a rejected config must complete without blocking later migrations"
[[ ! -e $test_dir/invalid-config/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "SSH migration removes a rejected config"
! grep -qF 'reload sshd.service' "$test_dir/invalid-config.calls" || fail "SSH migration must not reload rejected hardening"
pass "SSH migration fails safely when sshd rejects the config"
pass "SSH migration backs off when sshd rejects the config"
# The installed config is valid, so a failed reload only delays it until the
# next sshd restart; keep it staged rather than failing or removing it.
SSHD_ENABLED=1 SSHD_ACTIVE=1 SSHD_RELOAD_VALID=0 run_migration reload-fail >"$test_dir/reload-fail.output" 2>&1 ||
fail "a failed reload must complete without blocking later migrations"
[[ -e $test_dir/reload-fail/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "a failed reload keeps the valid hardening config staged"
pass "SSH migration keeps the hardening staged when sshd cannot reload"
# Privileges are the one genuinely retryable failure: stay pending so the
# login notifier prompts for a terminal run.
if SUDO_ALLOWED=0 SSHD_ENABLED=1 SSHD_ACTIVE=1 run_migration no-sudo >"$test_dir/no-sudo.output" 2>&1; then
fail "SSH migration must stay pending when privileges are unavailable"
fi
[[ ! -e $test_dir/no-sudo/root/etc/ssh/sshd_config.d/10-omarchy-hardening.conf ]] ||
fail "no hardening config is left behind without privileges"
pass "SSH migration stays pending until privileges are granted"