From 3c25cbe88adf8891b1e2032bd0607e6a9da10c8b Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Tue, 30 Jul 2024 10:53:57 +0200 Subject: [PATCH] vpn: add support for custom keys Custom keys are created by admin and specify networks directly, bypassing AD permissions. They are intended to join managed devices into networks where users are not allowed to create keys themselves. Also comprehend a set directly. --- web/static/vpn.js | 26 ++------------ web/system.py | 19 ++++++---- web/templates/base.html | 13 ++++--- web/templates/index.html | 4 ++- web/templates/vpn/custom.html | 68 +++++++++++++++++++++++++++++++++++ web/templates/vpn/index.html | 29 +++++++++++++-- web/templates/vpn/wg-fri.conf | 2 +- web/vpn.py | 33 ++++++++++++++--- 8 files changed, 152 insertions(+), 42 deletions(-) create mode 100644 web/templates/vpn/custom.html diff --git a/web/static/vpn.js b/web/static/vpn.js index c8e66af..5a846bd 100644 --- a/web/static/vpn.js +++ b/web/static/vpn.js @@ -17,7 +17,7 @@ function delKey(key) { } function fetchKeys() { - fetch('list', { + fetch(endpoint, { credentials: 'include' }) .then(response => { @@ -26,29 +26,7 @@ function fetchKeys() { return response.json(); }) .then(data => { - const keys = document.querySelector('ul.keys'); - keys.innerHTML = ''; - const warning = document.querySelector('p#active-key-warning'); - warning.hidden = true; - - for (let key of Object.values(data)) { - var a = document.createElement('a'); - a.innerText = '✖'; - a.href = ''; - a.addEventListener('click', event => { - delKey(key.key); - event.preventDefault(); - }); - var li = document.createElement('li'); - li.innerHTML = ' ' + (new Date(key.time*1000).toISOString().split('T')[0]) + - ' ' + key.key + ' ' + key.name + - (key.active ? ' ' : ''); - li.prepend(a); - keys.appendChild(li); - if (key.active) - warning.hidden = false; - } - document.querySelector('section.keys').style.display = (Object.keys(data).length ? 'unset' : 'none'); + update(Object.values(data)); }) .catch(error => console.error(error)); } diff --git a/web/system.py b/web/system.py index 3bb66b9..1bde640 100644 --- a/web/system.py +++ b/web/system.py @@ -77,17 +77,24 @@ def save_config(): settings = db.read('settings') version = settings['version'] = int(settings.get('version') or '0') + 1 - # Update IP sets with VPN addresses based on AD group membership. - vpn_groups = set([e['vpn'] for e in ipsets.values() if e.get('vpn')]) + # Find networks accessible to VPN users for each AD group. + vpn_groups = {e['vpn'] for e in ipsets.values() if e.get('vpn')} group_networks = { group: [name for name, data in ipsets.items() if data['vpn'] == group] for group in vpn_groups } + + # Add VPN addresses to IP sets. for ip, key in wireguard.items(): + # Find all networks this IP should belong to: + # - manually specified networks for custom keys, + # - networks accessible to any of the user’s groups. + key_networks = set(key.get('networks', ())) for group in user_groups.get(key.get('user', ''), ()): - for network in group_networks.get(group, ()): - ipsets[network]['ip'].append(f'{ip}/32') - if ip6 := key.get('ip6'): - ipsets[network]['ip6'].append(ip6) + key_networks |= set(group_networks.get(group, ())) + for network in key_networks: + ipsets[network]['ip'].append(f'{ip}/32') + if ip6 := key.get('ip6'): + ipsets[network]['ip6'].append(ip6) # Create config files. output = pathlib.Path.home() / 'config' / f'{version}' diff --git a/web/templates/base.html b/web/templates/base.html index add9ed1..7b0832c 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -9,7 +9,8 @@ body { margin: 1em auto; } code { - background-color: #eeeeee; + background-color: #f8f8f8; + padding: 0.1em 0.25em; } details { margin: 0.5em 1em; @@ -31,18 +32,22 @@ input:read-only { border-style: dotted; } pre { - background-color: #eeeeee; + background-color: #f8f8f8; border: 1px solid #cccccc; padding: 0.5em; } +table { + border-spacing: 0 0.1em; +} th { text-align: left; } th, td { padding-right: 1em; + vertical-align: middle; } -th { - border-bottom: 1px solid black; +tbody > tr:hover { + background-color: #f8f8f8; } ul.keys { margin: 0 0.5em 0.5em; diff --git a/web/templates/index.html b/web/templates/index.html index 01320ba..80ea4a6 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3,7 +3,7 @@
VPN -
urejanje ključev za WireGuard VPN +
urejanje ključev za oddaljeni dostop
Pravila
vklop / izklop pravil za požarni zid
@@ -20,6 +20,8 @@
pravila za posredovanje prometa
Netmap
statične 1:1 preslikave naslovov za strežniška omrežja +
VPN po meri +
urejanje ključev za oddaljeni dostop do posebnih omrežij
Nastavitve
nastavitve aplikacije FRIwall diff --git a/web/templates/vpn/custom.html b/web/templates/vpn/custom.html new file mode 100644 index 0000000..d03398a --- /dev/null +++ b/web/templates/vpn/custom.html @@ -0,0 +1,68 @@ +{% extends 'base.html' %} +{% block header %} + +{% endblock %} + +{% block content %} +

+Urejate ključe WireGuard s posebnimi dostopi. + + + + +
KljučIPIPv6NapravaOmrežja +
+ +

+

Nov ključ

+
+

+
+ +

+
+ +

+ +

+ + +
+ + + + + +{% endblock %} diff --git a/web/templates/vpn/index.html b/web/templates/vpn/index.html index c78a323..c47b61c 100644 --- a/web/templates/vpn/index.html +++ b/web/templates/vpn/index.html @@ -68,12 +68,37 @@ V nastavitvah lahko dodate ali odstranite vnose AllowedIPs. Ti dolo

Ključi

Če ključa ne uporabljamo, smo ga izgubili ali so nam ga ukradli, ga tukaj odstranimo. Trenutno so registrirani ključi: -

    -

    + + + +
    KljučIPIPv6Naprava +
    + +

    Ta ključ uporablja trenutna povezava. Če ga odstranite, bo prekinjena.

    + {% endblock %} diff --git a/web/templates/vpn/wg-fri.conf b/web/templates/vpn/wg-fri.conf index 09bbf9c..136490b 100644 --- a/web/templates/vpn/wg-fri.conf +++ b/web/templates/vpn/wg-fri.conf @@ -1,5 +1,5 @@ [Interface] -# {{ timestamp }} {{ current_user['username'] }} {{ name }} +# {{ timestamp }} {{ user }} {{ name }} # PublicKey = {{ pubkey }} PrivateKey = # paste private key here Address = {{ ip }}{% if ip6 %}, {{ ip6 }}{% endif %} diff --git a/web/vpn.py b/web/vpn.py index 46f0431..876fc50 100644 --- a/web/vpn.py +++ b/web/vpn.py @@ -18,15 +18,36 @@ wgkey_regex = re.compile(r'^[A-Za-z0-9/+=]{44}$') def index(): return flask.render_template('vpn/index.html') +@blueprint.route('/custom') +@flask_login.login_required +def custom(): + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') + with db.locked(): + keys = {ip: data for ip, data in db.read('wireguard').items() if data.get('networks') and not data.get('user')} + ipsets = db.read('networks') | db.read('ipsets') + return flask.render_template('vpn/custom.html', keys=keys, ipsets=ipsets.keys()) + @blueprint.route('/list') @flask_login.login_required def list(): # Return logged-in user’s keys, marking the key used for current connection (if any). user = flask_login.current_user.get_id() - return flask.jsonify({ - ip: data | {'active': flask.request.remote_addr in (ip, data.get('ip6'))} + return flask.jsonify([ + data | {'ip': ip, 'active': flask.request.remote_addr in (ip, data.get('ip6'))} for ip, data in db.load('wireguard').items() if data.get('user') == user - }) + ]) + +@blueprint.route('/list-custom') +@flask_login.login_required +def list_custom(): + # Return all custom keys. + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') + return flask.jsonify([ + data | {'ip': ip, 'active': flask.request.remote_addr in (ip, data.get('ip6'))} + for ip, data in db.load('wireguard').items() if data.get('networks') and not data.get('user') + ]) @blueprint.route('/new', methods=('POST',)) @flask_login.login_required @@ -76,7 +97,10 @@ def new(): return flask.Response('no more available IP addresses', status=500, mimetype='text/plain') # Add remaining attributes to new key and update key database. - key['user'] = flask_login.current_user.get_id() + if flask_login.current_user.is_admin and flask.request.json.get('networks'): + key['networks'] = flask.request.json.get('networks') + else: + key['user'] = flask_login.current_user.get_id() keys[str(ip)] = key db.write('wireguard', keys) @@ -93,6 +117,7 @@ def new(): 'ip': str(ip), 'ip6': key['ip6'], 'name': key['name'], + 'user': key.get('user', 'custom'), 'dns': settings.get('wg_dns', '') if options.get('use-dns', True) else False, 'allowed_nets': settings.get('wg_allowed_nets', ''), 'add_default': options.get('add-default', False),