nigig-org/nimanyatta/deploy/hardening.sh
andodeki fd8b0632ca
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
Include nimanyatta as normal tree (not embedded git)
2026-09-26 09:29:36 +03:00

165 lines
7.5 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# hardened-deploy.sh - One-shot hardening script for a fresh Ubuntu 22.04/24.04 VPS.
#
# Run as root over SSH immediately after first login, BEFORE deploying the app:
# ssh root@<vps-ip> 'bash -s' < deploy/hardening.sh
#
# What it does (in order):
# 1. Refuses to run unless you are root
# 2. Refuses to run if a deploy user already exists (prevents double-run)
# 3. Creates a dedicated non-root user with sudo privileges
# 4. Disables root SSH login
# 5. Disables SSH password authentication (key-based only)
# 6. Cleans up unnecessary SSH configuration directives
# 7. Configures firewall rules (UFW) - SSH + app ports only
# 8. Adds brute-force protection (fail2ban)
# 9. Validates sshd_config and reloads the SSH service
# 10. Prints SSH log monitoring instructions
#
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────────────────
# The non-root user that will own SSH and the deployment.
DEPLOY_USER="${DEPLOY_USER:-deploy}"
# Ports to open in the firewall besides SSH (space separated).
APP_PORTS="${APP_PORTS:-8080}"
# SSH port. Change to a non-standard port if you want.
SSH_PORT="${SSH_PORT:-22}"
# Directory where the deploy user's authorized_keys will be placed.
DEPLOY_HOME="/home/${DEPLOY_USER}"
# ── Colours for output ─────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
info() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
die() { echo -e "${RED}[x]${NC} $*"; exit 1; }
# ── Preconditions ──────────────────────────────────────────────────────────
[ "$(id -u)" -eq 0 ] || die "Must run as root. Re-run with: sudo bash deploy/hardening.sh"
if id "${DEPLOY_USER}" >/dev/null 2>&1; then
die "User '${DEPLOY_USER}' already exists. Refusing to re-run hardening (it is not idempotent)."
fi
if [ ! -f /etc/os-release ]; then
die "Cannot detect OS. This script targets Ubuntu 22.04/24.04."
fi
. /etc/os-release
case "${VERSION_ID:-}" in
22.04|24.04) : ;;
*) warn "Untested OS: ${PRETTY_NAME:-unknown}. Proceeding anyway." ;;
esac
# ── 1. Update system packages ──────────────────────────────────────────────
info "Updating package lists and upgrading system packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get upgrade -y -qq
apt-get install -y -qq ufw fail2ban unattended-upgrades apt-listchanges >/dev/null
# ── 2. Create the deploy user ──────────────────────────────────────────────
info "Creating non-root user '${DEPLOY_USER}' with sudo privileges"
useradd --create-home --shell /bin/bash --groups sudo "${DEPLOY_USER}"
mkdir -p "${DEPLOY_HOME}/.ssh"
chmod 700 "${DEPLOY_HOME}/.ssh"
# ── 3. Install your SSH key ────────────────────────────────────────────────
# If you run this via `ssh root@ip 'bash -s'`, the root key may not exist on disk.
if [ -f /root/.ssh/authorized_keys ]; then
info "Copying root authorized_keys to ${DEPLOY_USER}"
cp /root/.ssh/authorized_keys "${DEPLOY_HOME}/.ssh/authorized_keys"
else
warn "No /root/.ssh/authorized_keys found. Add your key manually:"
warn " ssh-copy-id ${DEPLOY_USER}@<vps-ip> (or) "
warn " echo 'ssh-ed25519 AAAA... your@email' | sudo tee ${DEPLOY_HOME}/.ssh/authorized_keys"
fi
chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${DEPLOY_HOME}/.ssh"
chmod 600 "${DEPLOY_HOME}/.ssh/authorized_keys"
# ── 4. Back up + rewrite sshd_config ───────────────────────────────────────
info "Hardening SSH configuration"
cp /etc/ssh/sshd_config "/etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)"
cat > /etc/ssh/sshd_config <<SSHCONF
# Hardened by nimanyatta deploy/hardening.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
Port ${SSH_PORT}
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
AllowUsers ${DEPLOY_USER}
MaxAuthTries 4
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
Protocol 2
SSHCONF
# ── 5. Validate + reload SSH ───────────────────────────────────────────────
info "Validating sshd_config"
sshd -t || die "sshd_config validation FAILED - fix manually, original saved as .bak"
info "Reloading SSH service (connection will NOT drop if config is valid)"
systemctl reload ssh || systemctl restart ssh
info "SSH reloaded successfully"
# ── 6. Firewall (UFW) ──────────────────────────────────────────────────────
info "Configuring UFW firewall"
ufw default deny incoming
ufw default allow outgoing
ufw allow "${SSH_PORT}/tcp" comment 'SSH'
for p in ${APP_PORTS}; do
ufw allow "${p}/tcp" comment "App port ${p}"
done
ufw --force enable
ufw status verbose
# ── 7. fail2ban brute-force protection ─────────────────────────────────────
info "Configuring fail2ban"
cat > /etc/fail2ban/jail.local <<FAIL2BAN
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ${SSH_PORT}
logpath = %(sshd_backend)s
FAIL2BAN
systemctl enable --now fail2ban
fail2ban-client status sshd 2>/dev/null || true
# ── 8. Automatic security updates ──────────────────────────────────────────
info "Enabling unattended security upgrades"
cat > /etc/apt/apt.conf.d/50unattended-upgrades.local <<UPD
Unattended-Upgrade::Allowed-Origins {
"\${distro_id}:\${distro_codename}-security";
"\${distro_id}:\${distro_codename}-updates";
};
Unattended-Upgrade::Automatic-Reboot "false";
UPD
systemctl enable --now unattended-upgrades
# ── 9. Summary ─────────────────────────────────────────────────────────────
cat <<SUMMARY
════════════════════════════════════════════════════════════════════
Hardening complete.
Deploy user : ${DEPLOY_USER}
SSH port : ${SSH_PORT}
Root login : DISABLED
Passwords : DISABLED (key-based auth only)
NEXT STEPS (do NOT close this SSH session yet):
1. In a NEW terminal, test login as ${DEPLOY_USER}:
ssh -p ${SSH_PORT} ${DEPLOY_USER}@<vps-ip>
2. Only after that login works, exit this session and deploy the app.
3. To watch for brute-force attempts:
sudo tail -f /var/log/auth.log | grep -E 'Failed|Invalid user'
sudo fail2ban-client status sshd
════════════════════════════════════════════════════════════════════
SUMMARY