summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-08-20 14:56:12 +0200
committerChristian Breunig <christian@breunig.cc>2023-08-20 15:02:59 +0200
commit0bfb81750045be9c8c82a8f8f7bb18f6e6136d94 (patch)
treeffd0138878c409a3e620dad153170f60e5e9b395
parentffb798b4678f3b1bd0a40cc42b1f0477470346dc (diff)
downloadvyos-1x-0bfb81750045be9c8c82a8f8f7bb18f6e6136d94.tar.gz
vyos-1x-0bfb81750045be9c8c82a8f8f7bb18f6e6136d94.zip
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 <accept|deny> * set interfaces wireless wlan0 security station-address accept mac <mac> * set interfaces wireless wlan0 security station-address deny mac <mac>
-rw-r--r--data/templates/wifi/hostapd.conf.j212
-rw-r--r--data/templates/wifi/hostapd_accept_station.conf.j27
-rw-r--r--data/templates/wifi/hostapd_deny_station.conf.j27
-rw-r--r--interface-definitions/include/interface/mac-multi.xml.i15
-rw-r--r--interface-definitions/interfaces-wireless.xml.in43
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py44
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py27
7 files changed, 144 insertions, 11 deletions
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 @@
+<!-- include start from interface/mac-multi.xml.i -->
+<leafNode name="mac">
+ <properties>
+ <help>Media Access Control (MAC) address</help>
+ <valueHelp>
+ <format>macaddr</format>
+ <description>Hardware (MAC) address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
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 @@
<help>Wireless security settings</help>
</properties>
<children>
+ <node name="station-address">
+ <properties>
+ <help>Station MAC address based authentication</help>
+ </properties>
+ <children>
+ <leafNode name="mode">
+ <properties>
+ <help>Select security operation mode</help>
+ <completionHelp>
+ <list>accept deny</list>
+ </completionHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept all clients unless found in deny list</description>
+ </valueHelp>
+ <valueHelp>
+ <format>deny</format>
+ <description>Deny all clients unless found in accept list</description>
+ </valueHelp>
+ <constraint>
+ <regex>(accept|deny)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>accept</defaultValue>
+ </leafNode>
+ <node name="accept">
+ <properties>
+ <help>Accept station MAC address</help>
+ </properties>
+ <children>
+ #include <include/interface/mac-multi.xml.i>
+ </children>
+ </node>
+ <node name="deny">
+ <properties>
+ <help>Deny station MAC address</help>
+ </properties>
+ <children>
+ #include <include/interface/mac-multi.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="wep">
<properties>
<help>Wired Equivalent Privacy (WEP) parameters</help>
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