86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
"""What the modules of the web service share: the paths of the configuration and of the state vpn-router-setup writes,
|
|
the limit of request bodies, the HTTP statuses of the failures of the router's modules, reading the configuration and
|
|
the state, changing a section of the configuration, the state of a unit, and the log."""
|
|
|
|
import json
|
|
import sys
|
|
|
|
import vpnrouter
|
|
import vpnrouter_auth
|
|
from vpnrouter_status import run
|
|
|
|
STATE_FILE = '/run/vpn-router/web.json'
|
|
|
|
CONFIG_FILE = '/etc/vpn-router/vpn-router.json'
|
|
|
|
#: The largest request body accepted, in bytes; a CSR or a certificate chain in PEM is a few kilobytes.
|
|
BODY_LIMIT = 65536
|
|
|
|
#: 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}
|
|
|
|
#: The HTTP status for each status of a vpnrouter_pki.Failure; any other is 500.
|
|
PKI_STATUS = {2: 400, 3: 404, 4: 409}
|
|
|
|
#: The HTTP status for each status of a vpnrouter_sites.Failure; any other is 500.
|
|
SITE_STATUS = {2: 400, 3: 404, 4: 409}
|
|
|
|
#: The bytes in a MiB, the unit of sites.upload_max_mb.
|
|
MIB = 1024 * 1024
|
|
|
|
|
|
def read_json(path):
|
|
"""The object in a JSON file, or {} when the file is missing or holds no object."""
|
|
try:
|
|
with open(path, encoding='utf-8') as handle:
|
|
data = json.load(handle)
|
|
except (OSError, ValueError):
|
|
return {}
|
|
return data if isinstance(data, dict) else {}
|
|
|
|
|
|
def section(configuration, name):
|
|
"""A section of the configuration, or {} when it is missing or not an object."""
|
|
value = configuration.get(name)
|
|
return value if isinstance(value, dict) else {}
|
|
|
|
|
|
def current():
|
|
"""What a request needs, read now: the features and facts setup resolved, and the signing key.
|
|
|
|
Before setup has run the router has no features, and without a key the protected paths and
|
|
/auth are not served.
|
|
"""
|
|
return dict(read_json(STATE_FILE), key=vpnrouter_auth.key())
|
|
|
|
|
|
def unit_state(unit):
|
|
result = run('systemctl', 'is-active', unit)
|
|
return result.stdout.strip() if result and result.stdout.strip() else 'unknown'
|
|
|
|
|
|
def section_change(name, body):
|
|
"""Change any of the settings of the configuration's section as /backup changes its own; the section, or the HTTP
|
|
status and the error document as a tuple."""
|
|
settings = {option: body[option] for option in vpnrouter.SCHEMA[name] if option in body}
|
|
try:
|
|
with vpnrouter.config_lock():
|
|
data = vpnrouter.read_settings()
|
|
current = data.get(name)
|
|
data[name] = {**(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 cfg[name]
|
|
|
|
|
|
def log(message):
|
|
print(f'vpn-router-web: {message}', file=sys.stderr, flush=True)
|