91 lines
3.1 KiB
Markdown
91 lines
3.1 KiB
Markdown
# 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@<vps-ip> '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@<vps-ip>` **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@<vps-ip> '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@<ip> 'bash -s' < deploy/hardening.sh` (hardening first, **before** the app).
|
||
3. In a new terminal: `ssh deploy@<ip>` → 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.
|