feature: Added an internally only exposed status web service.
Release / release (push) Successful in 17s

This commit is contained in:
slawek committed 2026-10-23 19:46:26 +02:00
1 parent 5402256ef6
commit 7163f167e0
10 files changed
+593

No files matched your search

@@ -10,8 +10,10 @@ firewall and NAT rules are written against them, so choosing the wrong one
would be a silent and security-relevant mistake.
"""
import json
import os
import pathlib
import re
import socket
import subprocess
import sys
@@ -24,13 +26,18 @@ from vpnrouter import fail
BEFORE_RULES = pathlib.Path('/etc/ufw/before.rules')
UFW_DEFAULTS = pathlib.Path('/etc/default/ufw')
WG_DIR = pathlib.Path('/etc/wireguard')
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.
SECRET_OPTION = re.compile(r'(^psk$)|secret|password|token|_key$')
BLOCKS = (
('# IPSEC RULES START', '# IPSEC RULES END'),
('# WIREGUARD RULES START', '# WIREGUARD RULES END'),
('# P2S DNS RULES START', '# P2S DNS RULES END'),
('# WEB RULES START', '# WEB RULES END'),
('# ROUTER FORWARD RULES START', '# ROUTER FORWARD RULES END'),
('# ROUTER NAT RULES START', '# ROUTER NAT RULES END'),
('# ROUTER MSS RULES START', '# ROUTER MSS RULES END'),
@@ -149,6 +156,14 @@ def filter_block(ctx):
]
lines += ['# P2S DNS RULES END']
if ctx['web_enabled'] and ctx['int_iface']:
lines += [
'',
'# WEB RULES START',
f'-A ufw-before-input -i {ctx["int_iface"]} -p tcp --dport {ctx["web_port"]} -j ACCEPT',
'# WEB RULES END',
]
if vpnrouter.routing_ready(ctx):
lines += ['', '# ROUTER FORWARD RULES START']
for subnet in ctx['local_cidr_list']:
@@ -309,6 +324,48 @@ def refresh_docker_rules():
run(['systemctl', 'restart', 'vpn-router-docker.service'], check=False)
def refresh_web(cfg, ctx):
"""Hand the web service what it needs and restart it.
The service takes the address to bind to, the port, the features of the
router, the secret that signs its tokens, the configuration with its
secrets masked and the facts resolved from the system from a file here, so
that it needs nothing but the standard library and the configuration code.
The file is readable by root only. The service ends quietly when
web.enabled is false.
"""
masked = {
section: {option: '********' if SECRET_OPTION.search(option) and value else value
for option, value in options.items()}
for section, options in cfg.items()
}
WEB_STATE.parent.mkdir(parents=True, exist_ok=True)
WEB_STATE.touch(mode=0o600)
WEB_STATE.chmod(0o600)
WEB_STATE.write_text(json.dumps({
'enabled': bool(ctx['web_enabled']),
'bind': ctx['int_addr'],
'port': ctx['web_port'],
'jwt_secret': cfg['web']['jwt_secret'],
'site_to_site': bool(vpnrouter.s2s_ready(ctx)),
'p2s': bool(vpnrouter.p2s_ready(ctx)),
'wireguard': bool(vpnrouter.wg_ready(ctx)),
'configuration': masked,
'network': {
'external_interface': ctx['wan_iface'],
'internal_interface': ctx['int_iface'],
'external_addresses': ctx['local_addr_list'],
'internal_address': ctx['int_addr'],
'internal_gateway': ctx['int_gateway_ip'],
'local_identity': ctx['local_id'],
'remote_identity': ctx['remote_id'],
'local_cidrs': ctx['local_cidr_list'],
'remote_cidrs': ctx['remote_cidr_list'],
},
}) + '\n', encoding='utf-8')
run(['systemctl', 'restart', 'vpn-router-web.service'], check=False)
def main():
cfg = vpnrouter.load_config()
ctx, changed = vpnrouter.configure(cfg)
@@ -348,6 +405,7 @@ def main():
run_platform_setup(cfg, ctx)
refresh_docker_rules()
refresh_web(cfg, ctx)
return 0