Compare commits
2 Commits
78194ec94e
...
44ad449e21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44ad449e21 | ||
|
|
3a679d7343 |
36
scripts/cli/commands/ssh/cli_ssh_commands.sh
Normal file
36
scripts/cli/commands/ssh/cli_ssh_commands.sh
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cliHandleSshCommands()
|
||||||
|
{
|
||||||
|
local action="$initial_command2"
|
||||||
|
local arg="$initial_command3"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
""|help)
|
||||||
|
cliShowSshHelp
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
local pw="off"; hostSshPasswordAuthEnabled && pw="on"
|
||||||
|
echo "user=$(hostSshUser) password_login=$pw authorized_keys=$(hostSshKeyCount)"
|
||||||
|
;;
|
||||||
|
key-add)
|
||||||
|
[[ -z "$arg" ]] && { isNotice "Usage: ssh key-add <base64-public-key>"; cliShowSshHelp; return; }
|
||||||
|
hostSshKeyAdd "$arg"
|
||||||
|
;;
|
||||||
|
key-remove)
|
||||||
|
[[ -z "$arg" ]] && { isNotice "Usage: ssh key-remove <fingerprint>"; cliShowSshHelp; return; }
|
||||||
|
hostSshKeyRemove "$arg"
|
||||||
|
;;
|
||||||
|
password-auth)
|
||||||
|
[[ -z "$arg" ]] && { isNotice "Usage: ssh password-auth <on|off>"; cliShowSshHelp; return; }
|
||||||
|
hostSshSetPasswordAuth "$arg"
|
||||||
|
;;
|
||||||
|
generate)
|
||||||
|
webuiGenerateSshAccess
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
isNotice "Unknown ssh action: $action"
|
||||||
|
cliShowSshHelp
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
23
scripts/cli/commands/ssh/cli_ssh_header.sh
Normal file
23
scripts/cli/commands/ssh/cli_ssh_header.sh
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cliShowSshHelp()
|
||||||
|
{
|
||||||
|
isHeader "LibrePortal SSH Access Commands"
|
||||||
|
echo "ssh status"
|
||||||
|
echo " Show the login user, password-login state, and authorized key count."
|
||||||
|
echo ""
|
||||||
|
echo "ssh key-add <base64-public-key>"
|
||||||
|
echo " Authorize a public key for SSH login (base64-encoded; the WebUI"
|
||||||
|
echo " encodes pasted keys for you)."
|
||||||
|
echo ""
|
||||||
|
echo "ssh key-remove <fingerprint>"
|
||||||
|
echo " Remove an authorized key by its SHA256 fingerprint."
|
||||||
|
echo ""
|
||||||
|
echo "ssh password-auth <on|off>"
|
||||||
|
echo " Enable/disable sshd password login. Disabling is refused unless at"
|
||||||
|
echo " least one key is authorized (lockout guard)."
|
||||||
|
echo ""
|
||||||
|
echo "ssh generate"
|
||||||
|
echo " Regenerate the WebUI SSH-access snapshot (access.json)."
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ files_libreportal_app=(
|
|||||||
"${restore_scripts[@]}"
|
"${restore_scripts[@]}"
|
||||||
"${setup_scripts[@]}"
|
"${setup_scripts[@]}"
|
||||||
"${source_scripts[@]}"
|
"${source_scripts[@]}"
|
||||||
|
"${ssh_scripts[@]}"
|
||||||
"${ssl_scripts[@]}"
|
"${ssl_scripts[@]}"
|
||||||
"${start_scripts[@]}"
|
"${start_scripts[@]}"
|
||||||
"${swapfile_scripts[@]}"
|
"${swapfile_scripts[@]}"
|
||||||
|
|||||||
@ -30,6 +30,8 @@ cli_scripts=(
|
|||||||
"cli/commands/restore/cli_restore_header.sh"
|
"cli/commands/restore/cli_restore_header.sh"
|
||||||
"cli/commands/setup/cli_setup_commands.sh"
|
"cli/commands/setup/cli_setup_commands.sh"
|
||||||
"cli/commands/setup/cli_setup_header.sh"
|
"cli/commands/setup/cli_setup_header.sh"
|
||||||
|
"cli/commands/ssh/cli_ssh_commands.sh"
|
||||||
|
"cli/commands/ssh/cli_ssh_header.sh"
|
||||||
"cli/commands/system/cli_system_commands.sh"
|
"cli/commands/system/cli_system_commands.sh"
|
||||||
"cli/commands/system/cli_system_header.sh"
|
"cli/commands/system/cli_system_header.sh"
|
||||||
"cli/commands/update/cli_update_commands.sh"
|
"cli/commands/update/cli_update_commands.sh"
|
||||||
|
|||||||
9
scripts/source/files/arrays/files_ssh.sh
Normal file
9
scripts/source/files/arrays/files_ssh.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This file is auto-generated by generate_arrays.sh
|
||||||
|
# Do not edit manually - run './scripts/source/files/generate_arrays.sh run' to regenerate
|
||||||
|
|
||||||
|
ssh_scripts=(
|
||||||
|
"ssh/host_access.sh"
|
||||||
|
|
||||||
|
)
|
||||||
@ -26,6 +26,7 @@ webui_scripts=(
|
|||||||
"webui/data/generators/config/webui_cli_config_set.sh"
|
"webui/data/generators/config/webui_cli_config_set.sh"
|
||||||
"webui/data/generators/config/webui_generate_configs.sh"
|
"webui/data/generators/config/webui_generate_configs.sh"
|
||||||
"webui/data/generators/config/webui_update_config.sh"
|
"webui/data/generators/config/webui_update_config.sh"
|
||||||
|
"webui/data/generators/system/webui_ssh_access.sh"
|
||||||
"webui/data/generators/system/webui_system_disk.sh"
|
"webui/data/generators/system/webui_system_disk.sh"
|
||||||
"webui/data/generators/system/webui_system_info.sh"
|
"webui/data/generators/system/webui_system_info.sh"
|
||||||
"webui/data/generators/system/webui_system_memory.sh"
|
"webui/data/generators/system/webui_system_memory.sh"
|
||||||
|
|||||||
@ -23,6 +23,7 @@ files_libreportal_cli=(
|
|||||||
"${restore_scripts[@]}"
|
"${restore_scripts[@]}"
|
||||||
"${setup_scripts[@]}"
|
"${setup_scripts[@]}"
|
||||||
"${source_scripts[@]}"
|
"${source_scripts[@]}"
|
||||||
|
"${ssh_scripts[@]}"
|
||||||
"${ssl_scripts[@]}"
|
"${ssl_scripts[@]}"
|
||||||
"${start_scripts[@]}"
|
"${start_scripts[@]}"
|
||||||
"${swapfile_scripts[@]}"
|
"${swapfile_scripts[@]}"
|
||||||
|
|||||||
163
scripts/ssh/host_access.sh
Normal file
163
scripts/ssh/host_access.sh
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#!/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 <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 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 <fingerprint>"; 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
|
||||||
|
}
|
||||||
51
scripts/webui/data/generators/system/webui_ssh_access.sh
Normal file
51
scripts/webui/data/generators/system/webui_ssh_access.sh
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Emit the admin SSH-access snapshot the WebUI reads: the install user, whether
|
||||||
|
# sshd allows password login, and the list of authorized public keys (type +
|
||||||
|
# fingerprint + comment). Public keys only — never anything secret.
|
||||||
|
|
||||||
|
webuiGenerateSshAccess()
|
||||||
|
{
|
||||||
|
local out_dir="$containers_dir/libreportal/frontend/data/ssh"
|
||||||
|
local out_file="$out_dir/access.json"
|
||||||
|
sudo mkdir -p "$out_dir"
|
||||||
|
|
||||||
|
local jsonEscape
|
||||||
|
jsonEscape() {
|
||||||
|
local s="$1"
|
||||||
|
s="${s//\\/\\\\}"; s="${s//\"/\\\"}"
|
||||||
|
s="${s//$'\t'/ }"; s="${s//$'\r'/}"; s="${s//$'\n'/ }"
|
||||||
|
printf '%s' "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
local user akf pw_auth
|
||||||
|
user=$(declare -f hostSshUser >/dev/null 2>&1 && hostSshUser || echo "${sudo_user_name:-libreportal}")
|
||||||
|
akf=$(declare -f hostSshAuthKeysFile >/dev/null 2>&1 && hostSshAuthKeysFile || echo "/home/$user/.ssh/authorized_keys")
|
||||||
|
if declare -f hostSshPasswordAuthEnabled >/dev/null 2>&1 && hostSshPasswordAuthEnabled; then
|
||||||
|
pw_auth="true"
|
||||||
|
else
|
||||||
|
pw_auth="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local keys_json="[" first=true line type comment info fpr
|
||||||
|
if sudo test -f "$akf"; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
type=$(awk '{print $1}' <<< "$line")
|
||||||
|
comment=$(awk '{ $1=""; $2=""; sub(/^[[:space:]]+/,""); print }' <<< "$line")
|
||||||
|
info=$(printf '%s\n' "$line" | ssh-keygen -l -f - 2>/dev/null)
|
||||||
|
[[ -z "$info" ]] && continue
|
||||||
|
fpr=$(awk '{print $2}' <<< "$info")
|
||||||
|
$first || keys_json+=","
|
||||||
|
first=false
|
||||||
|
keys_json+="{\"type\":\"$(jsonEscape "$type")\",\"fingerprint\":\"$(jsonEscape "$fpr")\",\"comment\":\"$(jsonEscape "$comment")\"}"
|
||||||
|
done < <(sudo cat "$akf")
|
||||||
|
fi
|
||||||
|
keys_json+="]"
|
||||||
|
|
||||||
|
printf '{"generated_at":"%s","user":"%s","password_auth":%s,"keys":%s}\n' \
|
||||||
|
"$(date -Iseconds)" "$(jsonEscape "$user")" "$pw_auth" "$keys_json" \
|
||||||
|
| sudo tee "$out_file" >/dev/null
|
||||||
|
createTouch "$out_file" "$docker_install_user" "silent"
|
||||||
|
isSuccessful "SSH access snapshot regenerated"
|
||||||
|
}
|
||||||
@ -85,6 +85,10 @@ webuiLibrePortalUpdate() {
|
|||||||
local result=$(webuiGenerateBackupLocations && webuiGenerateBackupDashboard && webuiGenerateBackupSnapshots all && webuiGenerateBackupAppStatus && webuiGenerateBackupEngines && webuiGenerateBackupSchema && webuiGenerateBackupPasswords)
|
local result=$(webuiGenerateBackupLocations && webuiGenerateBackupDashboard && webuiGenerateBackupSnapshots all && webuiGenerateBackupAppStatus && webuiGenerateBackupEngines && webuiGenerateBackupSchema && webuiGenerateBackupPasswords)
|
||||||
checkSuccess "Refreshed backup dashboard data..."
|
checkSuccess "Refreshed backup dashboard data..."
|
||||||
|
|
||||||
|
# SSH access snapshot (authorized keys + password-login state)
|
||||||
|
local result=$(webuiGenerateSshAccess)
|
||||||
|
checkSuccess "Refreshed SSH access data..."
|
||||||
|
|
||||||
# Sync app icons
|
# Sync app icons
|
||||||
local result=$(webuiSyncAppIcons)
|
local result=$(webuiSyncAppIcons)
|
||||||
checkSuccess "Synced app icons..."
|
checkSuccess "Synced app icons..."
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user