# Deployment security workflow A fresh VPS with a public IP is attacked within hours. This workflow hardens the server **before** the app is deployed, and re-verifies the state on every deploy. ## The two phases ### 1. One-time hardening (on the fresh VPS) ```bash # From your machine, immediately after first login as root: ssh root@ 'bash -s' < deploy/hardening.sh ``` The script refuses to run twice, and performs, in order: | Step | What it does | |------|--------------| | 1 | Updates system packages, installs `ufw`, `fail2ban`, `unattended-upgrades` | | 2 | Creates a non-root `deploy` user with sudo privileges | | 3 | Copies your root SSH key to the `deploy` user | | 4 | Rewrites `sshd_config`: no root login, no password auth, `AllowUsers deploy`, MaxAuthTries 4, keeps your SSH port | | 5 | Validates and reloads `sshd` (does not drop your session) | | 6 | UFW: deny all incoming except SSH + app ports (default `8080`) | | 7 | fail2ban: bans 5 failed attempts within 10 min for 1 hour | | 8 | Enables unattended security upgrades | > **Critical:** after the script prints its summary, open a **new terminal** and test > `ssh -p 22 deploy@` **before closing the root session**. Only after that > login works should you deploy the app. ### 2. Verify on every deploy (CI + manual) #### Automated (GitHub Actions) `.github/workflows/predeploy-security.yml` runs on every push to `main` / PR and blocks deploy if any job fails: - **gitleaks** – scans for leaked secrets in the repo - **shellcheck** – lints both deploy scripts - **cargo audit** – checks dependencies for known vulnerabilities - **release build** – confirms `broadcast_server` builds with `--features b_server` - **hardening verify** – syntax-checks and smoke-runs the verification script #### Manual (on the active VPS, any time) ```bash # Audits the live server state; exit 0 = hardened, non-zero = something reverted. sudo bash deploy/verify-hardening.sh ``` It prints `[PASS]`/`[FAIL]` per check, plus the last 10 unique brute-force usernames from `/var/log/auth.log`. ## Configuration Both scripts honour environment variables: ```bash DEPLOY_USER=deploy # the sudo user to create / check APP_PORTS="8080" # extra firewall ports (space-separated) SSH_PORT=22 # your SSH port ``` Example with custom values: ```bash SSH_PORT=2222 APP_PORTS="8080 9090" ssh root@ 'bash -s' < deploy/hardening.sh ``` ## Monitoring (the step you already started) ```bash # Tail for brute-force attempts sudo tail -f /var/log/auth.log | grep -E 'Failed|Invalid user' # Watch fail2ban bans sudo fail2ban-client status sshd # Blocklist grows here as attackers get banned sudo cat /etc/fail2ban/jail.local ``` ## Order of operations for a new deploy 1. Provision VPS → get public IP. 2. `ssh root@ 'bash -s' < deploy/hardening.sh` (hardening first, **before** the app). 3. In a new terminal: `ssh deploy@` → confirm it works, then close root session. 4. Deploy the app binary. 5. `sudo bash deploy/verify-hardening.sh` → confirm all `[PASS]`. 6. Push code → CI re-runs pre-deploy checks automatically.