From a9e063867a4afdcee1f03a643477acaeab313d28 Mon Sep 17 00:00:00 2001 From: Louis Simoneau Date: Fri, 10 Apr 2026 08:29:15 +1000 Subject: [PATCH] Harden SSH, add fail2ban, remove redundant setup.sh Disable password auth, restrict root login, limit auth retries. Add fail2ban with SSH jail (3 retries, 1hr ban). Remove setup.sh which predated Ansible and was no longer used. Co-Authored-By: Claude Opus 4.6 --- infra/ansible/playbook.yml | 52 ++++++++++++++++++++ infra/setup.sh | 97 -------------------------------------- 2 files changed, 52 insertions(+), 97 deletions(-) delete mode 100755 infra/setup.sh diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 1e1e737..9a80909 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -86,6 +86,48 @@ shell: /usr/sbin/nologin state: present + # ── SSH hardening ─────────────────────────────────────────────────────── + + - name: Harden SSH configuration + copy: + dest: /etc/ssh/sshd_config.d/hardening.conf + owner: root + group: root + mode: '0644' + content: | + PasswordAuthentication no + PermitRootLogin prohibit-password + MaxAuthTries 3 + notify: Restart sshd + + # ── Fail2ban ──────────────────────────────────────────────────────────── + + - name: Install fail2ban + apt: + name: fail2ban + state: present + + - name: Configure fail2ban SSH jail + copy: + dest: /etc/fail2ban/jail.local + owner: root + group: root + mode: '0644' + content: | + [sshd] + enabled = true + port = ssh + maxretry = 3 + bantime = 3600 + findtime = 600 + notify: Restart fail2ban + + - name: Enable and start fail2ban + systemd: + name: fail2ban + enabled: true + state: started + # ── UFW ───────────────────────────────────────────────────────────────── - name: Set UFW default incoming policy to deny @@ -381,3 +423,13 @@ systemd: name: goatcounter state: restarted + + - name: Restart sshd + systemd: + name: ssh + state: restarted + + - name: Restart fail2ban + systemd: + name: fail2ban + state: restarted diff --git a/infra/setup.sh b/infra/setup.sh deleted file mode 100755 index 0ef12c6..0000000 --- a/infra/setup.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# setup.sh — Provision a fresh Ubuntu 24.04 droplet for monotrope.au -# Run as root via: ssh root@ 'bash -s' < infra/setup.sh - -DEPLOY_USER="deploy" -SITE_DIR="/var/www/monotrope" -DEPLOY_PUBKEY="${DEPLOY_PUBKEY:-}" # Set this env var before running, or edit below - -echo "==> Updating packages" -apt-get update -y -apt-get upgrade -y - -# ── Caddy ───────────────────────────────────────────────────────────────── -echo "==> Installing Caddy" -apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl - -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ - | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg - -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \ - | tee /etc/apt/sources.list.d/caddy-stable.list - -apt-get update -y -apt-get install -y caddy - -# ── Site directory ───────────────────────────────────────────────────────── -echo "==> Creating www user and site directory" -id -u www &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin www -mkdir -p "$SITE_DIR" -chown www:www "$SITE_DIR" -chmod 755 "$SITE_DIR" - -# ── Caddyfile ────────────────────────────────────────────────────────────── -echo "==> Installing Caddyfile" -cp "$(dirname "$0")/Caddyfile" /etc/caddy/Caddyfile -chown root:caddy /etc/caddy/Caddyfile -chmod 640 /etc/caddy/Caddyfile - -systemctl enable caddy -systemctl restart caddy - -# ── UFW ──────────────────────────────────────────────────────────────────── -echo "==> Configuring UFW" -apt-get install -y ufw -ufw default deny incoming -ufw default allow outgoing -ufw allow ssh -ufw allow http -ufw allow https -ufw --force enable - -# ── Docker ──────────────────────────────────────────────────────────────── -echo "==> Installing Docker" -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ - | gpg --dearmor -o /etc/apt/keyrings/docker.gpg -chmod a+r /etc/apt/keyrings/docker.gpg - -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ - https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ - | tee /etc/apt/sources.list.d/docker.list - -apt-get update -y -apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - -systemctl enable docker - -# ── Deploy user ─────────────────────────────────────────────────────────── -echo "==> Creating deploy user" -id -u "$DEPLOY_USER" &>/dev/null || useradd --create-home --shell /bin/bash "$DEPLOY_USER" - -# Give deploy user write access to the site directory -chown -R "$DEPLOY_USER":www "$SITE_DIR" -chmod 775 "$SITE_DIR" - -# Set up SSH key auth -DEPLOY_HOME="/home/$DEPLOY_USER" -mkdir -p "$DEPLOY_HOME/.ssh" -chmod 700 "$DEPLOY_HOME/.ssh" -touch "$DEPLOY_HOME/.ssh/authorized_keys" -chmod 600 "$DEPLOY_HOME/.ssh/authorized_keys" -chown -R "$DEPLOY_USER":"$DEPLOY_USER" "$DEPLOY_HOME/.ssh" - -if [[ -n "$DEPLOY_PUBKEY" ]]; then - echo "$DEPLOY_PUBKEY" >> "$DEPLOY_HOME/.ssh/authorized_keys" - echo "==> Deploy public key installed" -else - echo "WARNING: DEPLOY_PUBKEY not set. Add your public key to $DEPLOY_HOME/.ssh/authorized_keys manually." -fi - -echo "" -echo "==> Done. Checklist:" -echo " - Point DNS A records for monotrope.au and www.monotrope.au to this server's IP" -echo " - If DEPLOY_PUBKEY was not set, add your key to $DEPLOY_HOME/.ssh/authorized_keys" -echo " - Run 'make deploy' from your local machine to push the site"