Profiler: validate server/PKI settings before they reach OpenVPN config

- Schema validators on the update models (ports, subnet/mask, routes,
  DNS, public host, loopback-only management address, MTU/MSS, script
  paths, PKI DN fields, key size and lifetimes).
- Script paths must be root-owned, non-writable files directly inside
  /etc/openvpn/scripts (services/validation.py).
- Generators refuse values with newlines, quotes, backslashes or control
  characters; router maps validation errors to HTTP 400.
- Add change record and links.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
iclaoudezinandClaude Sonnet 5.5 committed 2026-09-30 12:25:38 +00:00
1 parent 5de0501cbc
commit 05f44b9928
7 files changed
+279 -6

No files matched your search

@@ -0,0 +1,46 @@
# Server settings validation (2026-09-30)
Problem: values of the server/PKI settings (`PUT /profiles-api/config/server|pki`) were free strings that were rendered into `server.conf` (written by a root process) and passed to `easyrsa`. A user with a valid token could inject extra OpenVPN directives (for example `up`/`plugin`) or shell-relevant DN characters, which is a path to code execution as root.
## Defence in three layers
| Layer | Where | What it does |
|---|---|---|
| A. Schema | `APP_PROFILER/schemas.py` (`SystemSettingsUpdate`, `PKISettingUpdate`) | Rejects invalid values with 422. Applies to updates only, so already stored values never break `GET /config` |
| B. Scripts | `services/validation.py: check_script`, used in `services/generator.py` | `connect_script`/`disconnect_script` must be a file directly inside `/etc/openvpn/scripts/`, owned by root, not group/other-writable, in a root-owned non-writable directory (symlinks out of the directory are rejected) |
| C. Renderer | `services/generator.py` | Before writing `server.conf` / client `.ovpn`, every value is checked for newline, CR, NUL, other control characters, `"` and `\`; on violation nothing is written and the API returns 400 |
## Rules (layer A)
| Field | Rule |
|---|---|
| `port`, `management_port` | 1-65535 |
| `vpn_network` + `vpn_netmask` | valid IPv4 network address for a contiguous mask, prefix /8-/30 |
| `split_routes[]` | `a.b.c.d/nn` or `a.b.c.d mask`, at most 256 |
| `dns_servers[]` | IPv4/IPv6 addresses, at most 8 |
| `public_ip` | IP address or hostname |
| `management_interface_address` | loopback only |
| `tun_mtu`, `mssfix` | 576-9000, 536-1500 |
| `connect_script`, `disconnect_script` | empty or `/etc/openvpn/scripts/<letters, digits, . _ ->` |
| PKI `fqdn_ca`, `fqdn_server` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$`, no `..` |
| PKI `easyrsa_dn` | `cn_only` or `org` |
| PKI country / province, city, org, ou / email | `^[A-Z]{2}$` / `^[A-Za-z0-9 .,_-]{0,64}$` / simple email pattern |
| PKI `key_size`, days | 2048/3072/4096; 1-36500 |
## Results
| Check | Result |
|---|---|
| Round trip of current server and PKI settings via `PUT` | 200 |
| 17 malicious/invalid values (newline in DNS or routes, quote, `../` and `/tmp` scripts, port 0/70000, bad mask, host bits in network, non-loopback management, `a b;c` host, MTU 100, `/` in organisation, lowercase country, `../` in FQDN, key size 512) | all 422 with a clear message |
| 4 valid changes (DNS incl. IPv6, routes in both notations, hostname, allowed script path) | 200 |
| Script checks: root-owned 755 file / group-writable / symlink out of the directory / missing / outside the directory / traversal / empty | accepted / rejected / rejected / rejected / rejected / rejected / accepted |
| Layer C with unsafe values injected past the schema (newline in DNS, quote in route, newline in script, script outside the directory, newline in management address) | all blocked, nothing written |
| Regression: settings render to `server.conf` | identical to the live config |
Settings were restored after the tests and left unchanged.
## Notes
- The scripts directory `/etc/openvpn/scripts/` does not exist by default; create it as `root:root 755` and put root-owned `755` scripts there before enabling `user_defined_cdscripts`.
- Next step (planned): run the API as an unprivileged user with a root helper that re-validates the config before installing it. See the project plan.
+1
View File
@@ -13,6 +13,7 @@ Welcome to the documentation for the OpenVPN Monitor suite.
## 🛠 Changes and results
- [Security hardening (2026-09-30)](../Changes/2026-09-30_Security_Hardening.md)
- [Admin username change (2026-09-30)](../Changes/2026-09-30_Admin_Username_Change.md)
- [Settings validation (2026-09-30)](../Changes/2026-09-30_Settings_Validation.md)
- [Egress via Hysteria2 (2026-09-30)](../Changes/2026-09-30_Egress_via_Hysteria2.md)
## 🔍 Core Monitoring (`APP_CORE`)