diff options
author | Giga Murphy <giga1699@gmail.com> | 2023-08-18 00:26:51 +0000 |
---|---|---|
committer | Giga Murphy <giga1699@gmail.com> | 2023-08-18 00:26:51 +0000 |
commit | 33b9bc55f9e3d7ce6a5ff447a6b545a96915daf3 (patch) | |
tree | e1f8072a5f86d3ad2f69cc819b1f5afb05832cee | |
parent | 65ea7cef9fe922581e286bc539c4dc1e223c9d32 (diff) | |
download | vyos-1x-33b9bc55f9e3d7ce6a5ff447a6b545a96915daf3.tar.gz vyos-1x-33b9bc55f9e3d7ce6a5ff447a6b545a96915daf3.zip |
T5447: Initial support for MACsec static keys
-rw-r--r-- | interface-definitions/interfaces-macsec.xml.in | 46 | ||||
-rw-r--r-- | python/vyos/ifconfig/macsec.py | 30 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-macsec.py | 56 |
3 files changed, 117 insertions, 15 deletions
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 6bc28e44b..b81c9b40c 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -52,6 +52,52 @@ <valueless/> </properties> </leafNode> + <node name="static"> + <properties> + <help>Assign static MACSec keys instead of using MKA</help> + </properties> + <children> + <leafNode name="tx-key"> + <properties> + <help>Set the static transmit key</help> + <valueHelp> + <format>txt</format> + <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description> + </valueHelp> + <constraint> + <regex>[A-Fa-f0-9]{32}</regex> + <regex>[A-Fa-f0-9]{64}</regex> + </constraint> + </properties> + </leafNode> + <tagNode name="peer"> + <properties> + <help>peer alias</help> + <constraint> + <regex>[^ ]{1,100}</regex> + </constraint> + <constraintErrorMessage>peer alias too long (limit 100 characters)</constraintErrorMessage> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/interface/mac.xml.i> + <leafNode name="rx-key"> + <properties> + <help>Set the static receive key for peer</help> + <valueHelp> + <format>txt</format> + <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description> + </valueHelp> + <constraint> + <regex>[A-Fa-f0-9]{32}</regex> + <regex>[A-Fa-f0-9]{64}</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> <node name="mka"> <properties> <help>MACsec Key Agreement protocol (MKA)</help> diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index 1a78d18d8..6318a1688 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -39,12 +39,36 @@ class MACsecIf(Interface): def _create(self): """ Create MACsec interface in OS kernel. Interface is administrative - down by default. + down by default when not using static keys. """ + # create tunnel interface cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config) cmd += f' cipher {self.config["security"]["cipher"]}' self._cmd(cmd) - # interface is always A/D down. It needs to be enabled explicitly - self.set_admin_state('down') + # Check if using static keys + if 'static' in self.config["security"]: + # Set static TX key + cmd = 'ip macsec add {ifname} tx sa 0 pn 1 on key 00'.format(**self.config) + cmd += f' {self.config["security"]["static"]["tx_key"]}' + self._cmd(cmd) + + for peer, peer_config in self.config["security"]["static"]["peer"].items(): + if 'disable' in peer_config: + continue + + # Create the address + cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config) + cmd += f' {peer_config["mac"]}' + self._cmd(cmd) + # Add the rx-key to the address + cmd += f' sa 0 pn 1 on key 01 {peer_config["rx_key"]}' + self._cmd(cmd) + + # Set admin state to up + self.set_admin_state('up') + + else: + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 3f86e2638..45fa3f5af 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -89,18 +89,46 @@ def verify(macsec): raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) if dict_search('security.encrypt', macsec) != None: - if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: - raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + # Check that only static or MKA config is present + if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): + raise ConfigError('Only static or MKA can be used!') - cak_len = len(dict_search('security.mka.cak', macsec)) + # Logic to check static configuration + if dict_search('security.static', macsec) != None: - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32: - # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit - raise ConfigError('gcm-aes-128 requires a 128bit long key!') + tx_len = len(dict_search('security.static.tx_key', macsec)) - elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64: - # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit - raise ConfigError('gcm-aes-128 requires a 256bit long key!') + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != 32: + # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit + raise ConfigError('gcm-aes-128 requires a 128bit long key!') + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != 64: + # gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit + raise ConfigError('gcm-aes-256 requires a 256bit long key!') + + # Make sure at least one peer is defined + if 'peer' not in macsec['security']['static']: + raise ConfigError('Must have at least one peer defined for static MACsec') + + # For every enabled peer, make sure a MAC and rx-key is defined + for peer, peer_config in macsec['security']['static']['peer'].items(): + if 'disable' not in peer_config and ('mac' not in peer_config or 'rx_key' not in peer_config): + raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.') + + # Logic to check MKA configuration + else: + if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: + raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + + cak_len = len(dict_search('security.mka.cak', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32: + # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit + raise ConfigError('gcm-aes-128 requires a 128bit long key!') + + elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64: + # gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit + raise ConfigError('gcm-aes-256 requires a 256bit long key!') if 'source_interface' in macsec: # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad @@ -115,7 +143,9 @@ def verify(macsec): def generate(macsec): - render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) + # Only generate wpa_supplicant config if using MKA + if dict_search('security.static', macsec) == None: + render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) return None @@ -142,8 +172,10 @@ def apply(macsec): i = MACsecIf(**macsec) i.update(macsec) - if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: - call(f'systemctl reload-or-restart {systemd_service}') + #Only reload/restart if not using static keys + if dict_search('security.static', macsec) == None: + if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: + call(f'systemctl reload-or-restart {systemd_service}') return None |