Development

This commit is contained in:
Matthew Grotke 2026-05-25 19:59:42 -04:00
parent d0cfffac52
commit adcfe55c7c
24 changed files with 405 additions and 359 deletions

View file

@ -5,7 +5,7 @@ import re
from flask import Blueprint, make_response, redirect, flash, request
from auth import require_level
from config_utils import load_core, save_core_with_snapshot, verify_core_hash, CONFIGS_DIR, WEB_APP_DISPLAY_NAME
from config_utils import load_config, save_config_with_snapshot, verify_config_hash, CONFIGS_DIR, WEB_APP_DISPLAY_NAME
import sanitize
import validation as validate
@ -16,17 +16,17 @@ _MTU_MIN = 576
_MTU_MAX = 9000
def _wg_vlan(core):
return next((v for v in core.get('vlans', []) if v.get('is_vpn')), None)
def _wg_vlan(cfg):
return next((v for v in cfg.get('vlans', []) if v.get('is_vpn')), None)
def _wg_vlan_by_name(core, name):
return next((v for v in core.get('vlans', []) if v.get('is_vpn') and v.get('name') == name), None)
def _wg_vlan_by_name(cfg, name):
return next((v for v in cfg.get('vlans', []) if v.get('is_vpn') and v.get('name') == name), None)
def _find_peer_by_flat_idx(core, flat_idx):
def _find_peer_by_flat_idx(cfg, flat_idx):
i = 0
for vlan in core.get('vlans', []):
for vlan in cfg.get('vlans', []):
if not vlan.get('is_vpn'):
continue
peers = vlan.get('peers', [])
@ -37,8 +37,8 @@ def _find_peer_by_flat_idx(core, flat_idx):
return None, None
def _wg_iface(vlan, core):
wg_vlans = [v for v in core.get('vlans', []) if v.get('is_vpn')]
def _wg_iface(vlan, cfg):
wg_vlans = [v for v in cfg.get('vlans', []) if v.get('is_vpn')]
idx = next((i for i, v in enumerate(wg_vlans) if v is vlan), 0)
return f'wg{idx}'
@ -51,7 +51,7 @@ def _row_index():
def _hash_ok():
if not verify_core_hash(request.form.get('config_hash', '')):
if not verify_config_hash(request.form.get('config_hash', '')):
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
return False
return True
@ -115,8 +115,8 @@ def _build_client_conf(vlan, peer_name, peer_ip, private_key, server_pubkey):
def _conf_response(vlan, peer_name, peer_ip, private_key):
core = load_core()
iface = _wg_iface(vlan, core)
cfg = load_config()
iface = _wg_iface(vlan, cfg)
server_pub = _server_pubkey(iface)
if not server_pub:
flash('Peer saved. Run sudo python3 ~/routlin/core.py --apply to generate the server '
@ -164,13 +164,13 @@ def apply_vpn():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vpn_vlan = _wg_vlan(core)
cfg = load_config()
vpn_vlan = _wg_vlan(cfg)
if vpn_vlan is None:
flash('No WireGuard VLAN found in configuration.', 'error')
return redirect(_VIEW)
for v in core.get('vlans', []):
for v in cfg.get('vlans', []):
if v.get('is_vpn') and v is not vpn_vlan and v.get('vpn_information', {}).get('listen_port') == listen_port:
flash(f'Listen port {listen_port} is already used by another VPN VLAN.', 'error')
return redirect(_VIEW)
@ -191,15 +191,15 @@ def apply_vpn():
else:
overrides.pop('mtu', None)
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
return redirect(_VIEW)
vlan_name = vpn_vlan['name']
flash(save_core_with_snapshot(
core,
flash(save_config_with_snapshot(
cfg,
path=f'vlans.{vlan_name}.vpn_information', key=vlan_name, operation='edit',
before=before_info or None, after=copy.deepcopy(info),
description=f'Updated VPN configuration for {vlan_name}',
@ -229,8 +229,8 @@ def add_vpn_peer():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vpn_vlan = _wg_vlan_by_name(core, peer_vlan_nm)
cfg = load_config()
vpn_vlan = _wg_vlan_by_name(cfg, peer_vlan_nm)
if vpn_vlan is None:
flash(f'VPN VLAN "{peer_vlan_nm}" not found.', 'error')
return redirect(_VIEW)
@ -247,7 +247,7 @@ def add_vpn_peer():
if any(p.get('name') == peer_name for p in peers):
flash(f'A peer named "{peer_name}" already exists.', 'error')
return redirect(_VIEW)
for v in core.get('vlans', []):
for v in cfg.get('vlans', []):
if not v.get('is_vpn'):
continue
if any(p.get('ip') == peer_ip for p in v.get('peers', [])):
@ -263,14 +263,14 @@ def add_vpn_peer():
'enabled': enabled,
}
peers.append(entry)
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
return redirect(_VIEW)
save_core_with_snapshot(
core,
save_config_with_snapshot(
cfg,
path=f'vlans.{peer_vlan_nm}.peers', key=peer_name, operation='add',
before=None, after={k: v for k, v in entry.items() if k != 'public_key'},
description=f'Added VPN peer: {peer_name} ({peer_ip})',
@ -297,8 +297,8 @@ def edit_vpn_peer():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vlan, peer_idx = _find_peer_by_flat_idx(core, flat_idx)
cfg = load_config()
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
if vlan is None:
flash('Peer not found.', 'error')
return redirect(_VIEW)
@ -310,15 +310,15 @@ def edit_vpn_peer():
before = copy.deepcopy({k: peers[peer_idx].get(k) for k in ('name', 'split_tunnel', 'enabled')})
peers[peer_idx].update({'name': peer_name, 'split_tunnel': split_tunnel, 'enabled': enabled})
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
return redirect(_VIEW)
vlan_name = vlan['name']
flash(save_core_with_snapshot(
core,
flash(save_config_with_snapshot(
cfg,
path=f'vlans.{vlan_name}.peers', key=peer_name, operation='edit',
before=before, after={'name': peer_name, 'split_tunnel': split_tunnel, 'enabled': enabled},
description=f'Edited VPN peer: {peer_name}',
@ -336,8 +336,8 @@ def toggle_vpn_peer():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vlan, peer_idx = _find_peer_by_flat_idx(core, flat_idx)
cfg = load_config()
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
if vlan is None:
flash('Peer not found.', 'error')
return redirect(_VIEW)
@ -345,7 +345,7 @@ def toggle_vpn_peer():
peers = vlan.get('peers', [])
old_enabled = peers[peer_idx].get('enabled', True)
peers[peer_idx]['enabled'] = not old_enabled
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
@ -354,8 +354,8 @@ def toggle_vpn_peer():
peer_name = peers[peer_idx]['name']
vlan_name = vlan['name']
action = 'Enabled' if not old_enabled else 'Disabled'
flash(save_core_with_snapshot(
core,
flash(save_config_with_snapshot(
cfg,
path=f'vlans.{vlan_name}.peers', key=peer_name, operation='toggle',
before={'enabled': old_enabled}, after={'enabled': not old_enabled},
description=f'{action} VPN peer: {peer_name}',
@ -373,23 +373,23 @@ def delete_vpn_peer():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vlan, peer_idx = _find_peer_by_flat_idx(core, flat_idx)
cfg = load_config()
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
if vlan is None:
flash('Peer not found.', 'error')
return redirect(_VIEW)
peers = vlan.get('peers', [])
removed = peers.pop(peer_idx)
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
return redirect(_VIEW)
vlan_name = vlan['name']
flash(save_core_with_snapshot(
core,
flash(save_config_with_snapshot(
cfg,
path=f'vlans.{vlan_name}.peers', key=removed['name'], operation='delete',
before={k: removed.get(k) for k in ('name', 'ip', 'split_tunnel', 'enabled')},
after=None,
@ -408,8 +408,8 @@ def regenerate_vpn_peer():
if not _hash_ok():
return redirect(_VIEW)
core = load_core()
vlan, peer_idx = _find_peer_by_flat_idx(core, flat_idx)
cfg = load_config()
vlan, peer_idx = _find_peer_by_flat_idx(cfg, flat_idx)
if vlan is None:
flash('Peer not found.', 'error')
return redirect(_VIEW)
@ -418,15 +418,15 @@ def regenerate_vpn_peer():
peer = vlan['peers'][peer_idx]
old_pub_key = peer.get('public_key', '')
peer['public_key'] = public_key
errors = validate.validate_config(core)
errors = validate.validate_config(cfg)
if errors:
for msg in errors:
flash(msg, 'error')
return redirect(_VIEW)
vlan_name = vlan['name']
save_core_with_snapshot(
core,
save_config_with_snapshot(
cfg,
path=f'vlans.{vlan_name}.peers', key=peer['name'], operation='regenerate',
before={'public_key': old_pub_key}, after={'public_key': public_key},
description=f'Regenerated keypair for VPN peer: {peer["name"]}',