Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b29e0655 | ||
|
|
c377801be0 |
No files matched your search
@@ -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.
|
||||
@@ -413,12 +413,12 @@ 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
|
||||
access is configured, of `wg-quick@wg0` when WireGuard is, of `openvpn-server@vpn-router` when
|
||||
OpenVPN is, and of `slapd` when either OpenVPN or road-warrior access is; the number of connected
|
||||
The status holds the state of `vpn-router-setup` and `slapd`, of `strongswan` when site-to-site or
|
||||
road-warrior access is configured, of `wg-quick@wg0` when WireGuard is, and of
|
||||
`openvpn-server@vpn-router` when OpenVPN is; the number of connected
|
||||
OpenVPN clients, from the server's status file, which OpenVPN rewrites every minute; the package version; whether
|
||||
Docker is installed, and when it is, the state of `docker.service` and of `vpn-router-docker`
|
||||
(the Docker rules), which do not change `status`; whether the
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
vpn-router (0.5.3-1) unstable; urgency=medium
|
||||
|
||||
fix: the status always shows the state of slapd.
|
||||
|
||||
* /status and the status page show the state of slapd whether or not
|
||||
OpenVPN or road-warrior access is configured, since the package always
|
||||
runs it.
|
||||
|
||||
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Sat, 24 Oct 2026 12:00:00 +0200
|
||||
|
||||
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 <slawek@koszewscy.waw.pl> 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.
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -224,15 +224,13 @@ def openvpn_clients():
|
||||
|
||||
def collect(state):
|
||||
"""The status document. state says which features the router has."""
|
||||
units = {'vpn-router-setup': unit_state('vpn-router-setup.service')}
|
||||
units = {'vpn-router-setup': unit_state('vpn-router-setup.service'), 'slapd': unit_state('slapd.service')}
|
||||
if state.get('site_to_site') or state.get('p2s'):
|
||||
units['strongswan'] = unit_state('strongswan.service')
|
||||
if state.get('wireguard'):
|
||||
units['wg-quick@wg0'] = unit_state('wg-quick@wg0.service')
|
||||
if state.get('openvpn'):
|
||||
units['openvpn-server@vpn-router'] = unit_state('openvpn-server@vpn-router.service')
|
||||
if state.get('users'):
|
||||
units['slapd'] = unit_state('slapd.service')
|
||||
|
||||
status = {
|
||||
'status': 'ok' if all(value == 'active' for value in units.values()) else 'degraded',
|
||||
|
||||
Reference in new issue
Block a user