Development
This commit is contained in:
parent
a6031616df
commit
47b0e98e31
9 changed files with 304 additions and 0 deletions
|
|
@ -839,8 +839,10 @@ def load_datasource(spec):
|
|||
|
||||
|
||||
def collect_layout_tokens(cfg):
|
||||
import license
|
||||
net = cfg.get('network_interfaces', {})
|
||||
return {
|
||||
'GENERAL_LAN_INTERFACE': str(net.get('lan_interface', '-')),
|
||||
'VPN_VLAN_COUNT': str(sum(1 for v in cfg.get('vlans', []) if v.get('is_vpn'))),
|
||||
'PRO_LICENSE_JS': 'true' if license.is_pro() else 'false',
|
||||
}
|
||||
|
|
|
|||
2
docker/routlin-dash/app/license.py
Normal file
2
docker/routlin-dash/app/license.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
def is_pro():
|
||||
return False
|
||||
|
|
@ -8,9 +8,12 @@ from auth import require_level
|
|||
from config_utils import load_config, record_group, diff_fields, verify_config_hash
|
||||
import sanitize
|
||||
import mod_validation as validate
|
||||
import license
|
||||
|
||||
_PAGE = Path(__file__).parent.name
|
||||
|
||||
PRO_LICENSE = license.is_pro()
|
||||
|
||||
bp = Blueprint(_PAGE, __name__)
|
||||
|
||||
|
||||
|
|
@ -46,11 +49,16 @@ def vlans_addedit():
|
|||
radius_default = 'radius_default' in request.form
|
||||
mdns_reflection = 'mdns_reflection' in request.form
|
||||
dnsmasq_log_queries = 'dnsmasq_log_queries' in request.form
|
||||
restricted_vlan = 'restricted_vlan' in request.form
|
||||
use_blocklists = sanitize.filterlist(
|
||||
request.form.getlist('use_blocklists'),
|
||||
{b.get('name') for b in load_config().get('dns_blocking', {}).get('blocklists', [])},
|
||||
)
|
||||
|
||||
if restricted_vlan and not PRO_LICENSE:
|
||||
flash('Restricted VLAN requires a Routlin Pro license.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
||||
if not name:
|
||||
flash('Name is required.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
|
@ -261,6 +269,8 @@ def vlans_addedit():
|
|||
'use_blocklists': use_blocklists,
|
||||
'server_identities': new_identities,
|
||||
})
|
||||
if PRO_LICENSE:
|
||||
existing['restricted_vlan'] = restricted_vlan
|
||||
if dhcp_info:
|
||||
existing['dhcp_information'] = dhcp_info
|
||||
else:
|
||||
|
|
@ -318,6 +328,8 @@ def vlans_addedit():
|
|||
'mdns_reflection': mdns_reflection,
|
||||
'server_identities': new_identities,
|
||||
}
|
||||
if PRO_LICENSE:
|
||||
entry['restricted_vlan'] = restricted_vlan
|
||||
if dhcp_info:
|
||||
entry['dhcp_information'] = dhcp_info
|
||||
if is_vpn:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "raw_html",
|
||||
"html": "<span id=\"js-pro-license\" data-value=\"%PRO_LICENSE_JS%\" hidden></span>"
|
||||
},
|
||||
{
|
||||
"type": "info_bar",
|
||||
"variant": "info",
|
||||
|
|
@ -88,6 +92,16 @@
|
|||
"title_true": "DNS Queries Recorded",
|
||||
"title_false": "DNS Queries Not Recorded"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Restricted",
|
||||
"field": "restricted_vlan",
|
||||
"class": "col-narrow",
|
||||
"render": "badge_yes_no",
|
||||
"render_options": {
|
||||
"title_true": "Restricted VLAN",
|
||||
"title_false": "Not Restricted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"row_actions": [
|
||||
|
|
@ -334,6 +348,13 @@
|
|||
"input_type": "checkbox",
|
||||
"hint": "Log every DNS query. High volume - enable for debugging only."
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"label": "%RESTRICTED_VLAN_LABEL%",
|
||||
"name": "restricted_vlan",
|
||||
"input_type": "checkbox",
|
||||
"hint": "Block devices on this VLAN from communicating with the Internet. Block all LAN traffic as well (except where Inter-VLAN-Exception rules allow)."
|
||||
},
|
||||
{
|
||||
"type": "button_row",
|
||||
"items": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import json
|
||||
from config_utils import collect_layout_tokens, load_datasource
|
||||
from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR
|
||||
import license
|
||||
|
||||
PRO_LICENSE = license.is_pro()
|
||||
|
||||
|
||||
def collect_tokens(cfg):
|
||||
|
|
@ -10,6 +13,7 @@ def collect_tokens(cfg):
|
|||
tokens['EXISTING_VLAN_IDS_JSON'] = json.dumps([v.get('vlan_id') for v in vlans])
|
||||
tokens['EXISTING_VLAN_NAMES_JSON'] = json.dumps([v.get('name') for v in vlans])
|
||||
tokens['RADIUS_DEFAULT_VLAN'] = f'"{dv["name"]}" (VLAN {dv["vlan_id"]})' if dv else 'none set'
|
||||
tokens['RESTRICTED_VLAN_LABEL'] = 'Restricted VLAN' if PRO_LICENSE else 'Restricted VLAN (PRO FEATURE)'
|
||||
tokens['BLOCKLIST_NAME_OPTIONS'] = json.dumps([
|
||||
{'value': bl.get('name', ''), 'label': bl.get('description', bl.get('name', ''))}
|
||||
for bl in cfg.get('dns_blocking', {}).get('blocklists', [])
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ from flask import Blueprint, request, redirect, flash, send_file, abort, jsonify
|
|||
from auth import require_level
|
||||
from config_utils import CONFIGS_DIR, load_config, record_group, diff_fields
|
||||
import mod_validation as validate
|
||||
import license
|
||||
|
||||
_PAGE = Path(__file__).parent.name
|
||||
|
||||
PRO_LICENSE = license.is_pro()
|
||||
|
||||
bp = Blueprint(_PAGE, __name__)
|
||||
|
||||
RADIUS_SECRET_FILE = Path(CONFIGS_DIR) / '.radius-secret'
|
||||
|
|
@ -52,6 +55,27 @@ def options_save():
|
|||
return redirect(f'/{_PAGE}')
|
||||
|
||||
|
||||
@bp.route('/action/radius/auth_mode_save', methods=['POST'])
|
||||
@require_level('administrator')
|
||||
def auth_mode_save():
|
||||
auth_mode = request.form.get('auth_mode', 'mab')
|
||||
if auth_mode not in ('mab', 'eap_password', 'eap_credential'):
|
||||
flash('Invalid authentication mode.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
if auth_mode in ('eap_password', 'eap_credential') and not PRO_LICENSE:
|
||||
flash('This authentication mode requires a Routlin Pro license.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
||||
cfg = load_config()
|
||||
before = copy.deepcopy(cfg.get('radius', {}).get('options', {}))
|
||||
after = {**before, 'auth_mode': auth_mode}
|
||||
cfg.setdefault('radius', {})['options'] = after
|
||||
|
||||
changes = diff_fields(before, after)
|
||||
flash(record_group(cfg, 'radius.options', 'auth_mode', auth_mode, changes, 'core apply'), 'success')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
||||
|
||||
@bp.route('/action/radius/default_rule_save', methods=['POST'])
|
||||
@require_level('administrator')
|
||||
def default_rule_save():
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "raw_html",
|
||||
"html": "<span id=\"js-pro-license\" data-value=\"%PRO_LICENSE_JS%\" hidden></span>"
|
||||
},
|
||||
{
|
||||
"type": "info_bar",
|
||||
"variant": "info",
|
||||
|
|
@ -195,6 +199,49 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"label": "Extensible Authentication Protocol (EAP)",
|
||||
"client_requirement": "client_is_administrator+",
|
||||
"items": [
|
||||
{
|
||||
"type": "p",
|
||||
"text": "802.1X authentication modes require a Routlin Pro license."
|
||||
},
|
||||
{
|
||||
"type": "hr"
|
||||
},
|
||||
{
|
||||
"type": "form",
|
||||
"action": "/action/radius/auth_mode_save",
|
||||
"method": "post",
|
||||
"items": [
|
||||
{
|
||||
"type": "field",
|
||||
"label": "Authentication Mode",
|
||||
"name": "auth_mode",
|
||||
"input_type": "select",
|
||||
"value": "%RADIUS_AUTH_MODE%",
|
||||
"options": "%RADIUS_AUTH_MODE_OPTIONS%",
|
||||
"hint": "_"
|
||||
},
|
||||
{
|
||||
"type": "button_row",
|
||||
"items": [
|
||||
{
|
||||
"type": "button_primary",
|
||||
"text": "Save"
|
||||
},
|
||||
{
|
||||
"type": "button_cancel",
|
||||
"text": "Cancel"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"label": "EAP Settings",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import json
|
||||
import os
|
||||
from config_utils import collect_layout_tokens, CONFIGS_DIR
|
||||
import license
|
||||
|
||||
PRO_LICENSE = license.is_pro()
|
||||
|
||||
RADIUS_LOG_MAX = 50
|
||||
RADIUS_LOG_FILE = '/var/log/freeradius/radius.log'
|
||||
|
|
@ -65,6 +68,13 @@ def collect_tokens(cfg):
|
|||
fr_opts = fr.get('options', {})
|
||||
fr_gen = fr.get('general', {})
|
||||
tokens['RADIUS_MAC_FORMAT'] = fr_opts.get('mac_format', 'aabbccddeeff')
|
||||
tokens['RADIUS_AUTH_MODE'] = fr_opts.get('auth_mode', 'mab')
|
||||
pro_suffix = '' if PRO_LICENSE else ' (PRO REQUIRED)'
|
||||
tokens['RADIUS_AUTH_MODE_OPTIONS'] = json.dumps([
|
||||
{'value': 'mab', 'label': 'MAC Authentication Bypass (MAB)'},
|
||||
{'value': 'eap_password', 'label': f'802.1X - Client Username/Password{pro_suffix}'},
|
||||
{'value': 'eap_credential', 'label': f'802.1X - Client Certificate{pro_suffix}'},
|
||||
])
|
||||
tokens['RADIUS_APPLY_TO'] = fr_opts.get('apply_to', 'all')
|
||||
tokens['RADIUS_AP_IPS'] = json.dumps(fr_opts.get('ap_ips', []))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue