From 3f932b66a5523c2d80a94a476527f99161f1fca6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 20 May 2020 21:06:37 +0200 Subject: interface: T2023: remove superfluous at end of list --- python/vyos/ifconfig/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 61f2c6482..6e5cfad9e 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -51,7 +51,7 @@ class Interface(Control): # WireGuard to modify their display behaviour OperationalClass = Operational - options = ['debug', 'create',] + options = ['debug', 'create'] required = [] default = { 'type': '', -- cgit v1.2.3 From 0f98642dfbc6fd4b5eb9059abbb6e9767e0e0a8f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 20 May 2020 21:07:59 +0200 Subject: interface: T2023: adopt _delete() to common style --- python/vyos/ifconfig/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 6e5cfad9e..07efc6d97 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -265,7 +265,7 @@ class Interface(Control): # NOTE (Improvement): # after interface removal no other commands should be allowed # to be called and instead should raise an Exception: - cmd = 'ip link del dev {}'.format(self.config['ifname']) + cmd = 'ip link del dev {ifname}'.format(**self.config) return self._cmd(cmd) def get_mtu(self): -- cgit v1.2.3 From 2e8bd0ced8967644b0ad361df9b375075276593a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 20 May 2020 21:09:31 +0200 Subject: ifconfig: T2023: add initial MACsec abstraction --- python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/macsec.py | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 python/vyos/ifconfig/macsec.py (limited to 'python') diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 4d98901b7..1757adf26 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -42,3 +42,4 @@ from vyos.ifconfig.tunnel import SitIf from vyos.ifconfig.tunnel import Sit6RDIf from vyos.ifconfig.wireless import WiFiIf from vyos.ifconfig.l2tpv3 import L2TPv3If +from vyos.ifconfig.macsec import MACsecIf diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py new file mode 100644 index 000000000..cea3f8d13 --- /dev/null +++ b/python/vyos/ifconfig/macsec.py @@ -0,0 +1,73 @@ +# Copyright 2020 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +from vyos.ifconfig.interface import Interface + +@Interface.register +class MACsecIf(Interface): + """ + MACsec is an IEEE standard (IEEE 802.1AE) for MAC security, introduced in + 2006. It defines a way to establish a protocol independent connection + between two hosts with data confidentiality, authenticity and/or integrity, + using GCM-AES-128. MACsec operates on the Ethernet layer and as such is a + layer 2 protocol, which means it's designed to secure traffic within a + layer 2 network, including DHCP or ARP requests. It does not compete with + other security solutions such as IPsec (layer 3) or TLS (layer 4), as all + those solutions are used for their own specific use cases. + """ + + default = { + 'type': 'macsec', + 'cipher': '', + 'source_interface': '' + } + definition = { + **Interface.definition, + **{ + 'section': 'macsec', + 'prefixes': ['macsec', ], + }, + } + options = Interface.options + \ + ['cipher', 'source_interface'] + + def _create(self): + """ + Create MACsec interface in OS kernel. Interface is administrative + down by default. + """ + # create tunnel interface + cmd = 'ip link add link {source_interface} {ifname} type {type}' + cmd += ' cipher {cipher} encrypt on' + self._cmd(cmd.format(**self.config)) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + + @staticmethod + def get_config(): + """ + MACsec interfaces require a configuration when they are added using + iproute2. This static method will provide the configuration dictionary + used by this class. + + Example: + >> dict = MACsecIf().get_config() + """ + config = { + 'cipher': '', + 'source_interface': '', + } + return config -- 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 'python') 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 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 'python') 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