diff options
| -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 | 
