summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-09-14 22:18:54 +0200
committerChristian Breunig <christian@breunig.cc>2024-09-14 22:50:20 +0200
commit0ee8d5e35044e7480dac6a23e92d43744b8c5d36 (patch)
tree3bd91642e78cbba66cda5ff633bb356f3c913974 /python
parent5df36ba0e3c95efb2962ed54e614552f7425e173 (diff)
downloadvyos-1x-0ee8d5e35044e7480dac6a23e92d43744b8c5d36.tar.gz
vyos-1x-0ee8d5e35044e7480dac6a23e92d43744b8c5d36.zip
ethernet: T6709: move EAPoL support to common framework
Instead of having EAPoL (Extensible Authentication Protocol over Local Area Network) support only available for ethernet interfaces, move this to common ground at vyos.ifconfig.interface making it available for all sorts of interfaces by simply including the XML portion #include <include/interface/eapol.xml.i>
Diffstat (limited to 'python')
-rw-r--r--python/vyos/configverify.py17
-rw-r--r--python/vyos/ifconfig/interface.py72
2 files changed, 85 insertions, 4 deletions
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 59b67300d..92996f2ee 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -520,3 +520,20 @@ def verify_pki_dh_parameters(config: dict, dh_name: str, min_key_size: int=0):
dh_bits = dh_numbers.p.bit_length()
if dh_bits < min_key_size:
raise ConfigError(f'Minimum DH key-size is {min_key_size} bits!')
+
+def verify_eapol(config: dict):
+ """
+ Common helper function used by interface implementations to perform
+ recurring validation of EAPoL configuration.
+ """
+ if 'eapol' not in config:
+ return
+
+ if 'certificate' not in config['eapol']:
+ raise ConfigError('Certificate must be specified when using EAPoL!')
+
+ verify_pki_certificate(config, config['eapol']['certificate'], no_password_protected=True)
+
+ if 'ca_certificate' in config['eapol']:
+ for ca_cert in config['eapol']['ca_certificate']:
+ verify_pki_ca_certificate(config, ca_cert)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 72d3d3afe..31fcf6ca6 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -32,6 +32,12 @@ from vyos.configdict import list_diff
from vyos.configdict import dict_merge
from vyos.configdict import get_vlan_ids
from vyos.defaults import directories
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
+from vyos.pki import load_certificate
+from vyos.pki import wrap_private_key
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
from vyos.template import render
from vyos.utils.network import mac2eui64
from vyos.utils.dict import dict_search
@@ -41,9 +47,8 @@ from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import run
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
from vyos.utils.file import read_file
+from vyos.utils.file import write_file
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
from vyos.utils.assertion import assert_boolean
@@ -52,7 +57,6 @@ from vyos.utils.assertion import assert_mac
from vyos.utils.assertion import assert_mtu
from vyos.utils.assertion import assert_positive
from vyos.utils.assertion import assert_range
-
from vyos.ifconfig.control import Control
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
@@ -377,6 +381,9 @@ class Interface(Control):
>>> i = Interface('eth0')
>>> i.remove()
"""
+ # Stop WPA supplicant if EAPoL was in use
+ if is_systemd_service_active(f'wpa_supplicant-wired@{self.ifname}'):
+ self._cmd(f'systemctl stop wpa_supplicant-wired@{self.ifname}')
# remove all assigned IP addresses from interface - this is a bit redundant
# as the kernel will remove all addresses on interface deletion, but we
@@ -1522,6 +1529,61 @@ class Interface(Control):
return None
self.set_interface('per_client_thread', enable)
+ def set_eapol(self) -> None:
+ """ Take care about EAPoL supplicant daemon """
+
+ # XXX: wpa_supplicant works on the source interface
+ cfg_dir = '/run/wpa_supplicant'
+ wpa_supplicant_conf = f'{cfg_dir}/{self.ifname}.conf'
+ eapol_action='stop'
+
+ if 'eapol' in self.config:
+ # The default is a fallback to hw_id which is not present for any interface
+ # other then an ethernet interface. Thus we emulate hw_id by reading back the
+ # Kernel assigned MAC address
+ if 'hw_id' not in self.config:
+ self.config['hw_id'] = read_file(f'/sys/class/net/{self.ifname}/address')
+ render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', self.config)
+
+ cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_cert.pem')
+ cert_key_path = os.path.join(cfg_dir, f'{self.ifname}_cert.key')
+
+ cert_name = self.config['eapol']['certificate']
+ pki_cert = self.config['pki']['certificate'][cert_name]
+
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in self.config['pki']['ca'].values()} if 'ca' in self.config['pki'] else {}
+
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
+ write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
+
+ if 'ca_certificate' in self.config['eapol']:
+ ca_cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_ca.pem')
+ ca_chains = []
+
+ for ca_cert_name in self.config['eapol']['ca_certificate']:
+ pki_ca_cert = self.config['pki']['ca'][ca_cert_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append(
+ '\n'.join(encode_certificate(c) for c in ca_full_chain))
+
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
+
+ eapol_action='reload-or-restart'
+
+ # start/stop WPA supplicant service
+ self._cmd(f'systemctl {eapol_action} wpa_supplicant-wired@{self.ifname}')
+
+ if 'eapol' not in self.config:
+ # delete configuration on interface removal
+ if os.path.isfile(wpa_supplicant_conf):
+ os.unlink(wpa_supplicant_conf)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -1609,7 +1671,6 @@ class Interface(Control):
tmp = get_interface_config(config['ifname'])
if 'master' in tmp and tmp['master'] != bridge_if:
self.set_vrf('')
-
else:
self.set_vrf(config.get('vrf', ''))
@@ -1752,6 +1813,9 @@ class Interface(Control):
value = '1' if (tmp != None) else '0'
self.set_per_client_thread(value)
+ # enable/disable EAPoL (Extensible Authentication Protocol over Local Area Network)
+ self.set_eapol()
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as