diff --git a/README.md b/README.md index 1fd504a..c420204 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ lxs help # Show help | `lxs tool harden` | Baseline hardening: UFW + fail2ban + SSH key-only + unattended-upgrades | | `lxs tool root-password` | Change the root password (interactive or generated) | | `lxs tool update` | Update the server (apt update + upgrade + autoremove + autoclean) | +| `lxs tool root-ssh-login` | Enable or disable root login over SSH with a password | ## Project structure @@ -74,7 +75,8 @@ lxs/ ├── server-benchmark.sh ├── harden.sh ├── root-password.sh - └── update-server.sh + ├── update-server.sh + └── root-ssh-login.sh ``` After `lxs setup`, the full tree lives in `/usr/local/share/lxs/` and sub-scripts execute from disk. If `lxs.sh` is run without being installed (the one-liner mode), it falls back to downloading sub-scripts on demand. diff --git a/lxs.sh b/lxs.sh index 457ddec..12b05f5 100755 --- a/lxs.sh +++ b/lxs.sh @@ -289,6 +289,7 @@ cmd_tool() { harden) download_and_run "tools/harden.sh" "$@" ;; root-password) download_and_run "tools/root-password.sh" "$@" ;; update) download_and_run "tools/update-server.sh" "$@" ;; + root-ssh-login) download_and_run "tools/root-ssh-login.sh" "$@" ;; "") echo -e "${RED}[✗] Missing tool name. Try: lxs help${NC}"; return 1 ;; *) echo -e "${RED}[✗] Unknown tool: $tool. Try: lxs help${NC}"; return 1 ;; esac diff --git a/tools/index.sh b/tools/index.sh index 0d20299..1090755 100755 --- a/tools/index.sh +++ b/tools/index.sh @@ -62,9 +62,10 @@ menu_tools() { echo -e " ${YELLOW}[3]${NC} Harden Server" echo -e " ${GREEN}[4]${NC} Change Root Password" echo -e " ${CYAN}[5]${NC} Update Server" + echo -e " ${YELLOW}[6]${NC} Root SSH Password Login" echo -e " ${RED}[0]${NC} Back" echo "" - echo -e -n "${BOLD}Choice [0-5]: ${NC}" + echo -e -n "${BOLD}Choice [0-6]: ${NC}" read -r choice case $choice in @@ -73,8 +74,9 @@ menu_tools() { 3) run_sibling "tools/harden.sh" ;; 4) run_sibling "tools/root-password.sh" ;; 5) run_sibling "tools/update-server.sh" ;; + 6) run_sibling "tools/root-ssh-login.sh" ;; 0) return ;; - *) echo -e "${RED}[✗] Invalid option. Please select 0-5.${NC}"; sleep 1; continue ;; + *) echo -e "${RED}[✗] Invalid option. Please select 0-6.${NC}"; sleep 1; continue ;; esac if [ "$choice" != "0" ]; then diff --git a/tools/root-ssh-login.sh b/tools/root-ssh-login.sh new file mode 100755 index 0000000..da41bef --- /dev/null +++ b/tools/root-ssh-login.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +# LXS - Root SSH password login +# Description: Enable or disable root login over SSH with a password +# Author: LXS +# Date: 2025 + +# Load LXS common library (colors, separator, run_spinner, loggers, helpers) +LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}" +_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; } +eval "$_lib" +unset _lib +export LXS_LOG_FILE="/tmp/lxs_root_ssh_login.log" + +require_root "$0" "$@" + +set -u + +# Drop-in path. Numeric prefix `00-` makes it win over distro defaults — sshd +# applies the first match per option across the included files. +DROPIN_FILE="/etc/ssh/sshd_config.d/00-lxs-root-login.conf" +SSH_SERVICE="ssh" +command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^sshd\.service' && SSH_SERVICE="sshd" + +# ═══════════════════════════════════════════════════════════════════════════ +# Arguments +# ═══════════════════════════════════════════════════════════════════════════ + +ACTION="" + +for arg in "$@"; do + case "$arg" in + --enable|enable) ACTION="enable" ;; + --disable|disable) ACTION="disable" ;; + --status|status) ACTION="status" ;; + -h|--help) + cat <&2 + exit 1 + ;; + esac +done + +# ═══════════════════════════════════════════════════════════════════════════ +# Pre-checks +# ═══════════════════════════════════════════════════════════════════════════ + +if [ ! -d /etc/ssh ] || ! command -v sshd >/dev/null 2>&1; then + err "OpenSSH server is not installed." + exit 1 +fi + +# ═══════════════════════════════════════════════════════════════════════════ +# Helpers +# ═══════════════════════════════════════════════════════════════════════════ + +current_setting() { + local key=$1 + sshd -T 2>/dev/null | awk -v k="${key,,}" 'tolower($1)==k {print $2; exit}' +} + +show_status() { + local permit pwauth + permit=$(current_setting PermitRootLogin) + pwauth=$(current_setting PasswordAuthentication) + echo -e "${WHITE}${BOLD}Current SSH login settings${NC}" + show_separator + echo -e " PermitRootLogin: ${BOLD}${permit:-unknown}${NC}" + echo -e " PasswordAuthentication: ${BOLD}${pwauth:-unknown}${NC}" + if [ -f "$DROPIN_FILE" ]; then + echo -e " Drop-in: ${GRAY}${DROPIN_FILE}${NC}" + else + echo -e " Drop-in: ${GRAY}(none — distro defaults)${NC}" + fi + show_separator + if [ "${permit:-}" = "yes" ] && [ "${pwauth:-}" = "yes" ]; then + warn "Root password login is currently ENABLED." + else + ok "Root password login is currently DISABLED." + fi +} + +reload_sshd() { + if ! sshd -t 2>>"$LXS_LOG_FILE"; then + err "sshd config test failed — see ${LXS_LOG_FILE}. Reverting." + return 1 + fi + if systemctl reload "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then + ok "${SSH_SERVICE} reloaded" + return 0 + fi + # Fall back to restart (some minimal images ship without reload support) + if systemctl restart "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then + ok "${SSH_SERVICE} restarted" + return 0 + fi + err "Failed to reload/restart ${SSH_SERVICE} — see ${LXS_LOG_FILE}" + return 1 +} + +enable_root_login() { + # Warn if root has no password — enabling password auth would be useless. + local pw_status + pw_status=$(passwd -S root 2>/dev/null | awk '{print $2}') + case "$pw_status" in + L|NP) + warn "Root account has no usable password (status: ${pw_status})." + warn "Run 'lxs tool root-password' first, otherwise login will still fail." + ;; + esac + + cat > "${DROPIN_FILE}.tmp" <<'EOF' +# Managed by LXS (lxs tool root-ssh-login). Remove this file to revert. +PermitRootLogin yes +PasswordAuthentication yes +EOF + chmod 644 "${DROPIN_FILE}.tmp" + mv "${DROPIN_FILE}.tmp" "$DROPIN_FILE" + + if ! reload_sshd; then + rm -f "$DROPIN_FILE" + reload_sshd >/dev/null 2>&1 || true + return 1 + fi + ok "Root password login over SSH is now ENABLED" + warn "This weakens server security. Disable it again when no longer needed:" + echo -e " ${GRAY}lxs tool root-ssh-login --disable${NC}" +} + +disable_root_login() { + if [ ! -f "$DROPIN_FILE" ]; then + info "Drop-in not present — root password login already follows SSH defaults." + else + local backup="${DROPIN_FILE}.bak.$$" + mv "$DROPIN_FILE" "$backup" + if ! reload_sshd; then + mv "$backup" "$DROPIN_FILE" + reload_sshd >/dev/null 2>&1 || true + return 1 + fi + rm -f "$backup" + fi + ok "Root password login over SSH is now DISABLED" + echo "" + show_status +} + +# ═══════════════════════════════════════════════════════════════════════════ +# Menu (when no action is given on the CLI) +# ═══════════════════════════════════════════════════════════════════════════ + +if [ -z "$ACTION" ]; then + show_status + echo "" + echo -e " ${GREEN}[1]${NC} Enable root SSH password login" + echo -e " ${YELLOW}[2]${NC} Disable root SSH password login" + echo -e " ${RED}[0]${NC} Cancel" + echo "" + echo -e -n "${BOLD}Choice [0-2]: ${NC}" + read -r choice + case "$choice" in + 1) ACTION="enable" ;; + 2) ACTION="disable" ;; + 0|"") info "Cancelled."; exit 0 ;; + *) err "Invalid option."; exit 1 ;; + esac +fi + +case "$ACTION" in + enable) enable_root_login ;; + disable) disable_root_login ;; + status) show_status ;; +esac