From a245ec26cd0bc0ac2d159d7ce5a34077e3945139 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Sun, 25 Oct 2026 23:16:18 +0100 Subject: [PATCH] fix: /status/full of the status web service gives an authenticated client the whole status, whatever web.status_content says. --- README.md | 6 ++++++ debian-package/debian/changelog | 9 +++++++++ debian-package/src/usr/lib/vpn-router/web | 9 +++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d57ebb..7ec6861 100644 --- a/README.md +++ b/README.md @@ -904,6 +904,12 @@ curl -H 'Content-Type: application/json' -d '{"name": "alice", "password": "s3cr curl -H "Authorization: Bearer $TOKEN" http://10.0.0.4:7081/config ``` +### The /status/full page + +`/status/full`, with the same token as `/config`, shows the whole status document, as `/status` +does with `web.status_content` `full`, whatever that setting says, so the Router Manager shows +every service and tunnel while the public status page shows less. + ### The /config page `/config` shows what `/` and `/status` leave out, for a client such as manage-ui: diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index a3e5689..2a4c4a9 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,12 @@ +vpn-router (0.21.5-1) unstable; urgency=medium + + fix: /status/full of the status web service gives an authenticated client the whole status, whatever web.status_content says. + + * /status/full takes the token of /config and answers the status + document as web.status_content full shows it. + + -- Slawomir Koszewski Mon, 02 Nov 2026 12:00:00 +0100 + vpn-router (0.21.4-1) unstable; urgency=medium fix: vpn-router-setup, vpn-router-ca and /ca read a CA's key and CRL from the directory each as itself, instead of failing on a CA with a CRL. diff --git a/debian-package/src/usr/lib/vpn-router/web b/debian-package/src/usr/lib/vpn-router/web index 833186d..18c38cf 100644 --- a/debian-package/src/usr/lib/vpn-router/web +++ b/debian-package/src/usr/lib/vpn-router/web @@ -8,6 +8,8 @@ Pages: / the status for people: plain text for curl and wget, a styled page (Tailwind CSS from a CDN) for a browser /status the status as JSON + /status/full + the whole status as JSON, whatever web.status_content says, for the Router Manager /config the configuration without its secrets, and what the router resolved and runs, with the installed Caddy and where it comes from, as JSON /users the users in the router's directory, whether or not a P2S system is enabled: list, @@ -40,7 +42,7 @@ Pages: the databases of each PostgreSQL cluster on the router with their sizes, as JSON (see postgresql_databases()); /config has the clusters' settings -/config, /metrics/history, /postgresql, /users, /service-accounts, /groups, /site-to-site, /backup, /ca and /account need an HS256 JWT in an Authorization: Bearer header signed +/status/full, /config, /metrics/history, /postgresql, /users, /service-accounts, /groups, /site-to-site, /backup, /ca and /account need an HS256 JWT in an Authorization: Bearer header signed with vpnrouter_auth.WEB_KEY (see vpnrouter_auth.verify). A token of /auth names a user in sub; /account serves that user, and the other paths need a member of vpnrouter_users.ADMIN_GROUP. A token of vpn-router-token has the admin claim and names no user; it may call every path but @@ -1042,7 +1044,7 @@ class Handler(BaseHTTPRequestHandler): directory = next((name for name in ('users', 'service-accounts', 'groups', 'account', 'site-to-site', 'backup', 'ca') if path == f'/{name}' or path.startswith(f'/{name}/')), None) - protected = path in ('/config', '/metrics/history', '/postgresql') or directory is not None + protected = path in ('/status/full', '/config', '/metrics/history', '/postgresql') or directory is not None signing = path in ('/auth', '/auth/refresh', '/auth/logout') state = current() if protected or signing else {} claims = self.claims(state['key']) if protected and state['key'] else None @@ -1067,6 +1069,9 @@ class Handler(BaseHTTPRequestHandler): elif protected and (refused := self.refusal(claims, path, payload)): code, kind = refused[0], 'application/json' body = json.dumps({'error': refused[1]}, indent=2).encode() + b'\n' + elif path == '/status/full' and self.command in ('GET', 'HEAD'): + document = shown(self.status(), 'full', self.client()) + body, kind, code = json.dumps(document, indent=2).encode() + b'\n', 'application/json', 200 elif path == '/config' and self.command in ('GET', 'HEAD'): body, kind, code = json.dumps(details(state), indent=2).encode() + b'\n', 'application/json', 200 elif path == '/postgresql' and self.command in ('GET', 'HEAD'):