#!/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) sudo mkdir -p "$sshdir" sudo touch "$akf" sudo chmod 700 "$sshdir" sudo chmod 600 "$akf" sudo chown -R "$u":"$u" "$sshdir" } # Count valid authorized public keys. hostSshKeyCount() { local akf; akf=$(hostSshAuthKeysFile) sudo test -f "$akf" || { echo 0; return; } sudo grep -cvE '^[[:space:]]*($|#)' "$akf" 2>/dev/null || echo 0 } # True when sshd currently allows password authentication. hostSshPasswordAuthEnabled() { local v v=$(sudo 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 "; 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 sudo grep -qF "$body" "$akf" 2>/dev/null; then isNotice "That key is already authorized." else printf '%s\n' "$pub" | sudo tee -a "$akf" >/dev/null isSuccessful "SSH key authorized for $(hostSshUser)" fi sudo chown "$(hostSshUser)":"$(hostSshUser)" "$akf" sudo 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 "; return 1; } local akf; akf=$(hostSshAuthKeysFile) sudo 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 < <(sudo cat "$akf") if [[ "$removed" -eq 1 ]]; then sudo cp "$tmp" "$akf" sudo chown "$(hostSshUser)":"$(hostSshUser)" "$akf" sudo 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)" sudo cp "$sshd_config" "$backup" sudo sed -i '/^[[:space:]]*#\?[[:space:]]*PasswordAuthentication\b/d' "$sshd_config" echo "PasswordAuthentication $value" | sudo tee -a "$sshd_config" >/dev/null if ! sudo sshd -t 2>/dev/null; then isError "sshd config test failed — restoring backup, no change made." sudo cp "$backup" "$sshd_config" return 1 fi sudo systemctl reload ssh 2>/dev/null || sudo systemctl reload sshd 2>/dev/null isSuccessful "Password login ${want} (sshd reloaded; backup at $backup)" hostSshRefreshUi }