165 lines
7.5 KiB
Bash
Executable file
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
|