From 0bfb81750045be9c8c82a8f8f7bb18f6e6136d94 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 20 Aug 2023 14:56:12 +0200 Subject: wifi: T5491: allow white-/blacklisting station MAC addresses for security Station MAC address-based authentication means: * 'allow' accept all clients except the one on the deny list * 'deny' accept only clients listed on the accept list New CLI commands: * set interfaces wireless wlan0 security station-address mode * set interfaces wireless wlan0 security station-address accept mac * set interfaces wireless wlan0 security station-address deny mac --- data/templates/wifi/hostapd.conf.j2 | 12 +++++- data/templates/wifi/hostapd_accept_station.conf.j2 | 7 ++++ data/templates/wifi/hostapd_deny_station.conf.j2 | 7 ++++ .../include/interface/mac-multi.xml.i | 15 ++++++++ interface-definitions/interfaces-wireless.xml.in | 43 +++++++++++++++++++++ smoketest/scripts/cli/test_interfaces_wireless.py | 44 +++++++++++++++++++++- src/conf_mode/interfaces-wireless.py | 27 +++++++++---- 7 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 data/templates/wifi/hostapd_accept_station.conf.j2 create mode 100644 data/templates/wifi/hostapd_deny_station.conf.j2 create mode 100644 interface-definitions/include/interface/mac-multi.xml.i diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2 index f2312d2d4..613038597 100644 --- a/data/templates/wifi/hostapd.conf.j2 +++ b/data/templates/wifi/hostapd.conf.j2 @@ -430,14 +430,22 @@ ieee80211n={{ '1' if 'n' in mode or 'ac' in mode else '0' }} ignore_broadcast_ssid=1 {% endif %} -# Station MAC address -based authentication +{% if type is vyos_defined('access-point') %} +# Station MAC address-based authentication # Please note that this kind of access control requires a driver that uses # hostapd to take care of management frame processing and as such, this can be # used with driver=hostap or driver=nl80211, but not with driver=atheros. # 0 = accept unless in deny list # 1 = deny unless in accept list # 2 = use external RADIUS server (accept/deny lists are searched first) -macaddr_acl=0 +macaddr_acl={{ '0' if security.station_address.mode is vyos_defined('accept') else '1' }} + +# Accept/deny lists are read from separate files (containing list of +# MAC addresses, one per line). Use absolute path name to make sure that the +# files can be read on SIGHUP configuration reloads. +accept_mac_file={{ hostapd_accept_station_conf }} +deny_mac_file={{ hostapd_deny_station_conf }} +{% endif %} {% if max_stations is vyos_defined %} # Maximum number of stations allowed in station table. New stations will be diff --git a/data/templates/wifi/hostapd_accept_station.conf.j2 b/data/templates/wifi/hostapd_accept_station.conf.j2 new file mode 100644 index 000000000..a381c947c --- /dev/null +++ b/data/templates/wifi/hostapd_accept_station.conf.j2 @@ -0,0 +1,7 @@ +# List of MAC addresses that are allowed to authenticate (IEEE 802.11) +# with the AP +{% if security.station_address.accept.mac is vyos_defined %} +{% for mac in security.station_address.accept.mac %} +{{ mac | lower }} +{% endfor %} +{% endif %} diff --git a/data/templates/wifi/hostapd_deny_station.conf.j2 b/data/templates/wifi/hostapd_deny_station.conf.j2 new file mode 100644 index 000000000..fb2950dda --- /dev/null +++ b/data/templates/wifi/hostapd_deny_station.conf.j2 @@ -0,0 +1,7 @@ +# List of MAC addresses that are not allowed to authenticate +# (IEEE 802.11) with the access point +{% if security.station_address.deny.mac is vyos_defined %} +{% for mac in security.station_address.deny.mac %} +{{ mac | lower }} +{% endfor %} +{% endif %} diff --git a/interface-definitions/include/interface/mac-multi.xml.i b/interface-definitions/include/interface/mac-multi.xml.i new file mode 100644 index 000000000..458372e67 --- /dev/null +++ b/interface-definitions/include/interface/mac-multi.xml.i @@ -0,0 +1,15 @@ + + + + Media Access Control (MAC) address + + macaddr + Hardware (MAC) address + + + + + + + + diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 421d46c6e..88b858c07 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -595,6 +595,49 @@ Wireless security settings + + + Station MAC address based authentication + + + + + Select security operation mode + + accept deny + + + accept + Accept all clients unless found in deny list + + + deny + Deny all clients unless found in accept list + + + (accept|deny) + + + accept + + + + Accept station MAC address + + + #include + + + + + Deny station MAC address + + + #include + + + + Wired Equivalent Privacy (WEP) parameters diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index 875ca9dc6..f8686edd8 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -234,9 +234,51 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): self.assertIn(interface, bridge_members) self.cli_delete(bridge_path) - self.cli_delete(self._base_path) + + def test_wireless_security_station_address(self): + interface = 'wlan0' + ssid = 'VyOS-ACL' + + hostapd_accept_station_conf = f'/run/hostapd/{interface}_station_accept.conf' + hostapd_deny_station_conf = f'/run/hostapd/{interface}_station_deny.conf' + + accept_mac = ['00:00:00:00:ac:01', '00:00:00:00:ac:02', '00:00:00:00:ac:03', '00:00:00:00:ac:04'] + deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04'] + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'country-code', 'se']) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept']) + + for mac in accept_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'accept', 'mac', mac]) + for mac in deny_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'deny', 'mac', mac]) + self.cli_commit() + # in accept mode all addresses are allowed unless specified in the deny list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '0') + + accept_list = read_file(hostapd_accept_station_conf) + for mac in accept_mac: + self.assertIn(mac, accept_list) + + deny_list = read_file(hostapd_deny_station_conf) + for mac in deny_mac: + self.assertIn(mac, deny_list) + + # Switch mode accept -> deny + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'deny']) + self.cli_commit() + # In deny mode all addresses are denied unless specified in the allow list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '1') + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + if __name__ == '__main__': check_kmod('mac80211_hwsim') unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 29ab9713f..a1d978ebd 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -42,6 +42,8 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' hostapd_conf = '/run/hostapd/{ifname}.conf' +hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf' +hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf' def find_other_stations(conf, base, ifname): """ @@ -81,10 +83,12 @@ def get_config(config=None): if 'deleted' not in wifi: # then get_interface_dict provides default keys - if wifi.from_defaults(['security']): # if not set by user - del wifi['security'] + if wifi.from_defaults(['security', 'wep']): # if not set by user + del wifi['security']['wep'] + if wifi.from_defaults(['security', 'wpa']): # if not set by user + del wifi['security']['wpa'] - if 'security' in wifi and 'wpa' in wifi['security']: + if dict_search('security.wpa', wifi) != None: wpa_cipher = wifi['security']['wpa'].get('cipher') wpa_mode = wifi['security']['wpa'].get('mode') if not wpa_cipher: @@ -102,6 +106,10 @@ def get_config(config=None): tmp = find_other_stations(conf, base, wifi['ifname']) if tmp: wifi['station_interfaces'] = tmp + # used in hostapt.conf.j2 + wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) + wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) + return wifi def verify(wifi): @@ -189,7 +197,10 @@ def generate(wifi): if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): os.unlink(hostapd_conf.format(**wifi)) - + if os.path.isfile(hostapd_accept_station_conf.format(**wifi)): + os.unlink(hostapd_accept_station_conf.format(**wifi)) + if os.path.isfile(hostapd_deny_station_conf.format(**wifi)): + os.unlink(hostapd_deny_station_conf.format(**wifi)) if os.path.isfile(wpa_suppl_conf.format(**wifi)): os.unlink(wpa_suppl_conf.format(**wifi)) @@ -224,12 +235,12 @@ def generate(wifi): # render appropriate new config files depending on access-point or station mode if wifi['type'] == 'access-point': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', - wifi) + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) + render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi) + render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi) elif wifi['type'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', - wifi) + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi) return None -- cgit v1.2.3 From fefe14fa3df706d6075074c33e9700090afb2d5e Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 20 Aug 2023 14:59:24 +0200 Subject: wifi: T5491: import cleanup --- src/conf_mode/interfaces-wireless.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index a1d978ebd..02b4a2500 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -25,8 +25,6 @@ from vyos.configdict import get_interface_dict from vyos.configdict import dict_merge from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_source_interface from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf -- cgit v1.2.3 From 35a46e4abfcece1beefb898ebd7fcb688fc55de5 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 20 Aug 2023 14:59:49 +0200 Subject: netplug: T5491: invoke DHCP helpers also on wifi interfaces --- src/etc/netplug/netplugd.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf index ab4d826d6..7da3c67e8 100644 --- a/src/etc/netplug/netplugd.conf +++ b/src/etc/netplug/netplugd.conf @@ -1,3 +1,4 @@ eth* br* bond* +wlan* -- cgit v1.2.3