Development

This commit is contained in:
Matthew Grotke 2026-06-07 17:37:59 -04:00
parent 351ce39558
commit 3b2f645254
4 changed files with 34 additions and 30 deletions

View file

@ -71,7 +71,7 @@ def auth_mode_save():
eap_protocol = 'eap_peap' eap_protocol = 'eap_peap'
tunneled_reply = 'tunneled_reply' in request.form tunneled_reply = 'tunneled_reply' in request.form
include_length = 'include_length' in request.form include_length = 'include_length' in request.form
mab_fallback = 'mab_fallback' in request.form mab_first = 'mab_first' in request.form
inner_protocol = request.form.get('inner_protocol', '') inner_protocol = request.form.get('inner_protocol', '')
_valid_inner = { _valid_inner = {
@ -85,7 +85,7 @@ def auth_mode_save():
if auth_mode == 'eap_password': if auth_mode == 'eap_password':
after['eap_protocol'] = eap_protocol after['eap_protocol'] = eap_protocol
after['tunneled_reply'] = tunneled_reply and eap_protocol in ('eap_peap', 'eap_ttls') after['tunneled_reply'] = tunneled_reply and eap_protocol in ('eap_peap', 'eap_ttls')
after['mab_fallback'] = mab_fallback after['mab_first'] = mab_first
if eap_protocol in _valid_inner and inner_protocol in _valid_inner[eap_protocol]: if eap_protocol in _valid_inner and inner_protocol in _valid_inner[eap_protocol]:
after['inner_protocol'] = inner_protocol after['inner_protocol'] = inner_protocol
else: else:
@ -96,7 +96,7 @@ def auth_mode_save():
after.pop('include_length', None) after.pop('include_length', None)
elif auth_mode == 'eap_credential': elif auth_mode == 'eap_credential':
after['include_length'] = include_length after['include_length'] = include_length
after['mab_fallback'] = mab_fallback after['mab_first'] = mab_first
after.pop('eap_protocol', None) after.pop('eap_protocol', None)
after.pop('tunneled_reply', None) after.pop('tunneled_reply', None)
after.pop('inner_protocol', None) after.pop('inner_protocol', None)
@ -105,7 +105,7 @@ def auth_mode_save():
after.pop('tunneled_reply', None) after.pop('tunneled_reply', None)
after.pop('inner_protocol', None) after.pop('inner_protocol', None)
after.pop('include_length', None) after.pop('include_length', None)
after.pop('mab_fallback', None) after.pop('mab_first', None)
cfg.setdefault('radius', {})['options'] = after cfg.setdefault('radius', {})['options'] = after
changes = config_utils.diff_fields(before, after) changes = config_utils.diff_fields(before, after)

View file

@ -196,11 +196,11 @@
{ {
"type": "field", "type": "field",
"label": "", "label": "",
"name": "mab_fallback", "name": "mab_first",
"input_type": "checkbox", "input_type": "checkbox",
"checkbox_label": "Try MAB first before prompting supplicant", "checkbox_label": "Try MAB first before prompting supplicant",
"value": "%RADIUS_MAB_FALLBACK%", "value": "%RADIUS_MAB_FIRST%",
"hint": "When a device fails or skips 802.1X, RADIUS will attempt to authenticate it by MAC address before rejecting it. Useful for networks with a mix of 802.1X-capable and non-802.1X devices." "hint": "RADIUS checks the device's MAC address first. Known devices (those with a DHCP reservation) are admitted immediately without waiting for 802.1X negotiation or credential entry. Unknown devices fall through to 802.1X."
}, },
{ {
"type": "raw_html", "type": "raw_html",

View file

@ -122,7 +122,7 @@ def collect_tokens(cfg):
tokens['RADIUS_TUNNELED_REPLY'] = 'true' if fr_opts.get('tunneled_reply', False) else '' tokens['RADIUS_TUNNELED_REPLY'] = 'true' if fr_opts.get('tunneled_reply', False) else ''
tokens['RADIUS_INCLUDE_LENGTH'] = 'true' if fr_opts.get('include_length', False) else '' tokens['RADIUS_INCLUDE_LENGTH'] = 'true' if fr_opts.get('include_length', False) else ''
tokens['RADIUS_MAB_FALLBACK'] = 'true' if fr_opts.get('mab_fallback', False) else '' tokens['RADIUS_MAB_FIRST'] = 'true' if fr_opts.get('mab_first', True) else ''
vlans = cfg.get('vlans', []) vlans = cfg.get('vlans', [])
default_vlan = next((v['name'] for v in vlans if v.get('radius_default') is True), '') default_vlan = next((v['name'] for v in vlans if v.get('radius_default') is True), '')

View file

@ -118,9 +118,12 @@ def build_radius_users(data):
if default_vlan is None: if default_vlan is None:
return None return None
fr_opts = data.get('radius', {}).get('options', {}) fr_opts = data.get('radius', {}).get('options', {})
mac_fmt = fr_opts.get('mac_format', 'aabbccddeeff') mac_fmt = fr_opts.get('mac_format', 'aabbccddeeff')
apply_to = fr_opts.get('apply_to', 'all') apply_to = fr_opts.get('apply_to', 'all')
auth_mode = fr_opts.get('auth_mode', 'mab')
mab_first = fr_opts.get('mab_first', True)
emit_mac_entries = (auth_mode == 'mab') or mab_first
lines = [ lines = [
"# Generated by core.py -- do not edit manually.", "# Generated by core.py -- do not edit manually.",
@ -129,25 +132,26 @@ def build_radius_users(data):
] ]
vlan_by_name = {v["name"]: v for v in data.get("vlans", [])} vlan_by_name = {v["name"]: v for v in data.get("vlans", [])}
for r in data.get("dhcp_reservations", []): if emit_mac_entries:
if r.get("enabled") is not True: for r in data.get("dhcp_reservations", []):
continue if r.get("enabled") is not True:
raw_mac = r.get("mac", "") continue
if not raw_mac: raw_mac = r.get("mac", "")
continue if not raw_mac:
vlan = vlan_by_name.get(r.get("vlan", "")) continue
if not vlan: vlan = vlan_by_name.get(r.get("vlan", ""))
continue if not vlan:
mac = fmt_mac(raw_mac, mac_fmt) continue
vlan_id = vlan.get('vlan_id') mac = fmt_mac(raw_mac, mac_fmt)
lines += [ vlan_id = vlan.get('vlan_id')
f"# {r['description']} -> VLAN {vlan_id} ({vlan['name']})", lines += [
f"{mac} Cleartext-Password := \"{mac}\"", f"# {r['description']} -> VLAN {vlan_id} ({vlan['name']})",
f" Tunnel-Type = VLAN,", f"{mac} Cleartext-Password := \"{mac}\"",
f" Tunnel-Medium-Type = IEEE-802,", f" Tunnel-Type = VLAN,",
f" Tunnel-Private-Group-Id = \"{vlan_id}\"", f" Tunnel-Medium-Type = IEEE-802,",
"", f" Tunnel-Private-Group-Id = \"{vlan_id}\"",
] "",
]
default_id = default_vlan.get('vlan_id') default_id = default_vlan.get('vlan_id')
ap_ips = fr_opts.get('ap_ips', []) ap_ips = fr_opts.get('ap_ips', [])