From cbc293c31709af86af07f03a60818136eeef84fb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 12:36:46 +0200 Subject: ifconfig: T2653: add vyos.configverify.verify_source_interface() helper --- python/vyos/configverify.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 64eb80728..e2fffeca7 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -21,14 +21,14 @@ # NOTE: imports should be as local as possible to the function which # makes use of it! +from vyos import ConfigError + def verify_bridge_vrf(config): """ Common helper function used by interface implementations to perform recurring validation of VRF configuration """ from netifaces import interfaces - from vyos import ConfigError - if 'vrf' in config.keys(): if config['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) @@ -45,8 +45,6 @@ def verify_bridge_address(config): perform recurring validation of IP address assignmenr when interface also is part of a bridge. """ - from vyos import ConfigError - if {'is_bridge_member', 'address'} <= set(config): raise ConfigError( f'Cannot assign address to interface "{ifname}" as it is a ' @@ -59,9 +57,18 @@ def verify_bridge_delete(config): perform recurring validation of IP address assignmenr when interface also is part of a bridge. """ - from vyos import ConfigError - if 'is_bridge_member' in config.keys(): raise ConfigError( 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) + + +def verify_source_interface(config): + """ + Common helper function used by interface implementations to + perform recurring validation of the existence of a source-interface + required by e.g. peth/MACvlan, MACsec ... + """ + if not 'source_interface' in config.keys(): + raise ConfigError('Physical source-interface required for ' + 'interface "{ifname}"'.format(**config)) -- cgit v1.2.3 From e0bd74e399cc2693b6d442af52c9345e279db059 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 12:54:13 +0200 Subject: ifconfig: T2653: move macsec interface to get_config_dict() --- data/templates/macsec/wpa_supplicant.conf.tmpl | 21 +-- interface-definitions/interfaces-macsec.xml.in | 1 + python/vyos/configdict.py | 2 + python/vyos/ifconfig/interface.py | 3 - src/conf_mode/interfaces-macsec.py | 209 ++++++++----------------- 5 files changed, 77 insertions(+), 159 deletions(-) diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl index a614d23f5..1731bf160 100644 --- a/data/templates/macsec/wpa_supplicant.conf.tmpl +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -45,9 +45,10 @@ network={ # - the key server has decided to enable MACsec # 0: Encrypt traffic (default) # 1: Integrity only - macsec_integ_only={{ '0' if security_encrypt else '1' }} + macsec_integ_only={{ '0' if security is defined and security.encrypt is defined else '1' }} -{% if security_encrypt %} +{% if security is defined %} +{% if security.encrypt is defined %} # 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 @@ -56,21 +57,22 @@ network={ # 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 }} + 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 }} -{% endif %} -{% if security_replay_window %} + mka_priority={{ security.mka.priority }} +{% endif %} + +{% if security.replay_window is defined %} # macsec_replay_protect: IEEE 802.1X/MACsec replay protection # This setting applies only when MACsec is in use, i.e., # - macsec_policy is enabled # - the key server has decided to enable MACsec # 0: Replay protection disabled (default) # 1: Replay protection enabled - macsec_replay_protect={{ '1' if security_replay_window else '0' }} + macsec_replay_protect=1 # macsec_replay_window: IEEE 802.1X/MACsec replay protection window # This determines a window in which replay is tolerated, to allow receipt @@ -80,7 +82,8 @@ network={ # - the key server has decided to enable MACsec # 0: No replay window, strict check (default) # 1..2^32-1: number of packets that could be misordered - macsec_replay_window={{ security_replay_window }} + macsec_replay_window={{ security.replay_window }} +{% endif %} {% endif %} } diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 36605ab59..dfef387d2 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -83,6 +83,7 @@ + 255 diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 074dc0131..0dc7578d8 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -96,6 +96,8 @@ def dict_merge(source, destination): for key, value in source.items(): if key not in tmp.keys(): tmp[key] = value + elif isinstance(source[key], dict): + tmp[key] = dict_merge(source[key], tmp[key]) return tmp diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index a9af6ffdf..1819ffc82 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -796,6 +796,3 @@ class Interface(Control): # Interface administrative state state = 'down' if 'disable' in config.keys() else 'up' self.set_admin_state(state) - - import pprint - pprint.pprint(config) diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index a8966148f..ca5c32eeb 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -18,177 +18,111 @@ import os from copy import deepcopy from sys import exit -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff +from vyos.configdict import dict_merge from vyos.ifconfig import MACsecIf from vyos.template import render from vyos.util import call from vyos.validate import is_member +from vyos.configverify import verify_bridge_vrf +from vyos.configverify import verify_bridge_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface +from vyos.xml import defaults from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'address_remove': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'security_cipher': '', - 'security_encrypt': False, - 'security_mka_cak': '', - 'security_mka_ckn': '', - 'security_mka_priority': '255', - 'security_replay_window': '', - 'intf': '', - 'source_interface': '', - 'is_bridge_member': False, - 'vrf': '' -} +# XXX: workaround for https://phabricator.vyos.net/T2656 +default = {'security' : {'mka' : {'priority' : '255'}}} # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' - def get_config(): - macsec = deepcopy(default_config_data) + """ Retrive CLI config as dictionary. Dictionary can never be empty, + as at least the interface name will be added or a deleted flag """ 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'] - base_path = ['interfaces', 'macsec', macsec['intf']] + # retrieve interface default values + base = ['interfaces', 'macsec'] + #default_values = defaults(base) - # check if we are a member of any bridge - macsec['is_bridge_member'] = is_member(conf, macsec['intf'], 'bridge') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + base = base + [ifname] + macsec = conf.get_config_dict(base, key_mangling=('-', '_')) # Check if interface has been removed - if not conf.exists(base_path): - macsec['deleted'] = True - # When stopping wpa_supplicant we need to stop it via the physical - # interface - thus we need to retrieve ir from the effective config - if conf.exists_effective(base_path + ['source-interface']): - macsec['source_interface'] = conf.return_effective_value( - base_path + ['source-interface']) - - return macsec - - # set new configuration level - conf.set_level(base_path) - - # retrieve configured interface addresses - if conf.exists(['address']): - macsec['address'] = conf.return_values(['address']) - - # retrieve interface description - if conf.exists(['description']): - macsec['description'] = conf.return_value(['description']) - - # Disable this interface - if conf.exists(['disable']): - macsec['disable'] = True - - # retrieve interface cipher - if conf.exists(['security', 'cipher']): - macsec['security_cipher'] = conf.return_value(['security', 'cipher']) - - # Enable optional MACsec encryption - if conf.exists(['security', 'encrypt']): - macsec['security_encrypt'] = True - - # Secure Connectivity Association Key - if conf.exists(['security', 'mka', 'cak']): - macsec['security_mka_cak'] = conf.return_value( - ['security', 'mka', 'cak']) - - # Secure Connectivity Association Name - 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']) - - # IEEE 802.1X/MACsec replay protection - if conf.exists(['security', 'replay-window']): - macsec['security_replay_window'] = conf.return_value( - ['security', 'replay-window']) - - # 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']) + if macsec == {}: + tmp = { + 'deleted' : '', + 'source_interface' : conf.return_effective_value( + base + ['source-interface']) + } + macsec.update(tmp) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + macsec = dict_merge(default, macsec) + + # Add interface instance name into dictionary + macsec.update({'ifname': ifname}) + + # Check if we are a member of any bridge + bridge = is_member(conf, ifname, 'bridge') + if bridge: + tmp = {'is_bridge_member' : bridge} + macsec.update(tmp) return macsec def verify(macsec): - if macsec['deleted']: - if macsec['is_bridge_member']: - raise ConfigError( - 'Interface "{intf}" cannot be deleted as it is a ' - 'member of bridge "{is_bridge_member}"!'.format(**macsec)) - + if 'deleted' in macsec.keys(): + verify_bridge_delete(macsec) return None - if not macsec['source_interface']: - raise ConfigError('Physical source interface must be set for ' - 'MACsec "{intf}"'.format(**macsec)) + verify_source_interface(macsec) + verify_bridge_vrf(macsec) + verify_bridge_address(macsec) - if not macsec['security_cipher']: + if not (('security' in macsec.keys()) and + ('cipher' in macsec['security'].keys())): raise ConfigError( - 'Cipher suite must be set for MACsec "{intf}"'.format(**macsec)) + 'Cipher suite must be set for MACsec "{ifname}"'.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 (('security' in macsec.keys()) and + ('encrypt' in macsec['security'].keys())): + tmp = macsec.get('security') - if macsec['vrf']: - if macsec['vrf'] not in interfaces(): - raise ConfigError('VRF "{vrf}" does not exist'.format(**macsec)) - - if macsec['is_bridge_member']: - 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( - 'Cannot assign address to interface "{intf}" as it is' - 'a member of bridge "{is_bridge_member}"!'.format(**macsec)) + if not (('mka' in tmp.keys()) and + ('cak' in tmp['mka'].keys()) and + ('ckn' in tmp['mka'].keys())): + raise ConfigError('Missing mandatory MACsec security ' + 'keys as encryption is enabled!') return None def generate(macsec): render(wpa_suppl_conf.format(**macsec), - 'macsec/wpa_supplicant.conf.tmpl', macsec, permission=0o640) + 'macsec/wpa_supplicant.conf.tmpl', macsec) return None def apply(macsec): # Remove macsec interface - if macsec['deleted']: + if 'deleted' in macsec.keys(): call('systemctl stop wpa_supplicant-macsec@{source_interface}' .format(**macsec)) - MACsecIf(macsec['intf']).remove() + + MACsecIf(macsec['ifname']).remove() # delete configuration on interface removal if os.path.isfile(wpa_suppl_conf.format(**macsec)): @@ -198,35 +132,16 @@ def apply(macsec): # 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 + # XXX: subject of removal after completing T2653 + conf = deepcopy(MACsecIf.get_config()) conf['source_interface'] = macsec['source_interface'] - conf['security_cipher'] = macsec['security_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) - - # 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']) - - # Interface is administratively down by default, enable if desired - if not macsec['disable']: - i.set_admin_state('up') + i = MACsecIf(macsec['ifname'], **conf) + i.update(macsec) call('systemctl restart wpa_supplicant-macsec@{source_interface}' .format(**macsec)) -- cgit v1.2.3 From adfb4c81a41b9eec4b33a27a8d8db7184dbda6da Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 12:56:18 +0200 Subject: ifconfig: T2653: dummy: loopback: use same get_config() structure as MACsec --- src/conf_mode/interfaces-dummy.py | 12 ++++++------ src/conf_mode/interfaces-loopback.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 676211428..a8093ffa5 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -41,6 +41,10 @@ def get_config(): base = ['interfaces', 'dummy', ifname] dummy = conf.get_config_dict(base, key_mangling=('-', '_')) + # Check if interface has been removed + if dummy == {}: + dummy.update({'deleted' : ''}) + # store interface instance name in dictionary dummy.update({'ifname': ifname}) @@ -50,14 +54,10 @@ def get_config(): tmp = {'is_bridge_member' : bridge} dummy.update(tmp) - # Check if interface has been removed - tmp = {'deleted' : not conf.exists(base)} - dummy.update(tmp) - return dummy def verify(dummy): - if dummy['deleted']: + if 'deleted' in dummy.keys(): verify_bridge_delete(dummy) return None @@ -73,7 +73,7 @@ def apply(dummy): d = DummyIf(dummy['ifname']) # Remove dummy interface - if dummy['deleted']: + if 'deleted' in dummy.keys(): d.remove() else: d.update(dummy) diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 32e683c07..7c3d8663d 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -36,13 +36,13 @@ def get_config(): base = ['interfaces', 'loopback', ifname] loopback = conf.get_config_dict(base, key_mangling=('-', '_')) + # Check if interface has been removed + if loopback == {}: + loopback.update({'deleted' : ''}) + # store interface instance name in dictionary loopback.update({'ifname': ifname}) - # Check if interface has been removed - tmp = {'deleted' : not conf.exists(base)} - loopback.update(tmp) - return loopback def verify(loopback): @@ -53,7 +53,7 @@ def generate(loopback): def apply(loopback): l = LoopbackIf(loopback['ifname']) - if loopback['deleted']: + if 'deleted' in loopback.keys(): l.remove() else: l.update(loopback) -- cgit v1.2.3