From fe9d399a4e78a1637cb3a693e0470eaec1dd5de5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 20 May 2020 21:09:59 +0200 Subject: macsec: T2023: add initial XML and Python interfaces --- src/conf_mode/interfaces-macsec.py | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100755 src/conf_mode/interfaces-macsec.py (limited to 'src') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py new file mode 100755 index 000000000..db605295e --- /dev/null +++ b/src/conf_mode/interfaces-macsec.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from copy import deepcopy +from sys import exit +from netifaces import interfaces + +from vyos.ifconfig import MACsecIf +from vyos.configdict import list_diff +from vyos.config import Config +from vyos.validate import is_member +from vyos import ConfigError + +default_config_data = { + 'address': [], + 'address_remove': [], + 'cipher': 'gcm-aes-128', + 'deleted': False, + 'description': '', + 'disable': False, + 'intf': '', + 'source_interface': '', + 'is_bridge_member': False, + 'vrf': '' +} + +def get_config(): + macsec = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + if 'VYOS_TAGNODE_VALUE' not in os.environ: + raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + + macsec['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + + # check if we are a member of any bridge + macsec['is_bridge_member'] = is_member(conf, macsec['intf'], 'bridge') + + # Check if interface has been removed + if not conf.exists('interfaces macsec ' + macsec['intf']): + macsec['deleted'] = True + return macsec + + # set new configuration level + conf.set_level('interfaces macsec ' + macsec['intf']) + + # retrieve configured interface addresses + if conf.exists('address'): + macsec['address'] = conf.return_values('address') + + # retrieve interface cipher + if conf.exists('cipher'): + macsec['cipher'] = conf.return_value('cipher') + + # retrieve interface description + if conf.exists('description'): + macsec['description'] = conf.return_value('description') + + # Disable this interface + if conf.exists('disable'): + macsec['disable'] = True + + # Physical interface + if conf.exists(['source-interface']): + macsec['source_interface'] = conf.return_value(['source-interface']) + + # Determine interface addresses (currently effective) - to determine which + # address is no longer valid and needs to be removed from the interface + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + macsec['address_remove'] = list_diff(eff_addr, act_addr) + + # retrieve VRF instance + if conf.exists('vrf'): + macsec['vrf'] = conf.return_value('vrf') + + return macsec + +def verify(macsec): + if macsec['deleted']: + if macsec['is_bridge_member']: + raise ConfigError(( + f'Interface "{macsec["intf"]}" cannot be deleted as it is a ' + f'member of bridge "{macsec["is_bridge_member"]}"!')) + + return None + + if not macsec['source_interface']: + raise ConfigError(( + f'Physical source interface must be set for MACsec "{macsec["intf"]}"')) + + if macsec['vrf']: + if macsec['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{macsec["vrf"]}" does not exist') + + if macsec['is_bridge_member']: + raise ConfigError(( + f'Interface "{macsec["intf"]}" cannot be member of VRF ' + f'"{macsec["vrf"]}" and bridge "{macsec["is_bridge_member"]}" ' + f'at the same time!')) + + if macsec['is_bridge_member'] and macsec['address']: + raise ConfigError(( + f'Cannot assign address to interface "{macsec["intf"]}" ' + f'as it is a member of bridge "{macsec["is_bridge_member"]}"!')) + + return None + +def generate(macsec): + return None + +def apply(macsec): + # Remove macsec interface + if macsec['deleted']: + MACsecIf(macsec['intf']).remove() + else: + # MACsec interfaces require a configuration when they are added using + # iproute2. This static method will provide the configuration + # dictionary used by this class. + conf = deepcopy(MACsecIf.get_config()) + + # Assign MACsec instance configuration parameters to config dict + conf['source_interface'] = macsec['source_interface'] + conf['cipher'] = macsec['cipher'] + + # It is safe to "re-create" the interface always, there is a sanity check + # that the interface will only be create if its non existent + i = MACsecIf(macsec['intf'], **conf) + + # update interface description used e.g. within SNMP + i.set_alias(macsec['description']) + + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second + for addr in macsec['address_remove']: + i.del_addr(addr) + for addr in macsec['address']: + i.add_addr(addr) + + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not macsec['is_bridge_member']: + i.set_vrf(macsec['vrf']) + + # disable interface on demand + if macsec['disable']: + i.set_admin_state('down') + else: + i.set_admin_state('up') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From 04d03f5bdd262bbf95f09e6ba3f211ab1d459573 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 10:43:44 +0200 Subject: macsec: T2023: add optional encryption command By default MACsec only authenticates traffic but has support for optional encryption. Encryption can now be enabled using: set interfaces macsec encrypt --- interface-definitions/interfaces-macsec.xml.in | 6 ++++++ python/vyos/ifconfig/macsec.py | 7 ++++++- src/conf_mode/interfaces-macsec.py | 14 ++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 79837dfb5..13448e758 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -36,6 +36,12 @@ + + + Enable optional MACsec encryption + + + #include #include #include diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index cea3f8d13..1829df4ab 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -50,12 +50,17 @@ class MACsecIf(Interface): """ # create tunnel interface cmd = 'ip link add link {source_interface} {ifname} type {type}' - cmd += ' cipher {cipher} encrypt on' + cmd += ' cipher {cipher}' self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') + def set_encryption(self, on_off): + ifname = self.config['ifname'] + cmd = f'ip link set {ifname} type macsec encrypt {on_off}' + return self._cmd(cmd) + @staticmethod def get_config(): """ diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index db605295e..fcf23ed0f 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -33,6 +33,7 @@ default_config_data = { 'deleted': False, 'description': '', 'disable': False, + 'encrypt': 'off', 'intf': '', 'source_interface': '', 'is_bridge_member': False, @@ -76,6 +77,10 @@ def get_config(): if conf.exists('disable'): macsec['disable'] = True + # Enable optional MACsec encryption + if conf.exists('encrypt'): + macsec['encrypt'] = 'on' + # Physical interface if conf.exists(['source-interface']): macsec['source_interface'] = conf.return_value(['source-interface']) @@ -143,6 +148,9 @@ def apply(macsec): # that the interface will only be create if its non existent i = MACsecIf(macsec['intf'], **conf) + # Configure optional encryption + i.set_encryption(macsec['encrypt']) + # update interface description used e.g. within SNMP i.set_alias(macsec['description']) @@ -159,10 +167,8 @@ def apply(macsec): if not macsec['is_bridge_member']: i.set_vrf(macsec['vrf']) - # disable interface on demand - if macsec['disable']: - i.set_admin_state('down') - else: + # Interface is administratively down by default, enable if desired + if not macsec['disable']: i.set_admin_state('up') return None -- cgit v1.2.3 From 4a0c0b4e041d9f42d0b1fbfa3a259e76707338cb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 11:05:53 +0200 Subject: macsec: T2023: use list when working with Config() --- src/conf_mode/interfaces-macsec.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index fcf23ed0f..874fd6d62 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -49,36 +49,37 @@ def get_config(): raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') macsec['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + base_path = ['interfaces', 'macsec', macsec['intf']] # check if we are a member of any bridge macsec['is_bridge_member'] = is_member(conf, macsec['intf'], 'bridge') # Check if interface has been removed - if not conf.exists('interfaces macsec ' + macsec['intf']): + if not conf.exists(base_path): macsec['deleted'] = True return macsec # set new configuration level - conf.set_level('interfaces macsec ' + macsec['intf']) + conf.set_level(base_path) # retrieve configured interface addresses - if conf.exists('address'): - macsec['address'] = conf.return_values('address') + if conf.exists(['address']): + macsec['address'] = conf.return_values(['address']) # retrieve interface cipher - if conf.exists('cipher'): - macsec['cipher'] = conf.return_value('cipher') + if conf.exists(['cipher']): + macsec['cipher'] = conf.return_value(['cipher']) # retrieve interface description - if conf.exists('description'): - macsec['description'] = conf.return_value('description') + if conf.exists(['description']): + macsec['description'] = conf.return_value(['description']) # Disable this interface - if conf.exists('disable'): + if conf.exists(['disable']): macsec['disable'] = True # Enable optional MACsec encryption - if conf.exists('encrypt'): + if conf.exists(['encrypt']): macsec['encrypt'] = 'on' # Physical interface @@ -87,13 +88,13 @@ def get_config(): # Determine interface addresses (currently effective) - to determine which # address is no longer valid and needs to be removed from the interface - eff_addr = conf.return_effective_values('address') - act_addr = conf.return_values('address') + eff_addr = conf.return_effective_values(['address']) + act_addr = conf.return_values(['address']) macsec['address_remove'] = list_diff(eff_addr, act_addr) # retrieve VRF instance - if conf.exists('vrf'): - macsec['vrf'] = conf.return_value('vrf') + if conf.exists(['vrf']): + macsec['vrf'] = conf.return_value(['vrf']) return macsec -- cgit v1.2.3 From 576951171b25bf3f5427c45e40bd540105f558b3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 11:07:19 +0200 Subject: macsec: T2023: cipher suite is mandatory --- src/conf_mode/interfaces-macsec.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 874fd6d62..867df3eb6 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -29,7 +29,7 @@ from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], - 'cipher': 'gcm-aes-128', + 'cipher': '', 'deleted': False, 'description': '', 'disable': False, @@ -111,6 +111,10 @@ def verify(macsec): raise ConfigError(( f'Physical source interface must be set for MACsec "{macsec["intf"]}"')) + if not macsec['cipher']: + raise ConfigError(( + f'Cipher suite is mandatory for MACsec "{macsec["intf"]}"')) + if macsec['vrf']: if macsec['vrf'] not in interfaces(): raise ConfigError(f'VRF "{macsec["vrf"]}" does not exist') -- cgit v1.2.3 From 63a3110298e5f3f6d24d5ed57eff0a8abf27f6ac Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 12:48:09 +0200 Subject: macsec: T2023: cli: move "cipher" and "encryption" under new "secutiry" node This is best suited as a key is required, too. --- interface-definitions/interfaces-macsec.xml.in | 45 +++++++++++++++----------- src/conf_mode/interfaces-macsec.py | 15 +++++---- 2 files changed, 34 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index f16760112..53a347f11 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -17,27 +17,34 @@ #include - + - Cipher suite used - - gcm-aes-128 - - - gcm-aes-128 - Galois/Counter Mode of AES cipher with 128-bit key (default) - - - (gcm-aes-128) - + Security/Encryption Settings - - - - Enable optional MACsec encryption - - - + + + + Cipher suite used + + gcm-aes-128 + + + gcm-aes-128 + Galois/Counter Mode of AES cipher with 128-bit key (default) + + + (gcm-aes-128) + + + + + + Enable optional MACsec encryption + + + + + #include #include #include diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 867df3eb6..fefc50d99 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -20,9 +20,10 @@ from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import MACsecIf -from vyos.configdict import list_diff from vyos.config import Config +from vyos.configdict import list_diff +from vyos.ifconfig import MACsecIf +from vyos.template import render from vyos.validate import is_member from vyos import ConfigError @@ -66,10 +67,6 @@ def get_config(): if conf.exists(['address']): macsec['address'] = conf.return_values(['address']) - # retrieve interface cipher - if conf.exists(['cipher']): - macsec['cipher'] = conf.return_value(['cipher']) - # retrieve interface description if conf.exists(['description']): macsec['description'] = conf.return_value(['description']) @@ -78,8 +75,12 @@ def get_config(): if conf.exists(['disable']): macsec['disable'] = True + # retrieve interface cipher + if conf.exists(['security', 'cipher']): + macsec['cipher'] = conf.return_value(['security', 'cipher']) + # Enable optional MACsec encryption - if conf.exists(['encrypt']): + if conf.exists(['security', 'encrypt']): macsec['encrypt'] = 'on' # Physical interface -- cgit v1.2.3 From 3872f5995644a8a52358285d682a7103b54dde04 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 13:55:32 +0200 Subject: macsec: T2023: use wpa_supplicant for key management --- data/templates/macsec/wpa_supplicant.conf.tmpl | 53 ++++++++++++++++++++++++++ interface-definitions/interfaces-macsec.xml.in | 23 +++++++++++ python/vyos/ifconfig/macsec.py | 13 ++----- src/conf_mode/interfaces-macsec.py | 36 ++++++++++++----- src/systemd/wpa_supplicant-macsec@.service | 16 ++++++++ 5 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 data/templates/macsec/wpa_supplicant.conf.tmpl create mode 100644 src/systemd/wpa_supplicant-macsec@.service (limited to 'src') diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl new file mode 100644 index 000000000..b73d4b863 --- /dev/null +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -0,0 +1,53 @@ +# autogenerated by interfaces-macsec.py + +# see full documentation: +# https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf + +# For UNIX domain sockets (default on Linux and BSD): This is a directory that +# will be created for UNIX domain sockets for listening to requests from +# external programs (CLI/GUI, etc.) for status information and configuration. +# The socket file will be named based on the interface name, so multiple +# wpa_supplicant processes can be run at the same time if more than one +# interface is used. +# /var/run/wpa_supplicant is the recommended directory for sockets and by +# default, wpa_cli will use it when trying to connect with wpa_supplicant. +ctrl_interface=/run/wpa_supplicant + +# Note: When using MACsec, eapol_version shall be set to 3, which is +# defined in IEEE Std 802.1X-2010. +eapol_version=3 + +# No need to scan for access points in MACsec mode +ap_scan=0 + +# EAP fast re-authentication +fast_reauth=1 + +network={ + key_mgmt=NONE + + # Note: When using wired authentication (including MACsec drivers), + # eapol_flags must be set to 0 for the authentication to be completed + # successfully. + eapol_flags=0 + + # macsec_policy: IEEE 802.1X/MACsec options + # This determines how sessions are secured with MACsec (only for MACsec + # drivers). + # 0: MACsec not in use (default) + # 1: MACsec enabled - Should secure, accept key server's advice to + # determine whether to use a secure session or not. + macsec_policy=1 + + # macsec_integ_only: IEEE 802.1X/MACsec transmit mode + # This setting applies only when MACsec is in use, i.e., + # - macsec_policy is enabled + # - the key server has decided to enable MACsec + # 0: Encrypt traffic (default) + # 1: Integrity only + macsec_integ_only={{ '0' if security_encrypt else '1' }} + + mka_cak={{ security_key_cak }} + mka_ckn={{ security_key_ckn }} +} + diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 53a347f11..f76fef298 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -43,6 +43,29 @@ + + + Encryption keys + + + + + Secure Connectivity Association Key + + ^[A-Fa-f0-9]{32}$ + + + + + + Secure Connectivity Association Key Name + + ^[A-Fa-f0-9]{64}$ + + + + + #include diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index 1829df4ab..ea8c9807e 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -30,7 +30,7 @@ class MACsecIf(Interface): default = { 'type': 'macsec', - 'cipher': '', + 'security_cipher': '', 'source_interface': '' } definition = { @@ -41,7 +41,7 @@ class MACsecIf(Interface): }, } options = Interface.options + \ - ['cipher', 'source_interface'] + ['security_cipher', 'source_interface'] def _create(self): """ @@ -50,17 +50,12 @@ class MACsecIf(Interface): """ # create tunnel interface cmd = 'ip link add link {source_interface} {ifname} type {type}' - cmd += ' cipher {cipher}' + cmd += ' cipher {security_cipher}' self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') - def set_encryption(self, on_off): - ifname = self.config['ifname'] - cmd = f'ip link set {ifname} type macsec encrypt {on_off}' - return self._cmd(cmd) - @staticmethod def get_config(): """ @@ -72,7 +67,7 @@ class MACsecIf(Interface): >> dict = MACsecIf().get_config() """ config = { - 'cipher': '', + 'security_cipher': '', 'source_interface': '', } return config diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index fefc50d99..e59df6f90 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -22,19 +22,22 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import list_diff -from vyos.ifconfig import MACsecIf +from vyos.ifconfig import MACsecIf, Interface from vyos.template import render +from vyos.util import call from vyos.validate import is_member from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], - 'cipher': '', 'deleted': False, 'description': '', 'disable': False, - 'encrypt': 'off', + 'security_cipher': '', + 'security_encrypt': False, + 'security_key_cak': '', + 'security_key_ckn': '', 'intf': '', 'source_interface': '', 'is_bridge_member': False, @@ -77,11 +80,19 @@ def get_config(): # retrieve interface cipher if conf.exists(['security', 'cipher']): - macsec['cipher'] = conf.return_value(['security', 'cipher']) + macsec['security_cipher'] = conf.return_value(['security', 'cipher']) # Enable optional MACsec encryption if conf.exists(['security', 'encrypt']): - macsec['encrypt'] = 'on' + macsec['security_encrypt'] = True + + # Secure Connectivity Association Key + if conf.exists(['security', 'key', 'cak']): + macsec['security_key_cak'] = conf.return_value(['security', 'key', 'cak']) + + # Secure Connectivity Association Name + if conf.exists(['security', 'key', 'ckn']): + macsec['security_key_ckn'] = conf.return_value(['security', 'key', 'ckn']) # Physical interface if conf.exists(['source-interface']): @@ -112,7 +123,7 @@ def verify(macsec): raise ConfigError(( f'Physical source interface must be set for MACsec "{macsec["intf"]}"')) - if not macsec['cipher']: + if not macsec['security_cipher']: raise ConfigError(( f'Cipher suite is mandatory for MACsec "{macsec["intf"]}"')) @@ -134,12 +145,18 @@ def verify(macsec): return None def generate(macsec): + # XXX: wpa_supplicant works on the source interface not the resulting + # MACsec interface + conf = f'/run/wpa_supplicant/wpa_supplicant-{macsec["source_interface"]}.conf' + render(conf, 'macsec/wpa_supplicant.conf.tmpl', macsec, permission=0o640) return None def apply(macsec): # Remove macsec interface if macsec['deleted']: + call(f'systemctl stop wpa_supplicant-@{macsec["intf"]}.service') MACsecIf(macsec['intf']).remove() + else: # MACsec interfaces require a configuration when they are added using # iproute2. This static method will provide the configuration @@ -148,15 +165,12 @@ def apply(macsec): # Assign MACsec instance configuration parameters to config dict conf['source_interface'] = macsec['source_interface'] - conf['cipher'] = macsec['cipher'] + conf['security_cipher'] = macsec['security_cipher'] # It is safe to "re-create" the interface always, there is a sanity check # that the interface will only be create if its non existent i = MACsecIf(macsec['intf'], **conf) - # Configure optional encryption - i.set_encryption(macsec['encrypt']) - # update interface description used e.g. within SNMP i.set_alias(macsec['description']) @@ -177,6 +191,8 @@ def apply(macsec): if not macsec['disable']: i.set_admin_state('up') + call(f'systemctl restart wpa_supplicant-macsec@{macsec["source_interface"]}.service') + return None if __name__ == '__main__': diff --git a/src/systemd/wpa_supplicant-macsec@.service b/src/systemd/wpa_supplicant-macsec@.service new file mode 100644 index 000000000..7ad12e54e --- /dev/null +++ b/src/systemd/wpa_supplicant-macsec@.service @@ -0,0 +1,16 @@ +[Unit] +Description=WPA supplicant daemon (macsec-specific version) +Requires=sys-subsystem-net-devices-%i.device +ConditionPathExists=/run/wpa_supplicant/wpa_supplicant-%I.conf +After=vyos-router.service +RequiresMountsFor=/run + +# NetworkManager users will probably want the dbus version instead. + +[Service] +Type=simple +WorkingDirectory=/run/wpa_supplicant +ExecStart=/sbin/wpa_supplicant -c /run/wpa_supplicant/wpa_supplicant-%I.conf -Dmacsec_linux -i%I + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 68d54d8d79f257497c884b392a7e316e9a8e7d21 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 15:04:24 +0200 Subject: macsec: T2023: rename "security key" node to "security mka" MACsec always talks about MKA (MACsec Key Agreement protocol) thus the node should reflect that. --- data/templates/macsec/wpa_supplicant.conf.tmpl | 12 ++++++++++-- interface-definitions/interfaces-macsec.xml.in | 4 ++-- op-mode-definitions/generate-macsec-key.xml | 8 ++++---- src/conf_mode/interfaces-macsec.py | 13 +++++++------ 4 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl index b73d4b863..df7b6f153 100644 --- a/data/templates/macsec/wpa_supplicant.conf.tmpl +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -47,7 +47,15 @@ network={ # 1: Integrity only macsec_integ_only={{ '0' if security_encrypt else '1' }} - mka_cak={{ security_key_cak }} - mka_ckn={{ security_key_ckn }} + # mka_cak, mka_ckn, and mka_priority: IEEE 802.1X/MACsec pre-shared key mode + # This allows to configure MACsec with a pre-shared key using a (CAK,CKN) pair. + # In this mode, instances of wpa_supplicant can act as MACsec peers. The peer + # with lower priority will become the key server and start distributing SAKs. + # mka_cak (CAK = Secure Connectivity Association Key) takes a 16-byte (128-bit) + # hex-string (32 hex-digits) or a 32-byte (256-bit) hex-string (64 hex-digits) + # mka_ckn (CKN = CAK Name) takes a 1..32-bytes (8..256 bit) hex-string + # (2..64 hex-digits) + mka_cak={{ security_mka_cak }} + mka_ckn={{ security_mka_ckn }} } diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index f76fef298..02e6e7b3f 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -43,9 +43,9 @@ - + - Encryption keys + MACsec Key Agreement protocol (MKA) diff --git a/op-mode-definitions/generate-macsec-key.xml b/op-mode-definitions/generate-macsec-key.xml index 003669827..40d2b9061 100644 --- a/op-mode-definitions/generate-macsec-key.xml +++ b/op-mode-definitions/generate-macsec-key.xml @@ -2,18 +2,18 @@ - + - Generate MACsec secure channel key + Generate MACsec Key - + Generate MACsec connectivity association key (CAK) /usr/bin/hexdump -n 16 -e '4/4 "%08x" 1 "\n"' /dev/random - + Generate MACsec connectivity association name (CKN) diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index e59df6f90..79c57d978 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -36,8 +36,9 @@ default_config_data = { 'disable': False, 'security_cipher': '', 'security_encrypt': False, - 'security_key_cak': '', - 'security_key_ckn': '', + 'security_mka_cak': '', + 'security_mka_ckn': '', + 'security_mka_priority': '255', 'intf': '', 'source_interface': '', 'is_bridge_member': False, @@ -87,12 +88,12 @@ def get_config(): macsec['security_encrypt'] = True # Secure Connectivity Association Key - if conf.exists(['security', 'key', 'cak']): - macsec['security_key_cak'] = conf.return_value(['security', 'key', 'cak']) + if conf.exists(['security', 'mka', 'cak']): + macsec['security_mka_cak'] = conf.return_value(['security', 'mka', 'cak']) # Secure Connectivity Association Name - if conf.exists(['security', 'key', 'ckn']): - macsec['security_key_ckn'] = conf.return_value(['security', 'key', 'ckn']) + if conf.exists(['security', 'mka', 'ckn']): + macsec['security_mka_ckn'] = conf.return_value(['security', 'mka', 'ckn']) # Physical interface if conf.exists(['source-interface']): -- cgit v1.2.3 From 2417c2feedd62a59f0caa0c7a405c60e1f3be0e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 15:06:16 +0200 Subject: macsec: T2023: support MACsec Key Agreement protocol actor priority --- data/templates/macsec/wpa_supplicant.conf.tmpl | 4 ++++ interface-definitions/interfaces-macsec.xml.in | 12 ++++++++++++ src/conf_mode/interfaces-macsec.py | 4 ++++ 3 files changed, 20 insertions(+) (limited to 'src') diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl index df7b6f153..eee215418 100644 --- a/data/templates/macsec/wpa_supplicant.conf.tmpl +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -57,5 +57,9 @@ network={ # (2..64 hex-digits) mka_cak={{ security_mka_cak }} mka_ckn={{ security_mka_ckn }} + + # mka_priority (Priority of MKA Actor) is in 0..255 range with 255 being + # default priority + mka_priority={{ security_mka_priority }} } diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 02e6e7b3f..af3971595 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -64,6 +64,18 @@ + + + Priority of MACsec Key Agreement protocol (MKA) actor (default: 255) + + 0-255 + MACsec Key Agreement protocol (MKA) priority + + + + + + diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 79c57d978..780ef9b5f 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -95,6 +95,10 @@ def get_config(): if conf.exists(['security', 'mka', 'ckn']): macsec['security_mka_ckn'] = conf.return_value(['security', 'mka', 'ckn']) + # MACsec Key Agreement protocol (MKA) actor priority + if conf.exists(['security', 'mka', 'priority']): + macsec['security_mka_priority'] = conf.return_value(['security', 'mka', 'priority']) + # Physical interface if conf.exists(['source-interface']): macsec['source_interface'] = conf.return_value(['source-interface']) -- cgit v1.2.3 From d8d3c1cb5a5aed4ecee9ea1a17dafc09c98bdfaa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 15:57:58 +0200 Subject: macsec: T2023: improve verify() when encryption is enabled With enabled encryption keys must be configured. --- src/conf_mode/interfaces-macsec.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 780ef9b5f..efac92169 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -118,34 +118,37 @@ def get_config(): def verify(macsec): if macsec['deleted']: if macsec['is_bridge_member']: - raise ConfigError(( - f'Interface "{macsec["intf"]}" cannot be deleted as it is a ' - f'member of bridge "{macsec["is_bridge_member"]}"!')) + raise ConfigError( + f'Interface "{intf}" cannot be deleted as it is a ' + f'member of bridge "{is_bridge_member}"!'.format(**macsec)) return None if not macsec['source_interface']: - raise ConfigError(( - f'Physical source interface must be set for MACsec "{macsec["intf"]}"')) + raise ConfigError( + 'Physical source interface must be set for MACsec "{intf}"'.format(**macsec)) if not macsec['security_cipher']: - raise ConfigError(( - f'Cipher suite is mandatory for MACsec "{macsec["intf"]}"')) + raise ConfigError( + 'Cipher suite must be set for MACsec "{intf}"'.format(**macsec)) + + if macsec['security_encrypt']: + if not (macsec['security_mka_cak'] and macsec['security_mka_ckn']): + raise ConfigError('MACsec security keys mandartory when encryption is enabled') if macsec['vrf']: if macsec['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{macsec["vrf"]}" does not exist') + raise ConfigError('VRF "{vrf}" does not exist'.format(**macsec)) if macsec['is_bridge_member']: - raise ConfigError(( - f'Interface "{macsec["intf"]}" cannot be member of VRF ' - f'"{macsec["vrf"]}" and bridge "{macsec["is_bridge_member"]}" ' - f'at the same time!')) + raise ConfigError( + 'Interface "{intf}" cannot be member of VRF "{vrf}" and ' + 'bridge "{is_bridge_member}" at the same time!'.format(**macsec)) if macsec['is_bridge_member'] and macsec['address']: - raise ConfigError(( - f'Cannot assign address to interface "{macsec["intf"]}" ' - f'as it is a member of bridge "{macsec["is_bridge_member"]}"!')) + raise ConfigError( + 'Cannot assign address to interface "{intf}" as it is' + 'a member of bridge "{is_bridge_member}"!'.format(**macsec)) return None -- cgit v1.2.3 From 5df7e8f35234497c03d504ea838dbd7044c49bb3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 16:08:26 +0200 Subject: macsec: T2023: cleanup wpa_supplicant config file name --- src/conf_mode/interfaces-macsec.py | 8 +++++--- src/systemd/wpa_supplicant-macsec@.service | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index efac92169..ed88e877d 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -155,14 +155,16 @@ def verify(macsec): def generate(macsec): # XXX: wpa_supplicant works on the source interface not the resulting # MACsec interface - conf = f'/run/wpa_supplicant/wpa_supplicant-{macsec["source_interface"]}.conf' + wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' + conf = wpa_suppl_conf.format(**macsec) + render(conf, 'macsec/wpa_supplicant.conf.tmpl', macsec, permission=0o640) return None def apply(macsec): # Remove macsec interface if macsec['deleted']: - call(f'systemctl stop wpa_supplicant-@{macsec["intf"]}.service') + call('systemctl stop wpa_supplicant-macsec@{intf}.service'.format(**macsec)) MACsecIf(macsec['intf']).remove() else: @@ -199,7 +201,7 @@ def apply(macsec): if not macsec['disable']: i.set_admin_state('up') - call(f'systemctl restart wpa_supplicant-macsec@{macsec["source_interface"]}.service') + call('systemctl restart wpa_supplicant-macsec@{source_interface}.service'.format(**macsec)) return None diff --git a/src/systemd/wpa_supplicant-macsec@.service b/src/systemd/wpa_supplicant-macsec@.service index 7ad12e54e..9fad6b773 100644 --- a/src/systemd/wpa_supplicant-macsec@.service +++ b/src/systemd/wpa_supplicant-macsec@.service @@ -1,7 +1,7 @@ [Unit] Description=WPA supplicant daemon (macsec-specific version) Requires=sys-subsystem-net-devices-%i.device -ConditionPathExists=/run/wpa_supplicant/wpa_supplicant-%I.conf +ConditionPathExists=/run/wpa_supplicant/%I.conf After=vyos-router.service RequiresMountsFor=/run @@ -10,7 +10,8 @@ RequiresMountsFor=/run [Service] Type=simple WorkingDirectory=/run/wpa_supplicant -ExecStart=/sbin/wpa_supplicant -c /run/wpa_supplicant/wpa_supplicant-%I.conf -Dmacsec_linux -i%I +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart=/sbin/wpa_supplicant -c /run/wpa_supplicant/%I.conf -Dmacsec_linux -i%I [Install] WantedBy=multi-user.target -- cgit v1.2.3