diff options
Diffstat (limited to 'src/migration-scripts')
62 files changed, 4080 insertions, 814 deletions
| diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 index 03c45107b..5b8e8a163 100755 --- a/src/migration-scripts/bgp/0-to-1 +++ b/src/migration-scripts/bgp/0-to-1 @@ -14,7 +14,7 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process +# T3417: migrate BGP tagNode to node as we can only have one BGP process  from sys import argv  from sys import exit diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 index 96b939b47..a40d86e67 100755 --- a/src/migration-scripts/bgp/1-to-2 +++ b/src/migration-scripts/bgp/1-to-2 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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 @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  # T3741: no-ipv4-unicast is now enabled by default +# T5937: Migrate IPv6 BGP Neighbor Peer Groups  from sys import argv  from sys import exit @@ -66,6 +67,15 @@ else:              if not config.exists(afi_ipv4):                  config.set(afi_ipv4) +# Migrate IPv6 AFI peer-group +if config.exists(base + ['neighbor']): +    for neighbor in config.list_nodes(base + ['neighbor']): +        tmp_path = base + ['neighbor', neighbor, 'address-family', 'ipv6-unicast', 'peer-group'] +        if config.exists(tmp_path): +            peer_group = config.return_value(tmp_path) +            config.set(base + ['neighbor', neighbor, 'peer-group'], value=peer_group) +            config.delete(tmp_path) +  try:      with open(file_name, 'w') as f:          f.write(config.to_string()) diff --git a/src/migration-scripts/bgp/4-to-5 b/src/migration-scripts/bgp/4-to-5 new file mode 100755 index 000000000..c4eb9ec72 --- /dev/null +++ b/src/migration-scripts/bgp/4-to-5 @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Delete 'protocols bgp address-family ipv6-unicast route-target vpn +# import/export', if 'protocols bgp address-family ipv6-unicast +# route-target vpn both' exists + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +bgp_base = ['protocols', 'bgp'] +# Delete 'import/export' in default vrf if 'both' exists +if config.exists(bgp_base): +    for address_family in ['ipv4-unicast', 'ipv6-unicast']: +        rt_path = bgp_base + ['address-family', address_family, 'route-target', +                              'vpn'] +        if config.exists(rt_path + ['both']): +            if config.exists(rt_path + ['import']): +                config.delete(rt_path + ['import']) +            if config.exists(rt_path + ['export']): +                config.delete(rt_path + ['export']) + +# Delete import/export in vrfs if both exists +if config.exists(['vrf', 'name']): +    for vrf in config.list_nodes(['vrf', 'name']): +        vrf_base = ['vrf', 'name', vrf] +        for address_family in ['ipv4-unicast', 'ipv6-unicast']: +            rt_path = vrf_base + bgp_base + ['address-family', address_family, +                                             'route-target', 'vpn'] +            if config.exists(rt_path + ['both']): +                if config.exists(rt_path + ['import']): +                    config.delete(rt_path + ['import']) +                if config.exists(rt_path + ['export']): +                    config.delete(rt_path + ['export']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/cluster/1-to-2 b/src/migration-scripts/cluster/1-to-2 new file mode 100755 index 000000000..a2e589155 --- /dev/null +++ b/src/migration-scripts/cluster/1-to-2 @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import re +import sys + +from vyos.configtree import ConfigTree + +if __name__ == '__main__': +    if len(sys.argv) < 2: +        print("Must specify file name!") +        sys.exit(1) + +    file_name = sys.argv[1] + +    with open(file_name, 'r') as f: +        config_file = f.read() + +    config = ConfigTree(config_file) + +    if not config.exists(['cluster']): +        # Cluster is not set -- nothing to do at all +        sys.exit(0) + +    # If at least one cluster group is defined, we have real work to do. +    # If there are no groups, we remove the top-level cluster node at the end of this script anyway. +    if config.exists(['cluster', 'group']): +        # First, gather timer and interface settings to duplicate them in all groups, +        # since in the old cluster they are global, but in VRRP they are always per-group + +        global_interface = None +        if config.exists(['cluster', 'interface']): +            global_interface = config.return_value(['cluster', 'interface']) +        else: +            # Such configs shouldn't exist in practice because interface is a required option. +            # But since it's possible to specify interface inside 'service' options, +            # we may be able to convert such configs nonetheless. +            print("Warning: incorrect cluster config: interface is not defined.", file=sys.stderr) + +        # There are three timers: advertise-interval, dead-interval, and monitor-dead-interval +        # Only the first one makes sense for the VRRP, we translate it to advertise-interval +        advertise_interval = None +        if config.exists(['cluster', 'keepalive-interval']): +            advertise_interval = config.return_value(['cluster', 'keepalive-interval']) + +        if advertise_interval is not None: +            # Cluster had all timers in milliseconds, so we need to convert them to seconds +            # And ensure they are not shorter than one second +            advertise_interval = int(advertise_interval) // 1000 +            if advertise_interval < 1: +                advertise_interval = 1 + +        # Cluster had password as a global option, in VRRP it's per-group +        password = None +        if config.exists(['cluster', 'pre-shared-secret']): +            password = config.return_value(['cluster', 'pre-shared-secret']) + +        # Set up the stage for converting cluster groups to VRRP groups +        free_vrids = set(range(1,255)) +        vrrp_base_path = ['high-availability', 'vrrp', 'group'] +        if not config.exists(vrrp_base_path): +            # If VRRP is not set up, create a node and set it to 'tag node' +            # Setting it to 'tag' is not mandatory but it's better to be consistent +            # with configs produced by 'save' +            config.set(vrrp_base_path) +            config.set_tag(vrrp_base_path) +        else: +            # If there are VRRP groups already, we need to find the set of unused VRID numbers to avoid conflicts +            existing_vrids = set() +            for vg in config.list_nodes(vrrp_base_path): +                existing_vrids.add(int(config.return_value(vrrp_base_path + [vg, 'vrid']))) +            free_vrids = free_vrids.difference(existing_vrids) + +        # Now handle cluster groups +        groups = config.list_nodes(['cluster', 'group']) +        for g in groups: +            base_path = ['cluster', 'group', g] +            service_names = config.return_values(base_path + ['service']) + +            # Cluster used to allow services other than IP addresses, at least nominally +            # Whether that ever worked is a big question, but we need to consider that, +            # since configs with custom services are definitely impossible to meaningfully migrate now +            services = {"ip": [], "other": []} +            for s in service_names: +                if re.match(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2})(/[a-z]+\d+)?$', s): +                    services["ip"].append(s) +                else: +                    services["other"].append(s) + +            if services["other"]: +                print("Cluster config includes non-IP address services and cannot be migrated", file=sys.stderr) +                sys.exit(1) + +            # Cluster allowed virtual IPs for different interfaces within a single group. +            # VRRP groups are by definition bound to interfaces, so we cannot migrate such configurations. +            # Thus we need to find out if all addresses either leave the interface unspecified +            # (in that case the global 'cluster interface' option is used), +            # or have the same interface, or have the same interface as the global 'cluster interface'. + +            # First, we collect all addresses and check if they have interface specified +            # If not, we substitute the global interface option +            # or throw an error if it's not in the config. +            ips = [] +            for ip in services["ip"]: +                ip_with_intf = re.match(r'^(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/(?P<intf>[a-z]+\d+)$', ip) +                if ip_with_intf: +                    ips.append({"ip": ip_with_intf.group("ip"), "interface": ip_with_intf.group("intf")}) +                else: +                    if global_interface is not None: +                        ips.append({"ip": ip, "interface": global_interface}) +                    else: +                        print("Error: cluster has groups with IPs without interfaces and 'cluster interface' is not specified.", file=sys.stderr) +                        sys.exit(1) + +            # Then we check if all addresses are for the same interface. +            intfs_set = set(map(lambda i: i["interface"], ips)) +            if len(intfs_set) > 1: +                print("Error: cluster group has addresses for different interfaces", file=sys.stderr) +                sys.exit(1) + +            # If we got this far, the group is migratable. + +            # Extract the interface from the set -- we know there's only a single member. +            interface = intfs_set.pop() + +            addresses = list(map(lambda i: i["ip"], ips)) +            vrrp_path = ['high-availability', 'vrrp', 'group', g] + +            # If there's already a VRRP group with exactly the same name, +            # we probably shouldn't try to make up a unique name, just leave migration to the user... +            if config.exists(vrrp_path): +                print("Error: VRRP group with the same name as a cluster group already exists", file=sys.stderr) +                sys.exit(1) + +            config.set(vrrp_path + ['interface'], value=interface) +            for a in addresses: +                config.set(vrrp_path + ['virtual-address'], value=a, replace=False) + +            # Take the next free VRID and assign it to the group +            vrid = free_vrids.pop() +            config.set(vrrp_path + ['vrid'], value=vrid) + +            # Convert the monitor option to VRRP ping health check +            if config.exists(base_path + ['monitor']): +                monitor_ip = config.return_value(base_path + ['monitor']) +                config.set(vrrp_path + ['health-check', 'ping'], value=monitor_ip) + +            # Convert "auto-failback" to "no-preempt", if necessary +            if config.exists(base_path + ['auto-failback']): +                # It's a boolean node that requires "true" or "false" +                # so if it exists we still need to check its value +                auto_failback = config.return_value(base_path + ['auto-failback']) +                if auto_failback == "false": +                    config.set(vrrp_path + ['no-preempt']) +                else: +                    # It's "true" or we assume it is, which means preemption is desired, +                    # and in VRRP config it's the default +                    pass +            else: +                # The old default for that option is false +                config.set(vrrp_path + ['no-preempt']) + +            # Inject settings from the global cluster config that have to be per-group in VRRP +            if advertise_interval is not None: +                config.set(vrrp_path + ['advertise-interval'], value=advertise_interval) + +            if password is not None: +                config.set(vrrp_path + ['authentication', 'password'], value=password) +                config.set(vrrp_path + ['authentication', 'type'], value='plaintext-password') + +    # Finally, clean up the old cluster node +    config.delete(['cluster']) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        sys.exit(1) diff --git a/src/migration-scripts/conntrack/4-to-5 b/src/migration-scripts/conntrack/4-to-5 new file mode 100755 index 000000000..d2e5fc5fa --- /dev/null +++ b/src/migration-scripts/conntrack/4-to-5 @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5779: system conntrack timeout custom +# Before: +#   Protocols tcp, udp and icmp allowed. When using udp it did not work +#   Only ipv4 custom timeout rules +# Now: +#   Valid protocols are only tcp or udp. +#   Extend functionality to ipv6 and move ipv4 custom rules to new node: +#       set system conntrack timeout custom [ipv4 | ipv6] rule <rule> ... + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['system', 'conntrack'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +if config.exists(base + ['timeout', 'custom', 'rule']): +    for rule in config.list_nodes(base + ['timeout', 'custom', 'rule']): +        if config.exists(base + ['timeout', 'custom', 'rule', rule, 'protocol', 'tcp']): +            config.set(base + ['timeout', 'custom', 'ipv4', 'rule']) +            config.copy(base + ['timeout', 'custom', 'rule', rule], base + ['timeout', 'custom', 'ipv4', 'rule', rule]) +    config.delete(base + ['timeout', 'custom', 'rule']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7 new file mode 100755 index 000000000..ccf385a30 --- /dev/null +++ b/src/migration-scripts/dhcp-server/6-to-7 @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: Migrate to Kea +#        - global-parameters will not function +#        - shared-network-parameters will not function +#        - subnet-parameters will not function +#        - static-mapping-parameters will not function +#        - host-decl-name is on by default, option removed +#        - ping-check no longer supported +#        - failover is default enabled on all subnets that exist on failover servers + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 2): +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcp-server'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +if config.exists(base + ['host-decl-name']): +    config.delete(base + ['host-decl-name']) + +if config.exists(base + ['global-parameters']): +    config.delete(base + ['global-parameters']) + +if config.exists(base + ['shared-network-name']): +    for network in config.list_nodes(base + ['shared-network-name']): +        base_network = base + ['shared-network-name', network] + +        if config.exists(base_network + ['ping-check']): +            config.delete(base_network + ['ping-check']) + +        if config.exists(base_network + ['shared-network-parameters']): +            config.delete(base_network +['shared-network-parameters']) + +        if not config.exists(base_network + ['subnet']): +            continue + +        # Run this for every specified 'subnet' +        for subnet in config.list_nodes(base_network + ['subnet']): +            base_subnet = base_network + ['subnet', subnet] + +            if config.exists(base_subnet + ['enable-failover']): +                config.delete(base_subnet + ['enable-failover']) + +            if config.exists(base_subnet + ['ping-check']): +                config.delete(base_subnet + ['ping-check']) + +            if config.exists(base_subnet + ['subnet-parameters']): +                config.delete(base_subnet + ['subnet-parameters']) + +            if config.exists(base_subnet + ['static-mapping']): +                for mapping in config.list_nodes(base_subnet + ['static-mapping']): +                    if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): +                        config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8 new file mode 100755 index 000000000..151aa6d7b --- /dev/null +++ b/src/migration-scripts/dhcp-server/7-to-8 @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) +# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..." +#       to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..." + +import sys +import re +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcp-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +for network in config.list_nodes(base): +    # Run this for every specified 'subnet' +    if config.exists(base + [network, 'subnet']): +        for subnet in config.list_nodes(base + [network, 'subnet']): +            base_subnet = base + [network, 'subnet', subnet] +            if config.exists(base_subnet + ['static-mapping']): +                for hostname in config.list_nodes(base_subnet + ['static-mapping']): +                    base_mapping = base_subnet + ['static-mapping', hostname] + +                    # Rename the 'mac-address' node to 'mac' +                    if config.exists(base_mapping + ['mac-address']): +                        config.rename(base_mapping + ['mac-address'], 'mac') + +                    # Adjust hostname to have valid FQDN characters only +                    new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) +                    if new_hostname != hostname: +                        config.rename(base_mapping, new_hostname) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9 new file mode 100755 index 000000000..810e403a6 --- /dev/null +++ b/src/migration-scripts/dhcp-server/8-to-9 @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Migrate dhcp options under new option node +# - Add subnet IDs to existing subnets + +import sys +import re +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcp-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal', +                'client-prefix-length', 'default-router', 'domain-name', 'domain-search', +                'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server', +                'pop-server', 'server-identifier', 'smtp-server', 'static-route', +                'tftp-server-name', 'time-offset', 'time-server', 'time-zone', +                'vendor-option', 'wins-server', 'wpad-url'] + +subnet_id = 1 + +for network in config.list_nodes(base): +    for option in option_nodes: +        if config.exists(base + [network, option]): +            config.set(base + [network, 'option']) +            config.copy(base + [network, option], base + [network, 'option', option]) +            config.delete(base + [network, option]) + +    if config.exists(base + [network, 'subnet']): +        for subnet in config.list_nodes(base + [network, 'subnet']): +            base_subnet = base + [network, 'subnet', subnet] +             +            for option in option_nodes: +                if config.exists(base_subnet + [option]): +                    config.set(base_subnet + ['option']) +                    config.copy(base_subnet + [option], base_subnet + ['option', option]) +                    config.delete(base_subnet + [option]) + +            config.set(base_subnet + ['subnet-id'], value=subnet_id) +            subnet_id += 1 + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcpv6-server/1-to-2 b/src/migration-scripts/dhcpv6-server/1-to-2 new file mode 100755 index 000000000..cc5a8900a --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/1-to-2 @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: Migrate to Kea +# - Kea was meant to have support for key "prefix-highest" under PD which would allow an address range +#   However this seems to have never been implemented. A conversion to prefix length is needed (where possible). +#   Ref: https://lists.isc.org/pipermail/kea-users/2022-November/003686.html +# - Remove prefix temporary value, convert to multi leafNode (https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp6-srv.html#dhcpv6-server-limitations) + +import sys +from vyos.configtree import ConfigTree +from vyos.utils.network import ipv6_prefix_length + +if (len(sys.argv) < 1): +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcpv6-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +for network in config.list_nodes(base): +    if not config.exists(base + [network, 'subnet']): +        continue + +    for subnet in config.list_nodes(base + [network, 'subnet']): +        # Delete temporary value under address-range prefix, convert tagNode to leafNode multi +        if config.exists(base + [network, 'subnet', subnet, 'address-range', 'prefix']): +            prefix_base = base + [network, 'subnet', subnet, 'address-range', 'prefix'] +            prefixes = config.list_nodes(prefix_base) +             +            config.delete(prefix_base) + +            for prefix in prefixes: +                config.set(prefix_base, value=prefix, replace=False) + +        if config.exists(base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']): +            prefix_base = base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix'] + +            config.set(prefix_base) +            config.set_tag(prefix_base) + +            for start in config.list_nodes(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']): +                path = base + [network, 'subnet', subnet, 'prefix-delegation', 'start', start] + +                delegated_length = config.return_value(path + ['prefix-length']) +                stop = config.return_value(path + ['stop']) + +                prefix_length = ipv6_prefix_length(start, stop) + +                # This range could not be converted into a simple prefix length and must be skipped +                if not prefix_length: +                    continue + +                config.set(prefix_base + [start, 'delegated-length'], value=delegated_length) +                config.set(prefix_base + [start, 'prefix-length'], value=prefix_length) + +            config.delete(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcpv6-server/2-to-3 b/src/migration-scripts/dhcpv6-server/2-to-3 new file mode 100755 index 000000000..f4bdc1d1e --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/2-to-3 @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) +# - Adjust duid (old identifier) to comply with duid format +# - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> identifier ..." +#       to "service dhcpv6-server shared-network-name ... static-mapping <hostname> duid ..." +# - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac-address ..." +#       to "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac ..." + +import sys +import re +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcpv6-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +for network in config.list_nodes(base): +    # Run this for every specified 'subnet' +    if config.exists(base + [network, 'subnet']): +        for subnet in config.list_nodes(base + [network, 'subnet']): +            base_subnet = base + [network, 'subnet', subnet] +            if config.exists(base_subnet + ['static-mapping']): +                for hostname in config.list_nodes(base_subnet + ['static-mapping']): +                    base_mapping = base_subnet + ['static-mapping', hostname] +                    if config.exists(base_mapping + ['identifier']): + +                        # Adjust duid to comply with duid format (a:3:b:04:... => 0a:03:0b:04:...) +                        duid = config.return_value(base_mapping + ['identifier']) +                        new_duid = ':'.join(x.rjust(2,'0') for x in duid.split(':')) +                        if new_duid != duid: +                            config.set(base_mapping + ['identifier'], new_duid) + +                        # Rename the 'identifier' node to 'duid' +                        config.rename(base_mapping + ['identifier'], 'duid') + +                    # Rename the 'mac-address' node to 'mac' +                    if config.exists(base_mapping + ['mac-address']): +                        config.rename(base_mapping + ['mac-address'], 'mac') + +                    # Adjust hostname to have valid FQDN characters only +                    new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) +                    if new_hostname != hostname: +                        config.rename(base_mapping, new_hostname) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dhcpv6-server/3-to-4 b/src/migration-scripts/dhcpv6-server/3-to-4 new file mode 100755 index 000000000..4747ebd60 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/3-to-4 @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Add subnet IDs to existing subnets +# - Move options to option node +# - Migrate address-range to range tagNode + +import sys +import re +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['service', 'dhcpv6-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +option_nodes = ['captive-portal', 'domain-search', 'name-server', +                'nis-domain', 'nis-server', 'nisplus-domain', 'nisplus-server', +                'sip-server', 'sntp-server', 'vendor-option'] + +subnet_id = 1 + +for network in config.list_nodes(base): +    if config.exists(base + [network, 'subnet']): +        for subnet in config.list_nodes(base + [network, 'subnet']): +            base_subnet = base + [network, 'subnet', subnet] + +            if config.exists(base_subnet + ['address-range']): +                config.set(base_subnet + ['range']) +                config.set_tag(base_subnet + ['range']) + +                range_id = 1 + +                if config.exists(base_subnet + ['address-range', 'prefix']): +                    for prefix in config.return_values(base_subnet + ['address-range', 'prefix']): +                        config.set(base_subnet + ['range', range_id, 'prefix'], value=prefix) + +                        range_id += 1 + +                if config.exists(base_subnet + ['address-range', 'start']): +                    for start in config.list_nodes(base_subnet + ['address-range', 'start']): +                        stop = config.return_value(base_subnet + ['address-range', 'start', start, 'stop']) + +                        config.set(base_subnet + ['range', range_id, 'start'], value=start) +                        config.set(base_subnet + ['range', range_id, 'stop'], value=stop) + +                        range_id += 1 + +                config.delete(base_subnet + ['address-range']) + +            for option in option_nodes: +                if config.exists(base_subnet + [option]): +                    config.set(base_subnet + ['option']) +                    config.copy(base_subnet + [option], base_subnet + ['option', option]) +                    config.delete(base_subnet + [option]) + +            config.set(base_subnet + ['subnet-id'], value=subnet_id) +            subnet_id += 1 + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1 index d80e8d44a..b7674a9c8 100755 --- a/src/migration-scripts/dns-dynamic/0-to-1 +++ b/src/migration-scripts/dns-dynamic/0-to-1 @@ -25,8 +25,10 @@  #        to "service dns dynamic address <address> service <config> username ..."  # - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'  # - apply service protocol mapping upfront, they are not 'auto-detected' anymore +# - migrate web-options url to stricter format  import sys +import re  from vyos.configtree import ConfigTree  service_protocol_mapping = { @@ -81,20 +83,42 @@ for address in config.list_nodes(new_base_path):                  config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username')              # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'              if config.exists(new_base_path + [address, 'ipv6-enable']): -                config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], -                           value='ipv6', replace=False) +                config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6')                  config.delete(new_base_path + [address, 'ipv6-enable'])              # Apply service protocol mapping upfront, they are not 'auto-detected' anymore              if svc_cfg in service_protocol_mapping:                  config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'], -                           value=service_protocol_mapping.get(svc_cfg), replace=False) +                           service_protocol_mapping.get(svc_cfg)) -    # Migrate "service dns dynamic interface <interface> use-web" -    #      to "service dns dynamic address <address> web-options" -    # Also, rename <address> to 'web' literal for backward compatibility +    # If use-web is set, then: +    #   Move "service dns dynamic address <address> <service|rfc2136> <service> ..." +    #     to "service dns dynamic address web <service|rfc2136> <service>-<address> ..." +    #   Move "service dns dynamic address web use-web ..." +    #     to "service dns dynamic address web web-options ..." +    # Note: The config is named <service>-<address> to avoid name conflict with old entries      if config.exists(new_base_path + [address, 'use-web']): -        config.rename(new_base_path + [address], 'web') -        config.rename(new_base_path + ['web', 'use-web'], 'web-options') +        for svc_type in ['rfc2136', 'service']: +            if config.exists(new_base_path + [address, svc_type]): +                config.set(new_base_path + ['web', svc_type]) +                config.set_tag(new_base_path + ['web', svc_type]) +                for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]): +                    config.copy(new_base_path + [address, svc_type, svc_cfg], +                                new_base_path + ['web', svc_type, f'{svc_cfg}-{address}']) + +        # Multiple web-options were not supported, so copy only the first one +        # Also, migrate web-options url to stricter format and transition +        # checkip.dyndns.org to https://domains.google.com/checkip for better +        # TLS support (see: https://github.com/ddclient/ddclient/issues/597) +        if not config.exists(new_base_path + ['web', 'web-options']): +            config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options']) +            if config.exists(new_base_path + ['web', 'web-options', 'url']): +                url = config.return_value(new_base_path + ['web', 'web-options', 'url']) +                if re.search("^(https?://)?checkip\.dyndns\.org", url): +                    config.set(new_base_path + ['web', 'web-options', 'url'], 'https://domains.google.com/checkip') +                if not url.startswith(('http://', 'https://')): +                    config.set(new_base_path + ['web', 'web-options', 'url'], f'https://{url}') + +        config.delete(new_base_path + [address])  try:      with open(file_name, 'w') as f: diff --git a/src/migration-scripts/dns-dynamic/1-to-2 b/src/migration-scripts/dns-dynamic/1-to-2 new file mode 100755 index 000000000..8b599b57a --- /dev/null +++ b/src/migration-scripts/dns-dynamic/1-to-2 @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5708: +# - migrate "service dns dynamic timeout ..." +#        to "service dns dynamic interval ..." +# - remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" +# - migrate "service dns dynamic address <interface> service <service> protocol dnsexit" +#        to "service dns dynamic address <interface> service <service> protocol dnsexit2" + +import sys +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['service', 'dns', 'dynamic'] +timeout_path = base_path + ['timeout'] +address_path = base_path + ['address'] + +if not config.exists(base_path): +    # Nothing to do +    sys.exit(0) + +# Migrate "service dns dynamic timeout ..." +#      to "service dns dynamic interval ..." +if config.exists(timeout_path): +    config.rename(timeout_path, 'interval') + +# Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" +for address in config.list_nodes(address_path): +    if config.exists(address_path + [address, 'web-options']) and address != 'web': +        config.delete(address_path + [address, 'web-options']) + +# Migrate "service dns dynamic address <interface> service <service> protocol dnsexit" +#      to "service dns dynamic address <interface> service <service> protocol dnsexit2" +for address in config.list_nodes(address_path): +    for svc_cfg in config.list_nodes(address_path + [address, 'service']): +        if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']): +            protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol']) +            if protocol == 'dnsexit': +                config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3 new file mode 100755 index 000000000..4e0aa37d5 --- /dev/null +++ b/src/migration-scripts/dns-dynamic/2-to-3 @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5791: +# - migrate "service dns dynamic address web web-options ..." +#        to "service dns dynamic name <service> address web ..." (per service) +# - migrate "service dns dynamic address <address> rfc2136 <service> ..." +#        to "service dns dynamic name <service> address <interface> protocol 'nsupdate'" +# - migrate "service dns dynamic address <interface> service <service> ..." +#        to "service dns dynamic name <service> address <interface> ..." +# - normalize the all service names to conform with name constraints + +import sys +import re +from unicodedata import normalize +from vyos.configtree import ConfigTree + +def normalize_name(name): +    """Normalize service names to conform with name constraints. + +    This is necessary as part of migration because there were no constraints in +    the old name format. +    """ +    # Normalize unicode characters to ASCII (NFKD) +    # Replace all separators with hypens, strip leading and trailing hyphens +    name = normalize('NFKD', name).encode('ascii', 'ignore').decode() +    name = re.sub(r'(\s|_|\W)+', '-', name).strip('-') + +    return name + + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['service', 'dns', 'dynamic'] +address_path = base_path + ['address'] +name_path = base_path + ['name'] + +if not config.exists(address_path): +    # Nothing to do +    sys.exit(0) + +# config.copy does not recursively create a path, so initialize the name path as tagged node +if not config.exists(name_path): +    config.set(name_path) +    config.set_tag(name_path) + +for address in config.list_nodes(address_path): + +    address_path_tag = address_path + [address] + +    # Move web-option as a configuration in each service instead of top level web-option +    if config.exists(address_path_tag + ['web-options']) and address == 'web': +        for svc_type in ['service', 'rfc2136']: +            if config.exists(address_path_tag + [svc_type]): +                for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): +                    config.copy(address_path_tag + ['web-options'], +                                address_path_tag + [svc_type, svc_cfg, 'web-options']) +        config.delete(address_path_tag + ['web-options']) + +    for svc_type in ['service', 'rfc2136']: +        if config.exists(address_path_tag + [svc_type]): +            # Set protocol to 'nsupdate' for RFC2136 configuration +            if svc_type == 'rfc2136': +                for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']): +                    config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate') + +            # Add address as config value in each service before moving the service path +            # And then copy the services from 'address <interface> service <service>' +            #                              to 'name (service|rfc2136)-<service>-<address>' +            # Note: The new service is named (service|rfc2136)-<service>-<address> +            #       to avoid name conflict with old entries +            for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): +                config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address) +                config.copy(address_path_tag + [svc_type, svc_cfg], +                            name_path + ['-'.join([svc_type, svc_cfg, address])]) + +# Finally cleanup the old address path +config.delete(address_path) + +# Normalize the all service names to conform with name constraints +index = 1 +for name in config.list_nodes(name_path): +    new_name = normalize_name(name) +    if new_name != name: +        # Append index if there is still a name conflicts after normalization +        # For example, "foo-?(" and "foo-!)" both normalize to "foo-" +        if config.exists(name_path + [new_name]): +            new_name = f'{new_name}-{index}' +            index += 1 +        config.rename(name_path + [name], new_name) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/dns-dynamic/3-to-4 b/src/migration-scripts/dns-dynamic/3-to-4 new file mode 100755 index 000000000..b888a3b6b --- /dev/null +++ b/src/migration-scripts/dns-dynamic/3-to-4 @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5966: +# - migrate "service dns dynamic name <service> address <interface>" +#        to "service dns dynamic name <service> address interface <interface>" +#      when <interface> != 'web' +# - migrate "service dns dynamic name <service> web-options ..." +#        to "service dns dynamic name <service> address web ..." +#      when <interface> == 'web' + +import sys +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['service', 'dns', 'dynamic', 'name'] + +if not config.exists(base_path): +    # Nothing to do +    sys.exit(0) + +for service in config.list_nodes(base_path): + +    service_path = base_path + [service] + +    if config.exists(service_path + ['address']): +        address = config.return_value(service_path + ['address']) +        # 'address' is not a leaf node anymore, delete it first +        config.delete(service_path + ['address']) + +        # When address is an interface (not 'web'), move it to 'address interface' +        if address != 'web': +            config.set(service_path + ['address', 'interface'], address) + +        else: # address == 'web' +            # Relocate optional 'web-options' directly under 'address web' +            if config.exists(service_path + ['web-options']): +                # config.copy does not recursively create a path, so initialize it +                config.set(service_path + ['address']) +                config.copy(service_path + ['web-options'], +                            service_path + ['address', 'web']) +                config.delete(service_path + ['web-options']) + +            # ensure that valueless 'address web' still exists even if there are no 'web-options' +            if not config.exists(service_path + ['address', 'web']): +                config.set(service_path + ['address', 'web']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11 index 716c5a240..abb804a28 100755 --- a/src/migration-scripts/firewall/10-to-11 +++ b/src/migration-scripts/firewall/10-to-11 @@ -63,19 +63,11 @@ if not config.exists(base):  ### Migration of state policies  if config.exists(base + ['state-policy']): -    for family in ['ipv4', 'ipv6']: -        for hook in ['forward', 'input', 'output']: -            for priority in ['filter']: -                # Add default-action== accept for compatibility reasons: -                config.set(base + [family, hook, priority, 'default-action'], value='accept') -                position = 1 -                for state in config.list_nodes(base + ['state-policy']): -                    action = config.return_value(base + ['state-policy', state, 'action']) -                    config.set(base + [family, hook, priority, 'rule']) -                    config.set_tag(base + [family, hook, priority, 'rule']) -                    config.set(base + [family, hook, priority, 'rule', position, 'state', state], value='enable') -                    config.set(base + [family, hook, priority, 'rule', position, 'action'], value=action) -                    position = position + 1 +    for state in config.list_nodes(base + ['state-policy']): +        action = config.return_value(base + ['state-policy', state, 'action']) +        config.set(base + ['global-options', 'state-policy', state, 'action'], value=action) +        if config.exists(base + ['state-policy', state, 'log']): +            config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable')      config.delete(base + ['state-policy'])  ## migration of global options: @@ -88,12 +80,27 @@ for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv          config.delete(base + [option])  ### Migration of firewall name and ipv6-name +### Also migrate legacy 'accept' behaviour  if config.exists(base + ['name']):      config.set(['firewall', 'ipv4', 'name'])      config.set_tag(['firewall', 'ipv4', 'name'])      for ipv4name in config.list_nodes(base + ['name']):          config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) + +        if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']): +            action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action']) + +            if action == 'accept': +                config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return') + +        if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']): +            for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']): +                action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action']) + +                if action == 'accept': +                    config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return') +      config.delete(base + ['name'])  if config.exists(base + ['ipv6-name']): @@ -102,6 +109,20 @@ if config.exists(base + ['ipv6-name']):      for ipv6name in config.list_nodes(base + ['ipv6-name']):          config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) + +        if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']): +            action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action']) + +            if action == 'accept': +                config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return') + +        if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']): +            for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']): +                action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action']) + +                if action == 'accept': +                    config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return') +      config.delete(base + ['ipv6-name'])  ### Migration of firewall interface @@ -110,8 +131,8 @@ if config.exists(base + ['interface']):      inp_ipv4_rule = 5      fwd_ipv6_rule = 5      inp_ipv6_rule = 5 -    for iface in config.list_nodes(base + ['interface']): -        for direction in ['in', 'out', 'local']: +    for direction in ['in', 'out', 'local']: +        for iface in config.list_nodes(base + ['interface']):              if config.exists(base + ['interface', iface, direction]):                  if config.exists(base + ['interface', iface, direction, 'name']):                      target = config.return_value(base + ['interface', iface, direction, 'name']) @@ -181,191 +202,6 @@ if config.exists(base + ['interface']):      config.delete(base + ['interface']) - -### Migration of zones: -### User interface groups  -if config.exists(base + ['zone']): -    inp_ipv4_rule = 101 -    inp_ipv6_rule = 101 -    fwd_ipv4_rule = 101 -    fwd_ipv6_rule = 101 -    out_ipv4_rule = 101 -    out_ipv6_rule = 101 -    local_zone = 'False' - -    for zone in config.list_nodes(base + ['zone']): -        if config.exists(base + ['zone', zone, 'local-zone']): -            local_zone = 'True' -            # Add default-action== accept for compatibility reasons: -            config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv4', 'output', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'output', 'filter', 'default-action'], value='accept') -            for from_zone in config.list_nodes(base + ['zone', zone, 'from']): -                group_name = 'IG_' + from_zone -                if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']): -                    # ipv4 input ruleset -                    target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule']) -                    config.set_tag(base + ['ipv4', 'input', 'filter', 'rule']) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value='jump') -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                    inp_ipv4_rule = inp_ipv4_rule + 5 -                if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']): -                    # ipv6 input ruleset -                    target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule']) -                    config.set_tag(base + ['ipv6', 'input', 'filter', 'rule']) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value='jump') -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                    inp_ipv6_rule = inp_ipv6_rule + 5 - -            # Migrate: set firewall zone <zone> default-action <action> -            # Options: drop or reject. If not specified, is drop -            if config.exists(base + ['zone', zone, 'default-action']): -                local_def_action = config.return_value(base + ['zone', zone, 'default-action']) -            else: -                local_def_action = 'drop' -            config.set(base + ['ipv4', 'input', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'input', 'filter', 'rule']) -            config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value=local_def_action) -            config.set(base + ['ipv6', 'input', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'input', 'filter', 'rule']) -            config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value=local_def_action) -            if config.exists(base + ['zone', zone, 'enable-default-log']): -                config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'log'], value='enable') -                config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'log'], value='enable') - -        else: -        # It's not a local zone -            group_name = 'IG_' + zone -            # Add default-action== accept for compatibility reasons: -            config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') -            # intra-filtering migration. By default accept -            intra_zone_ipv4_action = 'accept' -            intra_zone_ipv6_action = 'accept' -             -            if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'action']): -                intra_zone_ipv4_action = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'action']) -                intra_zone_ipv6_action = intra_zone_ipv4_action -            else: -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']): -                    intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']) -                    intra_zone_ipv4_action = 'jump' -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']): -                    intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']) -                    intra_zone_ipv6_action = 'jump' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=intra_zone_ipv4_action) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=intra_zone_ipv6_action) -            if intra_zone_ipv4_action == 'jump': -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']): -                    intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']) -                    config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=intra_zone_ipv4_target) -            if intra_zone_ipv6_action == 'jump': -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']): -                    intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']) -                    config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=intra_zone_ipv6_target) -            fwd_ipv4_rule = fwd_ipv4_rule + 5 -            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -            if config.exists(base + ['zone', zone, 'interface']): -                # Create interface group IG_<zone> -                group_name = 'IG_' + zone -                config.set(base + ['group', 'interface-group'], value=group_name) -                config.set_tag(base + ['group', 'interface-group']) -                for iface in config.return_values(base + ['zone', zone, 'interface']): -                    config.set(base + ['group', 'interface-group', group_name, 'interface'], value=iface, replace=False) - -            if config.exists(base + ['zone', zone, 'from']): -                for from_zone in config.list_nodes(base + ['zone', zone, 'from']): -                    from_group = 'IG_' + from_zone -                    if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']): -                        target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']) -                        if config.exists(base + ['zone', from_zone, 'local-zone']): -                            # It's from LOCAL zone -> Output filtering  -                            config.set(base + ['ipv4', 'output', 'filter', 'rule']) -                            config.set_tag(base + ['ipv4', 'output', 'filter', 'rule']) -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value='jump') -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                            out_ipv4_rule = out_ipv4_rule + 5 -                        else: -                            # It's not LOCAL zone -> forward filtering -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -                            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=from_group) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value='jump') -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                            fwd_ipv4_rule = fwd_ipv4_rule + 5 -                    if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']): -                        target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']) -                        if config.exists(base + ['zone', from_zone, 'local-zone']): -                            # It's from LOCAL zone -> Output filtering -                            config.set(base + ['ipv6', 'output', 'filter', 'rule']) -                            config.set_tag(base + ['ipv6', 'output', 'filter', 'rule']) -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value='jump') -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                            out_ipv6_rule = out_ipv6_rule + 5 -                        else: -                            # It's not LOCAL zone -> forward filtering -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -                            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=from_group) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value='jump') -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -            ## Now need to migrate: set firewall zone <zone> default-action <action>    # action=drop if not specified. -            if config.exists(base + ['zone', zone, 'default-action']): -                def_action = config.return_value(base + ['zone', zone, 'default-action']) -            else: -                def_action = 'drop' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=def_action) -            description = 'zone_' + zone + ' default-action' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'description'], value=description) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=def_action) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'description'], value=description) - -            if config.exists(base + ['zone', zone, 'enable-default-log']): -                config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'log'], value='enable') -                config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'log'], value='enable') -            fwd_ipv4_rule = fwd_ipv4_rule + 5 -            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -    # Migrate default-action (force to be drop in output chain) if local zone is defined -    if local_zone == 'True': -        # General drop in output change if needed -        config.set(base + ['ipv4', 'output', 'filter', 'rule']) -        config.set_tag(base + ['ipv4', 'output', 'filter', 'rule']) -        config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value=local_def_action) -        config.set(base + ['ipv6', 'output', 'filter', 'rule']) -        config.set_tag(base + ['ipv6', 'output', 'filter', 'rule']) -        config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value=local_def_action) - -    config.delete(base + ['zone']) - -###### END migration zones -  try:      with open(file_name, 'w') as f:          f.write(config.to_string()) diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12 new file mode 100755 index 000000000..ba8374d66 --- /dev/null +++ b/src/migration-scripts/firewall/11-to-12 @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface> +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group> +# To +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface> +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group> + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['firewall'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +## Migration from base chains +#if config.exists(base + ['interface', iface, direction]): +for family in ['ipv4', 'ipv6']: +    if config.exists(base + [family]): +        for hook in ['forward', 'input', 'output', 'name']: +            if config.exists(base + [family, hook]): +                for priority in config.list_nodes(base + [family, hook]): +                    if config.exists(base + [family, hook, priority, 'rule']): +                        for rule in config.list_nodes(base + [family, hook, priority, 'rule']): +                            for direction in ['inbound-interface', 'outbound-interface']: +                                if config.exists(base + [family, hook, priority, 'rule', rule, direction]): +                                    if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']): +                                        iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) +                                        config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface) +                                        config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) +                                    elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']): +                                        group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) +                                        config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group) +                                        config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1)
\ No newline at end of file diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13 new file mode 100755 index 000000000..8396dd9d1 --- /dev/null +++ b/src/migration-scripts/firewall/12-to-13 @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5729: Switch to valueless whenever is possible. +# From +    # set firewall ... rule <rule> log enable +    # set firewall ... rule <rule> state <state> enable +    # set firewall ... rule <rule> log disable +    # set firewall ... rule <rule> state <state> disable +# To +    # set firewall ... rule <rule> log +    # set firewall ... rule <rule> state <state> +    # Remove command if log=disable or <state>=disable + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['firewall'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +# State Policy logs: +if config.exists(base + ['global-options', 'state-policy']): +    for state in config.list_nodes(base + ['global-options', 'state-policy']): +        if config.exists(base + ['global-options', 'state-policy', state, 'log']): +            log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log']) +            config.delete(base + ['global-options', 'state-policy', state, 'log']) +            if log_value == 'enable': +                config.set(base + ['global-options', 'state-policy', state, 'log']) + +for family in ['ipv4', 'ipv6', 'bridge']: +    if config.exists(base + [family]): +        for hook in ['forward', 'input', 'output', 'name']: +            if config.exists(base + [family, hook]): +                for priority in config.list_nodes(base + [family, hook]): +                    if config.exists(base + [family, hook, priority, 'rule']): +                        for rule in config.list_nodes(base + [family, hook, priority, 'rule']): +                            # Log +                            if config.exists(base + [family, hook, priority, 'rule', rule, 'log']): +                                log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log']) +                                config.delete(base + [family, hook, priority, 'rule', rule, 'log']) +                                if log_value == 'enable': +                                    config.set(base + [family, hook, priority, 'rule', rule, 'log']) +                            # State +                            if config.exists(base + [family, hook, priority, 'rule', rule, 'state']): +                                flag_enable = 'False' +                                for state in ['established', 'invalid', 'new', 'related']: +                                    if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]): +                                        state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state]) +                                        config.delete(base + [family, hook, priority, 'rule', rule, 'state', state]) +                                        if state_value == 'enable': +                                            config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False) +                                            flag_enable = 'True' +                                if flag_enable == 'False': +                                    config.delete(base + [family, hook, priority, 'rule', rule, 'state']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1)
\ No newline at end of file diff --git a/src/migration-scripts/firewall/13-to-14 b/src/migration-scripts/firewall/13-to-14 new file mode 100755 index 000000000..f45ff0674 --- /dev/null +++ b/src/migration-scripts/firewall/13-to-14 @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5834: Rename 'enable-default-log' to 'default-log' +# From +    # set firewall ... filter enable-default-log +    # set firewall ... name <name> enable-default-log +# To +    # set firewall ... filter default-log +    # set firewall ... name <name> default-log + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['firewall'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +for family in ['ipv4', 'ipv6', 'bridge']: +    if config.exists(base + [family]): +        for hook in ['forward', 'input', 'output', 'name']: +            if config.exists(base + [family, hook]): +                for priority in config.list_nodes(base + [family, hook]): +                    if config.exists(base + [family, hook, priority, 'enable-default-log']): +                        config.rename(base + [family, hook, priority, 'enable-default-log'], 'default-log') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index 9ad887acc..b918833e9 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -73,6 +73,7 @@ icmp_translations = {      #  Time Exceeded      'ttl-zero-during-transit': [11, 0],      'ttl-zero-during-reassembly': [11, 1], +    'ttl-exceeded': 'time-exceeded',      # Parameter Problem      'ip-header-bad': [12, 0],      'required-option-missing': [12, 1] @@ -87,8 +88,14 @@ icmpv6_translations = {      'communication-prohibited': [1, 1],      'address-unreachble': [1, 3],      'port-unreachable': [1, 4], -    # Redirect +    # nd      'redirect': 'nd-redirect', +    'router-solicitation': 'nd-router-solicit', +    'router-advertisement': 'nd-router-advert', +    'neighbour-solicitation': 'nd-neighbor-solicit', +    'neighbor-solicitation': 'nd-neighbor-solicit', +    'neighbour-advertisement': 'nd-neighbor-advert', +    'neighbor-advertisement': 'nd-neighbor-advert',      #  Time Exceeded      'ttl-zero-during-transit': [3, 0],      'ttl-zero-during-reassembly': [3, 1], diff --git a/src/migration-scripts/https/1-to-2 b/src/migration-scripts/https/1-to-2 index b1cf37ea6..1a2cdc1e7 100755 --- a/src/migration-scripts/https/1-to-2 +++ b/src/migration-scripts/https/1-to-2 @@ -15,7 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  # * Move 'api virtual-host' list to 'api-restrict virtual-host' so it -#   is owned by https.py instead of http-api.py +#   is owned by service_https.py  import sys diff --git a/src/migration-scripts/https/4-to-5 b/src/migration-scripts/https/4-to-5 new file mode 100755 index 000000000..0dfb6ac19 --- /dev/null +++ b/src/migration-scripts/https/4-to-5 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5762: http: api: smoketests fail as they can not establish IPv6 connection +#        to uvicorn backend server, always make the UNIX domain socket the +#        default way of communication + +import sys + +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base = ['service', 'https'] +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +# Delete "socket" CLI option - we always use UNIX domain sockets for +# NGINX <-> API server communication +if config.exists(base + ['api', 'socket']): +    config.delete(base + ['api', 'socket']) + +# There is no need for an API service port, as UNIX domain sockets +# are used +if config.exists(base + ['api', 'port']): +    config.delete(base + ['api', 'port']) + +# rename listen-port -> port ver virtual-host +if config.exists(base + ['virtual-host']): +    for vhost in config.list_nodes(base + ['virtual-host']): +        if config.exists(base + ['virtual-host', vhost, 'listen-port']): +            config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6 new file mode 100755 index 000000000..0090adccb --- /dev/null +++ b/src/migration-scripts/https/5-to-6 @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5886: Add support for ACME protocol (LetsEncrypt), migrate https certbot +#        to new "pki certificate" CLI tree +# T5902: Remove virtual-host + +import os +import sys + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.utils.process import cmd + +vyos_certbot_dir = directories['certbot'] + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base = ['service', 'https'] +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +if config.exists(base + ['certificates', 'certbot']): +    # both domain-name and email must be set on CLI - ensured by previous verify() +    domain_names = config.return_values(base + ['certificates', 'certbot', 'domain-name']) +    email = config.return_value(base + ['certificates', 'certbot', 'email']) +    config.delete(base + ['certificates', 'certbot']) + +    # Set default certname based on domain-name +    cert_name = 'https-' + domain_names[0].split('.')[0] +    # Overwrite certname from previous certbot calls if available +    # We can not use python code like os.scandir due to filesystem permissions. +    # This must be run as root +    certbot_live = f'{vyos_certbot_dir}/live/' # we need the trailing / +    if os.path.exists(certbot_live): +        tmp = cmd(f'sudo find {certbot_live} -maxdepth 1 -type d') +        tmp = tmp.split() # tmp = ['/config/auth/letsencrypt/live', '/config/auth/letsencrypt/live/router.vyos.net'] +        tmp.remove(certbot_live) +        cert_name = tmp[0].replace(certbot_live, '') + +    config.set(['pki', 'certificate', cert_name, 'acme', 'email'], value=email) +    config.set_tag(['pki', 'certificate']) +    for domain in domain_names: +        config.set(['pki', 'certificate', cert_name, 'acme', 'domain-name'], value=domain, replace=False) + +    # Update Webserver certificate +    config.set(base + ['certificates', 'certificate'], value=cert_name) + +if config.exists(base + ['virtual-host']): +    allow_client = [] +    listen_port = [] +    listen_address = [] +    for virtual_host in config.list_nodes(base + ['virtual-host']): +        allow_path = base + ['virtual-host', virtual_host, 'allow-client', 'address'] +        if config.exists(allow_path): +            tmp = config.return_values(allow_path) +            allow_client.extend(tmp) + +        port_path = base + ['virtual-host', virtual_host, 'listen-port'] +        if config.exists(port_path): +            tmp = config.return_value(port_path) +            listen_port.append(tmp) + +        listen_address_path = base + ['virtual-host', virtual_host, 'listen-address'] +        if config.exists(listen_address_path): +            tmp = config.return_value(listen_address_path) +            listen_address.append(tmp) + +    config.delete(base + ['virtual-host']) +    for client in allow_client: +        config.set(base + ['allow-client', 'address'], value=client, replace=False) + +    #  clear listen-address if "all" were specified +    if '*' in listen_address: +        listen_address = [] +    for address in listen_address: +        config.set(base + ['listen-address'], value=address, replace=False) + + + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 index 8b21fce51..04e023e77 100755 --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -13,133 +13,45 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. - -from sys import argv -from sys import exit +# +# Deletes Wireguard peers if they have the same public key as the router has. +import sys  from vyos.configtree import ConfigTree - -def migrate_ospf(config, path, interface): -    path = path + ['ospf'] -    if config.exists(path): -        new_base = ['protocols', 'ospf', 'interface'] -        config.set(new_base) -        config.set_tag(new_base) -        config.copy(path, new_base + [interface]) -        config.delete(path) - -        # if "ip ospf" was the only setting, we can clean out the empty -        # ip node afterwards -        if len(config.list_nodes(path[:-1])) == 0: -            config.delete(path[:-1]) - -def migrate_ospfv3(config, path, interface): -    path = path + ['ospfv3'] -    if config.exists(path): -        new_base = ['protocols', 'ospfv3', 'interface'] -        config.set(new_base) -        config.set_tag(new_base) -        config.copy(path, new_base + [interface]) -        config.delete(path) - -        # if "ipv6 ospfv3" was the only setting, we can clean out the empty -        # ip node afterwards -        if len(config.list_nodes(path[:-1])) == 0: -            config.delete(path[:-1]) - -def migrate_rip(config, path, interface): -    path = path + ['rip'] -    if config.exists(path): -        new_base = ['protocols', 'rip', 'interface'] -        config.set(new_base) -        config.set_tag(new_base) -        config.copy(path, new_base + [interface]) -        config.delete(path) - -        # if "ip rip" was the only setting, we can clean out the empty -        # ip node afterwards -        if len(config.list_nodes(path[:-1])) == 0: -            config.delete(path[:-1]) - -def migrate_ripng(config, path, interface): -    path = path + ['ripng'] -    if config.exists(path): -        new_base = ['protocols', 'ripng', 'interface'] -        config.set(new_base) -        config.set_tag(new_base) -        config.copy(path, new_base + [interface]) -        config.delete(path) - -        # if "ipv6 ripng" was the only setting, we can clean out the empty -        # ip node afterwards -        if len(config.list_nodes(path[:-1])) == 0: -            config.delete(path[:-1]) +from vyos.utils.network import is_wireguard_key_pair  if __name__ == '__main__': -    if len(argv) < 2: +    if len(sys.argv) < 2:          print("Must specify file name!") -        exit(1) +        sys.exit(1) + +    file_name = sys.argv[1] -    file_name = argv[1]      with open(file_name, 'r') as f:          config_file = f.read()      config = ConfigTree(config_file) - -    # -    # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" -    # -    for type in config.list_nodes(['interfaces']): -        for interface in config.list_nodes(['interfaces', type]): -            ip_base = ['interfaces', type, interface, 'ip'] -            ipv6_base = ['interfaces', type, interface, 'ipv6'] -            migrate_rip(config, ip_base, interface) -            migrate_ripng(config, ipv6_base, interface) -            migrate_ospf(config, ip_base, interface) -            migrate_ospfv3(config, ipv6_base, interface) - -            vif_path = ['interfaces', type, interface, 'vif'] -            if config.exists(vif_path): -                for vif in config.list_nodes(vif_path): -                    vif_ip_base = vif_path + [vif, 'ip'] -                    vif_ipv6_base = vif_path + [vif, 'ipv6'] -                    ifname = f'{interface}.{vif}' - -                    migrate_rip(config, vif_ip_base, ifname) -                    migrate_ripng(config, vif_ipv6_base, ifname) -                    migrate_ospf(config, vif_ip_base, ifname) -                    migrate_ospfv3(config, vif_ipv6_base, ifname) - - -            vif_s_path = ['interfaces', type, interface, 'vif-s'] -            if config.exists(vif_s_path): -                for vif_s in config.list_nodes(vif_s_path): -                    vif_s_ip_base = vif_s_path + [vif_s, 'ip'] -                    vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] - -                    # vif-c interfaces MUST be migrated before their parent vif-s -                    # interface as the migrate_*() functions delete the path! -                    vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] -                    if config.exists(vif_c_path): -                        for vif_c in config.list_nodes(vif_c_path): -                            vif_c_ip_base = vif_c_path + [vif_c, 'ip'] -                            vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] -                            ifname = f'{interface}.{vif_s}.{vif_c}' - -                            migrate_rip(config, vif_c_ip_base, ifname) -                            migrate_ripng(config, vif_c_ipv6_base, ifname) -                            migrate_ospf(config, vif_c_ip_base, ifname) -                            migrate_ospfv3(config, vif_c_ipv6_base, ifname) - - -                    ifname = f'{interface}.{vif_s}' -                    migrate_rip(config, vif_s_ip_base, ifname) -                    migrate_ripng(config, vif_s_ipv6_base, ifname) -                    migrate_ospf(config, vif_s_ip_base, ifname) -                    migrate_ospfv3(config, vif_s_ipv6_base, ifname) +    base = ['interfaces', 'wireguard'] +    if not config.exists(base): +        # Nothing to do +        sys.exit(0) +    for interface in config.list_nodes(base): +        if not config.exists(base + [interface, 'private-key']): +            continue +        private_key = config.return_value(base + [interface, 'private-key']) +        interface_base = base + [interface] +        if config.exists(interface_base + ['peer']): +            for peer in config.list_nodes(interface_base + ['peer']): +                peer_base = interface_base + ['peer', peer] +                if not config.exists(peer_base + ['public-key']): +                    continue +                peer_public_key = config.return_value(peer_base + ['public-key']) +                if not config.exists(peer_base + ['disable']) \ +                        and is_wireguard_key_pair(private_key, peer_public_key): +                    config.set(peer_base + ['disable'])      try:          with open(file_name, 'w') as f:              f.write(config.to_string())      except OSError as e:          print("Failed to save the modified config: {}".format(e)) -        exit(1) +        sys.exit(1) diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24 index 8fd79ecc6..8b21fce51 100755 --- a/src/migration-scripts/interfaces/23-to-24 +++ b/src/migration-scripts/interfaces/23-to-24 @@ -14,47 +14,132 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported -# having a VTI interface in the CLI but no IPSec configuration - drop VTI -# configuration if this is the case for VyOS 1.4 - -import sys +from sys import argv +from sys import exit  from vyos.configtree import ConfigTree +def migrate_ospf(config, path, interface): +    path = path + ['ospf'] +    if config.exists(path): +        new_base = ['protocols', 'ospf', 'interface'] +        config.set(new_base) +        config.set_tag(new_base) +        config.copy(path, new_base + [interface]) +        config.delete(path) + +        # if "ip ospf" was the only setting, we can clean out the empty +        # ip node afterwards +        if len(config.list_nodes(path[:-1])) == 0: +            config.delete(path[:-1]) + +def migrate_ospfv3(config, path, interface): +    path = path + ['ospfv3'] +    if config.exists(path): +        new_base = ['protocols', 'ospfv3', 'interface'] +        config.set(new_base) +        config.set_tag(new_base) +        config.copy(path, new_base + [interface]) +        config.delete(path) + +        # if "ipv6 ospfv3" was the only setting, we can clean out the empty +        # ip node afterwards +        if len(config.list_nodes(path[:-1])) == 0: +            config.delete(path[:-1]) + +def migrate_rip(config, path, interface): +    path = path + ['rip'] +    if config.exists(path): +        new_base = ['protocols', 'rip', 'interface'] +        config.set(new_base) +        config.set_tag(new_base) +        config.copy(path, new_base + [interface]) +        config.delete(path) + +        # if "ip rip" was the only setting, we can clean out the empty +        # ip node afterwards +        if len(config.list_nodes(path[:-1])) == 0: +            config.delete(path[:-1]) + +def migrate_ripng(config, path, interface): +    path = path + ['ripng'] +    if config.exists(path): +        new_base = ['protocols', 'ripng', 'interface'] +        config.set(new_base) +        config.set_tag(new_base) +        config.copy(path, new_base + [interface]) +        config.delete(path) + +        # if "ipv6 ripng" was the only setting, we can clean out the empty +        # ip node afterwards +        if len(config.list_nodes(path[:-1])) == 0: +            config.delete(path[:-1]) +  if __name__ == '__main__': -    if len(sys.argv) < 2: +    if len(argv) < 2:          print("Must specify file name!") -        sys.exit(1) - -    file_name = sys.argv[1] +        exit(1) +    file_name = argv[1]      with open(file_name, 'r') as f:          config_file = f.read()      config = ConfigTree(config_file) -    base = ['interfaces', 'vti'] -    if not config.exists(base): -        # Nothing to do -        sys.exit(0) - -    ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] -    for interface in config.list_nodes(base): -        found = False -        if config.exists(ipsec_base): -            for peer in config.list_nodes(ipsec_base): -                if config.exists(ipsec_base + [peer, 'vti', 'bind']): -                    tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) -                    if tmp == interface: -                        # Interface was found and we no longer need to search -                        # for it in our IPSec peers -                        found = True -                        break -        if not found: -            config.delete(base + [interface]) + +    # +    # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" +    # +    for type in config.list_nodes(['interfaces']): +        for interface in config.list_nodes(['interfaces', type]): +            ip_base = ['interfaces', type, interface, 'ip'] +            ipv6_base = ['interfaces', type, interface, 'ipv6'] +            migrate_rip(config, ip_base, interface) +            migrate_ripng(config, ipv6_base, interface) +            migrate_ospf(config, ip_base, interface) +            migrate_ospfv3(config, ipv6_base, interface) + +            vif_path = ['interfaces', type, interface, 'vif'] +            if config.exists(vif_path): +                for vif in config.list_nodes(vif_path): +                    vif_ip_base = vif_path + [vif, 'ip'] +                    vif_ipv6_base = vif_path + [vif, 'ipv6'] +                    ifname = f'{interface}.{vif}' + +                    migrate_rip(config, vif_ip_base, ifname) +                    migrate_ripng(config, vif_ipv6_base, ifname) +                    migrate_ospf(config, vif_ip_base, ifname) +                    migrate_ospfv3(config, vif_ipv6_base, ifname) + + +            vif_s_path = ['interfaces', type, interface, 'vif-s'] +            if config.exists(vif_s_path): +                for vif_s in config.list_nodes(vif_s_path): +                    vif_s_ip_base = vif_s_path + [vif_s, 'ip'] +                    vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] + +                    # vif-c interfaces MUST be migrated before their parent vif-s +                    # interface as the migrate_*() functions delete the path! +                    vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] +                    if config.exists(vif_c_path): +                        for vif_c in config.list_nodes(vif_c_path): +                            vif_c_ip_base = vif_c_path + [vif_c, 'ip'] +                            vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] +                            ifname = f'{interface}.{vif_s}.{vif_c}' + +                            migrate_rip(config, vif_c_ip_base, ifname) +                            migrate_ripng(config, vif_c_ipv6_base, ifname) +                            migrate_ospf(config, vif_c_ip_base, ifname) +                            migrate_ospfv3(config, vif_c_ipv6_base, ifname) + + +                    ifname = f'{interface}.{vif_s}' +                    migrate_rip(config, vif_s_ip_base, ifname) +                    migrate_ripng(config, vif_s_ipv6_base, ifname) +                    migrate_ospf(config, vif_s_ip_base, ifname) +                    migrate_ospfv3(config, vif_s_ipv6_base, ifname)      try:          with open(file_name, 'w') as f:              f.write(config.to_string())      except OSError as e:          print("Failed to save the modified config: {}".format(e)) -        sys.exit(1) +        exit(1) diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25 index 9aa6ea5e3..8fd79ecc6 100755 --- a/src/migration-scripts/interfaces/24-to-25 +++ b/src/migration-scripts/interfaces/24-to-25 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021 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 @@ -14,374 +14,47 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# Migrate Wireguard to store keys in CLI -# Migrate EAPoL to PKI configuration +# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported +# having a VTI interface in the CLI but no IPSec configuration - drop VTI +# configuration if this is the case for VyOS 1.4 -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 -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.utils.process import run - -def wrapped_pem_to_config_value(pem): -    out = [] -    for line in pem.strip().split("\n"): -        if not line or line.startswith("-----") or line[0] == '#': -            continue -        out.append(line) -    return "".join(out) - -def read_file_for_pki(config_auth_path): -    full_path = os.path.join(AUTH_DIR, config_auth_path) -    output = None - -    if os.path.isfile(full_path): -        if not os.access(full_path, os.R_OK): -            run(f'sudo chmod 644 {full_path}') - -        with open(full_path, 'r') as f: -            output = f.read() - -    return output -if len(sys.argv) < 2: -    print("Must specify file name!") -    sys.exit(1) +if __name__ == '__main__': +    if len(sys.argv) < 2: +        print("Must specify file name!") +        sys.exit(1) -file_name = sys.argv[1] +    file_name = sys.argv[1] -with open(file_name, 'r') as f: -    config_file = f.read() +    with open(file_name, 'r') as f: +        config_file = f.read() -config = ConfigTree(config_file) +    config = ConfigTree(config_file) +    base = ['interfaces', 'vti'] +    if not config.exists(base): +        # Nothing to do +        sys.exit(0) -AUTH_DIR = '/config/auth' -pki_base = ['pki'] - -# OpenVPN -base = ['interfaces', 'openvpn'] - -if config.exists(base): +    ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer']      for interface in config.list_nodes(base): -        x509_base = base + [interface, 'tls'] -        pki_name = f'openvpn_{interface}' - -        if config.exists(base + [interface, 'shared-secret-key-file']): -            if not config.exists(pki_base + ['openvpn', 'shared-secret']): -                config.set(pki_base + ['openvpn', 'shared-secret']) -                config.set_tag(pki_base + ['openvpn', 'shared-secret']) - -            key_file = config.return_value(base + [interface, 'shared-secret-key-file']) -            key = read_file_for_pki(key_file) -            key_pki_name = f'{pki_name}_shared' - -            if key: -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') -                config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) -            else: -                print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') - -            config.delete(base + [interface, 'shared-secret-key-file']) - -        if not config.exists(base + [interface, 'tls']): -            continue - -        if config.exists(base + [interface, 'tls', 'auth-file']): -            if not config.exists(pki_base + ['openvpn', 'shared-secret']): -                config.set(pki_base + ['openvpn', 'shared-secret']) -                config.set_tag(pki_base + ['openvpn', 'shared-secret']) - -            key_file = config.return_value(base + [interface, 'tls', 'auth-file']) -            key = read_file_for_pki(key_file) -            key_pki_name = f'{pki_name}_auth' - -            if key: -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') -                config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) -            else: -                print(f'Failed to migrate auth-key on openvpn interface {interface}') - -            config.delete(base + [interface, 'tls', 'auth-file']) - -        if config.exists(base + [interface, 'tls', 'crypt-file']): -            if not config.exists(pki_base + ['openvpn', 'shared-secret']): -                config.set(pki_base + ['openvpn', 'shared-secret']) -                config.set_tag(pki_base + ['openvpn', 'shared-secret']) - -            key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) -            key = read_file_for_pki(key_file) -            key_pki_name = f'{pki_name}_crypt' - -            if key: -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) -                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') -                config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) -            else: -                print(f'Failed to migrate crypt-key on openvpn interface {interface}') - -            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']) -                config.set_tag(pki_base + ['ca']) - -            cert_file = config.return_value(x509_base + ['ca-cert-file']) -            cert_path = os.path.join(AUTH_DIR, cert_file) - -            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: -                    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}') - -            config.delete(x509_base + ['ca-cert-file']) - -        if config.exists(x509_base + ['crl-file']): -            if not config.exists(pki_base + ['ca']): -                config.set(pki_base + ['ca']) -                config.set_tag(pki_base + ['ca']) - -            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): -                    run(f'sudo chmod 644 {crl_path}') - -                with open(crl_path, 'r') as f: -                    crl_data = f.read() -                    crl = load_crl(crl_data, wrap_tags=False) - -                    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', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) -            else: -                print(f'Failed to migrate CRL on openvpn interface {interface}') - -            config.delete(x509_base + ['crl-file']) - -        if config.exists(x509_base + ['cert-file']): -            if not config.exists(pki_base + ['certificate']): -                config.set(pki_base + ['certificate']) -                config.set_tag(pki_base + ['certificate']) - -            cert_file = config.return_value(x509_base + ['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 + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) -                config.set(x509_base + ['certificate'], value=pki_name) -            else: -                print(f'Failed to migrate certificate on openvpn interface {interface}') - -            config.delete(x509_base + ['cert-file']) - -        if config.exists(x509_base + ['key-file']): -            key_file = config.return_value(x509_base + ['key-file']) -            key_path = os.path.join(AUTH_DIR, key_file) -            key = None - -            if os.path.isfile(key_path): -                if not os.access(key_path, os.R_OK): -                    run(f'sudo chmod 644 {key_path}') - -                with open(key_path, 'r') as f: -                    key_data = f.read() -                    key = load_private_key(key_data, passphrase=None, wrap_tags=False) - -            if key: -                key_pem = encode_private_key(key, passphrase=None) -                config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) -            else: -                print(f'Failed to migrate private key on openvpn interface {interface}') - -            config.delete(x509_base + ['key-file']) - -        if config.exists(x509_base + ['dh-file']): -            if not config.exists(pki_base + ['dh']): -                config.set(pki_base + ['dh']) -                config.set_tag(pki_base + ['dh']) - -            dh_file = config.return_value(x509_base + ['dh-file']) -            dh_path = os.path.join(AUTH_DIR, dh_file) -            dh = None - -            if os.path.isfile(dh_path): -                if not os.access(dh_path, os.R_OK): -                    run(f'sudo chmod 644 {dh_path}') - -                with open(dh_path, 'r') as f: -                    dh_data = f.read() -                    dh = load_dh_parameters(dh_data, wrap_tags=False) - -            if dh: -                dh_pem = encode_dh_parameters(dh) -                config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) -                config.set(x509_base + ['dh-params'], value=pki_name) -            else: -                print(f'Failed to migrate DH parameters on openvpn interface {interface}') - -            config.delete(x509_base + ['dh-file']) - -# Wireguard -base = ['interfaces', 'wireguard'] - -if config.exists(base): -    for interface in config.list_nodes(base): -        private_key_path = base + [interface, 'private-key'] - -        key_file = 'default' -        if config.exists(private_key_path): -            key_file = config.return_value(private_key_path) - -        full_key_path = f'/config/auth/wireguard/{key_file}/private.key' - -        if not os.path.exists(full_key_path): -            print(f'Could not find wireguard private key for migration on interface "{interface}"') -            continue - -        with open(full_key_path, 'r') as f: -            key_data = f.read().strip() -            config.set(private_key_path, value=key_data) - -        for peer in config.list_nodes(base + [interface, 'peer']): -            config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') - -# Ethernet EAPoL -base = ['interfaces', 'ethernet'] - -if config.exists(base): -    for interface in config.list_nodes(base): -        if not config.exists(base + [interface, 'eapol']): -            continue - -        x509_base = base + [interface, 'eapol'] -        pki_name = f'eapol_{interface}' - -        if config.exists(x509_base + ['ca-cert-file']): -            if not config.exists(pki_base + ['ca']): -                config.set(pki_base + ['ca']) -                config.set_tag(pki_base + ['ca']) - -            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) -            else: -                print(f'Failed to migrate CA certificate on eapol config for interface {interface}') - -            config.delete(x509_base + ['ca-cert-file']) - -        if config.exists(x509_base + ['cert-file']): -            if not config.exists(pki_base + ['certificate']): -                config.set(pki_base + ['certificate']) -                config.set_tag(pki_base + ['certificate']) - -            cert_file = config.return_value(x509_base + ['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 + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) -                config.set(x509_base + ['certificate'], value=pki_name) -            else: -                print(f'Failed to migrate certificate on eapol config for interface {interface}') - -            config.delete(x509_base + ['cert-file']) - -        if config.exists(x509_base + ['key-file']): -            key_file = config.return_value(x509_base + ['key-file']) -            key_path = os.path.join(AUTH_DIR, key_file) -            key = None - -            if os.path.isfile(key_path): -                if not os.access(key_path, os.R_OK): -                    run(f'sudo chmod 644 {key_path}') - -                with open(key_path, 'r') as f: -                    key_data = f.read() -                    key = load_private_key(key_data, passphrase=None, wrap_tags=False) - -            if key: -                key_pem = encode_private_key(key, passphrase=None) -                config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) -            else: -                print(f'Failed to migrate private key on eapol config for interface {interface}') - -            config.delete(x509_base + ['key-file']) - -try: -    with open(file_name, 'w') as f: -        f.write(config.to_string()) -except OSError as e: -    print("Failed to save the modified config: {}".format(e)) -    sys.exit(1) +        found = False +        if config.exists(ipsec_base): +            for peer in config.list_nodes(ipsec_base): +                if config.exists(ipsec_base + [peer, 'vti', 'bind']): +                    tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) +                    if tmp == interface: +                        # Interface was found and we no longer need to search +                        # for it in our IPSec peers +                        found = True +                        break +        if not found: +            config.delete(base + [interface]) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        sys.exit(1) diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 index 4967a29fa..9aa6ea5e3 100755 --- a/src/migration-scripts/interfaces/25-to-26 +++ b/src/migration-scripts/interfaces/25-to-26 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -14,41 +14,374 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# T4384: pppoe: replace default-route CLI option with common CLI nodes already -#        present for DHCP +# Migrate Wireguard to store keys in CLI +# Migrate EAPoL to PKI configuration -from sys import argv +import os +import sys -from vyos.ethtool import Ethtool  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 +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.utils.process import run -if len(argv) < 2: +def wrapped_pem_to_config_value(pem): +    out = [] +    for line in pem.strip().split("\n"): +        if not line or line.startswith("-----") or line[0] == '#': +            continue +        out.append(line) +    return "".join(out) + +def read_file_for_pki(config_auth_path): +    full_path = os.path.join(AUTH_DIR, config_auth_path) +    output = None + +    if os.path.isfile(full_path): +        if not os.access(full_path, os.R_OK): +            run(f'sudo chmod 644 {full_path}') + +        with open(full_path, 'r') as f: +            output = f.read() + +    return output + +if len(sys.argv) < 2:      print("Must specify file name!") -    exit(1) +    sys.exit(1) + +file_name = sys.argv[1] -file_name = argv[1]  with open(file_name, 'r') as f:      config_file = f.read() -base = ['interfaces', 'pppoe']  config = ConfigTree(config_file) -if not config.exists(base): -    exit(0) +AUTH_DIR = '/config/auth' +pki_base = ['pki'] + +# OpenVPN +base = ['interfaces', 'openvpn'] + +if config.exists(base): +    for interface in config.list_nodes(base): +        x509_base = base + [interface, 'tls'] +        pki_name = f'openvpn_{interface}' + +        if config.exists(base + [interface, 'shared-secret-key-file']): +            if not config.exists(pki_base + ['openvpn', 'shared-secret']): +                config.set(pki_base + ['openvpn', 'shared-secret']) +                config.set_tag(pki_base + ['openvpn', 'shared-secret']) + +            key_file = config.return_value(base + [interface, 'shared-secret-key-file']) +            key = read_file_for_pki(key_file) +            key_pki_name = f'{pki_name}_shared' + +            if key: +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') +                config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) +            else: +                print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') + +            config.delete(base + [interface, 'shared-secret-key-file']) + +        if not config.exists(base + [interface, 'tls']): +            continue + +        if config.exists(base + [interface, 'tls', 'auth-file']): +            if not config.exists(pki_base + ['openvpn', 'shared-secret']): +                config.set(pki_base + ['openvpn', 'shared-secret']) +                config.set_tag(pki_base + ['openvpn', 'shared-secret']) + +            key_file = config.return_value(base + [interface, 'tls', 'auth-file']) +            key = read_file_for_pki(key_file) +            key_pki_name = f'{pki_name}_auth' + +            if key: +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') +                config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) +            else: +                print(f'Failed to migrate auth-key on openvpn interface {interface}') + +            config.delete(base + [interface, 'tls', 'auth-file']) + +        if config.exists(base + [interface, 'tls', 'crypt-file']): +            if not config.exists(pki_base + ['openvpn', 'shared-secret']): +                config.set(pki_base + ['openvpn', 'shared-secret']) +                config.set_tag(pki_base + ['openvpn', 'shared-secret']) + +            key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) +            key = read_file_for_pki(key_file) +            key_pki_name = f'{pki_name}_crypt' + +            if key: +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) +                config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') +                config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) +            else: +                print(f'Failed to migrate crypt-key on openvpn interface {interface}') + +            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']) +                config.set_tag(pki_base + ['ca']) + +            cert_file = config.return_value(x509_base + ['ca-cert-file']) +            cert_path = os.path.join(AUTH_DIR, cert_file) + +            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: +                    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}') + +            config.delete(x509_base + ['ca-cert-file']) + +        if config.exists(x509_base + ['crl-file']): +            if not config.exists(pki_base + ['ca']): +                config.set(pki_base + ['ca']) +                config.set_tag(pki_base + ['ca']) + +            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): +                    run(f'sudo chmod 644 {crl_path}') + +                with open(crl_path, 'r') as f: +                    crl_data = f.read() +                    crl = load_crl(crl_data, wrap_tags=False) + +                    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', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) +            else: +                print(f'Failed to migrate CRL on openvpn interface {interface}') + +            config.delete(x509_base + ['crl-file']) + +        if config.exists(x509_base + ['cert-file']): +            if not config.exists(pki_base + ['certificate']): +                config.set(pki_base + ['certificate']) +                config.set_tag(pki_base + ['certificate']) + +            cert_file = config.return_value(x509_base + ['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 + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) +                config.set(x509_base + ['certificate'], value=pki_name) +            else: +                print(f'Failed to migrate certificate on openvpn interface {interface}') + +            config.delete(x509_base + ['cert-file']) + +        if config.exists(x509_base + ['key-file']): +            key_file = config.return_value(x509_base + ['key-file']) +            key_path = os.path.join(AUTH_DIR, key_file) +            key = None + +            if os.path.isfile(key_path): +                if not os.access(key_path, os.R_OK): +                    run(f'sudo chmod 644 {key_path}') + +                with open(key_path, 'r') as f: +                    key_data = f.read() +                    key = load_private_key(key_data, passphrase=None, wrap_tags=False) + +            if key: +                key_pem = encode_private_key(key, passphrase=None) +                config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) +            else: +                print(f'Failed to migrate private key on openvpn interface {interface}') + +            config.delete(x509_base + ['key-file']) + +        if config.exists(x509_base + ['dh-file']): +            if not config.exists(pki_base + ['dh']): +                config.set(pki_base + ['dh']) +                config.set_tag(pki_base + ['dh']) + +            dh_file = config.return_value(x509_base + ['dh-file']) +            dh_path = os.path.join(AUTH_DIR, dh_file) +            dh = None + +            if os.path.isfile(dh_path): +                if not os.access(dh_path, os.R_OK): +                    run(f'sudo chmod 644 {dh_path}') + +                with open(dh_path, 'r') as f: +                    dh_data = f.read() +                    dh = load_dh_parameters(dh_data, wrap_tags=False) + +            if dh: +                dh_pem = encode_dh_parameters(dh) +                config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) +                config.set(x509_base + ['dh-params'], value=pki_name) +            else: +                print(f'Failed to migrate DH parameters on openvpn interface {interface}') + +            config.delete(x509_base + ['dh-file']) + +# Wireguard +base = ['interfaces', 'wireguard'] + +if config.exists(base): +    for interface in config.list_nodes(base): +        private_key_path = base + [interface, 'private-key'] + +        key_file = 'default' +        if config.exists(private_key_path): +            key_file = config.return_value(private_key_path) + +        full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + +        if not os.path.exists(full_key_path): +            print(f'Could not find wireguard private key for migration on interface "{interface}"') +            continue + +        with open(full_key_path, 'r') as f: +            key_data = f.read().strip() +            config.set(private_key_path, value=key_data) + +        for peer in config.list_nodes(base + [interface, 'peer']): +            config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') + +# Ethernet EAPoL +base = ['interfaces', 'ethernet'] + +if config.exists(base): +    for interface in config.list_nodes(base): +        if not config.exists(base + [interface, 'eapol']): +            continue + +        x509_base = base + [interface, 'eapol'] +        pki_name = f'eapol_{interface}' + +        if config.exists(x509_base + ['ca-cert-file']): +            if not config.exists(pki_base + ['ca']): +                config.set(pki_base + ['ca']) +                config.set_tag(pki_base + ['ca']) + +            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) +            else: +                print(f'Failed to migrate CA certificate on eapol config for interface {interface}') + +            config.delete(x509_base + ['ca-cert-file']) + +        if config.exists(x509_base + ['cert-file']): +            if not config.exists(pki_base + ['certificate']): +                config.set(pki_base + ['certificate']) +                config.set_tag(pki_base + ['certificate']) + +            cert_file = config.return_value(x509_base + ['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 + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) +                config.set(x509_base + ['certificate'], value=pki_name) +            else: +                print(f'Failed to migrate certificate on eapol config for interface {interface}') + +            config.delete(x509_base + ['cert-file']) + +        if config.exists(x509_base + ['key-file']): +            key_file = config.return_value(x509_base + ['key-file']) +            key_path = os.path.join(AUTH_DIR, key_file) +            key = None + +            if os.path.isfile(key_path): +                if not os.access(key_path, os.R_OK): +                    run(f'sudo chmod 644 {key_path}') + +                with open(key_path, 'r') as f: +                    key_data = f.read() +                    key = load_private_key(key_data, passphrase=None, wrap_tags=False) + +            if key: +                key_pem = encode_private_key(key, passphrase=None) +                config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) +            else: +                print(f'Failed to migrate private key on eapol config for interface {interface}') -for ifname in config.list_nodes(base): -    tmp_config = base + [ifname, 'default-route'] -    if config.exists(tmp_config): -        # Retrieve current config value -        value = config.return_value(tmp_config) -        # Delete old Config node -        config.delete(tmp_config) -        if value == 'none': -            config.set(base + [ifname, 'no-default-route']) +            config.delete(x509_base + ['key-file'])  try:      with open(file_name, 'w') as f:          f.write(config.to_string())  except OSError as e: -    print(f'Failed to save the modified config: {e}') -    exit(1) +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 index a0d043d11..4967a29fa 100755 --- a/src/migration-scripts/interfaces/26-to-27 +++ b/src/migration-scripts/interfaces/26-to-27 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright (C) 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 @@ -14,8 +14,8 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node -#        to "authentication username" +# T4384: pppoe: replace default-route CLI option with common CLI nodes already +#        present for DHCP  from sys import argv @@ -30,16 +30,21 @@ file_name = argv[1]  with open(file_name, 'r') as f:      config_file = f.read() +base = ['interfaces', 'pppoe']  config = ConfigTree(config_file) -for type in ['pppoe', 'sstpc-client', 'wwam']: -    base = ['interfaces', type] -    if not config.exists(base): -        continue -    for interface in config.list_nodes(base): -        auth_base = base + [interface, 'authentication', 'user'] -        if config.exists(auth_base): -            config.rename(auth_base, 'username') +if not config.exists(base): +    exit(0) + +for ifname in config.list_nodes(base): +    tmp_config = base + [ifname, 'default-route'] +    if config.exists(tmp_config): +        # Retrieve current config value +        value = config.return_value(tmp_config) +        # Delete old Config node +        config.delete(tmp_config) +        if value == 'none': +            config.set(base + [ifname, 'no-default-route'])  try:      with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28 index ad5bfa653..a0d043d11 100755 --- a/src/migration-scripts/interfaces/27-to-28 +++ b/src/migration-scripts/interfaces/27-to-28 @@ -14,8 +14,8 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast" -#        valueless node. +# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node +#        to "authentication username"  from sys import argv @@ -30,21 +30,16 @@ file_name = argv[1]  with open(file_name, 'r') as f:      config_file = f.read() -base = ['interfaces', 'tunnel']  config = ConfigTree(config_file) -if not config.exists(base): -    exit(0) - -for ifname in config.list_nodes(base): -    multicast_base = base + [ifname, 'multicast'] -    if config.exists(multicast_base): -        tmp = config.return_value(multicast_base) -        print(tmp) -        # Delete old Config node -        config.delete(multicast_base) -        if tmp == 'enable': -            config.set(base + [ifname, 'enable-multicast']) +for type in ['pppoe', 'sstpc-client', 'wwam']: +    base = ['interfaces', type] +    if not config.exists(base): +        continue +    for interface in config.list_nodes(base): +        auth_base = base + [interface, 'authentication', 'user'] +        if config.exists(auth_base): +            config.rename(auth_base, 'username')  try:      with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29 index acb6ee1fb..ad5bfa653 100755 --- a/src/migration-scripts/interfaces/28-to-29 +++ b/src/migration-scripts/interfaces/28-to-29 @@ -14,7 +14,8 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# T5286: remove XDP support in favour of VPP +# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast" +#        valueless node.  from sys import argv @@ -29,17 +30,21 @@ file_name = argv[1]  with open(file_name, 'r') as f:      config_file = f.read() -supports_xdp = ['bonding', 'ethernet'] +base = ['interfaces', 'tunnel']  config = ConfigTree(config_file) -for if_type in supports_xdp: -    base = ['interfaces', if_type] -    if not config.exists(base): -        continue -    for interface in config.list_nodes(base): -        if_base = base + [interface] -        if config.exists(if_base + ['xdp']): -            config.delete(if_base + ['xdp']) +if not config.exists(base): +    exit(0) + +for ifname in config.list_nodes(base): +    multicast_base = base + [ifname, 'multicast'] +    if config.exists(multicast_base): +        tmp = config.return_value(multicast_base) +        print(tmp) +        # Delete old Config node +        config.delete(multicast_base) +        if tmp == 'enable': +            config.set(base + [ifname, 'enable-multicast'])  try:      with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30 index 97e1b329c..acb6ee1fb 100755 --- a/src/migration-scripts/interfaces/29-to-30 +++ b/src/migration-scripts/interfaces/29-to-30 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2023 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 @@ -13,42 +13,37 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# Deletes Wireguard peers if they have the same public key as the router has. -import sys + +# T5286: remove XDP support in favour of VPP + +from sys import argv + +from vyos.ethtool import Ethtool  from vyos.configtree import ConfigTree -from vyos.utils.network import is_wireguard_key_pair -if __name__ == '__main__': -    if len(sys.argv) < 2: -        print("Must specify file name!") -        sys.exit(1) +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) -    file_name = sys.argv[1] +file_name = argv[1] +with open(file_name, 'r') as f: +    config_file = f.read() -    with open(file_name, 'r') as f: -        config_file = f.read() +supports_xdp = ['bonding', 'ethernet'] +config = ConfigTree(config_file) -    config = ConfigTree(config_file) -    base = ['interfaces', 'wireguard'] +for if_type in supports_xdp: +    base = ['interfaces', if_type]      if not config.exists(base): -        # Nothing to do -        sys.exit(0) +        continue      for interface in config.list_nodes(base): -        private_key = config.return_value(base + [interface, 'private-key']) -        interface_base = base + [interface] -        if config.exists(interface_base + ['peer']): -            for peer in config.list_nodes(interface_base + ['peer']): -                peer_base = interface_base + ['peer', peer] -                peer_public_key = config.return_value(peer_base + ['public-key']) -                if config.exists(peer_base + ['public-key']): -                    if not config.exists(peer_base + ['disable']) \ -                            and is_wireguard_key_pair(private_key, peer_public_key): -                        config.set(peer_base + ['disable']) - -    try: -        with open(file_name, 'w') as f: -            f.write(config.to_string()) -    except OSError as e: -        print("Failed to save the modified config: {}".format(e)) -        sys.exit(1) +        if_base = base + [interface] +        if config.exists(if_base + ['xdp']): +            config.delete(if_base + ['xdp']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/interfaces/30-to-31 b/src/migration-scripts/interfaces/30-to-31 new file mode 100755 index 000000000..894106ef4 --- /dev/null +++ b/src/migration-scripts/interfaces/30-to-31 @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# +# Deletes Wireguard peers if they have the same public key as the router has. + +import json +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.ifconfig import EthernetIf +from vyos.ifconfig import BondIf +from vyos.utils.dict import dict_to_paths_values + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: +    config_file = f.read() +    base = ['interfaces', 'bonding'] + +config = ConfigTree(config_file) +if not config.exists(base): +    # Nothing to do +    exit(0) +for bond in config.list_nodes(base): +    member_base = base + [bond, 'member', 'interface'] +    if config.exists(member_base): +        for interface in config.return_values(member_base): +            if_base = ['interfaces', 'ethernet', interface] +            if config.exists(if_base): +                config_ethernet = json.loads(config.get_subtree(if_base).to_json()) +                eth_dict_paths = dict_to_paths_values(config_ethernet) +                for option_path, option_value in eth_dict_paths.items(): +                    # If option is allowed for changing then continue +                    converted_path = option_path.replace('-','_') +                    if converted_path in EthernetIf.get_bond_member_allowed_options(): +                        continue +                    # if option is inherited from bond then continue +                    if converted_path in BondIf.get_inherit_bond_options(): +                        continue +                    option_path_list = option_path.split('.') +                    config.delete(if_base + option_path_list) +                    del option_path_list[-1] +                    # delete empty node from config +                    while len(option_path_list) > 0: +                        if config.list_nodes(if_base + option_path_list): +                            break +                        config.delete(if_base + option_path_list) +                        del option_path_list[-1] + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 new file mode 100755 index 000000000..0fc27b70a --- /dev/null +++ b/src/migration-scripts/interfaces/31-to-32 @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# +# T5671: change port to IANA assigned default port +# T5759: change default MTU 1450 -> 1500 + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['interfaces', 'vxlan'] + +config = ConfigTree(config_file) +if not config.exists(base): +    # Nothing to do +    exit(0) + +for vxlan in config.list_nodes(base): +    if config.exists(base + [vxlan, 'external']): +        config.delete(base + [vxlan, 'external']) +        config.set(base + [vxlan, 'parameters', 'external']) + +    if not config.exists(base + [vxlan, 'port']): +        config.set(base + [vxlan, 'port'], value='8472') + +    if not config.exists(base + [vxlan, 'mtu']): +        config.set(base + [vxlan, 'mtu'], value='1450') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2 new file mode 100755 index 000000000..11d7911e9 --- /dev/null +++ b/src/migration-scripts/ipoe-server/1-to-2 @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - changed cli of all named pools +# - moved gateway-address from pool to global configuration with / netmask +#   gateway can exist without pool if radius is used +#   and Framed-ip-address is transmited +# - There are several gateway-addresses in ipoe +# - default-pool by migration. +#       1. The first pool that contains next-poll. +#       2. Else, the first pool in the list + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'ipoe-server'] +pool_base = base + ['client-ip-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) +default_pool = '' +gateway = '' + +#named pool migration +namedpools_base = pool_base + ['name'] + +for pool_name in config.list_nodes(namedpools_base): +    pool_path = namedpools_base + [pool_name] +    if config.exists(pool_path + ['subnet']): +        subnet = config.return_value(pool_path + ['subnet']) +        config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) +        # Get netmask from subnet +        mask = subnet.split("/")[1] +    if config.exists(pool_path + ['next-pool']): +        next_pool = config.return_value(pool_path + ['next-pool']) +        config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) +        if not default_pool: +            default_pool = pool_name +    if config.exists(pool_path + ['gateway-address']) and mask: +        gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}' +        config.set(base + ['gateway-address'], value=gateway, replace=False) + +if not default_pool and config.list_nodes(namedpools_base): +    default_pool = config.list_nodes(namedpools_base)[0] + +config.delete(namedpools_base) + +if default_pool: +    config.set(base + ['default-pool'], value=default_pool) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/ipoe-server/2-to-3 b/src/migration-scripts/ipoe-server/2-to-3 new file mode 100755 index 000000000..d4ae0a7ba --- /dev/null +++ b/src/migration-scripts/ipoe-server/2-to-3 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'ipoe-server'] +pool_base = base + ['client-ipv6-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +ipv6_pool_name = 'ipv6-pool' +config.copy(pool_base, pool_base + [ipv6_pool_name]) + +if config.exists(pool_base + ['prefix']): +    config.delete(pool_base + ['prefix']) +    config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) +if config.exists(pool_base + ['delegate']): +    config.delete(pool_base + ['delegate']) + +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/ipsec/12-to-13 b/src/migration-scripts/ipsec/12-to-13 new file mode 100755 index 000000000..c11f708bd --- /dev/null +++ b/src/migration-scripts/ipsec/12-to-13 @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Changed value of dead-peer-detection.action from hold to trap +# Changed value of close-action from hold to trap and from restart to start + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['vpn', 'ipsec', 'ike-group'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) +else: +    for ike_group in config.list_nodes(base): +        base_dpd_action = base + [ike_group, 'dead-peer-detection', 'action'] +        base_close_action = base + [ike_group, 'close-action'] +        if config.exists(base_dpd_action) and config.return_value(base_dpd_action) == 'hold': +            config.set(base_dpd_action, 'trap', replace=True) +        if config.exists(base_close_action): +            if config.return_value(base_close_action) == 'hold': +                config.set(base_close_action, 'trap', replace=True) +            if config.return_value(base_close_action) == 'restart': +                config.set(base_close_action, 'start', replace=True) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5 new file mode 100755 index 000000000..3176f895a --- /dev/null +++ b/src/migration-scripts/l2tp/4-to-5 @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +#       'start-stop' migrate to namedpool 'default-range-pool' +#       'subnet' migrate to namedpool 'default-subnet-pool' +#       'default-subnet-pool' is the next pool for 'default-range-pool' + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.base import Warning + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +pool_base = base + ['client-ip-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) +default_pool = '' +range_pool_name = 'default-range-pool' + +if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): +    def is_legalrange(ip1: str, ip2: str, mask: str): +        from ipaddress import IPv4Interface +        interface1 = IPv4Interface(f'{ip1}/{mask}') + +        interface2 = IPv4Interface(f'{ip2}/{mask}') +        return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + +    start_ip = config.return_value(pool_base + ['start']) +    stop_ip = config.return_value(pool_base + ['stop']) +    if is_legalrange(start_ip, stop_ip,'24'): +        ip_range = f'{start_ip}-{stop_ip}' +        config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) +        default_pool = range_pool_name +    else: +        Warning( +            f'L2TP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + +    config.delete(pool_base + ['start']) +    config.delete(pool_base + ['stop']) + +if config.exists(pool_base + ['subnet']): +    for subnet in config.return_values(pool_base + ['subnet']): +        config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + +    config.delete(pool_base + ['subnet']) +    default_pool = range_pool_name + +if default_pool: +    config.set(base + ['default-pool'], value=default_pool) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/l2tp/5-to-6 b/src/migration-scripts/l2tp/5-to-6 new file mode 100755 index 000000000..ca0b13dcc --- /dev/null +++ b/src/migration-scripts/l2tp/5-to-6 @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +if not config.exists(base): +    exit(0) + +#migrate idle to ppp option lcp-echo-timeout +idle_path = base + ['idle'] +if config.exists(idle_path): +    config.set(base + ['ppp-options', 'lcp-echo-timeout'], +               value=config.return_value(idle_path)) +    config.delete(idle_path) + +#migrate mppe from authentication to ppp-otion +mppe_path = base + ['authentication', 'mppe'] +if config.exists(mppe_path): +    config.set(base + ['ppp-options', 'mppe'], +               value=config.return_value(mppe_path)) +    config.delete(mppe_path) + +#migrate require to protocol +require_path = base + ['authentication', 'require'] +if config.exists(require_path): +    protocols = list(config.return_values(require_path)) +    for protocol in protocols: +        config.set(base + ['authentication', 'protocols'], value=protocol, +                   replace=False) +    config.delete(require_path) +else: +    config.set(base + ['authentication', 'protocols'], value='mschap-v2') + +#migrate default gateway if not exist +if not config.exists(base + ['gateway-address']): +    config.set(base + ['gateway-address'], value='10.255.255.0') + +#migrate authentication radius timeout +rad_timeout_path = base + ['authentication', 'radius', 'timeout'] +if config.exists(rad_timeout_path): +    if int(config.return_value(rad_timeout_path)) > 60: +        config.set(rad_timeout_path, value=60) + +#migrate authentication radius acct timeout +rad_acct_timeout_path = base + ['authentication', 'radius', 'acct-timeout'] +if config.exists(rad_acct_timeout_path): +    if int(config.return_value(rad_acct_timeout_path)) > 60: +        config.set(rad_acct_timeout_path,value=60) + +#migrate authentication radius max-try +rad_max_try_path = base + ['authentication', 'radius', 'max-try'] +if config.exists(rad_max_try_path): +    if int(config.return_value(rad_max_try_path)) > 20: +        config.set(rad_max_try_path, value=20) + +#migrate dae-server to dynamic-author +dae_path_old = base + ['authentication', 'radius', 'dae-server'] +dae_path_new = base + ['authentication', 'radius', 'dynamic-author'] + +if config.exists(dae_path_old + ['ip-address']): +    config.set(dae_path_new + ['server'], +               value=config.return_value(dae_path_old + ['ip-address'])) + +if config.exists(dae_path_old + ['port']): +    config.set(dae_path_new + ['port'], +               value=config.return_value(dae_path_old + ['port'])) + +if config.exists(dae_path_old + ['secret']): +    config.set(dae_path_new + ['key'], +               value=config.return_value(dae_path_old + ['secret'])) + +if config.exists(dae_path_old): +    config.delete(dae_path_old) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/l2tp/6-to-7 b/src/migration-scripts/l2tp/6-to-7 new file mode 100755 index 000000000..f49c4ab08 --- /dev/null +++ b/src/migration-scripts/l2tp/6-to-7 @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +pool_base = base + ['client-ipv6-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +ipv6_pool_name = 'ipv6-pool' +config.copy(pool_base, pool_base + [ipv6_pool_name]) + +if config.exists(pool_base + ['prefix']): +    config.delete(pool_base + ['prefix']) +    config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) +if config.exists(pool_base + ['delegate']): +    config.delete(pool_base + ['delegate']) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/l2tp/7-to-8 b/src/migration-scripts/l2tp/7-to-8 new file mode 100755 index 000000000..4956e1155 --- /dev/null +++ b/src/migration-scripts/l2tp/7-to-8 @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Migrate from 'ccp-disable' to 'ppp-options.disable-ccp' +# Migration ipv6 options + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +if not config.exists(base): +    exit(0) + +#CCP migration +if config.exists(base + ['ccp-disable']): +    config.delete(base + ['ccp-disable']) +    config.set(base + ['ppp-options', 'disable-ccp']) + +#IPV6 options migrations +if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): +    intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) +    if intf_peer_id == 'ipv4': +        intf_peer_id = 'ipv4-addr' +    config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) +    config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + +if config.exists(base + ['ppp-options','ipv6-intf-id']): +    intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) +    config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) +    config.delete(base + ['ppp-options','ipv6-intf-id']) + +if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): +    config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) +    config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1 index a936cbdfc..a99356062 100755 --- a/src/migration-scripts/lldp/0-to-1 +++ b/src/migration-scripts/lldp/0-to-1 @@ -1,4 +1,18 @@  #!/usr/bin/env python3 +# +# Copyright (C) 2020 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>.  # Delete "set service lldp interface <interface> location civic-based" option  # as it was broken most of the time anyways diff --git a/src/migration-scripts/lldp/1-to-2 b/src/migration-scripts/lldp/1-to-2 new file mode 100755 index 000000000..35efb25db --- /dev/null +++ b/src/migration-scripts/lldp/1-to-2 @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5855: migrate "set service lldp snmp enable" -> `set service lldp snmp" + +import sys + +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'lldp'] +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +if config.exists(base + ['snmp']): +    enabled = config.exists(base + ['snmp', 'enable']) +    config.delete(base + ['snmp']) +    if enabled: config.set(base + ['snmp']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/nat/5-to-6 b/src/migration-scripts/nat/5-to-6 new file mode 100755 index 000000000..c83b93d84 --- /dev/null +++ b/src/migration-scripts/nat/5-to-6 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5643: move from 'set nat [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['nat']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat', direction]): +        continue + +    for rule in config.list_nodes(['nat', direction, 'rule']): +        base = ['nat', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                tmp = config.return_value(base + [iface]) +                if tmp: +                    config.delete(base + [iface]) +                    config.set(base + [iface, 'interface-name'], value=tmp) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7 new file mode 100755 index 000000000..a2e735394 --- /dev/null +++ b/src/migration-scripts/nat/6-to-7 @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-group <iface_group>' +# to +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>' +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>' +# Also remove command if interface == any + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['nat']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat', direction]): +        continue + +    for rule in config.list_nodes(['nat', direction, 'rule']): +        base = ['nat', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                if config.exists(base + [iface, 'interface-name']): +                    tmp = config.return_value(base + [iface, 'interface-name']) +                    if tmp != 'any': +                        config.delete(base + [iface, 'interface-name']) +                        config.set(base + [iface, 'name'], value=tmp) +                    else: +                        config.delete(base + [iface]) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/nat66/1-to-2 b/src/migration-scripts/nat66/1-to-2 new file mode 100755 index 000000000..b7d4e3f6b --- /dev/null +++ b/src/migration-scripts/nat66/1-to-2 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +#   'set nat66 [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +#   'set nat66 [source|destination] rule X [inbound-interface|outbound interface] name <iface>' + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +if not config.exists(['nat66']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat66', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat66', direction]): +        continue + +    for rule in config.list_nodes(['nat66', direction, 'rule']): +        base = ['nat66', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                tmp = config.return_value(base + [iface]) +                config.delete(base + [iface]) +                config.set(base + [iface, 'name'], value=tmp) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/nat66/2-to-3 b/src/migration-scripts/nat66/2-to-3 new file mode 100755 index 000000000..f34f170b3 --- /dev/null +++ b/src/migration-scripts/nat66/2-to-3 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['nat66', 'source'] +new_base = ['service', 'ndp-proxy', 'interface'] + +config = ConfigTree(config_file) +if not config.exists(base): +    # Nothing to do +    exit(0) + +for rule in config.list_nodes(base + ['rule']): +    base_rule = base + ['rule', rule] + +    interface = None +    if config.exists(base_rule + ['outbound-interface', 'name']): +        interface = config.return_value(base_rule + ['outbound-interface', 'name']) +    else: +        continue + +    prefix_base = base_rule + ['source', 'prefix'] +    if config.exists(prefix_base): +        prefix = config.return_value(prefix_base) +        config.set(new_base + [interface, 'prefix', prefix, 'mode'], value='static') +        config.set_tag(new_base) +        config.set_tag(new_base + [interface, 'prefix']) + +        if config.exists(base_rule + ['disable']): +            config.set(new_base + [interface, 'prefix', prefix, 'disable']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/openvpn/0-to-1 b/src/migration-scripts/openvpn/0-to-1 new file mode 100755 index 000000000..24bb38d3c --- /dev/null +++ b/src/migration-scripts/openvpn/0-to-1 @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +# Removes outdated ciphers (DES and Blowfish) from OpenVPN configs + +import sys + +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['interfaces', 'openvpn']): +    # Nothing to do +    sys.exit(0) +else: +    ovpn_intfs = config.list_nodes(['interfaces', 'openvpn']) +    for	i in ovpn_intfs: +        # Remove DES and Blowfish from 'encryption cipher' +        cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] +        if config.exists(cipher_path): +            cipher = config.return_value(cipher_path) +            if cipher in ['des', 'bf128', 'bf256']: +                config.delete(cipher_path) + +        ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] +        if config.exists(ncp_cipher_path): +            ncp_ciphers = config.return_values(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers']) +            if 'des' in ncp_ciphers: +                config.delete_value(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'], 'des') + +        # Clean up the encryption subtree if the migration procedure left it empty +        if config.exists(['interfaces', 'openvpn', i, 'encryption']) and \ +           (config.list_nodes(['interfaces', 'openvpn', i, 'encryption']) == []): +            config.delete(['interfaces', 'openvpn', i, 'encryption']) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        sys.exit(1) diff --git a/src/migration-scripts/ospf/0-to-1 b/src/migration-scripts/ospf/0-to-1 index 8f02acada..a6cb9feb8 100755 --- a/src/migration-scripts/ospf/0-to-1 +++ b/src/migration-scripts/ospf/0-to-1 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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 @@ -28,6 +28,7 @@ def ospf_passive_migration(config, ospf_base):                      default = True                      continue                  config.set(ospf_base + ['interface', interface, 'passive']) +                config.set_tag(ospf_base + ['interface'])              config.delete(ospf_base + ['passive-interface'])              config.set(ospf_base + ['passive-interface'], value='default') @@ -35,6 +36,7 @@ def ospf_passive_migration(config, ospf_base):          if config.exists(ospf_base + ['passive-interface-exclude']):              for interface in config.return_values(ospf_base + ['passive-interface-exclude']):                  config.set(ospf_base + ['interface', interface, 'passive', 'disable']) +                config.set_tag(ospf_base + ['interface'])              config.delete(ospf_base + ['passive-interface-exclude'])  if len(argv) < 2: diff --git a/src/migration-scripts/pim/0-to-1 b/src/migration-scripts/pim/0-to-1 new file mode 100755 index 000000000..bf8af733c --- /dev/null +++ b/src/migration-scripts/pim/0-to-1 @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5736: igmp: migrate "protocols igmp" to "protocols pim" + +import sys +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +base = ['protocols', 'igmp'] +pim_base = ['protocols', 'pim'] +if not config.exists(base): +    # Nothing to do +    sys.exit(0) + +for interface in config.list_nodes(base + ['interface']): +    base_igmp_iface = base + ['interface', interface] +    pim_base_iface = pim_base + ['interface', interface] + +    # Create IGMP note under PIM interface +    if not config.exists(pim_base_iface + ['igmp']): +        config.set(pim_base_iface + ['igmp']) + +    if config.exists(base_igmp_iface + ['join']): +        config.copy(base_igmp_iface + ['join'], pim_base_iface + ['igmp', 'join']) +        config.set_tag(pim_base_iface + ['igmp', 'join']) + +        new_join_base = pim_base_iface + ['igmp', 'join'] +        for address in config.list_nodes(new_join_base): +            if config.exists(new_join_base + [address, 'source']): +                config.rename(new_join_base + [address, 'source'], 'source-address') + +    if config.exists(base_igmp_iface + ['query-interval']): +        config.copy(base_igmp_iface + ['query-interval'], pim_base_iface + ['igmp', 'query-interval']) + +    if config.exists(base_igmp_iface + ['query-max-response-time']): +        config.copy(base_igmp_iface + ['query-max-response-time'], pim_base_iface + ['igmp', 'query-max-response-time']) + +    if config.exists(base_igmp_iface + ['version']): +        config.copy(base_igmp_iface + ['version'], pim_base_iface + ['igmp', 'version']) + +config.delete(base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    sys.exit(1) diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 index f6f889c35..5b8fee17e 100755 --- a/src/migration-scripts/policy/4-to-5 +++ b/src/migration-scripts/policy/4-to-5 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 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 @@ -37,7 +37,53 @@ base4 = ['policy', 'route']  base6 = ['policy', 'route6']  config = ConfigTree(config_file) + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): +    """Delete unexpected policy on interfaces in cases when +       policy does not exist but inreface has a policy configuration +       Example T5941: +         set interfaces bonding bond0 vif 995 policy +    """ +    if_path = ['interfaces', iftype, ifname] + +    if vif: +        if_path += ['vif', vif] +    elif vifs: +        if_path += ['vif-s', vifs] +        if vifc: +            if_path += ['vif-c', vifc] + +    if not config.exists(if_path + ['policy']): +        return + +    config.delete(if_path + ['policy']) + +  if not config.exists(base4) and not config.exists(base6): +    # Delete orphaned nodes on interfaces T5941 +    for iftype in config.list_nodes(['interfaces']): +        for ifname in config.list_nodes(['interfaces', iftype]): +            delete_orphaned_interface_policy(config, iftype, ifname) + +            if config.exists(['interfaces', iftype, ifname, 'vif']): +                for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): +                    delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + +            if config.exists(['interfaces', iftype, ifname, 'vif-s']): +                for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): +                    delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + +                    if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                        for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                            delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        exit(1) +      # Nothing to do      exit(0) diff --git a/src/migration-scripts/policy/6-to-7 b/src/migration-scripts/policy/6-to-7 new file mode 100755 index 000000000..727b8487a --- /dev/null +++ b/src/migration-scripts/policy/6-to-7 @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5729: Switch to valueless whenever is possible. +# From +    # set policy [route | route6] ... rule <rule> log enable +    # set policy [route | route6] ... rule <rule> log disable +# To +    # set policy [route | route6] ... rule <rule> log +    # Remove command if log=disable + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['policy'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +for family in ['route', 'route6']: +    if config.exists(base + [family]): +         +        for policy_name in config.list_nodes(base + [family]): +            if config.exists(base + [family, policy_name, 'rule']): +                for rule in config.list_nodes(base + [family, policy_name, 'rule']): +                    # Log +                    if config.exists(base + [family, policy_name, 'rule', rule, 'log']): +                        log_value = config.return_value(base + [family, policy_name, 'rule', rule, 'log']) +                        config.delete(base + [family, policy_name, 'rule', rule, 'log']) +                        if log_value == 'enable': +                            config.set(base + [family, policy_name, 'rule', rule, 'log']) +                    # State +                    if config.exists(base + [family, policy_name, 'rule', rule, 'state']): +                        flag_enable = 'False' +                        for state in ['established', 'invalid', 'new', 'related']: +                            if config.exists(base + [family, policy_name, 'rule', rule, 'state', state]): +                                state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state]) +                                config.delete(base + [family, policy_name, 'rule', rule, 'state', state]) +                                if state_value == 'enable': +                                    config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False) +                                    flag_enable = 'True' +                        if flag_enable == 'False': +                            config.delete(base + [family, policy_name, 'rule', rule, 'state']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1)
\ No newline at end of file diff --git a/src/migration-scripts/policy/7-to-8 b/src/migration-scripts/policy/7-to-8 new file mode 100755 index 000000000..73eece1a6 --- /dev/null +++ b/src/migration-scripts/policy/7-to-8 @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T5834: Rename 'enable-default-log' to 'default-log' +# From +    # set policy [route | route 6] <route> enable-default-log +# To +    # set policy [route | route 6] <route> default-log + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['policy'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +for family in ['route', 'route6']: +    if config.exists(base + [family]): + +        for policy_name in config.list_nodes(base + [family]): +            if config.exists(base + [family, policy_name, 'enable-default-log']): +                config.rename(base + [family, policy_name, 'enable-default-log'], 'default-log') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7 new file mode 100755 index 000000000..b94ce57f9 --- /dev/null +++ b/src/migration-scripts/pppoe-server/6-to-7 @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +#       'start-stop' migrate to namedpool 'default-range-pool' +#       'subnet' migrate to namedpool 'default-subnet-pool' +#       'default-subnet-pool' is the next pool for 'default-range-pool' +# - There is only one gateway-address, take the first which is configured +# - default-pool by migration. +#       1. If authentication mode = 'local' then it is first named pool. +#       If there are not named pools, namedless pool will be default. +#       2. If authentication mode = 'radius' then namedless pool will be default + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.base import Warning + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +pool_base = base + ['client-ip-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +default_pool = '' +range_pool_name = 'default-range-pool' + +#Default nameless pools migrations +if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): +    def is_legalrange(ip1: str, ip2: str, mask: str): +        from ipaddress import IPv4Interface +        interface1 = IPv4Interface(f'{ip1}/{mask}') +        interface2 = IPv4Interface(f'{ip2}/{mask}') +        return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + +    start_ip = config.return_value(pool_base + ['start']) +    stop_ip = config.return_value(pool_base + ['stop']) +    if is_legalrange(start_ip, stop_ip, '24'): +        ip_range = f'{start_ip}-{stop_ip}' +        config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) +        default_pool = range_pool_name +    else: +        Warning( +            f'PPPoE client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') +    config.delete(pool_base + ['start']) +    config.delete(pool_base + ['stop']) + +if config.exists(pool_base + ['subnet']): +    default_pool = range_pool_name +    for subnet in config.return_values(pool_base + ['subnet']): +        config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) +    config.delete(pool_base + ['subnet']) + +gateway = '' +if config.exists(base + ['gateway-address']): +    gateway = config.return_value(base + ['gateway-address']) + +#named pool migration +namedpools_base = pool_base + ['name'] +if config.exists(namedpools_base): +    if config.exists(base + ['authentication', 'mode']): +        if config.return_value(base + ['authentication', 'mode']) == 'local': +            if config.list_nodes(namedpools_base): +                default_pool = config.list_nodes(namedpools_base)[0] + +    for pool_name in config.list_nodes(namedpools_base): +        pool_path = namedpools_base + [pool_name] +        if config.exists(pool_path + ['subnet']): +            subnet = config.return_value(pool_path + ['subnet']) +            config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) +        if config.exists(pool_path + ['next-pool']): +            next_pool = config.return_value(pool_path + ['next-pool']) +            config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) +        if not gateway: +            if config.exists(pool_path + ['gateway-address']): +                gateway = config.return_value(pool_path + ['gateway-address']) + +    config.delete(namedpools_base) + +if gateway: +    config.set(base + ['gateway-address'], value=gateway) +if default_pool: +    config.set(base + ['default-pool'], value=default_pool) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/pppoe-server/7-to-8 b/src/migration-scripts/pppoe-server/7-to-8 new file mode 100755 index 000000000..b0d9bb464 --- /dev/null +++ b/src/migration-scripts/pppoe-server/7-to-8 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +pool_base = base + ['client-ipv6-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +ipv6_pool_name = 'ipv6-pool' +config.copy(pool_base, pool_base + [ipv6_pool_name]) + +if config.exists(pool_base + ['prefix']): +    config.delete(pool_base + ['prefix']) +    config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) +if config.exists(pool_base + ['delegate']): +    config.delete(pool_base + ['delegate']) + +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/pppoe-server/8-to-9 b/src/migration-scripts/pppoe-server/8-to-9 new file mode 100755 index 000000000..ad75c28a1 --- /dev/null +++ b/src/migration-scripts/pppoe-server/8-to-9 @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Change from 'ccp' to 'disable-ccp' in ppp-option section +# Migration ipv6 options + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +if not config.exists(base): +    exit(0) + +#CCP migration +if config.exists(base + ['ppp-options', 'ccp']): +    config.delete(base + ['ppp-options', 'ccp']) +else: +    config.set(base + ['ppp-options', 'disable-ccp']) + +#IPV6 options migrations +if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): +    intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) +    if intf_peer_id == 'ipv4': +        intf_peer_id = 'ipv4-addr' +    config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) +    config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + +if config.exists(base + ['ppp-options','ipv6-intf-id']): +    intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) +    config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) +    config.delete(base + ['ppp-options','ipv6-intf-id']) + +if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): +    config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) +    config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3 new file mode 100755 index 000000000..091cb68ec --- /dev/null +++ b/src/migration-scripts/pptp/2-to-3 @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +#       'start-stop' migrate to namedpool 'default-range-pool' +#       'default-subnet-pool' is the next pool for 'default-range-pool' + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.base import Warning + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'pptp', 'remote-access'] +pool_base = base + ['client-ip-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +range_pool_name = 'default-range-pool' + +if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): +    def is_legalrange(ip1: str, ip2: str, mask: str): +        from ipaddress import IPv4Interface +        interface1 = IPv4Interface(f'{ip1}/{mask}') +        interface2 = IPv4Interface(f'{ip2}/{mask}') +        return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + +    start_ip = config.return_value(pool_base + ['start']) +    stop_ip = config.return_value(pool_base + ['stop']) +    if is_legalrange(start_ip, stop_ip, '24'): +        ip_range = f'{start_ip}-{stop_ip}' +        config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) +        config.set(base + ['default-pool'], value=range_pool_name) +    else: +        Warning( +            f'PPTP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + +    config.delete(pool_base + ['start']) +    config.delete(pool_base + ['stop']) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/pptp/3-to-4 b/src/migration-scripts/pptp/3-to-4 new file mode 100755 index 000000000..0a8dad2f4 --- /dev/null +++ b/src/migration-scripts/pptp/3-to-4 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - Move 'mppe' from 'authentication' node to 'ppp-options' + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'pptp', 'remote-access'] + +if not config.exists(base): +    exit(0) + +if config.exists(base + ['authentication','mppe']): +    mppe = config.return_value(base + ['authentication','mppe']) +    config.set(base + ['ppp-options', 'mppe'], value=mppe, replace=True) +    config.delete(base + ['authentication','mppe']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 index cca32d06e..666811e5a 100755 --- a/src/migration-scripts/qos/1-to-2 +++ b/src/migration-scripts/qos/1-to-2 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 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 @@ -40,7 +40,53 @@ with open(file_name, 'r') as f:  base = ['traffic-policy']  config = ConfigTree(config_file) + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): +    """Delete unexpected traffic-policy on interfaces in cases when +       policy does not exist but inreface has a policy configuration +       Example T5941: +         set interfaces bonding bond0 vif 995 traffic-policy +    """ +    if_path = ['interfaces', iftype, ifname] + +    if vif: +        if_path += ['vif', vif] +    elif vifs: +        if_path += ['vif-s', vifs] +        if vifc: +            if_path += ['vif-c', vifc] + +    if not config.exists(if_path + ['traffic-policy']): +        return + +    config.delete(if_path + ['traffic-policy']) + +  if not config.exists(base): +    # Delete orphaned nodes on interfaces T5941 +    for iftype in config.list_nodes(['interfaces']): +        for ifname in config.list_nodes(['interfaces', iftype]): +            delete_orphaned_interface_policy(config, iftype, ifname) + +            if config.exists(['interfaces', iftype, ifname, 'vif']): +                for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): +                    delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + +            if config.exists(['interfaces', iftype, ifname, 'vif-s']): +                for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): +                    delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + +                    if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                        for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                            delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        exit(1) +      # Nothing to do      exit(0) diff --git a/src/migration-scripts/rpki/1-to-2 b/src/migration-scripts/rpki/1-to-2 new file mode 100755 index 000000000..559440bba --- /dev/null +++ b/src/migration-scripts/rpki/1-to-2 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T6011: rpki: known-hosts-file is no longer supported bxy FRR CLI, +#        remove VyOS CLI node + +from sys import exit +from sys import argv +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['protocols', 'rpki'] +config = ConfigTree(config_file) + +# Nothing to do +if not config.exists(base): +    exit(0) + +if config.exists(base + ['cache']): +    for cache in config.list_nodes(base + ['cache']): +        ssh_node = base + ['cache', cache, 'ssh'] +        if config.exists(ssh_node + ['known-hosts-file']): +            config.delete(ssh_node + ['known-hosts-file']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5 new file mode 100755 index 000000000..95e482713 --- /dev/null +++ b/src/migration-scripts/sstp/4-to-5 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - move all pool to named pools +#       'subnet' migrate to namedpool 'default-subnet-pool' +#       'default-subnet-pool' is the next pool for 'default-range-pool' + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'sstp'] +pool_base = base + ['client-ip-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +range_pool_name = 'default-range-pool' + +if config.exists(pool_base + ['subnet']): +    default_pool = range_pool_name +    for subnet in config.return_values(pool_base + ['subnet']): +        config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) +    config.delete(pool_base + ['subnet']) +    config.set(base + ['default-pool'], value=default_pool) +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/sstp/5-to-6 b/src/migration-scripts/sstp/5-to-6 new file mode 100755 index 000000000..bac9975b2 --- /dev/null +++ b/src/migration-scripts/sstp/5-to-6 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Migrating to named ipv6 pools + +import os +import pprint + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'sstp'] +pool_base = base + ['client-ipv6-pool'] +if not config.exists(base): +    exit(0) + +if not config.exists(pool_base): +    exit(0) + +ipv6_pool_name = 'ipv6-pool' +config.copy(pool_base, pool_base + [ipv6_pool_name]) + +if config.exists(pool_base + ['prefix']): +    config.delete(pool_base + ['prefix']) +    config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) +if config.exists(pool_base + ['delegate']): +    config.delete(pool_base + ['delegate']) + +# format as tag node +config.set_tag(pool_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/system/26-to-27 b/src/migration-scripts/system/26-to-27 new file mode 100755 index 000000000..80bb82cbd --- /dev/null +++ b/src/migration-scripts/system/26-to-27 @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# +# T5877: migrate 'system domain-search domain' to 'system domain-search' + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['system', 'domain-search'] +config = ConfigTree(config_file) + +if not config.exists(base): +    exit(0) + +if config.exists(base + ['domain']): +    entries = config.return_values(base + ['domain']) +    config.delete(base + ['domain']) +    for entry in entries: +        config.set(base, value=entry, replace=False) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) | 
