Fix: Gate SSH hardening on a verified key
This commit is contained in:
@@ -33,6 +33,60 @@ for arg in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
# The public bootstrap contract runs this branch as an ordinary user with a
|
||||
# stubbed root identity. Keep its filesystem adapter unavailable to a real root
|
||||
# shell so it cannot redirect a real installation by accident.
|
||||
BOOT_ROOT="${PANAMA_BOOT_FIXTURE_ROOT:-}"
|
||||
if [[ -n "$BOOT_ROOT" && "$EUID" -eq 0 ]]; then
|
||||
echo "boot: PANAMA_BOOT_FIXTURE_ROOT is test-only" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
system_path() {
|
||||
local path="$1"
|
||||
[[ "$path" == /* ]] || return 2
|
||||
printf '%s%s\n' "$BOOT_ROOT" "$path"
|
||||
}
|
||||
|
||||
safe_authorized_keys() {
|
||||
local username="$1" user_home="$2" uid ssh_dir keys
|
||||
uid="$(id -u "$username")" || return 1
|
||||
[[ "$uid" =~ ^[0-9]+$ && "$uid" != 0 && "$user_home" == /* ]] || return 1
|
||||
ssh_dir="$user_home/.ssh"
|
||||
keys="$ssh_dir/authorized_keys"
|
||||
[[ -d "$ssh_dir" && ! -L "$ssh_dir" && -f "$keys" && ! -L "$keys" ]] || return 1
|
||||
[[ "$(stat -Lc '%u:%a' "$ssh_dir")" == "$uid:700" ]] || return 1
|
||||
[[ "$(stat -Lc '%u:%a' "$keys")" == "$uid:600" ]] || return 1
|
||||
grep -qEv '^[[:space:]]*(#|$)' "$keys"
|
||||
}
|
||||
|
||||
safe_root_authorized_keys() {
|
||||
local keys
|
||||
keys="$(system_path /root/.ssh/authorized_keys)" || return 1
|
||||
[[ -f "$keys" && ! -L "$keys" ]] || return 1
|
||||
[[ "$(stat -Lc '%u:%a' "$keys")" == '0:600' ]] || return 1
|
||||
grep -qEv '^[[:space:]]*(#|$)' "$keys"
|
||||
}
|
||||
|
||||
harden_server_ssh() {
|
||||
local username="$1" user_home="$2" sshd_dir sshd_dropin harden
|
||||
sshd_dir="$(system_path /etc/ssh/sshd_config.d)" || return 1
|
||||
sshd_dropin="$sshd_dir/90-panama.conf"
|
||||
|
||||
if [[ -f "$sshd_dropin" ]]; then
|
||||
echo "sshd is already hardened ($sshd_dropin)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf 'Harden sshd (disable root login and password auth)? [Y/n]: '
|
||||
read -r harden </dev/tty || harden=""
|
||||
if [[ ! "$harden" =~ ^[Nn] ]]; then
|
||||
printf 'PermitRootLogin no\nPasswordAuthentication no\n' >"$sshd_dropin"
|
||||
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
||||
echo "Wrote $sshd_dropin; make sure your key works before logging out."
|
||||
fi
|
||||
}
|
||||
|
||||
# Panama assumes Fedora's repositories and package names.
|
||||
if ! grep -qi '^ID=fedora' /etc/os-release 2>/dev/null; then
|
||||
echo "This looks like something other than Fedora; Panama only supports Fedora." >&2
|
||||
@@ -84,33 +138,46 @@ if [[ "$(id -u)" -eq 0 ]]; then
|
||||
passwd "$username" </dev/tty
|
||||
fi
|
||||
|
||||
# The key that reached root is the key that should reach the user, or the
|
||||
# next SSH login has no way in once root logins are closed below.
|
||||
user_home="$(getent passwd "$username" | cut -d: -f6)"
|
||||
if [[ -s /root/.ssh/authorized_keys && ! -s "$user_home/.ssh/authorized_keys" ]]; then
|
||||
echo "Copying root's authorized_keys to $username"
|
||||
mkdir -p "$user_home/.ssh"
|
||||
cp /root/.ssh/authorized_keys "$user_home/.ssh/authorized_keys"
|
||||
chmod 700 "$user_home/.ssh"
|
||||
chmod 600 "$user_home/.ssh/authorized_keys"
|
||||
chown -R "$username:$username" "$user_home/.ssh"
|
||||
# Do not close root/password access until the account's key is an exact,
|
||||
# usable login path. The fixture adapter resolves these logical system paths
|
||||
# beneath a temporary root; ordinary execution receives the original paths.
|
||||
logical_user_home="$(getent passwd "$username" | cut -d: -f6)"
|
||||
user_home=""
|
||||
if [[ "$logical_user_home" == /* ]]; then
|
||||
user_home="$(system_path "$logical_user_home")" || true
|
||||
fi
|
||||
bootstrap_home="$user_home"
|
||||
if [[ -z "$bootstrap_home" ]]; then
|
||||
bootstrap_home="$(system_path "/home/$username")"
|
||||
fi
|
||||
|
||||
# Offered rather than imposed, defaulting to yes: a VPS keeps its provider's
|
||||
# web console, so locking password and root logins out of sshd is
|
||||
# recoverable even when it goes wrong. Written as a drop-in so it never
|
||||
# fights the distribution's own sshd_config.
|
||||
SSHD_DROPIN=/etc/ssh/sshd_config.d/90-panama.conf
|
||||
if [[ -f "$SSHD_DROPIN" ]]; then
|
||||
echo "sshd is already hardened ($SSHD_DROPIN)"
|
||||
else
|
||||
printf 'Harden sshd (disable root login and password auth)? [Y/n]: '
|
||||
read -r harden </dev/tty || harden=""
|
||||
if [[ ! "$harden" =~ ^[Nn] ]]; then
|
||||
printf 'PermitRootLogin no\nPasswordAuthentication no\n' >"$SSHD_DROPIN"
|
||||
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
||||
echo "Wrote $SSHD_DROPIN; make sure your key works before logging out."
|
||||
user_ssh_dir="$user_home/.ssh"
|
||||
user_keys="$user_ssh_dir/authorized_keys"
|
||||
if [[ -n "$user_home" && ! -e "$user_keys" && ! -L "$user_keys" \
|
||||
&& ! -L "$user_ssh_dir" ]] && safe_root_authorized_keys; then
|
||||
copy_root_key=0
|
||||
if [[ ! -e "$user_ssh_dir" ]]; then
|
||||
mkdir -p "$user_ssh_dir"
|
||||
copy_root_key=1
|
||||
elif [[ ! -d "$user_ssh_dir" \
|
||||
|| "$(stat -Lc '%u:%a' "$user_ssh_dir")" != "$(id -u "$username"):700" ]]; then
|
||||
echo "SSH hardening unavailable: $username has no safe authorized_keys" >&2
|
||||
else
|
||||
copy_root_key=1
|
||||
fi
|
||||
if (( copy_root_key )); then
|
||||
echo "Copying root's authorized_keys to $username"
|
||||
cp "$(system_path /root/.ssh/authorized_keys)" "$user_keys"
|
||||
chmod 700 "$user_ssh_dir"
|
||||
chmod 600 "$user_keys"
|
||||
chown "$username:$username" "$user_ssh_dir" "$user_keys"
|
||||
fi
|
||||
fi
|
||||
|
||||
if safe_authorized_keys "$username" "$user_home"; then
|
||||
harden_server_ssh "$username" "$user_home"
|
||||
else
|
||||
echo "SSH hardening unavailable: $username has no safe authorized_keys" >&2
|
||||
fi
|
||||
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
@@ -121,13 +188,13 @@ if [[ "$(id -u)" -eq 0 ]]; then
|
||||
# Cloned straight into the user's home and owned by them: this is the
|
||||
# checkout `panama update` will pull from for the life of the machine, and
|
||||
# a root-owned .git in a user's home is a wound that never heals.
|
||||
PANAMA_PATH="$user_home/.local/share/Panama"
|
||||
PANAMA_PATH="$bootstrap_home/.local/share/Panama"
|
||||
if [[ -d "$PANAMA_PATH/.git" ]]; then
|
||||
echo "Panama is already cloned at $PANAMA_PATH; updating"
|
||||
runuser -u "$username" -- git -C "$PANAMA_PATH" pull --ff-only \
|
||||
|| echo "Could not fast-forward; installing from the clone as it is" >&2
|
||||
else
|
||||
runuser -u "$username" -- mkdir -p "$user_home/.local/share"
|
||||
runuser -u "$username" -- mkdir -p "$bootstrap_home/.local/share"
|
||||
runuser -u "$username" -- git clone "$REPO_URL" "$PANAMA_PATH"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user