feature: the router keeps its metrics in PostgreSQL, serves them for an OpenTelemetry Collector and pushes them over OTLP.
Release / release (push) Successful in 14s
Release / release (push) Successful in 14s
This commit is contained in:
1 parent
83c1c50d8d
commit
38151bf5be
20 files changed
+1153
-128
No files matched your search
@@ -21,6 +21,7 @@ import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import secrets
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -59,6 +60,9 @@ MANAGER_CONFIG = pathlib.Path('/etc/vpn-router-manager/manager.json')
|
||||
MANAGER_ADMIN = pathlib.Path('/etc/vpn-router-manager/admin.json')
|
||||
MANAGER_PORT = 7080
|
||||
|
||||
#: The password of the metrics service account, which setup makes and keeps (root only).
|
||||
METRICS_SECRET = pathlib.Path('/etc/vpn-router/metrics.secret')
|
||||
|
||||
#: The configuration of Caddy, the HTTPS proxy, which the package's drop-in for caddy.service names;
|
||||
#: Caddy runs as the user and group CADDY_GROUP, which the caddy package creates.
|
||||
CADDYFILE = pathlib.Path('/etc/caddy/vpn-router.Caddyfile')
|
||||
@@ -79,6 +83,7 @@ BLOCKS = (
|
||||
('# BOOTSTRAP RULES START', '# BOOTSTRAP RULES END'),
|
||||
('# MANAGER RULES START', '# MANAGER RULES END'),
|
||||
('# HTTPS RULES START', '# HTTPS RULES END'),
|
||||
('# METRICS RULES START', '# METRICS 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'),
|
||||
@@ -223,7 +228,7 @@ def filter_block(ctx):
|
||||
]
|
||||
lines += ['# P2S DNS RULES END']
|
||||
|
||||
# The web service and the Router Manager choose their interfaces themselves, so their ports are open on both.
|
||||
# The web service, the Router Manager and the metrics service choose their interfaces themselves, so their ports are open on both.
|
||||
interfaces = [name for name in (ctx['int_iface'], ctx['wan_iface']) if name]
|
||||
if ctx['web_enabled'] and interfaces:
|
||||
lines += [
|
||||
@@ -250,10 +255,19 @@ def filter_block(ctx):
|
||||
'# MANAGER RULES END',
|
||||
]
|
||||
|
||||
if vpnrouter.metrics_ready(ctx) and interfaces:
|
||||
lines += [
|
||||
'',
|
||||
'# METRICS RULES START',
|
||||
*(f'-A ufw-before-input -i {name} -p tcp --dport {ctx["metrics_port"]} -j ACCEPT' for name in interfaces),
|
||||
'# METRICS RULES END',
|
||||
]
|
||||
|
||||
# Let's Encrypt reaches the HTTP challenge on the external interface; the HTTPS ports are open on both.
|
||||
if vpnrouter.https_ready(ctx) and interfaces:
|
||||
ports = [port for wanted, port in ((ctx['web_enabled'], ctx['https_web_port']),
|
||||
(MANAGER.exists(), ctx['https_manager_port'])) if wanted]
|
||||
(MANAGER.exists(), ctx['https_manager_port']),
|
||||
(vpnrouter.metrics_ready(ctx), ctx['metrics_https_port'])) if wanted]
|
||||
lines += [
|
||||
'',
|
||||
'# HTTPS RULES START',
|
||||
@@ -439,6 +453,57 @@ def manage_admin():
|
||||
return ''
|
||||
|
||||
|
||||
def manage_metrics_account(ctx):
|
||||
"""Keep the service account metrics.service_account names, whose password is in METRICS_SECRET; the problem, or ''.
|
||||
|
||||
While metrics are enabled, setup owns the account: it adds it as a service account with a random password when
|
||||
it is missing, and gives it a new random password, ending any lockout, when METRICS_SECRET is missing or does not
|
||||
hold its password. A disabled account is left as it is. An account of that name that is not a service account is
|
||||
left alone and is a problem.
|
||||
"""
|
||||
if not vpnrouter.metrics_ready(ctx):
|
||||
return ''
|
||||
name = ctx['metrics_service_account']
|
||||
try:
|
||||
vpnrouter_users.check_name(name)
|
||||
try:
|
||||
password = METRICS_SECRET.read_text(encoding='utf-8').strip()
|
||||
except OSError:
|
||||
password = ''
|
||||
with vpnrouter_users.session() as connection:
|
||||
try:
|
||||
current = vpnrouter_users.entry(connection, name)
|
||||
except vpnrouter_users.Failure as failure:
|
||||
if failure.status != 3:
|
||||
raise
|
||||
current = None
|
||||
if current is not None and not vpnrouter_users.service(current):
|
||||
return f'metrics.service_account: {name} is a user, not a service account'
|
||||
# An administrator who disabled the account meant it; its password is left as it is.
|
||||
if current is not None and not vpnrouter_users.summary(current)['enabled']:
|
||||
return ''
|
||||
valid = False
|
||||
if current is not None and password:
|
||||
try:
|
||||
vpnrouter_users.login(name, password, None)
|
||||
valid = True
|
||||
except vpnrouter_users.Failure:
|
||||
pass
|
||||
if not valid:
|
||||
password = secrets.token_urlsafe(32)
|
||||
METRICS_SECRET.touch(mode=0o600)
|
||||
METRICS_SECRET.chmod(0o600)
|
||||
METRICS_SECRET.write_text(password + '\n', encoding='utf-8')
|
||||
if current is None:
|
||||
vpnrouter_users.add(connection, name, password, {}, service=True)
|
||||
else:
|
||||
vpnrouter_users.passwd(connection, name, password)
|
||||
vpnrouter_users.unlock(connection, name)
|
||||
except (OSError, ldap.LDAPError, vpnrouter_users.Failure) as exc:
|
||||
return f'the metrics service account cannot be kept: {exc}'
|
||||
return ''
|
||||
|
||||
|
||||
def manage_openvpn(ctx, changed):
|
||||
"""Run the OpenVPN server when it is configured, and stop it when it is not."""
|
||||
unit = 'openvpn-server@vpn-router'
|
||||
@@ -455,11 +520,12 @@ def manage_openvpn(ctx, changed):
|
||||
|
||||
|
||||
def manage_caddy(ctx):
|
||||
"""Run Caddy, the HTTPS proxy of the web service and the Router Manager, while HTTPS is ready, and stop it when not.
|
||||
"""Run Caddy, the HTTPS proxy of the web service, the Router Manager and the metrics service, while HTTPS is ready, and stop it when not.
|
||||
|
||||
CADDYFILE serves the web service on https.web_port and, while the manager is installed, the
|
||||
manager on https.manager_port, both for wan.local_fqdn with a certificate from Let's Encrypt,
|
||||
and both calling the service on its loopback address.
|
||||
CADDYFILE serves the web service on https.web_port, while the manager is installed the
|
||||
manager on https.manager_port, and while metrics are enabled the metrics service on
|
||||
metrics.https_port, all for wan.local_fqdn with a certificate from Let's Encrypt, and all
|
||||
calling the service on its loopback address.
|
||||
"""
|
||||
if not vpnrouter.https_ready(ctx):
|
||||
vpnrouter.remove(CADDYFILE)
|
||||
@@ -480,6 +546,20 @@ def manage_caddy(ctx):
|
||||
run(['systemctl', 'reload', CADDY_UNIT], check=False)
|
||||
|
||||
|
||||
def manage_metrics(ctx):
|
||||
"""Run the metrics service while metrics are enabled, and stop it when not.
|
||||
|
||||
The service reads its settings itself, so it is restarted only to run the code of a new package version, which
|
||||
every run of setup after an upgrade does.
|
||||
"""
|
||||
unit = 'vpn-router-metrics.service'
|
||||
if not vpnrouter.metrics_ready(ctx):
|
||||
run(['systemctl', 'disable', '--now', unit], check=False)
|
||||
return
|
||||
run(['systemctl', 'enable', unit], check=False)
|
||||
run(['systemctl', 'restart', unit], check=False)
|
||||
|
||||
|
||||
def run_platform_setup(cfg, ctx):
|
||||
"""Let the platform module apply its own state, if it defines any."""
|
||||
module = vpnrouter.platform_module(cfg)
|
||||
@@ -621,6 +701,10 @@ def main():
|
||||
failed_step = 'Router Manager admin'
|
||||
progress('running', failed_step)
|
||||
problem = manage_admin()
|
||||
if not problem:
|
||||
failed_step = 'metrics service account'
|
||||
progress('running', failed_step)
|
||||
problem = manage_metrics_account(ctx)
|
||||
progress('running', 'OpenVPN')
|
||||
manage_openvpn(ctx, changed)
|
||||
progress('running', 'HTTPS proxy')
|
||||
@@ -636,6 +720,8 @@ def main():
|
||||
refresh_docker_rules()
|
||||
progress('running', 'web service state')
|
||||
refresh_web(cfg, ctx)
|
||||
progress('running', 'metrics service')
|
||||
manage_metrics(ctx)
|
||||
if problem:
|
||||
progress('failed', failed_step, problem)
|
||||
print(f'vpn-router: error: {problem}', file=sys.stderr)
|
||||
|
||||
Reference in new issue
Block a user