diff --git a/ADR.md b/ADR.md index 4d16768..ad6d3e5 100644 --- a/ADR.md +++ b/ADR.md @@ -55,7 +55,7 @@ address is picked up at the next run of `vpn-router-setup`. ## ADR-3: /config is authenticated with a short-lived JWT signed with a shared secret -Status: Accepted; amended by ADR-7 +Status: Accepted; amended by ADR-7 and ADR-8 Context: A client such as manage-ui needs the configuration and the resolved state of the router, which `/` and `/status` must not show. The service has only the standard library, listens @@ -174,3 +174,18 @@ setup is in, and follows a changed address, port or `web.enabled` within 30 seco restart. `web.json` no longer holds the address, the port or the secret, so the secret is only in `vpn-router.json`. A change of the secret applies to the next request. The interfaces are read every 30 seconds, which costs a few `ip` commands. + +## ADR-8: /config leaves secret options out + +Status: Accepted + +Context: `/config` showed the secret options of the configuration with their values replaced by +`********`. That still tells a reader which secrets are set, and a secret has no use outside the +router, so a client never needs it. + +Decision: The configuration that `vpn-router-setup` writes for the web service has no secret +options at all. They are recognised by name as before: `psk`, and options with `secret`, +`password`, `token` or a `_key` ending in the name. + +Consequences: No secret reaches the web service's state file or any answer of the API. A client +cannot tell whether a secret is set. A new secret option still needs a name that matches. diff --git a/README.md b/README.md index 18a92ca..9c80fd0 100644 --- a/README.md +++ b/README.md @@ -413,7 +413,7 @@ authentication, and shows no configuration: no key, identity, address or subnet. | --- | --- | | `/` | The status for people: plain text for `curl` and `wget`, a page styled with Tailwind CSS for a browser, in the browser's light or dark scheme, that reloads itself every minute (the style sheet is loaded from a CDN, so the browser needs internet access; the text is readable without it) | | `/status` | The same status as JSON | -| `/config` | The configuration with its secrets masked, and what the router resolved and runs, as JSON; needs a token | +| `/config` | The configuration without its secret options, and what the router resolved and runs, as JSON; needs a token | | `/users` | The users of OpenVPN and road-warrior access; needs a token | The status holds the state of `vpn-router-setup`, of `strongswan` when site-to-site or road-warrior @@ -437,7 +437,7 @@ curl http://10.0.0.4:8080/status | Key | Content | | --- | --- | -| `configuration` | `vpn-router.json` with the defaults applied and the secrets masked (`psk`, `jwt_secret`, and any option named like a password, token or key) | +| `configuration` | `vpn-router.json` with the defaults applied, without its secret options (`psk`, `jwt_secret`, and any option named like a password, token or key) | | `network` | The external and internal interface, their addresses, the internal gateway, the local and remote IKE identities and subnets, and the IPv4 routes as `ip -j -4 route` prints them | | `ipsec` | `connections` and `sas`: one object per connection and per IKE SA, read from `swanctl --list-conns --pretty` and `swanctl --list-sas --pretty`, with the connection's name under `name` and swanctl's keys and string values | | `wireguard` | The public key and port of `wg0`, and each peer's public key, endpoint, allowed IPs, handshake age and traffic; no private or preshared key | diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index cdaafd1..7221e6e 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,13 @@ +vpn-router (0.5.2-1) unstable; urgency=medium + + fix: /config no longer shows secret options. + + * The configuration shown by /config leaves out the secret options (psk, + jwt_secret, and options named like a password, token or key) instead of + showing them masked. + + -- Slawomir Koszewski Sat, 24 Oct 2026 11:00:00 +0200 + vpn-router (0.5.1-1) unstable; urgency=medium fix: the web service works in every state of the router. diff --git a/debian-package/src/usr/lib/vpn-router/setup b/debian-package/src/usr/lib/vpn-router/setup index cab3bb4..f5b676c 100755 --- a/debian-package/src/usr/lib/vpn-router/setup +++ b/debian-package/src/usr/lib/vpn-router/setup @@ -33,7 +33,7 @@ WEB_STATE = pathlib.Path('/run/vpn-router/web.json') FILTER_ANCHOR = '# End required lines' -#: Configuration options whose values are masked wherever the configuration is shown. +#: Configuration options left out wherever the configuration is shown. SECRET_OPTION = re.compile(r'(^psk$)|secret|password|token|_key$') BLOCKS = ( @@ -381,14 +381,13 @@ def refresh_web(cfg, ctx): """Hand the web service the features and facts it shows. The service reads the file at each request: the features of the router, - the configuration with its secrets masked and the facts resolved from the - system, which it cannot work out with the standard library alone. The file - is readable by root only. The service follows a changed address or port - itself. + the configuration without its secret options and the facts resolved from + the system, which it cannot work out with the standard library alone. The + file is readable by root only. The service follows a changed address or + port itself. """ - masked = { - section: {option: '********' if SECRET_OPTION.search(option) and value else value - for option, value in options.items()} + public = { + section: {option: value for option, value in options.items() if not SECRET_OPTION.search(option)} for section, options in cfg.items() } WEB_STATE.parent.mkdir(parents=True, exist_ok=True) @@ -400,7 +399,7 @@ def refresh_web(cfg, ctx): 'wireguard': bool(vpnrouter.wg_ready(ctx)), 'openvpn': bool(vpnrouter.openvpn_ready(ctx)), 'users': bool(vpnrouter.p2s_ready(ctx) or vpnrouter.openvpn_ready(ctx)), - 'configuration': masked, + 'configuration': public, 'network': { 'external_interface': ctx['wan_iface'], 'internal_interface': ctx['int_iface'],