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:
co-authored by
Claude Fable 5
parent
ca4f596a14
commit
986962bb64
+53
-14
@@ -11,6 +11,15 @@ as_root() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Passwords staying enabled is the state the machine has been living with, so a
|
||||
# condition this migration cannot repair completes with a notice instead of
|
||||
# failing and holding up every migration queued behind it. Only missing
|
||||
# privileges stay pending below, because rerunning from a terminal fixes that.
|
||||
skip() {
|
||||
echo "$1 SSH password authentication remains enabled; run omarchy-setup-security-sshd to harden manually."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# The fixed setup command writes this file itself. Its presence is also the
|
||||
# machine-wide completion state, so migrations run by another account no-op.
|
||||
if [[ -e $config || -L $config ]]; then
|
||||
@@ -26,10 +35,39 @@ if ! systemctl is-enabled --quiet sshd.service 2>/dev/null &&
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# sshd reads authorized_keys one entry per line, while ssh-keygen -lf
|
||||
# fingerprints whole files in formats sshd does not accept there — a private
|
||||
# key copied in by mistake passes the file-level check even though sshd finds
|
||||
# no usable entry in it. Ask sshd's question instead: does any single line
|
||||
# parse as a public key?
|
||||
has_usable_key() {
|
||||
local line
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
if [[ $line =~ ^[[:space:]]*(#|$) ]]; then
|
||||
continue
|
||||
fi
|
||||
if ssh-keygen -lf /dev/stdin <<<"$line" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
done <"$authorized_keys"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ ! -f $authorized_keys || -L $authorized_keys || ! -s $authorized_keys || ! -r $authorized_keys ]] ||
|
||||
! ssh-keygen -lf "$authorized_keys" >/dev/null 2>&1; then
|
||||
echo "Leaving SSH password authentication unchanged because $authorized_keys has no usable public key."
|
||||
exit 0
|
||||
! has_usable_key; then
|
||||
skip "$authorized_keys has no usable public key."
|
||||
fi
|
||||
|
||||
# Under StrictModes, sshd's default, a group- or world-writable home directory,
|
||||
# ~/.ssh, or authorized_keys makes sshd ignore the key that just validated, and
|
||||
# passwords would then be the only way in. Tighten the two paths the setup
|
||||
# command owns, exactly as it does; the home directory is not ours to change.
|
||||
home_mode=$(stat -c '%a' "$HOME" 2>/dev/null) || skip "Could not inspect the permissions on $HOME."
|
||||
if (( 8#$home_mode & 8#022 )); then
|
||||
skip "$HOME is group- or world-writable, so sshd would ignore the authorized key."
|
||||
fi
|
||||
if ! chmod 700 "$HOME/.ssh" || ! chmod 600 "$authorized_keys"; then
|
||||
skip "Could not tighten the permissions on $authorized_keys."
|
||||
fi
|
||||
|
||||
echo "Disabling SSH password authentication on the existing key-based SSH setup..."
|
||||
@@ -44,26 +82,27 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Syntax alone is insufficient because sshd uses the first value it reads. If
|
||||
# another administrator rule wins, remove our ineffective file and keep the
|
||||
# migration pending rather than claiming the machine is protected.
|
||||
# The drop-in itself is always valid, so a rejection means the configuration
|
||||
# was already broken before it arrived — the administrator's to repair.
|
||||
if ! as_root sshd -t; then
|
||||
as_root rm -f -- "$config" || true
|
||||
echo "sshd rejected the hardening config. Fix the SSH configuration and run omarchy-migrate again." >&2
|
||||
exit 1
|
||||
skip "sshd rejected its configuration."
|
||||
fi
|
||||
|
||||
effective_config=$(as_root sshd -T) || {
|
||||
as_root rm -f -- "$config" || true
|
||||
echo "Could not inspect sshd's effective configuration. Run omarchy-migrate again after fixing SSH." >&2
|
||||
exit 1
|
||||
skip "Could not inspect sshd's effective configuration."
|
||||
}
|
||||
|
||||
# Syntax alone is insufficient because sshd uses the first value it reads. An
|
||||
# sshd_config predating the packaged sshd_config.d Include never reads the
|
||||
# drop-in at all, and an earlier administrator rule overrides it. Either way
|
||||
# the file is ineffective: remove it rather than claiming the machine is
|
||||
# protected.
|
||||
if ! grep -qixF "passwordauthentication no" <<<"$effective_config" ||
|
||||
! grep -qixF "kbdinteractiveauthentication no" <<<"$effective_config"; then
|
||||
as_root rm -f -- "$config" || true
|
||||
echo "Another SSH rule keeps password authentication enabled. Fix its ordering and run omarchy-migrate again." >&2
|
||||
exit 1
|
||||
skip "sshd does not apply the hardening drop-in, so an earlier rule or a config without the sshd_config.d include wins."
|
||||
fi
|
||||
|
||||
# An enabled but deliberately stopped daemon picks the file up on its next
|
||||
@@ -71,7 +110,7 @@ fi
|
||||
# sessions survive while new ones get the hardened policy.
|
||||
if systemctl is-active --quiet sshd.service 2>/dev/null; then
|
||||
if ! as_root systemctl reload sshd.service; then
|
||||
echo "The hardening config is valid but sshd could not reload it. Run omarchy-migrate again after fixing the service." >&2
|
||||
exit 1
|
||||
echo "The hardening config is installed and valid, but sshd did not reload; it takes effect when sshd next restarts." >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user