Files
OpenVPN-Monitoring-Simple/DOCS/Changes/2026-09-30_Settings_Validation.md
iclaoudezinandClaude Sonnet 5.5 05f44b9928 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>
2026-09-30 12:25:38 +00:00

3.4 KiB

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.