Docs: concise README, split deployment guides, add change records
- README: short overview, quick start, config table and links. - DOCS/General: Deployment_Docker.md and Deployment_Native.md (system services, HTTPS, host hardening); refresh Index.md. - DOCS/Changes: security hardening, admin username change and egress via Hysteria2 with results and verification. - Drop mentions of the built-in admin/password account. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11c1b6379b
commit
9b2882d5f4
9 files changed
+294
-46
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# Security hardening (2026-09-30)
|
||||
|
||||
Scope: a native (OpenRC) deployment of this suite on an internet-facing host. Findings from a review of exposed services, the fixes and their verification.
|
||||
|
||||
## Findings and results
|
||||
|
||||
| # | Severity | Finding | Fix | Result |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Critical | SSH accepted passwords for `root` (`PasswordAuthentication yes`, cloud-init drop-in overrode the main config); the host was already being brute-forced | `/etc/ssh/sshd_config.d/00-hardening.conf`: `PasswordAuthentication no`, `KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`, `MaxAuthTries 3`, `LoginGraceTime 30` | key login works; password login → `Permission denied (publickey)` |
|
||||
| 2 | High | Path traversal in Profiler: `username` was an unvalidated string used in `client-config/<username>.ovpn` and as an `easyrsa` argument, service runs as root | pattern `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` in `schemas.py`; `validate_username()` in `services/pki.py`; realpath containment checks in `routers/profiles.py` and `services/generator.py` | `../../tmp/pwn`, `a/b`, `..`, `-x` → 422, no file created; valid profile create/revoke works |
|
||||
| 3 | High | 2FA bypass: the temporary token issued after the password step was accepted by every protected route | `token_required` (Flask) and `verify_token` (Profiler) reject tokens with `is_2fa_pending`; only `/api/auth/verify-2fa` uses them | temp token → 401 on `/api/v1/user/me`, `/profiles-api/config`, `change-username`; full token → OK |
|
||||
| 4 | Medium | `enable_2fa` logged the OTP and TOTP secret | log line reduced to "Attempting 2FA activation" | no secrets in logs |
|
||||
| 5 | Medium | CORS `*` with credentials on both APIs | allowed origin restricted to the panel origin; Profiler methods/headers narrowed | foreign `Origin` gets no `Access-Control-Allow-Origin` |
|
||||
| 6 | Medium | No brute-force throttling outside the app rate limit | fail2ban jails `sshd`, `sshd-ddos`, `ovpmon-login` (401/429/503 on `POST /api/auth/login`); admin IP in `ignoreip` | jails active, bans observed for SSH scanners |
|
||||
| 7 | Medium | Panel served over plain HTTP | Nginx TLS on the panel port (TLS 1.2/1.3, HSTS, `nosniff`, `X-Frame-Options`, `no-store`, login `limit_req` 5 r/min, HTTP→HTTPS redirect via `error_page 497`) | HTTPS 200, TLS 1.1 rejected, rate limit returns 503 |
|
||||
|
||||
Checked, no issue: JWT algorithm pinned to HS256, random 32-byte secret; SQL f-strings only use internal table/column names; backends bound to `127.0.0.1`; Nginx workers unprivileged.
|
||||
|
||||
## Residual risks
|
||||
|
||||
- Self-signed certificate: verify the fingerprint on first visit; use a CA-issued certificate when a domain exists.
|
||||
- The APIs run as root; split privileged OpenVPN/PKI operations into a separate helper.
|
||||
- Enable 2FA for the admin account.
|
||||
- Changes were applied in place on the host; keep them in the repository (see the commit "Harden auth and API").
|
||||
|
||||
## Rollback
|
||||
|
||||
Backups were taken on the host before each change: `sshd_config`, `ovpmon.conf` (Nginx), application files in `/root/app-bak/`, previous UI build `/var/www/ovpmon.bak`.
|
||||
Reference in new issue
Block a user