feat: unified launcher, multi-OS hardening, login alerts & auto-updates
Restructure around a single entry point (automations.sh) with a Gum wizard and a self-extracting bundle for repo-less installs. Add scripts/oslib.sh so the provisioning scripts (setup-host, harden-ssh, harden-jumphost, sshuser) run on Alpine/Debian/Alma; seed root keys from globals/. - ntfy SSH-login alerts (user, source IP, key, region, jump target) via pam_exec - daily auto-updates with AUTO_REBOOT=idle (reboots only when no SSH active) and opt-in Alpine stable-branch upgrades - generic + per-deployment cloud-init; Gitea release workflow on tag - README/LICENSE/.gitignore/.gitattributes (force LF); repo URLs -> Gitea
This commit is contained in:
+50
-16
@@ -1,10 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# sshuser -- manage SSH users on a hardened Alpine box.
|
||||
# sshuser -- manage SSH users on a hardened box (Alpine, Debian, or Alma).
|
||||
#
|
||||
# Two roles, matching harden-jumphost.sh:
|
||||
# admin -> group ssh-admins, shell /bin/ash (full shell)
|
||||
# jumper -> group ssh-jumpers, shell /sbin/nologin (ProxyJump only)
|
||||
# admin -> group ssh-admins, interactive shell (full shell)
|
||||
# jumper -> group ssh-jumpers, nologin shell (ProxyJump only)
|
||||
#
|
||||
# The interactive shell and nologin paths, and the user-management commands,
|
||||
# differ per distro -- this script detects the OS and adapts (it's installed
|
||||
# standalone, so it can't share scripts/oslib.sh).
|
||||
#
|
||||
# Two modes:
|
||||
# - TUI (gum) : run with no command, or any command with missing args
|
||||
@@ -12,7 +16,7 @@
|
||||
#
|
||||
# Install:
|
||||
# install -m 0755 sshuser.sh /usr/local/bin/sshuser
|
||||
# apk add gum # only needed for TUI mode
|
||||
# gum is only needed for TUI mode (apk add gum / apt install gum / dnf install gum)
|
||||
#
|
||||
# Usage:
|
||||
# sshuser # interactive TUI
|
||||
@@ -30,8 +34,40 @@ set -euo pipefail
|
||||
|
||||
ADMIN_GROUP="ssh-admins"
|
||||
JUMPER_GROUP="ssh-jumpers"
|
||||
ADMIN_SHELL="/bin/ash"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OS detection + per-distro user-management primitives. (Standalone install,
|
||||
# so we can't source oslib.sh -- this is the minimal slice we need.)
|
||||
# ---------------------------------------------------------------------------
|
||||
_OS_FAMILY=alpine
|
||||
if [[ -r /etc/os-release ]]; then
|
||||
_osid="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
|
||||
_oslike="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")"
|
||||
case " ${_osid} ${_oslike} " in
|
||||
*" debian "*|*" ubuntu "*) _OS_FAMILY=debian ;;
|
||||
*" rhel "*|*" fedora "*|*" centos "*|*" almalinux "*|*" rocky "*) _OS_FAMILY=rhel ;;
|
||||
*" alpine "*) _OS_FAMILY=alpine ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Admin (interactive) shell: ash on Alpine, bash elsewhere.
|
||||
case "$_OS_FAMILY" in
|
||||
alpine) ADMIN_SHELL="/bin/ash" ;;
|
||||
*) ADMIN_SHELL="/bin/bash" ;;
|
||||
esac
|
||||
[[ -x "$ADMIN_SHELL" ]] || ADMIN_SHELL="/bin/sh"
|
||||
# Nologin path: /usr/sbin/nologin on Debian, /sbin/nologin on Alpine/Alma.
|
||||
JUMPER_SHELL="/sbin/nologin"
|
||||
[[ -x "$JUMPER_SHELL" ]] || for _p in /usr/sbin/nologin /sbin/nologin; do
|
||||
[[ -x "$_p" ]] && { JUMPER_SHELL="$_p"; break; }
|
||||
done
|
||||
|
||||
user_create() { case "$_OS_FAMILY" in alpine) adduser -D -s "$2" -g "" "$1";; *) useradd -m -s "$2" "$1";; esac; }
|
||||
user_join_group() { case "$_OS_FAMILY" in alpine) adduser "$1" "$2";; *) usermod -aG "$2" "$1";; esac; }
|
||||
user_leave_group() { case "$_OS_FAMILY" in alpine) deluser "$1" "$2" 2>/dev/null || true;; *) gpasswd -d "$1" "$2" 2>/dev/null || true;; esac; }
|
||||
user_delete() { case "$_OS_FAMILY" in alpine) deluser --remove-home "$1" 2>/dev/null || deluser "$1";; *) userdel -r "$1" 2>/dev/null || userdel "$1";; esac; }
|
||||
set_user_shell() { usermod -s "$2" "$1" 2>/dev/null || chsh -s "$2" "$1" 2>/dev/null \
|
||||
|| sed -i "s|^\($1:.*:\)[^:]*$|\1$2|" /etc/passwd; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -147,8 +183,8 @@ cmd_add() {
|
||||
confirm "Create user $user as $role (group $group, shell $shell)?" || { warn "Aborted."; exit 1; }
|
||||
|
||||
log "Creating $user..."
|
||||
adduser -D -s "$shell" -g "" "$user"
|
||||
adduser "$user" "$group"
|
||||
user_create "$user" "$shell"
|
||||
user_join_group "$user" "$group"
|
||||
|
||||
if [[ -n "$key" ]]; then
|
||||
local ak; ak=$(ssh_dir_setup "$user")
|
||||
@@ -184,7 +220,7 @@ cmd_edit() {
|
||||
"add ssh key") ADD_KEY_ARG=$(ask "Paste SSH public key") ;;
|
||||
"remove ssh key") REMOVE_KEY_ARG=$(ask "Substring of key to remove (comment is fine)") ;;
|
||||
"change role") ROLE_ARG=$(choose "New role" admin jumper) ;;
|
||||
"change shell") SHELL_ARG=$(ask "New shell" "/bin/ash") ;;
|
||||
"change shell") SHELL_ARG=$(ask "New shell" "$ADMIN_SHELL") ;;
|
||||
*) warn "Cancelled."; return 0 ;;
|
||||
esac
|
||||
fi
|
||||
@@ -195,16 +231,14 @@ cmd_edit() {
|
||||
# Remove from the other ssh group, add to the target.
|
||||
local other
|
||||
[[ "$new_group" == "$ADMIN_GROUP" ]] && other="$JUMPER_GROUP" || other="$ADMIN_GROUP"
|
||||
deluser "$user" "$other" 2>/dev/null || true
|
||||
adduser "$user" "$new_group" 2>/dev/null || true
|
||||
usermod -s "$new_shell" "$user" 2>/dev/null || \
|
||||
sed -i "s|^\($user:.*:\)[^:]*$|\1$new_shell|" /etc/passwd
|
||||
user_leave_group "$user" "$other"
|
||||
user_join_group "$user" "$new_group"
|
||||
set_user_shell "$user" "$new_shell"
|
||||
fi
|
||||
|
||||
if [[ -n "${SHELL_ARG:-}" ]]; then
|
||||
log "Setting $user shell to $SHELL_ARG"
|
||||
usermod -s "$SHELL_ARG" "$user" 2>/dev/null || \
|
||||
sed -i "s|^\($user:.*:\)[^:]*$|\1$SHELL_ARG|" /etc/passwd
|
||||
set_user_shell "$user" "$SHELL_ARG"
|
||||
fi
|
||||
|
||||
if [[ -n "${ADD_KEY_ARG:-}" ]]; then
|
||||
@@ -238,7 +272,7 @@ cmd_remove() {
|
||||
|
||||
confirm "DELETE user $user and their home directory?" || { warn "Aborted."; exit 1; }
|
||||
log "Deleting $user..."
|
||||
deluser --remove-home "$user" 2>/dev/null || deluser "$user"
|
||||
user_delete "$user"
|
||||
log "Done."
|
||||
}
|
||||
|
||||
@@ -294,7 +328,7 @@ cmd_show() {
|
||||
}
|
||||
|
||||
cmd_tui() {
|
||||
have_gum || err "TUI mode requires gum: 'apk add gum'. Or use CLI flags (sshuser --help)."
|
||||
have_gum || err "TUI mode requires gum (apk/apt/dnf install gum). Or use CLI flags (sshuser --help)."
|
||||
local action
|
||||
action=$(choose "What do you want to do?" \
|
||||
"add user" "edit user" "remove user" "list users" "show user" "quit")
|
||||
|
||||
Reference in New Issue
Block a user