firewall_initial_setup + firewall_clear_rules (ufw/ufw-docker), host_access.sh (sshd/-T/-t, /etc/ssh, authorized_keys, systemctl reload), set_socket_permissions (docker socket test/chmod), and webui_install_systemd (systemd unit tee + systemctl) -> runSystem. These stay real-root in both modes and define part of the eventual scoped allowlist. Left the 'sudo -u <manager> crontab' run-as-manager lines for a dedicated pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
164 lines
5.5 KiB
Bash
164 lines
5.5 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.
|
|
|
|
# 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()
|
|
{
|
|
local u sshdir akf
|
|
u=$(hostSshUser)
|
|
sshdir="$(hostSshHome)/.ssh"
|
|
akf=$(hostSshAuthKeysFile)
|
|
runSystem mkdir -p "$sshdir"
|
|
runSystem touch "$akf"
|
|
runSystem chmod 700 "$sshdir"
|
|
runSystem chmod 600 "$akf"
|
|
runSystem chown -R "$u":"$u" "$sshdir"
|
|
}
|
|
|
|
# Count valid authorized public keys.
|
|
hostSshKeyCount()
|
|
{
|
|
local akf; akf=$(hostSshAuthKeysFile)
|
|
runSystem test -f "$akf" || { echo 0; return; }
|
|
runSystem grep -cvE '^[[:space:]]*($|#)' "$akf" 2>/dev/null || echo 0
|
|
}
|
|
|
|
# True when sshd currently allows password authentication.
|
|
hostSshPasswordAuthEnabled()
|
|
{
|
|
local v
|
|
v=$(runSystem sshd -T 2>/dev/null | awk '/^passwordauthentication/ {print $2}')
|
|
[[ -z "$v" ]] && v=$(grep -iE '^[[:space:]]*PasswordAuthentication' "$sshd_config" 2>/dev/null | tail -1 | awk '{print tolower($2)}')
|
|
[[ "$v" == "no" ]] && return 1
|
|
return 0 # default-on when unspecified
|
|
}
|
|
|
|
# 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 pub
|
|
pub=$(echo "$key_b64" | base64 -d 2>/dev/null | tr -d '\r' | grep -vE '^[[:space:]]*$' | head -1)
|
|
[[ -z "$pub" ]] && { isError "Empty key after decode"; return 1; }
|
|
|
|
if ! printf '%s\n' "$pub" | ssh-keygen -l -f - >/dev/null 2>&1; then
|
|
isError "Not a valid SSH public key (expected e.g. 'ssh-ed25519 AAAA... comment')"
|
|
return 1
|
|
fi
|
|
|
|
hostSshEnsureDir
|
|
local akf body
|
|
akf=$(hostSshAuthKeysFile)
|
|
body=$(awk '{print $2}' <<< "$pub")
|
|
if runSystem grep -qF "$body" "$akf" 2>/dev/null; then
|
|
isNotice "That key is already authorized."
|
|
else
|
|
printf '%s\n' "$pub" | runSystem tee -a "$akf" >/dev/null
|
|
isSuccessful "SSH key authorized for $(hostSshUser)"
|
|
fi
|
|
runSystem chown "$(hostSshUser)":"$(hostSshUser)" "$akf"
|
|
runSystem chmod 600 "$akf"
|
|
hostSshRefreshUi
|
|
}
|
|
|
|
# Remove the authorized key whose fingerprint matches $1. Guards against
|
|
# removing the last key while password auth is off (that would lock you out).
|
|
hostSshKeyRemove()
|
|
{
|
|
local fp="$1"
|
|
[[ -z "$fp" ]] && { isError "hostSshKeyRemove requires <fingerprint>"; return 1; }
|
|
local akf; akf=$(hostSshAuthKeysFile)
|
|
runSystem test -f "$akf" || { isError "No authorized_keys file"; return 1; }
|
|
|
|
if ! hostSshPasswordAuthEnabled && [[ "$(hostSshKeyCount)" -le 1 ]]; 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
|
|
|
|
local tmp; tmp=$(mktemp)
|
|
local removed=0 line lfp
|
|
while IFS= read -r line; do
|
|
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
|
|
printf '%s\n' "$line" >> "$tmp"; continue
|
|
fi
|
|
lfp=$(printf '%s\n' "$line" | ssh-keygen -l -f - 2>/dev/null | awk '{print $2}')
|
|
if [[ -n "$lfp" && "$lfp" == "$fp" ]]; then
|
|
removed=1; continue
|
|
fi
|
|
printf '%s\n' "$line" >> "$tmp"
|
|
done < <(runSystem cat "$akf")
|
|
|
|
if [[ "$removed" -eq 1 ]]; then
|
|
runSystem cp "$tmp" "$akf"
|
|
runSystem chown "$(hostSshUser)":"$(hostSshUser)" "$akf"
|
|
runSystem chmod 600 "$akf"
|
|
isSuccessful "Removed SSH key $fp"
|
|
else
|
|
isNotice "No key matched fingerprint $fp"
|
|
fi
|
|
rm -f "$tmp"
|
|
hostSshRefreshUi
|
|
}
|
|
|
|
# Enable/disable sshd password authentication. Disabling is guarded: 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
|
|
|
|
if [[ "$want" == "off" && "$(hostSshKeyCount)" -lt 1 ]]; then
|
|
isError "Refusing to disable password login with no authorized keys — add a key first or you'll be locked out."
|
|
return 1
|
|
fi
|
|
|
|
local value="yes"; [[ "$want" == "off" ]] && value="no"
|
|
local backup="${sshd_config}.libreportal.$(date +%s)"
|
|
runSystem cp "$sshd_config" "$backup"
|
|
runSystem sed -i '/^[[:space:]]*#\?[[:space:]]*PasswordAuthentication\b/d' "$sshd_config"
|
|
echo "PasswordAuthentication $value" | runSystem tee -a "$sshd_config" >/dev/null
|
|
|
|
if ! runSystem sshd -t 2>/dev/null; then
|
|
isError "sshd config test failed — restoring backup, no change made."
|
|
runSystem cp "$backup" "$sshd_config"
|
|
return 1
|
|
fi
|
|
runSystem systemctl reload ssh 2>/dev/null || runSystem systemctl reload sshd 2>/dev/null
|
|
isSuccessful "Password login ${want} (sshd reloaded; backup at $backup)"
|
|
hostSshRefreshUi
|
|
}
|