feature: /remote-access of the web service shows and changes the settings of IKEv2 road-warrior, OpenVPN and WireGuard access and whether each runs, for the Router Manager's Remote access page.
Release / release (push) Successful in 14s
Release / release (push) Successful in 14s
This commit is contained in:
1 parent
07dbe91d0c
commit
9255d56276
4 files changed
+86
-3
No files matched your search
@@ -901,7 +901,7 @@ curl http://10.0.0.4:7081/status
|
|||||||
|
|
||||||
### Tokens and signing in
|
### Tokens and signing in
|
||||||
|
|
||||||
`/config`, `/users`, `/service-accounts`, `/groups`, `/site-to-site`, `/shell` and `/account` need a JWT in an `Authorization: Bearer` header, signed with
|
`/config`, `/users`, `/service-accounts`, `/groups`, `/site-to-site`, `/backup`, `/remote-access`, `/ca`, `/shell` and `/account` need a JWT in an `Authorization: Bearer` header, signed with
|
||||||
HS256 by the key the web service makes on its first run, `/etc/vpn-router/web.key` (root only).
|
HS256 by the key the web service makes on its first run, `/etc/vpn-router/web.key` (root only).
|
||||||
The key never leaves the router; while it does not exist these paths and `/auth` are not served
|
The key never leaves the router; while it does not exist these paths and `/auth` are not served
|
||||||
(404). A missing, expired or wrongly signed token, or one of another algorithm, gets 401 with
|
(404). A missing, expired or wrongly signed token, or one of another algorithm, gets 401 with
|
||||||
@@ -1142,6 +1142,23 @@ The answer of `GET /backup` holds:
|
|||||||
|
|
||||||
A change is made, checked and applied as for `/site-to-site`.
|
A change is made, checked and applied as for `/site-to-site`.
|
||||||
|
|
||||||
|
### The /remote-access API
|
||||||
|
|
||||||
|
`/remote-access` shows and changes the remote access systems, IKEv2 road-warrior (`p2s`),
|
||||||
|
OpenVPN (`openvpn`) and WireGuard (`wireguard`), with the same token as `/users`.
|
||||||
|
|
||||||
|
| Request | Body | Answer |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET /remote-access` | | `{"p2s", "openvpn", "wireguard", "running", "local_fqdn"}` |
|
||||||
|
| `PUT /remote-access/<system>` | any of the settings of the section `p2s`, `openvpn` or `wireguard` | the section |
|
||||||
|
|
||||||
|
`running` tells for each system whether setup runs it. IKEv2 and OpenVPN run once enabled with an
|
||||||
|
address pool, `wan.local_fqdn` set and the router's VPN server certificate issued by the P2S CA;
|
||||||
|
WireGuard once enabled with an address. A system enabled without its address pool or address is
|
||||||
|
refused. A change is made, checked and applied as for `/site-to-site`. The router's firewall
|
||||||
|
follows the settings; a cloud firewall in front of the router, such as an Azure network security
|
||||||
|
group, must allow the OpenVPN and WireGuard ports itself.
|
||||||
|
|
||||||
### The /ca API
|
### The /ca API
|
||||||
|
|
||||||
`/ca` manages the PKI (see Certificates) with the same token as `/users`, with the module
|
`/ca` manages the PKI (see Certificates) with the same token as `/users`, with the module
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
vpn-router (0.24.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
feature: /remote-access of the web service shows and changes the settings of IKEv2 road-warrior, OpenVPN and WireGuard access and whether each runs, for the Router Manager's Remote access page.
|
||||||
|
|
||||||
|
* GET /remote-access answers the p2s, openvpn and wireguard sections,
|
||||||
|
which of them setup runs, and wan.local_fqdn; PUT
|
||||||
|
/remote-access/<system> changes one section and restarts setup.
|
||||||
|
* p2s.enabled and openvpn.enabled need their address_pool, and
|
||||||
|
wireguard.enabled its address.
|
||||||
|
|
||||||
|
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Mon, 02 Nov 2026 23:00:00 +0100
|
||||||
|
|
||||||
vpn-router (0.23.2-1) unstable; urgency=medium
|
vpn-router (0.23.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
fix: the shell of a user's own POSIX account opens on the first setup that sets up console login, and the journal records each refused shell with the reason.
|
fix: the shell of a user's own POSIX account opens on the first setup that sets up console login, and the journal records each refused shell with the reason.
|
||||||
|
|||||||
@@ -435,6 +435,9 @@ def validate(cfg):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ConfigError(f'openvpn.address_pool: {openvpn_pool!r} is not an IPv4 network')
|
raise ConfigError(f'openvpn.address_pool: {openvpn_pool!r} is not an IPv4 network')
|
||||||
|
for system, option in (('p2s', 'address_pool'), ('openvpn', 'address_pool'), ('wireguard', 'address')):
|
||||||
|
if _get(cfg, system, 'enabled') and not _get(cfg, system, option):
|
||||||
|
raise ConfigError(f'{system}.enabled needs {system}.{option}')
|
||||||
_check_address(_get(cfg, 'local', 'int_addr'), 'local.int_addr')
|
_check_address(_get(cfg, 'local', 'int_addr'), 'local.int_addr')
|
||||||
_check_address(_get(cfg, 'local', 'int_gateway_ip'), 'local.int_gateway_ip')
|
_check_address(_get(cfg, 'local', 'int_gateway_ip'), 'local.int_gateway_ip')
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ Pages:
|
|||||||
applied by vpn-router-setup (see site_to_site())
|
applied by vpn-router-setup (see site_to_site())
|
||||||
/backup the backup settings, the last run and the local archives: show, change and run
|
/backup the backup settings, the last run and the local archives: show, change and run
|
||||||
now (see backup())
|
now (see backup())
|
||||||
|
/remote-access
|
||||||
|
the settings of IKEv2 road-warrior, OpenVPN and WireGuard access and whether each
|
||||||
|
runs: show and change, applied by vpn-router-setup (see remote_access())
|
||||||
/ca the PKI: its settings, the root and issuing CAs, the certificates issued; change the
|
/ca the PKI: its settings, the root and issuing CAs, the certificates issued; change the
|
||||||
settings, issue and revoke certificates, and take the certificate of an issuing CA
|
settings, issue and revoke certificates, and take the certificate of an issuing CA
|
||||||
and a root CRL signed offline (see ca())
|
and a root CRL signed offline (see ca())
|
||||||
@@ -46,7 +49,7 @@ Pages:
|
|||||||
the databases of each PostgreSQL cluster on the router with their sizes, as JSON (see
|
the databases of each PostgreSQL cluster on the router with their sizes, as JSON (see
|
||||||
postgresql_databases()); /config has the clusters' settings
|
postgresql_databases()); /config has the clusters' settings
|
||||||
|
|
||||||
/status/full, /config, /metrics/history, /postgresql, /users, /service-accounts, /groups, /site-to-site, /backup, /ca, /shell 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, /remote-access, /ca, /shell 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;
|
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
|
/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
|
token of vpn-router-token has the admin claim and names no user; it may call every path but
|
||||||
@@ -150,6 +153,9 @@ BODY_LIMIT = 65536
|
|||||||
#: The settings of a site_to_site connection that /site-to-site adds and changes.
|
#: The settings of a site_to_site connection that /site-to-site adds and changes.
|
||||||
CONNECTION_SETTINGS = ('addrs', 'id', 'cidrs', 'local_cidrs', 'local_id_mode', 'auth', 'psk', 'remote_ca')
|
CONNECTION_SETTINGS = ('addrs', 'id', 'cidrs', 'local_cidrs', 'local_id_mode', 'auth', 'psk', 'remote_ca')
|
||||||
|
|
||||||
|
#: The sections of the remote access systems /remote-access changes: IKEv2 road-warrior, OpenVPN and WireGuard.
|
||||||
|
REMOTE_ACCESS = ('p2s', 'openvpn', 'wireguard')
|
||||||
|
|
||||||
#: The HTTP status for each status of a vpnrouter_users.Failure; any other is 500.
|
#: The HTTP status for each status of a vpnrouter_users.Failure; any other is 500.
|
||||||
USER_STATUS = {2: 400, 3: 404, 4: 409, 5: 401}
|
USER_STATUS = {2: 400, 3: 404, 4: 409, 5: 401}
|
||||||
|
|
||||||
@@ -814,6 +820,48 @@ def backup(method, path, body):
|
|||||||
return 200, {'settings': backup_settings(cfg['backup'])}
|
return 200, {'settings': backup_settings(cfg['backup'])}
|
||||||
|
|
||||||
|
|
||||||
|
def remote_access(state, method, path, body):
|
||||||
|
"""Answer a /remote-access request: the HTTP status and the JSON document.
|
||||||
|
|
||||||
|
GET /remote-access {"p2s", "openvpn", "wireguard", "running", "local_fqdn"}: the settings of the three
|
||||||
|
remote access systems (REMOTE_ACCESS), whether setup runs each (running: {p2s,
|
||||||
|
openvpn, wireguard}), and wan.local_fqdn, which IKEv2 and OpenVPN need with the
|
||||||
|
router's VPN server certificate
|
||||||
|
PUT /remote-access/<system> change any of the settings of the system's section; answers the section
|
||||||
|
|
||||||
|
A change is made as /site-to-site makes one, and applied by restarting vpn-router-setup without waiting for it.
|
||||||
|
"""
|
||||||
|
if body == 'invalid' or (body is not None and not isinstance(body, dict)):
|
||||||
|
return 400, {'error': 'the body must be a JSON object'}
|
||||||
|
body = body or {}
|
||||||
|
parts = path.strip('/').split('/')
|
||||||
|
try:
|
||||||
|
if (method, len(parts)) == ('GET', 1):
|
||||||
|
cfg = vpnrouter.load_config()
|
||||||
|
return 200, {**{system: cfg[system] for system in REMOTE_ACCESS},
|
||||||
|
'running': {system: bool(state.get(system)) for system in REMOTE_ACCESS},
|
||||||
|
'local_fqdn': cfg['wan']['local_fqdn']}
|
||||||
|
if method != 'PUT' or len(parts) != 2 or parts[1] not in REMOTE_ACCESS:
|
||||||
|
return 404, {'error': f'no route {method} {path}'}
|
||||||
|
system = parts[1]
|
||||||
|
settings = {option: body[option] for option in vpnrouter.SCHEMA[system] if option in body}
|
||||||
|
with vpnrouter.config_lock():
|
||||||
|
data = vpnrouter.read_settings()
|
||||||
|
current = data.get(system)
|
||||||
|
data[system] = {**(current if isinstance(current, dict) else {}), **settings}
|
||||||
|
cfg = vpnrouter.with_defaults(data)
|
||||||
|
vpnrouter.validate(cfg)
|
||||||
|
vpnrouter.save_config(data)
|
||||||
|
except vpnrouter.ConfigError as exc:
|
||||||
|
return 400, {'error': str(exc)}
|
||||||
|
except OSError as exc:
|
||||||
|
return 500, {'error': f'the configuration cannot be written: {exc}'}
|
||||||
|
except SystemExit as exc:
|
||||||
|
return 500, {'error': str(exc.code)}
|
||||||
|
run('systemctl', 'restart', '--no-block', 'vpn-router-setup.service')
|
||||||
|
return 200, cfg[system]
|
||||||
|
|
||||||
|
|
||||||
def ca(method, path, body):
|
def ca(method, path, body):
|
||||||
"""Answer a /ca request with vpnrouter_pki: the HTTP status and the JSON document.
|
"""Answer a /ca request with vpnrouter_pki: the HTTP status and the JSON document.
|
||||||
|
|
||||||
@@ -1319,7 +1367,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
if self.command not in ('GET', 'HEAD'):
|
if self.command not in ('GET', 'HEAD'):
|
||||||
self.close_connection = True
|
self.close_connection = True
|
||||||
directory = next((name for name in ('users', 'service-accounts', 'groups', 'account', 'site-to-site', 'backup',
|
directory = next((name for name in ('users', 'service-accounts', 'groups', 'account', 'site-to-site', 'backup',
|
||||||
'ca', 'shell')
|
'ca', 'shell', 'remote-access')
|
||||||
if path == f'/{name}' or path.startswith(f'/{name}/')), None)
|
if path == f'/{name}' or path.startswith(f'/{name}/')), None)
|
||||||
protected = path in ('/status/full', '/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')
|
signing = path in ('/auth', '/auth/refresh', '/auth/logout')
|
||||||
@@ -1371,6 +1419,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
elif directory == 'site-to-site':
|
elif directory == 'site-to-site':
|
||||||
code, document = site_to_site(self.command, path, payload)
|
code, document = site_to_site(self.command, path, payload)
|
||||||
body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json'
|
body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json'
|
||||||
|
elif directory == 'remote-access':
|
||||||
|
code, document = remote_access(state, self.command, path, payload)
|
||||||
|
body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json'
|
||||||
elif directory == 'backup':
|
elif directory == 'backup':
|
||||||
code, document = backup(self.command, path, payload)
|
code, document = backup(self.command, path, payload)
|
||||||
body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json'
|
body, kind = json.dumps(document, indent=2).encode() + b'\n', 'application/json'
|
||||||
|
|||||||
Reference in new issue
Block a user