diff options
Diffstat (limited to 'src/migration-scripts')
-rwxr-xr-x | src/migration-scripts/https/2-to-3 | 86 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/18-to-19 | 174 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/20-to-21 | 145 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/21-to-22 | 60 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/22-to-23 | 369 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/5-to-6 | 12 | ||||
-rwxr-xr-x | src/migration-scripts/ipsec/4-to-5 | 14 | ||||
-rwxr-xr-x | src/migration-scripts/ipsec/5-to-6 | 93 | ||||
-rwxr-xr-x | src/migration-scripts/ipsec/6-to-7 | 169 | ||||
-rwxr-xr-x | src/migration-scripts/ipsec/7-to-8 | 125 | ||||
-rwxr-xr-x | src/migration-scripts/l2tp/3-to-4 | 169 | ||||
-rwxr-xr-x | src/migration-scripts/openconnect/0-to-1 | 136 | ||||
-rwxr-xr-x | src/migration-scripts/policy/0-to-1 | 65 | ||||
-rwxr-xr-x | src/migration-scripts/quagga/7-to-8 | 99 | ||||
-rwxr-xr-x | src/migration-scripts/quagga/8-to-9 | 114 | ||||
-rwxr-xr-x | src/migration-scripts/sstp/3-to-4 | 136 | ||||
-rwxr-xr-x | src/migration-scripts/system/20-to-21 | 57 | ||||
-rwxr-xr-x | src/migration-scripts/vrf/0-to-1 | 10 | ||||
-rwxr-xr-x | src/migration-scripts/vrf/1-to-2 | 1 | ||||
-rwxr-xr-x | src/migration-scripts/vrf/2-to-3 | 144 |
20 files changed, 1972 insertions, 206 deletions
diff --git a/src/migration-scripts/https/2-to-3 b/src/migration-scripts/https/2-to-3 new file mode 100755 index 000000000..fa29fdd18 --- /dev/null +++ b/src/migration-scripts/https/2-to-3 @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 system signed certificate to use PKI + +import sys + +from vyos.configtree import ConfigTree +from vyos.pki import create_certificate +from vyos.pki import create_certificate_request +from vyos.pki import create_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key + +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', 'certificates'] +pki_base = ['pki'] + +if not config.exists(base + ['system-generated-certificate']): + sys.exit(0) + +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) + +if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + +valid_days = 365 +if config.exists(base + ['system-generated-certificate', 'lifetime']): + valid_days = int(config.return_value(base + ['system-generated-certificate', 'lifetime'])) + +key = create_private_key('rsa', 2048) +subject = {'country': 'GB', 'state': 'N/A', 'locality': 'N/A', 'organization': 'VyOS', 'common_name': 'vyos'} +cert_req = create_certificate_request(subject, key, ['vyos']) +cert = create_certificate(cert_req, cert_req, key, valid_days) + +if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', 'generated_https', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + +if key: + key_pem = encode_private_key(key) + config.set(pki_base + ['certificate', 'generated_https', 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + +if cert and key: + config.set(base + ['certificate'], value='generated_https') +else: + print('Failed to migrate system-generated-certificate from https service') + +config.delete(base + ['system-generated-certificate']) + +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/18-to-19 b/src/migration-scripts/interfaces/18-to-19 index 06e07572f..a12c4a6cd 100755 --- a/src/migration-scripts/interfaces/18-to-19 +++ b/src/migration-scripts/interfaces/18-to-19 @@ -14,65 +14,31 @@ # 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 - -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]) +from vyos.configtree import ConfigTree -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) +def replace_nat_interfaces(config, old, new): + if not config.exists(['nat']): + return + for direction in ['destination', 'source']: + conf_direction = ['nat', direction, 'rule'] + if not config.exists(conf_direction): + return + for rule in config.list_nodes(conf_direction): + conf_rule = conf_direction + [rule] + if config.exists(conf_rule + ['inbound-interface']): + tmp = config.return_value(conf_rule + ['inbound-interface']) + if tmp == old: + config.set(conf_rule + ['inbound-interface'], value=new) + if config.exists(conf_rule + ['outbound-interface']): + tmp = config.return_value(conf_rule + ['outbound-interface']) + if tmp == old: + config.set(conf_rule + ['outbound-interface'], value=new) - # 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(argv) < 1): @@ -80,62 +46,58 @@ if __name__ == '__main__': exit(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', 'wirelessmodem'] + if not config.exists(base): + # Nothing to do + exit(0) + + new_base = ['interfaces', 'wwan'] + config.set(new_base) + config.set_tag(new_base) + for old_interface in config.list_nodes(base): + # convert usb0b1.3p1.2 device identifier and extract 1.3 usb bus id + usb = config.return_value(base + [old_interface, 'device']) + device = usb.split('b')[-1] + busid = device.split('p')[0] + for new_interface in os.listdir('/sys/class/net'): + # we are only interested in interfaces starting with wwan + if not new_interface.startswith('wwan'): + continue + device = os.readlink(f'/sys/class/net/{new_interface}/device') + device = device.split(':')[0] + if busid in device: + config.copy(base + [old_interface], new_base + [new_interface]) + replace_nat_interfaces(config, old_interface, new_interface) + + config.delete(base) + + # Now that we have copied the old wirelessmodem interfaces to wwan + # we can start to migrate also individual config items. + for interface in config.list_nodes(new_base): + # we do no longer need the USB device name + config.delete(new_base + [interface, 'device']) + # set/unset DNS configuration + dns = new_base + [interface, 'no-peer-dns'] + if config.exists(dns): + config.delete(dns) + else: + config.set(['system', 'name-servers-dhcp'], value=interface, replace=False) + + # Backup distance is now handled by DHCP option "default-route-distance" + distance = dns = new_base + [interface, 'backup', 'distance'] + old_default_distance = '10' + if config.exists(distance): + old_default_distance = config.return_value(distance) + config.delete(distance) + config.set(new_base + [interface, 'dhcp-options', 'default-route-distance'], value=old_default_distance) + + # the new wwan interface use regular IP addressing + config.set(new_base + [interface, 'address'], value='dhcp') try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 new file mode 100755 index 000000000..06e07572f --- /dev/null +++ b/src/migration-scripts/interfaces/20-to-21 @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 +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(argv) < 1): + 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) + + # + # 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)) + exit(1) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 new file mode 100755 index 000000000..d1ec2ad3e --- /dev/null +++ b/src/migration-scripts/interfaces/21-to-22 @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# 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 vyos.configtree import ConfigTree + +if __name__ == '__main__': + 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() + + 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]) + + 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 new file mode 100755 index 000000000..93ce9215f --- /dev/null +++ b/src/migration-scripts/interfaces/22-to-23 @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 Wireguard to store keys in CLI +# Migrate EAPoL to PKI configuration + +import os +import sys +from vyos.configtree import ConfigTree +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.util 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) < 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() + +config = ConfigTree(config_file) + +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']) + + 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 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 + + 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) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_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) diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 index 1291751d8..ae79c1d1b 100755 --- a/src/migration-scripts/interfaces/5-to-6 +++ b/src/migration-scripts/interfaces/5-to-6 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -55,6 +55,16 @@ def copy_rtradv(c, old_base, interface): min_max = interval.split('-')[0] c.set(new_base + ['interval', min_max], value=tmp) + # cleanup boolean nodes in individual route + route_base = new_base + ['route'] + if c.exists(route_base): + for route in config.list_nodes(route_base): + if c.exists(route_base + [route, 'remove-route']): + tmp = c.return_value(route_base + [route, 'remove-route']) + c.delete(route_base + [route, 'remove-route']) + if tmp == 'false': + c.set(route_base + [route, 'no-remove-route']) + # cleanup boolean nodes in individual prefix prefix_base = new_base + ['prefix'] if c.exists(prefix_base): diff --git a/src/migration-scripts/ipsec/4-to-5 b/src/migration-scripts/ipsec/4-to-5 index b64aa8462..4e959a7bf 100755 --- a/src/migration-scripts/ipsec/4-to-5 +++ b/src/migration-scripts/ipsec/4-to-5 @@ -1,4 +1,18 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2019 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/>. # log-modes have changed, keyword all to any diff --git a/src/migration-scripts/ipsec/5-to-6 b/src/migration-scripts/ipsec/5-to-6 new file mode 100755 index 000000000..e9adee01b --- /dev/null +++ b/src/migration-scripts/ipsec/5-to-6 @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# Remove deprecated strongSwan options from VyOS CLI +# - vpn ipsec nat-traversal enable +# - vpn ipsec nat-networks allowed-network + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + 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'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# Delete CLI nodes whose config options got removed by strongSwan +for cli_node in ['nat-traversal', 'nat-networks']: + if config.exists(base + [cli_node]): + config.delete(base + [cli_node]) + +# Remove options only valid in Openswan +if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + if not config.exists(base + ['site-to-site', 'peer', peer, 'tunnel']): + continue + for tunnel in config.list_nodes(base + ['site-to-site', 'peer', peer, 'tunnel']): + # allow-public-networks - Sets a value in ipsec.conf that was only ever valid in Openswan on kernel 2.6 + nat_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-nat-networks'] + if config.exists(nat_networks): + config.delete(nat_networks) + + # allow-nat-networks - Also sets a value only valid in Openswan + public_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-public-networks'] + if config.exists(public_networks): + config.delete(public_networks) + +# Rename "logging log-level" and "logging log-modes" to something more human friendly +log = base + ['logging'] +if config.exists(log): + config.rename(log, 'log') + log = base + ['log'] + +log_level = log + ['log-level'] +if config.exists(log_level): + config.rename(log_level, 'level') + +log_mode = log + ['log-modes'] +if config.exists(log_mode): + config.rename(log_mode, 'subsystem') + +# Rename "ipsec-interfaces interface" to "interface" +base_interfaces = base + ['ipsec-interfaces', 'interface'] +if config.exists(base_interfaces): + config.copy(base_interfaces, base + ['interface']) + config.delete(base_interfaces) + +# Remove deprecated "auto-update" option +tmp = base + ['auto-update'] +if config.exists(tmp): + config.delete(tmp) + +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/ipsec/6-to-7 b/src/migration-scripts/ipsec/6-to-7 new file mode 100755 index 000000000..788a87095 --- /dev/null +++ b/src/migration-scripts/ipsec/6-to-7 @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 /config/auth certificates and keys into PKI configuration + +import os + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.util import run + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +pki_base = ['pki'] +ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] + +config = ConfigTree(config_file) +changes_made = False + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +if config.exists(ipsec_site_base): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + for peer in config.list_nodes(ipsec_site_base): + if not config.exists(ipsec_site_base + [peer, 'authentication', 'x509']): + continue + + changes_made = True + + peer_x509_base = ipsec_site_base + [peer, 'authentication', 'x509'] + pki_name = 'peer_' + peer.replace(".", "-") + + if config.exists(peer_x509_base + ['cert-file']): + cert_file = config.return_value(peer_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(peer_x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on peer "{peer}"') + + config.delete(peer_x509_base + ['cert-file']) + + if config.exists(peer_x509_base + ['ca-cert-file']): + ca_cert_file = config.return_value(peer_x509_base + ['ca-cert-file']) + ca_cert_path = os.path.join(AUTH_DIR, ca_cert_file) + ca_cert = None + + if os.path.isfile(ca_cert_path): + if not os.access(ca_cert_path, os.R_OK): + run(f'sudo chmod 644 {ca_cert_path}') + + with open(ca_cert_path, 'r') as f: + ca_cert_data = f.read() + ca_cert = load_certificate(ca_cert_data, wrap_tags=False) + + if ca_cert: + ca_cert_pem = encode_certificate(ca_cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(ca_cert_pem)) + config.set(peer_x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on peer "{peer}"') + + config.delete(peer_x509_base + ['ca-cert-file']) + + if config.exists(peer_x509_base + ['crl-file']): + crl_file = config.return_value(peer_x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = 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) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on peer "{peer}"') + + config.delete(peer_x509_base + ['crl-file']) + + if config.exists(peer_x509_base + ['key', 'file']): + key_file = config.return_value(peer_x509_base + ['key', 'file']) + key_passphrase = None + + if config.exists(peer_x509_base + ['key', 'password']): + key_passphrase = config.return_value(peer_x509_base + ['key', 'password']) + + 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=key_passphrase, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=key_passphrase) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if key_passphrase: + config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) + config.set(peer_x509_base + ['private-key-passphrase'], value=key_passphrase) + else: + print(f'Failed to migrate private key on peer "{peer}"') + + config.delete(peer_x509_base + ['key']) + +if changes_made: + 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/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8 new file mode 100755 index 000000000..5d48b2875 --- /dev/null +++ b/src/migration-scripts/ipsec/7-to-8 @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 rsa keys into PKI configuration + +import base64 +import os +import struct + +from cryptography.hazmat.primitives.asymmetric import rsa + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.pki import load_public_key +from vyos.pki import load_private_key +from vyos.pki import encode_public_key +from vyos.pki import encode_private_key + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +pki_base = ['pki'] +ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] +rsa_keys_base = ['vpn', 'rsa-keys'] + +config = ConfigTree(config_file) + +LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] + +def migrate_from_vyatta_key(data): + data = base64.b64decode(data[2:]) + length = struct.unpack('B', data[:1])[0] + e = int.from_bytes(data[1:1+length], 'big') + n = int.from_bytes(data[1+length:], 'big') + public_numbers = rsa.RSAPublicNumbers(e, n) + return public_numbers.public_key() + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +local_key_name = 'localhost' + +if config.exists(rsa_keys_base): + if not config.exists(pki_base + ['key-pair']): + config.set(pki_base + ['key-pair']) + config.set_tag(pki_base + ['key-pair']) + + if config.exists(rsa_keys_base + ['local-key', 'file']): + local_file = config.return_value(rsa_keys_base + ['local-key', 'file']) + local_path = None + local_key = None + + for path in LOCAL_KEY_PATHS: + full_path = os.path.join(path, local_file) + if os.path.exists(full_path): + local_path = full_path + break + + if local_path: + with open(local_path, 'r') as f: + local_key_data = f.read() + local_key = load_private_key(local_key_data, wrap_tags=False) + + if local_key: + local_key_pem = encode_private_key(local_key) + config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem)) + else: + print('Failed to migrate local RSA key') + + if config.exists(rsa_keys_base + ['rsa-key-name']): + for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']): + if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']): + continue + + vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']) + public_key = migrate_from_vyatta_key(vyatta_key) + + if public_key: + public_key_pem = encode_public_key(public_key) + config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem)) + else: + print(f'Failed to migrate rsa-key "{rsa_name}"') + + config.delete(rsa_keys_base) + +if config.exists(ipsec_site_base): + for peer in config.list_nodes(ipsec_site_base): + mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode']) + + if mode != 'rsa': + continue + + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name) + + remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name) + config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-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/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4 new file mode 100755 index 000000000..18eabadec --- /dev/null +++ b/src/migration-scripts/l2tp/3-to-4 @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# - remove primary/secondary identifier from nameserver +# - TODO: remove radius server req-limit + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.util import run + +if (len(argv) < 1): + 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', 'ipsec-settings'] +pki_base = ['pki'] + +if not config.exists(base): + exit(0) + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +if not config.exists(base + ['authentication', 'x509']): + exit(0) + +x509_base = base + ['authentication', 'x509'] +pki_name = 'l2tp_remote_access' + +if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + +if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + +if config.exists(x509_base + ['ca-cert-file']): + 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 l2tp remote-access config') + + config.delete(x509_base + ['ca-cert-file']) + +if config.exists(x509_base + ['crl-file']): + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = 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_certificate(crl_data, wrap_tags=False) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on l2tp remote-access config') + + config.delete(x509_base + ['crl-file']) + +if config.exists(x509_base + ['server-cert-file']): + cert_file = config.return_value(x509_base + ['server-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 l2tp remote-access config') + + config.delete(x509_base + ['server-cert-file']) + +if config.exists(x509_base + ['server-key-file']): + key_file = config.return_value(x509_base + ['server-key-file']) + key_passphrase = None + + if config.exists(x509_base + ['server-key-password']): + key_passphrase = config.return_value(x509_base + ['server-key-password']) + + 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=key_passphrase, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=key_passphrase) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if key_passphrase: + config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) + config.set(x509_base + ['private-key-passphrase'], value=key_passphrase) + else: + print(f'Failed to migrate private key on l2tp remote-access config') + + config.delete(x509_base + ['server-key-file']) + if config.exists(x509_base + ['server-key-password']): + config.delete(x509_base + ['server-key-password']) + +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/openconnect/0-to-1 b/src/migration-scripts/openconnect/0-to-1 new file mode 100755 index 000000000..83cd09143 --- /dev/null +++ b/src/migration-scripts/openconnect/0-to-1 @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# - Update SSL to use PKI configuration + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.util import run + +if (len(argv) < 1): + 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', 'openconnect'] +pki_base = ['pki'] + +if not config.exists(base): + exit(0) + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +if not config.exists(base + ['ssl']): + exit(0) + +x509_base = base + ['ssl'] +pki_name = 'openconnect' + +if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + +if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + +if config.exists(x509_base + ['ca-cert-file']): + 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 openconnect config') + + config.delete(x509_base + ['ca-cert-file']) + +if config.exists(x509_base + ['cert-file']): + 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 openconnect config') + + 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 openconnect config') + + 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)) + exit(1) diff --git a/src/migration-scripts/policy/0-to-1 b/src/migration-scripts/policy/0-to-1 new file mode 100755 index 000000000..7134920ad --- /dev/null +++ b/src/migration-scripts/policy/0-to-1 @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# T3631: route-map: migrate "set extcommunity-rt" and "set extcommunity-soo" +# to "set extcommunity rt|soo" to match FRR syntax + + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['policy', 'route-map'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + + +for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + + if config.exists(base_rule + ['set', 'extcommunity-rt']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-rt']) + config.delete(base_rule + ['set', 'extcommunity-rt']) + config.set(base_rule + ['set', 'extcommunity', 'rt'], value=tmp) + + + if config.exists(base_rule + ['set', 'extcommunity-soo']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-soo']) + config.delete(base_rule + ['set', 'extcommunity-soo']) + config.set(base_rule + ['set', 'extcommunity', 'soo'], value=tmp) + +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/quagga/7-to-8 b/src/migration-scripts/quagga/7-to-8 index 9c277a6f1..15c44924f 100755 --- a/src/migration-scripts/quagga/7-to-8 +++ b/src/migration-scripts/quagga/7-to-8 @@ -14,61 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# - T2450: drop interface-route and interface-route6 from "protocols static" +# - T3391: Migrate "maximum-paths" setting from "protocols bgp asn maximum-paths" +# under the IPv4 address-family tree. Reason is we currently have no way in +# configuring this for IPv6 address-family. This mimics the FRR configuration. from sys import argv from sys import exit - from vyos.configtree import ConfigTree -def migrate_interface_route(config, base, path, route_route6): - """ Generic migration function which can be called on every instance of - interface-route, beeing it ipv4, ipv6 or nested under the "static table" nodes. - - What we do? - - Drop 'interface-route' or 'interface-route6' and migrate the route unter the - 'route' or 'route6' tag node. - """ - if config.exists(base + path): - for route in config.list_nodes(base + path): - interface = config.list_nodes(base + path + [route, 'next-hop-interface']) - - tmp = base + path + [route, 'next-hop-interface'] - for interface in config.list_nodes(tmp): - new_base = base + [route_route6, route, 'interface'] - config.set(new_base) - config.set_tag(base + [route_route6]) - config.set_tag(new_base) - config.copy(tmp + [interface], new_base + [interface]) - - config.delete(base + path) - -def migrate_route(config, base, path, route_route6): - """ Generic migration function which can be called on every instance of - route, beeing it ipv4, ipv6 or even nested under the static table nodes. - - What we do? - - for consistency reasons rename next-hop-interface to interface - - for consistency reasons rename next-hop-vrf to vrf - """ - if config.exists(base + path): - for route in config.list_nodes(base + path): - next_hop = base + path + [route, 'next-hop'] - if config.exists(next_hop): - for gateway in config.list_nodes(next_hop): - # IPv4 routes calls it next-hop-interface, rename this to - # interface instead so it's consitent with IPv6 - interface_path = next_hop + [gateway, 'next-hop-interface'] - if config.exists(interface_path): - config.rename(interface_path, 'interface') - - # When VRFs got introduced, I (c-po) named it next-hop-vrf, - # we can also call it vrf which is simply shorter. - vrf_path = next_hop + [gateway, 'next-hop-vrf'] - if config.exists(vrf_path): - config.rename(vrf_path, 'vrf') - - if (len(argv) < 2): print("Must specify file name!") exit(1) @@ -78,41 +31,27 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['protocols', 'static'] - +base = ['protocols', 'bgp'] config = ConfigTree(config_file) + if not config.exists(base): # Nothing to do exit(0) -# Migrate interface-route into route -migrate_interface_route(config, base, ['interface-route'], 'route') - -# Migrate interface-route6 into route6 -migrate_interface_route(config, base, ['interface-route6'], 'route6') - -# Cleanup nodes inside route -migrate_route(config, base, ['route'], 'route') - -# Cleanup nodes inside route6 -migrate_route(config, base, ['route6'], 'route6') - -# -# PBR table cleanup -table_path = base + ['table'] -if config.exists(table_path): - for table in config.list_nodes(table_path): - # Migrate interface-route into route - migrate_interface_route(config, table_path + [table], ['interface-route'], 'route') - - # Migrate interface-route6 into route6 - migrate_interface_route(config, table_path + [table], ['interface-route6'], 'route6') - - # Cleanup nodes inside route - migrate_route(config, table_path + [table], ['route'], 'route') - - # Cleanup nodes inside route6 - migrate_route(config, table_path + [table], ['route6'], 'route6') +# Check if BGP is actually configured and obtain the ASN +asn_list = config.list_nodes(base) +if asn_list: + # There's always just one BGP node, if any + bgp_base = base + [asn_list[0]] + + maximum_paths = bgp_base + ['maximum-paths'] + if config.exists(maximum_paths): + for bgp_type in ['ebgp', 'ibgp']: + if config.exists(maximum_paths + [bgp_type]): + new_base = bgp_base + ['address-family', 'ipv4-unicast', 'maximum-paths'] + config.set(new_base) + config.copy(maximum_paths + [bgp_type], new_base + [bgp_type]) + config.delete(maximum_paths) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/quagga/8-to-9 b/src/migration-scripts/quagga/8-to-9 index 15c44924f..38507bd3d 100755 --- a/src/migration-scripts/quagga/8-to-9 +++ b/src/migration-scripts/quagga/8-to-9 @@ -14,14 +14,76 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# - T3391: Migrate "maximum-paths" setting from "protocols bgp asn maximum-paths" -# under the IPv4 address-family tree. Reason is we currently have no way in -# configuring this for IPv6 address-family. This mimics the FRR configuration. +# - T2450: drop interface-route and interface-route6 from "protocols static" from sys import argv from sys import exit + from vyos.configtree import ConfigTree +def migrate_interface_route(config, base, path, route_route6): + """ Generic migration function which can be called on every instance of + interface-route, beeing it ipv4, ipv6 or nested under the "static table" nodes. + + What we do? + - Drop 'interface-route' or 'interface-route6' and migrate the route unter the + 'route' or 'route6' tag node. + """ + if config.exists(base + path): + for route in config.list_nodes(base + path): + interface = config.list_nodes(base + path + [route, 'next-hop-interface']) + + tmp = base + path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = base + [route_route6, route, 'interface'] + config.set(new_base) + config.set_tag(base + [route_route6]) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(base + path) + +def migrate_route(config, base, path, route_route6): + """ Generic migration function which can be called on every instance of + route, beeing it ipv4, ipv6 or even nested under the static table nodes. + + What we do? + - for consistency reasons rename next-hop-interface to interface + - for consistency reasons rename next-hop-vrf to vrf + """ + if config.exists(base + path): + for route in config.list_nodes(base + path): + next_hop = base + path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + # IPv4 routes calls it next-hop-interface, rename this to + # interface instead so it's consitent with IPv6 + interface_path = next_hop + [gateway, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + # When VRFs got introduced, I (c-po) named it next-hop-vrf, + # we can also call it vrf which is simply shorter. + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = base + path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + # IPv4 routes calls it next-hop-interface, rename this to + # interface instead so it's consitent with IPv6 + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + # When VRFs got introduced, I (c-po) named it next-hop-vrf, + # we can also call it vrf which is simply shorter. + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + if (len(argv) < 2): print("Must specify file name!") exit(1) @@ -31,27 +93,41 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['protocols', 'bgp'] -config = ConfigTree(config_file) +base = ['protocols', 'static'] +config = ConfigTree(config_file) if not config.exists(base): # Nothing to do exit(0) -# Check if BGP is actually configured and obtain the ASN -asn_list = config.list_nodes(base) -if asn_list: - # There's always just one BGP node, if any - bgp_base = base + [asn_list[0]] - - maximum_paths = bgp_base + ['maximum-paths'] - if config.exists(maximum_paths): - for bgp_type in ['ebgp', 'ibgp']: - if config.exists(maximum_paths + [bgp_type]): - new_base = bgp_base + ['address-family', 'ipv4-unicast', 'maximum-paths'] - config.set(new_base) - config.copy(maximum_paths + [bgp_type], new_base + [bgp_type]) - config.delete(maximum_paths) +# Migrate interface-route into route +migrate_interface_route(config, base, ['interface-route'], 'route') + +# Migrate interface-route6 into route6 +migrate_interface_route(config, base, ['interface-route6'], 'route6') + +# Cleanup nodes inside route +migrate_route(config, base, ['route'], 'route') + +# Cleanup nodes inside route6 +migrate_route(config, base, ['route6'], 'route6') + +# +# PBR table cleanup +table_path = base + ['table'] +if config.exists(table_path): + for table in config.list_nodes(table_path): + # Migrate interface-route into route + migrate_interface_route(config, table_path + [table], ['interface-route'], 'route') + + # Migrate interface-route6 into route6 + migrate_interface_route(config, table_path + [table], ['interface-route6'], 'route6') + + # Cleanup nodes inside route + migrate_route(config, table_path + [table], ['route'], 'route') + + # Cleanup nodes inside route6 + migrate_route(config, table_path + [table], ['route6'], 'route6') try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/sstp/3-to-4 b/src/migration-scripts/sstp/3-to-4 new file mode 100755 index 000000000..0568f043f --- /dev/null +++ b/src/migration-scripts/sstp/3-to-4 @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# - Update SSL to use PKI configuration + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_private_key +from vyos.util import run + +if (len(argv) < 1): + 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'] +pki_base = ['pki'] + +if not config.exists(base): + exit(0) + +AUTH_DIR = '/config/auth' + +def wrapped_pem_to_config_value(pem): + return "".join(pem.strip().split("\n")[1:-1]) + +if not config.exists(base + ['ssl']): + exit(0) + +x509_base = base + ['ssl'] +pki_name = 'sstp' + +if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + +if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + +if config.exists(x509_base + ['ca-cert-file']): + 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 sstp config') + + config.delete(x509_base + ['ca-cert-file']) + +if config.exists(x509_base + ['cert-file']): + 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 sstp config') + + 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 sstp config') + + 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)) + exit(1) diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 new file mode 100755 index 000000000..ad41be646 --- /dev/null +++ b/src/migration-scripts/system/20-to-21 @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# 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 +# 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 exit, argv +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['system', 'sysctl'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +for all_custom in ['all', 'custom']: + if config.exists(base + [all_custom]): + for key in config.list_nodes(base + [all_custom]): + tmp = config.return_value(base + [all_custom, key, 'value']) + config.set(base + ['parameter', key, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [all_custom]) + +for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: + if config.exists(base + [ipv4_param]): + tmp = config.return_value(base + [ipv4_param]) + config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [ipv4_param]) + +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/vrf/0-to-1 b/src/migration-scripts/vrf/0-to-1 index 29b2fab74..2b41ef3c7 100755 --- a/src/migration-scripts/vrf/0-to-1 +++ b/src/migration-scripts/vrf/0-to-1 @@ -91,6 +91,16 @@ for vrf in config.list_nodes(base): if config.exists(vrf_path): config.rename(vrf_path, 'vrf') + next_hop = route_path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + # # Cleanup nodes inside route6 # diff --git a/src/migration-scripts/vrf/1-to-2 b/src/migration-scripts/vrf/1-to-2 index 20128e957..9bc704e02 100755 --- a/src/migration-scripts/vrf/1-to-2 +++ b/src/migration-scripts/vrf/1-to-2 @@ -49,6 +49,7 @@ for vrf in config.list_nodes(base): new_static_base = vrf_base + [vrf, 'protocols'] config.set(new_static_base) config.copy(static_base, new_static_base + ['static']) + config.set_tag(new_static_base + ['static', 'route']) # Now delete the old configuration config.delete(base) diff --git a/src/migration-scripts/vrf/2-to-3 b/src/migration-scripts/vrf/2-to-3 new file mode 100755 index 000000000..8e0f97141 --- /dev/null +++ b/src/migration-scripts/vrf/2-to-3 @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# +# 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 +# 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/>. + +# Since connection tracking zones are int16, VRFs tables maximum value must +# be limited to 65535 +# Also, interface names in nftables cannot start from numbers, +# so VRF name should not start from a number + +from sys import argv +from sys import exit +from random import randrange +from random import choice +from string import ascii_lowercase +from vyos.configtree import ConfigTree +import re + + +# Helper function to find all config items with a VRF name +def _search_vrfs(config_commands, vrf_name): + vrf_values = [] + # Regex to find path of config command with old VRF + regex_filter = re.compile(rf'^set (?P<cmd_path>[^\']+vrf) \'{vrf_name}\'$') + # Check each command for VRF value + for config_command in config_commands: + search_result = regex_filter.search(config_command) + if search_result: + # Append VRF command to a list + vrf_values.append(search_result.group('cmd_path').split()) + if vrf_values: + return vrf_values + else: + return None + + +# Helper function to find all config items with a table number +def _search_tables(config_commands, table_num): + table_items = {'table_tags': [], 'table_values': []} + # Regex to find values and nodes with a table number + regex_tags = re.compile(rf'^set (?P<cmd_path>[^\']+table {table_num}) ?.*$') + regex_values = re.compile( + rf'^set (?P<cmd_path>[^\']+table) \'{table_num}\'$') + for config_command in config_commands: + # Search for tag nodes + search_result = regex_tags.search(config_command) + if search_result: + # Append table node path to a tag nodes list + cmd_path = search_result.group('cmd_path').split() + if cmd_path not in table_items['table_tags']: + table_items['table_tags'].append(cmd_path) + # Search for value nodes + search_result = regex_values.search(config_command) + if search_result: + # Append table node path to a value nodes list + table_items['table_values'].append( + search_result.group('cmd_path').split()) + return table_items + + +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 = ['vrf', 'name'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# Get a list of all currently used VRFs and tables +vrfs_current = {} +for vrf in config.list_nodes(base): + vrfs_current[vrf] = int(config.return_value(base + [vrf, 'table'])) + +# Check VRF names and table numbers +name_regex = re.compile(r'^\d.*$') +for vrf_name, vrf_table in vrfs_current.items(): + # Check table number + if vrf_table > 65535: + # Find new unused table number + vrfs_current[vrf_name] = None + while not vrfs_current[vrf_name]: + table_random = randrange(100, 65535) + if table_random not in vrfs_current.values(): + vrfs_current[vrf_name] = table_random + # Update number to a new one + config.set(['vrf', 'name', vrf_name, 'table'], + vrfs_current[vrf_name], + replace=True) + # Check config items with old table number and replace to new one + config_commands = config.to_commands().split('\n') + table_config_lines = _search_tables(config_commands, vrf_table) + # Rename table nodes + if table_config_lines.get('table_tags'): + for table_config_path in table_config_lines.get('table_tags'): + config.rename(table_config_path, f'{vrfs_current[vrf_name]}') + # Replace table values + if table_config_lines.get('table_values'): + for table_config_path in table_config_lines.get('table_values'): + config.set(table_config_path, + f'{vrfs_current[vrf_name]}', + replace=True) + + # Check VRF name + if name_regex.match(vrf_name): + vrf_name_new = None + while not vrf_name_new: + vrf_name_rand = f'{choice(ascii_lowercase)}{vrf_name}'[:15] + if vrf_name_rand not in vrfs_current: + vrf_name_new = vrf_name_rand + # Update VRF name to a new one + config.rename(['vrf', 'name', vrf_name], vrf_name_new) + # Check config items with old VRF name and replace to new one + config_commands = config.to_commands().split('\n') + vrf_config_lines = _search_vrfs(config_commands, vrf_name) + # Rename VRF to a new name + if vrf_config_lines: + for vrf_value_path in vrf_config_lines: + config.set(vrf_value_path, vrf_name_new, replace=True) + +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) |