diff options
36 files changed, 242 insertions, 66 deletions
| diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2 index 6902dc05a..ed15b32f0 100644 --- a/data/templates/router-advert/radvd.conf.j2 +++ b/data/templates/router-advert/radvd.conf.j2 @@ -55,6 +55,9 @@ interface {{ iface }} {  {%         endif %}  {%         if iface_config.name_server is vyos_defined %}      RDNSS {{ iface_config.name_server | join(" ") }} { +{%             if iface_config.name_server_lifetime is vyos_defined %} +        AdvRDNSSLifetime {{ iface_config.name_server_lifetime }}; +{%             endif %}      };  {%         endif %}  {%         if iface_config.dnssl is vyos_defined %} diff --git a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i index b9dd59bea..5057ed9ae 100644 --- a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i +++ b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i @@ -1,4 +1,4 @@ -<!-- include start from address-ipv4-ipv6-dhcp.xml.i --> +<!-- include start from interface/address-ipv4-ipv6-dhcp.xml.i -->  <leafNode name="address">    <properties>      <help>IP address</help> diff --git a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i index 519622050..d689da5aa 100644 --- a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i +++ b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i @@ -1,4 +1,4 @@ -<!-- include start from address-ipv4-ipv6.xml.i --> +<!-- include start from interface/address-ipv4-ipv6.xml.i -->  <leafNode name="address">    <properties>      <help>IP address</help> diff --git a/interface-definitions/include/pki/ca-certificate-multi.xml.i b/interface-definitions/include/pki/ca-certificate-multi.xml.i new file mode 100644 index 000000000..646131b54 --- /dev/null +++ b/interface-definitions/include/pki/ca-certificate-multi.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pki/ca-certificate-multi.xml.i --> +<leafNode name="ca-certificate"> +  <properties> +    <help>Certificate Authority chain in PKI configuration</help> +    <completionHelp> +      <path>pki ca</path> +    </completionHelp> +    <valueHelp> +      <format>txt</format> +      <description>Name of CA in PKI configuration</description> +    </valueHelp> +    <multi/> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 60edf3ce2..48ee1efbc 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -73,12 +73,18 @@            </leafNode>            <node name="igmp">              <properties> -              <help>Internet Group Management Protocol (IGMP) settings</help> +              <help>Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) settings</help>              </properties>              <children>                <leafNode name="querier">                  <properties> -                  <help>Enable IGMP querier</help> +                  <help>Enable IGMP/MLD querier</help> +                  <valueless/> +                </properties> +              </leafNode> +              <leafNode name="snooping"> +                <properties> +                  <help>Enable IGMP/MLD snooping</help>                    <valueless/>                  </properties>                </leafNode> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index f1cbf8468..6cbd91ff4 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -741,7 +741,7 @@                  </properties>                </leafNode>                #include <include/pki/certificate.xml.i> -              #include <include/pki/ca-certificate.xml.i> +              #include <include/pki/ca-certificate-multi.xml.i>                <leafNode name="dh-params">                  <properties>                    <help>Diffie Hellman parameters (server only)</help> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 664914baa..9674cfc0e 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -4,7 +4,7 @@      <children>        <tagNode name="pppoe" owner="${vyos_conf_scripts_dir}/interfaces-pppoe.py">          <properties> -          <help>Point-to-Point Protocol over Ethernet (PPPoE)</help> +          <help>Point-to-Point Protocol over Ethernet (PPPoE) Interface</help>            <priority>322</priority>            <constraint>              <regex>pppoe[0-9]+</regex> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 6b62f4c61..53e6445fa 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -4,7 +4,7 @@      <children>        <tagNode name="pseudo-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-pseudo-ethernet.py">          <properties> -          <help>Pseudo Ethernet</help> +          <help>Pseudo Ethernet Interface (Macvlan)</help>            <priority>321</priority>            <constraint>              <regex>peth[0-9]+</regex> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index b471c3b92..aa83a04b2 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -4,7 +4,7 @@      <children>        <tagNode name="vti" owner="${vyos_conf_scripts_dir}/interfaces-vti.py">          <properties> -          <help>Virtual Tunnel interface</help> +          <help>Virtual Tunnel Interface (XFRM)</help>            <priority>381</priority>            <constraint>              <regex>vti[0-9]+</regex> @@ -16,19 +16,7 @@            </valueHelp>          </properties>          <children> -          <leafNode name="address"> -            <properties> -              <help>IP address</help> -              <valueHelp> -                <format>ipv4net</format> -                <description>IPv4 address and prefix length</description> -              </valueHelp> -              <constraint> -                <validator name="ipv4-host"/> -              </constraint> -              <multi/> -            </properties> -          </leafNode> +          #include <include/interface/address-ipv4-ipv6.xml.i>            #include <include/interface/description.xml.i>            #include <include/interface/disable.xml.i>            #include <include/interface/ipv4-options.xml.i> diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index be8e30c18..43ca659e9 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -6,7 +6,7 @@        <node name="mpls" owner="${vyos_conf_scripts_dir}/protocols_mpls.py">          <properties>            <help>Multiprotocol Label Switching (MPLS)</help> -          <priority>299</priority> +          <priority>400</priority>          </properties>          <children>            <node name="ldp"> diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service-conntrack-sync.xml.in index 6fa6fc5f9..6fa6fc5f9 100644 --- a/interface-definitions/service_conntrack-sync.xml.in +++ b/interface-definitions/service-conntrack-sync.xml.in diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service-console-server.xml.in index e9591ad87..e9591ad87 100644 --- a/interface-definitions/service_console-server.xml.in +++ b/interface-definitions/service-console-server.xml.in diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in index e222467b1..e222467b1 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service-ipoe-server.xml.in diff --git a/interface-definitions/service_mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in index 9a94f1488..9a94f1488 100644 --- a/interface-definitions/service_mdns-repeater.xml.in +++ b/interface-definitions/service-mdns-repeater.xml.in diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in index d0d9202c1..d0d9202c1 100644 --- a/interface-definitions/service_monitoring_telegraf.xml.in +++ b/interface-definitions/service-monitoring-telegraf.xml.in diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in index 50f42849b..50f42849b 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service-pppoe-server.xml.in diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service-router-advert.xml.in index 40dac23ca..258b7b749 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service-router-advert.xml.in @@ -136,6 +136,23 @@                  </children>                </node>                #include <include/name-server-ipv6.xml.i> +              <leafNode name="name-server-lifetime"> +                <properties> +                  <help>Maximum duration how long the RDNSS entries are used</help> +                  <valueHelp> +                    <format>u32:0</format> +                    <description>Name-servers should no longer be used</description> +                  </valueHelp> +                  <valueHelp> +                    <format>u32:1-7200</format> +                    <description>Maximum interval in seconds</description> +                  </valueHelp> +                  <constraint> +                    <validator name="numeric" argument="--range 1-7200"/> +                  </constraint> +                  <constraintErrorMessage>Maximum interval must be between 1 and 7200 seconds</constraintErrorMessage> +                </properties> +              </leafNode>                <leafNode name="other-config-flag">                  <properties>                    <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help> diff --git a/interface-definitions/service_sla.xml.in b/interface-definitions/service-sla.xml.in index 0c4f8a591..0c4f8a591 100644 --- a/interface-definitions/service_sla.xml.in +++ b/interface-definitions/service-sla.xml.in diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service-upnp.xml.in index a129b7260..a129b7260 100644 --- a/interface-definitions/service_upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service-webproxy.xml.in index e4609b699..e4609b699 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service-webproxy.xml.in diff --git a/interface-definitions/intel_qat.xml.in b/interface-definitions/system-acceleration-qat.xml.in index 812484184..812484184 100644 --- a/interface-definitions/intel_qat.xml.in +++ b/interface-definitions/system-acceleration-qat.xml.in diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in index d36fbb024..d36fbb024 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn-ipsec.xml.in diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index f734283e7..f734283e7 100644 --- a/interface-definitions/vpn_l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 21b47125d..21b47125d 100644 --- a/interface-definitions/vpn_openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn-pptp.xml.in index 28a53acb9..28a53acb9 100644 --- a/interface-definitions/vpn_pptp.xml.in +++ b/interface-definitions/vpn-pptp.xml.in diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn-sstp.xml.in index 195d581df..195d581df 100644 --- a/interface-definitions/vpn_sstp.xml.in +++ b/interface-definitions/vpn-sstp.xml.in diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index ffd9c590f..e4db69c1f 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -90,6 +90,10 @@ class BridgeIf(Interface):              'validate': assert_boolean,              'location': '/sys/class/net/{ifname}/bridge/multicast_querier',          }, +        'multicast_snooping': { +            'validate': assert_boolean, +            'location': '/sys/class/net/{ifname}/bridge/multicast_snooping', +        },      }}      _command_set = {**Interface._command_set, **{ @@ -198,6 +202,18 @@ class BridgeIf(Interface):          """          self.set_interface('multicast_querier', enable) +    def set_multicast_snooping(self, enable): +        """ +        Enable or disable multicast snooping on the bridge. + +        Use enable=1 to enable or enable=0 to disable + +        Example: +        >>> from vyos.ifconfig import Interface +        >>> BridgeIf('br0').set_multicast_snooping(1) +        """ +        self.set_interface('multicast_snooping', enable) +      def add_port(self, interface):          """          Add physical interface to bridge (member port) @@ -257,6 +273,11 @@ class BridgeIf(Interface):          value = '1' if 'stp' in config else '0'          self.set_stp(value) +        # enable or disable multicast snooping +        tmp = dict_search('igmp.snooping', config) +        value = '1' if (tmp != None) else '0' +        self.set_multicast_snooping(value) +          # enable or disable IGMP querier          tmp = dict_search('igmp.querier', config)          value = '1' if (tmp != None) else '0' diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py index c50cd5ce9..dc99d365a 100644 --- a/python/vyos/ifconfig/vti.py +++ b/python/vyos/ifconfig/vti.py @@ -1,4 +1,4 @@ -# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2021-2022 VyOS maintainers and contributors <maintainers@vyos.io>  #  # This library is free software; you can redistribute it and/or  # modify it under the terms of the GNU Lesser General Public @@ -53,3 +53,7 @@ class VTIIf(Interface):          self._cmd(cmd.format(**self.config))          self.set_interface('admin_state', 'down') + +    def get_mac(self): +        """ Get a synthetic MAC address. """ +        return self.get_mac_synthetic() diff --git a/python/vyos/pki.py b/python/vyos/pki.py index fd91fc9bf..cd15e3878 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -332,6 +332,54 @@ def verify_certificate(cert, ca_cert):      except InvalidSignature:          return False +def verify_crl(crl, ca_cert): +    # Verify CRL was signed by specified CA +    if ca_cert.subject != crl.issuer: +        return False + +    ca_public_key = ca_cert.public_key() +    try: +        if isinstance(ca_public_key, rsa.RSAPublicKeyWithSerialization): +            ca_public_key.verify( +                crl.signature, +                crl.tbs_certlist_bytes, +                padding=padding.PKCS1v15(), +                algorithm=crl.signature_hash_algorithm) +        elif isinstance(ca_public_key, dsa.DSAPublicKeyWithSerialization): +            ca_public_key.verify( +                crl.signature, +                crl.tbs_certlist_bytes, +                algorithm=crl.signature_hash_algorithm) +        elif isinstance(ca_public_key, ec.EllipticCurvePublicKeyWithSerialization): +            ca_public_key.verify( +                crl.signature, +                crl.tbs_certlist_bytes, +                signature_algorithm=ec.ECDSA(crl.signature_hash_algorithm)) +        else: +            return False # We cannot verify it +        return True +    except InvalidSignature: +        return False + +def verify_ca_chain(sorted_names, pki_node): +    if len(sorted_names) == 1: # Single cert, no chain +        return True + +    for name in sorted_names: +        cert = load_certificate(pki_node[name]['certificate']) +        verified = False +        for ca_name in sorted_names: +            if name == ca_name: +                continue +            ca_cert = load_certificate(pki_node[ca_name]['certificate']) +            if verify_certificate(cert, ca_cert): +                verified = True +                break +        if not verified and name != sorted_names[-1]: +            # Only permit top-most certificate to fail verify (e.g. signed by public CA not explicitly in chain) +            return False +    return True +  # Certificate chain  def find_parent(cert, ca_certs): @@ -357,3 +405,16 @@ def find_chain(cert, ca_certs):              chain.append(parent)      return chain + +def sort_ca_chain(ca_names, pki_node): +    def ca_cmp(ca_name1, ca_name2, pki_node): +        cert1 = load_certificate(pki_node[ca_name1]['certificate']) +        cert2 = load_certificate(pki_node[ca_name2]['certificate']) + +        if verify_certificate(cert1, cert2): # cert1 is child of cert2 +            return -1 +        return 1 + +    from functools import cmp_to_key +    return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node))) + diff --git a/python/vyos/util.py b/python/vyos/util.py index 0d62fbfe9..bee5d7aec 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -197,7 +197,7 @@ def read_file(fname, defaultonfailure=None):              return defaultonfailure          raise e -def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None): +def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False):      """      Write content of data to given fname, should defaultonfailure be not None,      it is returned on failure to read. @@ -212,7 +212,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N      try:          """ Write a file to string """          bytes = 0 -        with open(fname, 'w') as f: +        with open(fname, 'w' if not append else 'a') as f:              bytes = f.write(data)          chown(fname, user, group)          chmod(fname, mode) diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index 63d955738..fb8ed2714 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -120,7 +120,7 @@ interfaces {          persistent-tunnel          remote-host 192.0.2.10          tls { -            ca-cert-file /config/auth/ovpn_test_ca.pem +            ca-cert-file /config/auth/ovpn_test_chain.pem              cert-file /config/auth/ovpn_test_server.pem              key-file /config/auth/ovpn_test_server.key              auth-file /config/auth/ovpn_test_tls_auth.key @@ -152,7 +152,7 @@ interfaces {          remote-host 01.foo.com          remote-port 1194          tls { -            ca-cert-file /config/auth/ovpn_test_ca.pem +            ca-cert-file /config/auth/ovpn_test_chain.pem              auth-file /config/auth/ovpn_test_tls_auth.key          }      } diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index 4875fb5d1..1168c05cd 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -17,6 +17,7 @@  import re  import unittest +from vyos.configsession import ConfigSessionError  from base_vyostest_shim import VyOSUnitTestSHIM  from vyos.util import read_file @@ -93,6 +94,7 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):      def test_dns(self):          nameserver = ['2001:db8::1', '2001:db8::2']          dnssl = ['vyos.net', 'vyos.io'] +        ns_lifetime = '599'          self.cli_set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity'])          self.cli_set(base_path + ['other-config-flag']) @@ -102,6 +104,14 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):          for sl in dnssl:              self.cli_set(base_path + ['dnssl', sl]) +        self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) +        # The value, if not 0, must be at least interval max (defaults to 600). +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() + +        ns_lifetime = '600' +        self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) +          # commit changes          self.cli_commit() @@ -110,8 +120,12 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):          tmp = 'RDNSS ' + ' '.join(nameserver) + ' {'          self.assertIn(tmp, config) +        tmp = f'AdvRDNSSLifetime {ns_lifetime};' +        self.assertIn(tmp, config) +          tmp = 'DNSSL ' + ' '.join(dnssl) + ' {'          self.assertIn(tmp, config) +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 2110fd9e0..ac3dc536b 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -90,10 +90,10 @@ def get_config(config=None):              container['name'][name] = dict_merge(default_values, container['name'][name])      # Delete container network, delete containers -    tmp = node_changed(conf, base + ['container', 'network']) +    tmp = node_changed(conf, base + ['network'])      if tmp: container.update({'network_remove' : tmp}) -    tmp = node_changed(conf, base + ['container', 'name']) +    tmp = node_changed(conf, base + ['name'])      if tmp: container.update({'container_remove' : tmp})      return container @@ -132,7 +132,7 @@ def verify(container):                  # Check if the specified container network exists                  network_name = list(container_config['network'])[0] -                if network_name not in container['network']: +                if network_name not in container.get('network', {}):                      raise ConfigError(f'Container network "{network_name}" does not exist!')                  if 'address' in container_config['network'][network_name]: @@ -270,12 +270,13 @@ def apply(container):      # Option "--force" allows to delete containers with any status      if 'container_remove' in container:          for name in container['container_remove']: -            call(f'podman stop {name}') +            call(f'podman stop --time 3 {name}')              call(f'podman rm --force {name}')      # Delete old networks if needed      if 'network_remove' in container:          for network in container['network_remove']: +            call(f'podman network rm {network}')              tmp = f'/etc/cni/net.d/{network}.conflist'              if os.path.exists(tmp):                  os.unlink(tmp) @@ -294,7 +295,7 @@ def apply(container):                  # check if there is a container by that name running                  tmp = _cmd('podman ps -a --format "{{.Names}}"')                  if name in tmp: -                    _cmd(f'podman stop {name}') +                    _cmd(f'podman stop --time 3 {name}')                      _cmd(f'podman rm --force {name}')                  continue diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 4750ca3e8..280a62b9a 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -39,6 +39,8 @@ from vyos.configverify import verify_mirror_redirect  from vyos.ifconfig import VTunIf  from vyos.pki import load_dh_parameters  from vyos.pki import load_private_key +from vyos.pki import sort_ca_chain +from vyos.pki import verify_ca_chain  from vyos.pki import wrap_certificate  from vyos.pki import wrap_crl  from vyos.pki import wrap_dh_parameters @@ -148,8 +150,14 @@ def verify_pki(openvpn):          if 'ca_certificate' not in tls:              raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}') -        if tls['ca_certificate'] not in pki['ca']: -            raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') +        for ca_name in tls['ca_certificate']: +            if ca_name not in pki['ca']: +                raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + +        if len(tls['ca_certificate']) > 1: +            sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) +            if not verify_ca_chain(sorted_chain, pki['ca']): +                raise ConfigError(f'CA certificates are not a valid chain')          if mode != 'client' and 'auth_key' not in tls:              if 'certificate' not in tls: @@ -516,21 +524,28 @@ def generate_pki_files(openvpn):      if tls:          if 'ca_certificate' in tls: -            cert_name = tls['ca_certificate'] -            pki_ca = pki['ca'][cert_name] +            cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') +            crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') -            if 'certificate' in pki_ca: -                cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') -                write_file(cert_path, wrap_certificate(pki_ca['certificate']), -                           user=user, group=group, mode=0o600) +            if os.path.exists(cert_path): +                os.unlink(cert_path) + +            if os.path.exists(crl_path): +                os.unlink(crl_path) + +            for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']): +                pki_ca = pki['ca'][cert_name] + +                if 'certificate' in pki_ca: +                    write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n", +                               user=user, group=group, mode=0o600, append=True) -            if 'crl' in pki_ca: -                for crl in pki_ca['crl']: -                    crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') -                    write_file(crl_path, wrap_crl(crl), user=user, group=group, -                               mode=0o600) +                if 'crl' in pki_ca: +                    for crl in pki_ca['crl']: +                        write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group, +                                   mode=0o600, append=True) -                openvpn['tls']['crl'] = True +                    openvpn['tls']['crl'] = True          if 'certificate' in tls:              cert_name = tls['certificate'] diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 71b758399..ff7caaa84 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -17,7 +17,7 @@  import os  from sys import exit - +from vyos.base import Warning  from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.template import render @@ -79,22 +79,35 @@ def verify(rtradv):      if 'interface' not in rtradv:          return None -    for interface in rtradv['interface']: -        interface = rtradv['interface'][interface] +    for interface, interface_config in rtradv['interface'].items():          if 'prefix' in interface: -            for prefix in interface['prefix']: -                prefix = interface['prefix'][prefix] -                valid_lifetime = prefix['valid_lifetime'] +            for prefix, prefix_config in interface_config['prefix'].items(): +                valid_lifetime = prefix_config['valid_lifetime']                  if valid_lifetime == 'infinity':                      valid_lifetime = 4294967295 -                preferred_lifetime = prefix['preferred_lifetime'] +                preferred_lifetime = prefix_config['preferred_lifetime']                  if preferred_lifetime == 'infinity':                      preferred_lifetime = 4294967295                  if not (int(valid_lifetime) > int(preferred_lifetime)):                      raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') +        if 'name_server_lifetime' in interface_config: +            # man page states: +            # The maximum duration how long the RDNSS entries are used for name +            # resolution. A value of 0 means the nameserver must no longer be +            # used. The value, if not 0, must be at least MaxRtrAdvInterval. To +            # ensure stale RDNSS info gets removed in a timely fashion, this +            # should not be greater than 2*MaxRtrAdvInterval. +            lifetime = int(interface_config['name_server_lifetime']) +            interval_max = int(interface_config['interval']['max']) +            if lifetime > 0: +                if lifetime < int(interval_max): +                    raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!') +                if lifetime > 2* interval_max: +                    Warning(f'RDNSS lifetime should not exceed "{2 * interval_max}" which is two times "interval max"!') +      return None  def generate(rtradv): @@ -105,15 +118,16 @@ def generate(rtradv):      return None  def apply(rtradv): +    systemd_service = 'radvd.service'      if not rtradv:          # bail out early - looks like removal from running config -        call('systemctl stop radvd.service') +        call(f'systemctl stop {systemd_service}')          if os.path.exists(config_file):              os.unlink(config_file)          return None -    call('systemctl restart radvd.service') +    call(f'systemctl reload-or-restart {systemd_service}')      return None diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25 index 93ce9215f..4095f2a3e 100755 --- a/src/migration-scripts/interfaces/24-to-25 +++ b/src/migration-scripts/interfaces/24-to-25 @@ -20,6 +20,7 @@  import os  import sys  from vyos.configtree import ConfigTree +from vyos.pki import CERT_BEGIN  from vyos.pki import load_certificate  from vyos.pki import load_crl  from vyos.pki import load_dh_parameters @@ -27,6 +28,7 @@ from vyos.pki import load_private_key  from vyos.pki import encode_certificate  from vyos.pki import encode_dh_parameters  from vyos.pki import encode_private_key +from vyos.pki import verify_crl  from vyos.util import run  def wrapped_pem_to_config_value(pem): @@ -129,6 +131,8 @@ if config.exists(base):              config.delete(base + [interface, 'tls', 'crypt-file']) +        ca_certs = {} +          if config.exists(x509_base + ['ca-cert-file']):              if not config.exists(pki_base + ['ca']):                  config.set(pki_base + ['ca']) @@ -136,20 +140,27 @@ if config.exists(base):              cert_file = config.return_value(x509_base + ['ca-cert-file'])              cert_path = os.path.join(AUTH_DIR, cert_file) -            cert = None              if os.path.isfile(cert_path):                  if not os.access(cert_path, os.R_OK):                      run(f'sudo chmod 644 {cert_path}')                  with open(cert_path, 'r') as f: -                    cert_data = f.read() -                    cert = load_certificate(cert_data, wrap_tags=False) - -            if cert: -                cert_pem = encode_certificate(cert) -                config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) -                config.set(x509_base + ['ca-certificate'], value=pki_name) +                    certs_str = f.read() +                    certs_data = certs_str.split(CERT_BEGIN) +                    index = 1 +                    for cert_data in certs_data[1:]: +                        cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) + +                        if cert: +                            ca_certs[f'{pki_name}_{index}'] = cert +                            cert_pem = encode_certificate(cert) +                            config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) +                            config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) +                        else: +                            print(f'Failed to migrate CA certificate on openvpn interface {interface}') + +                        index += 1              else:                  print(f'Failed to migrate CA certificate on openvpn interface {interface}') @@ -163,6 +174,7 @@ if config.exists(base):              crl_file = config.return_value(x509_base + ['crl-file'])              crl_path = os.path.join(AUTH_DIR, crl_file)              crl = None +            crl_ca_name = None              if os.path.isfile(crl_path):                  if not os.access(crl_path, os.R_OK): @@ -172,9 +184,14 @@ if config.exists(base):                      crl_data = f.read()                      crl = load_crl(crl_data, wrap_tags=False) -            if crl: +                    for ca_name, ca_cert in ca_certs.items(): +                        if verify_crl(crl, ca_cert): +                            crl_ca_name = ca_name +                            break + +            if crl and crl_ca_name:                  crl_pem = encode_certificate(crl) -                config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) +                config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))              else:                  print(f'Failed to migrate CRL on openvpn interface {interface}') | 
