Development

This commit is contained in:
Matthew Grotke 2026-06-05 21:28:05 -04:00
parent a6031616df
commit 47b0e98e31
9 changed files with 304 additions and 0 deletions

View file

@ -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():

View file

@ -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",

View file

@ -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', []))