Two more runtime root file-primitive subsystems moved behind self-
validating root-owned helpers so the scoped sudoers needn't grant blanket
sudo sed/tee/cp on /etc (which is root-equivalent — sudo arg wildcards
match across '/', so even path-scoped entries are bypassable):
- scripts/system/libreportal-dns: {clear|add <ip>} — edits /etc/resolv.conf
only, validates the IP argument
- scripts/system/libreportal-ssh-access: authorized_keys + sshd
PasswordAuthentication management, with the lockout guards moved INTO the
helper (the trust boundary) so a compromised manager can't bypass them
- run_privileged: _runRootHelper dispatcher + runResolv / runSshAccess
(runOwnership now uses it too)
- init.sh: initRootHelpers installs all three helpers root:root 0755 with
the manager name baked in
- setup_dns -> runResolv (+ ping de-sudo'd, works unprivileged); host_access
+ webui_ssh_access -> runSshAccess
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
121 lines
3.7 KiB
Bash
121 lines
3.7 KiB
Bash
#!/bin/bash
|
|
|
|
# Admin SSH access to THIS host. Manages the install user's authorized_keys —
|
|
# paste a public key to grant access — and, behind a lockout guard, sshd
|
|
# password authentication. Everything here is on-demand only: nothing runs
|
|
# during install or deploy. LibrePortal is the *server* here, so the admin
|
|
# brings their own public key; we never handle their private key.
|
|
#
|
|
# All the privileged work (editing ~/.ssh and /etc/ssh/sshd_config) lives in the
|
|
# root-owned helper /usr/local/sbin/libreportal-ssh-access (runSshAccess), which
|
|
# also enforces the lockout guards in the trust boundary. These functions are the
|
|
# manager-side CLI/WebUI front for it: they shape arguments and print the UX.
|
|
|
|
# The user admins actually log in as (has sudo). Falls back to libreportal.
|
|
hostSshUser()
|
|
{
|
|
echo "${sudo_user_name:-libreportal}"
|
|
}
|
|
|
|
hostSshHome()
|
|
{
|
|
local u; u=$(hostSshUser)
|
|
[[ "$u" == "root" ]] && { echo "/root"; return; }
|
|
echo "/home/$u"
|
|
}
|
|
|
|
hostSshAuthKeysFile()
|
|
{
|
|
echo "$(hostSshHome)/.ssh/authorized_keys"
|
|
}
|
|
|
|
# Refresh the WebUI access snapshot after a change. No-op if generator absent.
|
|
hostSshRefreshUi()
|
|
{
|
|
declare -f webuiGenerateSshAccess >/dev/null 2>&1 && webuiGenerateSshAccess >/dev/null 2>&1
|
|
return 0
|
|
}
|
|
|
|
hostSshEnsureDir()
|
|
{
|
|
runSshAccess ensure-dir
|
|
}
|
|
|
|
# Count valid authorized public keys.
|
|
hostSshKeyCount()
|
|
{
|
|
runSshAccess key-count 2>/dev/null || echo 0
|
|
}
|
|
|
|
# True when sshd currently allows password authentication.
|
|
hostSshPasswordAuthEnabled()
|
|
{
|
|
[[ "$(runSshAccess pw-status 2>/dev/null)" != "off" ]]
|
|
}
|
|
|
|
# Add a base64-encoded PUBLIC key to the install user's authorized_keys.
|
|
hostSshKeyAdd()
|
|
{
|
|
local key_b64="$1"
|
|
[[ -z "$key_b64" ]] && { isError "hostSshKeyAdd requires <base64-public-key>"; return 1; }
|
|
|
|
local out rc
|
|
out=$(runSshAccess key-add "$key_b64")
|
|
rc=$?
|
|
case "$out" in
|
|
added) isSuccessful "SSH key authorized for $(hostSshUser)" ;;
|
|
already-authorized) isNotice "That key is already authorized." ;;
|
|
*)
|
|
[[ $rc -ne 0 ]] && { isError "Could not add key (not a valid SSH public key?)"; return 1; }
|
|
;;
|
|
esac
|
|
hostSshRefreshUi
|
|
}
|
|
|
|
# Remove the authorized key whose fingerprint matches $1. The helper guards
|
|
# against removing the last key while password auth is off (lockout).
|
|
hostSshKeyRemove()
|
|
{
|
|
local fp="$1"
|
|
[[ -z "$fp" ]] && { isError "hostSshKeyRemove requires <fingerprint>"; return 1; }
|
|
|
|
local out rc
|
|
out=$(runSshAccess key-remove "$fp" 2>&1)
|
|
rc=$?
|
|
if [[ $rc -eq 2 ]]; then
|
|
isError "Refusing to remove the last key while password login is disabled — you'd be locked out. Re-enable password login first."
|
|
return 1
|
|
fi
|
|
case "$out" in
|
|
removed) isSuccessful "Removed SSH key $fp" ;;
|
|
no-match) isNotice "No key matched fingerprint $fp" ;;
|
|
*) [[ $rc -ne 0 ]] && { isError "Could not remove key."; return 1; } ;;
|
|
esac
|
|
hostSshRefreshUi
|
|
}
|
|
|
|
# Enable/disable sshd password authentication. Disabling is guarded (helper-side):
|
|
# there must be at least one authorized key, or you'd lock yourself out.
|
|
hostSshSetPasswordAuth()
|
|
{
|
|
local want="$1" # on|off
|
|
case "$want" in
|
|
on|off) ;;
|
|
*) isError "hostSshSetPasswordAuth requires on|off"; return 1 ;;
|
|
esac
|
|
|
|
local out rc
|
|
out=$(runSshAccess pw-set "$want" 2>&1)
|
|
rc=$?
|
|
if [[ $rc -eq 2 ]]; then
|
|
isError "Refusing to disable password login with no authorized keys — add a key first or you'll be locked out."
|
|
return 1
|
|
fi
|
|
if [[ $rc -ne 0 ]]; then
|
|
isError "sshd config test failed — restored backup, no change made."
|
|
return 1
|
|
fi
|
|
isSuccessful "Password login ${want} (sshd reloaded)."
|
|
hostSshRefreshUi
|
|
}
|