diff options
Diffstat (limited to 'src')
28 files changed, 598 insertions, 540 deletions
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 53bc37882..5101c1e79 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -17,14 +17,17 @@ import os from sys import exit -from copy import deepcopy from vyos.config import Config +from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client -from vyos import ConfigError -from vyos.util import call, chown +from vyos.util import call +from vyos.util import chown +from vyos.util import vyos_dict_search from vyos.template import render +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,136 +38,84 @@ pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.l pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' -default_config_data = { - 'allow_from': [], - 'cache_size': 10000, - 'export_hosts_file': 'yes', - 'listen_address': [], - 'name_servers': [], - 'negative_ttl': 3600, - 'system': False, - 'domains': {}, - 'dnssec': 'process-no-validate', - 'dhcp_interfaces': [] -} - hostsd_tag = 'static' -def get_config(conf): - dns = deepcopy(default_config_data) +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'dns', 'forwarding'] - if not conf.exists(base): return None - conf.set_level(base) - - if conf.exists(['allow-from']): - dns['allow_from'] = conf.return_values(['allow-from']) - - if conf.exists(['cache-size']): - cache_size = conf.return_value(['cache-size']) - dns['cache_size'] = cache_size - - if conf.exists('negative-ttl'): - negative_ttl = conf.return_value(['negative-ttl']) - dns['negative_ttl'] = negative_ttl - - if conf.exists(['domain']): - for domain in conf.list_nodes(['domain']): - conf.set_level(base + ['domain', domain]) - entry = { - 'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])), - 'addNTA': conf.exists(['addnta']), - 'recursion-desired': conf.exists(['recursion-desired']) - } - dns['domains'][domain] = entry - - conf.set_level(base) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + dns = dict_merge(default_values, dns) - if conf.exists(['ignore-hosts-file']): - dns['export_hosts_file'] = "no" + # some additions to the default dictionary + if 'system' in dns: + base_nameservers = ['system', 'name-server'] + if conf.exists(base_nameservers): + dns.update({'system_name_server': conf.return_values(base_nameservers)}) - if conf.exists(['name-server']): - dns['name_servers'] = bracketize_ipv6_addrs( - conf.return_values(['name-server'])) - - if conf.exists(['system']): - dns['system'] = True - - if conf.exists(['listen-address']): - dns['listen_address'] = conf.return_values(['listen-address']) - - if conf.exists(['dnssec']): - dns['dnssec'] = conf.return_value(['dnssec']) - - if conf.exists(['dhcp']): - dns['dhcp_interfaces'] = conf.return_values(['dhcp']) + base_nameservers_dhcp = ['system', 'name-servers-dhcp'] + if conf.exists(base_nameservers_dhcp): + dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) return dns -def bracketize_ipv6_addrs(addrs): - """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched.""" - return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs] - -def verify(conf, dns): +def verify(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None - if not dns['listen_address']: - raise ConfigError( - "Error: DNS forwarding requires a listen-address") - - if not dns['allow_from']: - raise ConfigError( - "Error: DNS forwarding requires an allow-from network") - - if dns['domains']: - for domain in dns['domains']: - if not dns['domains'][domain]['nslist']: - raise ConfigError(( - f'Error: No server configured for domain {domain}')) - - no_system_nameservers = False - conf.set_level([]) - if dns['system'] and not ( - conf.exists(['system', 'name-server']) or - conf.exists(['system', 'name-servers-dhcp']) ): - no_system_nameservers = True - print(("DNS forwarding warning: No 'system name-server' or " - "'system name-servers-dhcp' set\n")) - - if (no_system_nameservers or not dns['system']) and not ( - dns['name_servers'] or dns['dhcp_interfaces']): - print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' " - "nameservers set. Forwarding will operate as a recursor.\n")) + if 'listen_address' not in dns: + raise ConfigError('DNS forwarding requires a listen-address') + + if 'allow_from' not in dns: + raise ConfigError('DNS forwarding requires an allow-from network') + + # we can not use vyos_dict_search() when testing for domain servers + # as a domain will contains dot's which is out dictionary delimiter. + if 'domain' in dns: + for domain in dns['domain']: + if 'server' not in dns['domain'][domain]: + raise ConfigError(f'No server configured for domain {domain}!') + + if 'system' in dns: + if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): + print("Warning: No 'system name-server' or 'system " \ + "name-servers-dhcp' configured") return None def generate(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', - dns, user=pdns_rec_user, group=pdns_rec_group) + dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', - dns, user=pdns_rec_user, group=pdns_rec_group) + dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) - for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: - with open(f, 'a'): + for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(file, 'a'): pass - chown(f, user=pdns_rec_user, group=pdns_rec_group) + chown(file, user=pdns_rec_user, group=pdns_rec_group) return None def apply(dns): - if dns is None: + if not dns: # DNS forwarding is removed in the commit - call("systemctl stop pdns-recursor.service") + call('systemctl stop pdns-recursor.service') + if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) else: @@ -174,8 +125,8 @@ def apply(dns): # add static nameservers to hostsd so they can be joined with other # sources hc.delete_name_servers([hostsd_tag]) - if dns['name_servers']: - hc.add_name_servers({hostsd_tag: dns['name_servers']}) + if 'name_server' in dns: + hc.add_name_servers({hostsd_tag: dns['name_server']}) # delete all nameserver tags hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor()) @@ -184,32 +135,33 @@ def apply(dns): # our own tag (static) hc.add_name_server_tags_recursor([hostsd_tag]) - if dns['system']: + if 'system' in dns: hc.add_name_server_tags_recursor(['system']) else: hc.delete_name_server_tags_recursor(['system']) # add dhcp nameserver tags for configured interfaces - for intf in dns['dhcp_interfaces']: - hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ]) + if 'system_name_server_dhcp' in dns: + for interface in dns['system_name_server_dhcp']: + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) # hostsd will generate the forward-zones file # the list and keys() are required as get returns a dict, not list hc.delete_forward_zones(list(hc.get_forward_zones().keys())) - if dns['domains']: - hc.add_forward_zones(dns['domains']) + if 'domain' in dns: + hc.add_forward_zones(dns['domain']) # call hostsd to generate forward-zones and its lua-config-file hc.apply() ### finally (re)start pdns-recursor - call("systemctl restart pdns-recursor.service") + call('systemctl restart pdns-recursor.service') if __name__ == '__main__': try: - conf = Config() - c = get_config(conf) - verify(conf, c) + c = get_config() + verify(c) generate(c) apply(c) except ConfigError as e: diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a9679b47c..9763620ac 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -22,15 +22,18 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface +from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf from vyos.ifconfig import Section -from vyos.validate import is_member +from vyos.util import vyos_dict_search from vyos.validate import has_address_configured from vyos import ConfigError from vyos import airbag @@ -98,18 +101,21 @@ def get_config(config=None): # also present the interfaces to be removed from the bond as dictionary bond['member'].update({'interface_remove': tmp}) - if 'member' in bond and 'interface' in bond['member']: + if vyos_dict_search('member.interface', bond): for interface, interface_config in bond['member']['interface'].items(): - # Check if we are a member of another bond device + # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') - if tmp: - interface_config.update({'is_bridge_member' : tmp}) + if tmp: interface_config.update({'is_bridge_member' : tmp}) - # Check if we are a member of a bond device + # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') if tmp and tmp != bond['ifname']: interface_config.update({'is_bond_member' : tmp}) + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: interface_config.update({'is_source_interface' : tmp}) + # bond members must not have an assigned address tmp = has_address_configured(conf, interface) if tmp: interface_config.update({'has_address' : ''}) @@ -136,6 +142,7 @@ def verify(bond): raise ConfigError('Option primary - mode dependency failed, not' 'supported in mode {mode}!'.format(**bond)) + verify_mtu_ipv6(bond) verify_address(bond) verify_dhcpv6(bond) verify_vrf(bond) @@ -144,10 +151,9 @@ def verify(bond): verify_vlan_config(bond) bond_name = bond['ifname'] - if 'member' in bond: - member = bond.get('member') - for interface, interface_config in member.get('interface', {}).items(): - error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + if vyos_dict_search('member.interface', bond): + for interface, interface_config in bond['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bond, ' if interface == 'lo': raise ConfigError('Loopback interface "lo" can not be added to a bond') @@ -163,6 +169,10 @@ def verify(bond): tmp = interface_config['is_bond_member'] raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + if 'has_address' in interface_config: raise ConfigError(error_msg + 'it has an address assigned!') diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 47c8c05f9..485decb17 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -22,13 +22,16 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import node_changed +from vyos.configdict import is_member +from vyos.configdict import is_source_interface from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf -from vyos.validate import is_member, has_address_configured +from vyos.validate import has_address_configured from vyos.xml import defaults from vyos.util import cmd +from vyos.util import vyos_dict_search from vyos import ConfigError from vyos import airbag @@ -54,8 +57,8 @@ def get_config(config=None): else: bridge.update({'member': {'interface_remove': tmp }}) - if 'member' in bridge and 'interface' in bridge['member']: - # XXX TT2665 we need a copy of the dict keys for iteration, else we will get: + if vyos_dict_search('member.interface', bridge): + # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: # RuntimeError: dictionary changed size during iteration for interface in list(bridge['member']['interface']): for key in ['cost', 'priority']: @@ -69,20 +72,22 @@ def get_config(config=None): for interface, interface_config in bridge['member']['interface'].items(): interface_config.update(default_member_values) - # Check if we are a member of another bridge device + # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') if tmp and tmp != bridge['ifname']: interface_config.update({'is_bridge_member' : tmp}) - # Check if we are a member of a bond device + # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') - if tmp: - interface_config.update({'is_bond_member' : tmp}) + if tmp: interface_config.update({'is_bond_member' : tmp}) + + # Check if member interface is used as source-interface on another interface + tmp = is_source_interface(conf, interface) + if tmp: interface_config.update({'is_source_interface' : tmp}) # Bridge members must not have an assigned address tmp = has_address_configured(conf, interface) - if tmp: - interface_config.update({'has_address' : ''}) + if tmp: interface_config.update({'has_address' : ''}) return bridge @@ -93,11 +98,9 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) - if 'member' in bridge: - member = bridge.get('member') - bridge_name = bridge['ifname'] - for interface, interface_config in member.get('interface', {}).items(): - error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", ' + if vyos_dict_search('member.interface', bridge): + for interface, interface_config in bridge['member']['interface'].items(): + error_msg = f'Can not add interface "{interface}" to bridge, ' if interface == 'lo': raise ConfigError('Loopback interface "lo" can not be added to a bridge') @@ -113,6 +116,10 @@ def verify(bridge): tmp = interface_config['is_bond_member'] raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + if 'is_source_interface' in interface_config: + tmp = interface_config['is_source_interface'] + raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!') + if 'has_address' in interface_config: raise ConfigError(error_msg + 'it has an address assigned!') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index a8df64cce..1f622c003 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -20,11 +20,13 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_address -from vyos.configverify import verify_vrf +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_mtu +from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf from vyos.ifconfig import EthernetIf from vyos import ConfigError from vyos import airbag @@ -41,6 +43,7 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'ethernet'] ethernet = get_interface_dict(conf, base) + return ethernet def verify(ethernet): @@ -57,6 +60,8 @@ def verify(ethernet): if ethernet.get('speed', None) != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') + verify_mtu(ethernet) + verify_mtu_ipv6(ethernet) verify_dhcpv6(ethernet) verify_address(ethernet) verify_vrf(ethernet) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index cc2cf025a..979a5612e 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -17,12 +17,12 @@ import os from sys import exit -from copy import deepcopy from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_address +from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete from vyos.ifconfig import GeneveIf from vyos import ConfigError @@ -48,6 +48,7 @@ def verify(geneve): verify_bridge_delete(geneve) return None + verify_mtu_ipv6(geneve) verify_address(geneve) if 'remote' not in geneve: @@ -62,7 +63,6 @@ def verify(geneve): def generate(geneve): return None - def apply(geneve): # Check if GENEVE interface already exists if geneve['ifname'] in interfaces(): @@ -72,10 +72,11 @@ def apply(geneve): g.remove() if 'deleted' not in geneve: - # GENEVE interface needs to be created on-block - # instead of passing a ton of arguments, I just use a dict - # that is managed by vyos.ifconfig - conf = deepcopy(GeneveIf.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = GeneveIf.get_config() # Assign GENEVE instance configuration parameters to config dict conf['vni'] = geneve['vni'] diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 144cee5fe..1118143e4 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -17,7 +17,6 @@ import os from sys import exit -from copy import deepcopy from netifaces import interfaces from vyos.config import Config @@ -25,6 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod from vyos.validate import is_addr_assigned @@ -81,6 +81,7 @@ def verify(l2tpv3): raise ConfigError('L2TPv3 local-ip address ' '"{local_ip}" is not configured!'.format(**l2tpv3)) + verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) return None @@ -88,10 +89,11 @@ def generate(l2tpv3): return None def apply(l2tpv3): - # L2TPv3 interface needs to be created/deleted on-block, instead of - # passing a ton of arguments, I just use a dict that is managed by - # vyos.ifconfig - conf = deepcopy(L2TPv3If.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = L2TPv3If.get_config() # Check if L2TPv3 interface already exists if l2tpv3['ifname'] in interfaces(): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 2866ccc0a..0a20a121b 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -16,17 +16,18 @@ import os -from copy import deepcopy from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.ifconfig import MACsecIf +from vyos.ifconfig import Interface from vyos.template import render from vyos.util import call from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_source_interface from vyos import ConfigError from vyos import airbag @@ -47,6 +48,14 @@ def get_config(config=None): base = ['interfaces', 'macsec'] macsec = get_interface_dict(conf, base) + # MACsec is "special" the default MTU is 1460 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + # base MTU for MACsec is 1468 bytes, but we leave room for 802.1ad and + # 802.1q VLAN tags, thus the limit is 1460 bytes. + macsec['mtu'] = '1460' + # Check if interface has been removed if 'deleted' in macsec: source_interface = conf.return_effective_value( @@ -63,6 +72,7 @@ def verify(macsec): verify_source_interface(macsec) verify_vrf(macsec) + verify_mtu_ipv6(macsec) verify_address(macsec) if not (('security' in macsec) and @@ -80,6 +90,15 @@ def verify(macsec): raise ConfigError('Missing mandatory MACsec security ' 'keys as encryption is enabled!') + if 'source_interface' in macsec: + # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad + # and 802.1q) - we need to check the underlaying MTU if our configured + # MTU is at least 40 bytes less then the MTU of our physical interface. + lower_mtu = Interface(macsec['source_interface']).get_mtu() + if lower_mtu < (int(macsec['mtu']) + 40): + raise ConfigError('MACsec overhead does not fit into underlaying device MTU,\n' \ + f'{underlay_mtu} bytes is too small!') + return None @@ -102,12 +121,11 @@ def apply(macsec): os.unlink(wpa_suppl_conf.format(**macsec)) else: - # MACsec interfaces require a configuration when they are added using - # iproute2. This static method will provide the configuration - # dictionary used by this class. - - # XXX: subject of removal after completing T2653 - conf = deepcopy(MACsecIf.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = MACsecIf.get_config() conf['source_interface'] = macsec['source_interface'] conf['security_cipher'] = macsec['security']['cipher'] diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 958b305dd..518dbdc0e 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -26,10 +26,11 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import list_diff +from vyos.configdict import is_member from vyos.ifconfig import VTunIf from vyos.template import render from vyos.util import call, chown, chmod_600, chmod_755 -from vyos.validate import is_addr_assigned, is_member, is_ipv4 +from vyos.validate import is_addr_assigned, is_ipv4 from vyos import ConfigError from vyos import airbag @@ -256,7 +257,10 @@ def get_config(config=None): if conf.exists('encryption ncp-ciphers'): _ncp_ciphers = [] for enc in conf.return_values('encryption ncp-ciphers'): - if enc == 'des': + if enc == 'none': + _ncp_ciphers.append('none') + _ncp_ciphers.append('NONE') + elif enc == 'des': _ncp_ciphers.append('des-cbc') _ncp_ciphers.append('DES-CBC') elif enc == '3des': @@ -943,6 +947,9 @@ def verify(openvpn): else: print('Diffie-Hellman prime file is unspecified, assuming ECDH') + if openvpn['encryption'] == 'none': + print('Warning: "encryption none" was specified. NO encryption will be performed and tunnelled data WILL be transmitted in clear text over the network!') + # # Auth user/pass # diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 1b4b9e4ee..ee3b142c8 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -24,6 +24,7 @@ from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf +from vyos.configverify import verify_mtu_ipv6 from vyos.template import render from vyos.util import call from vyos import ConfigError @@ -57,6 +58,7 @@ def verify(pppoe): verify_source_interface(pppoe) verify_vrf(pppoe) + verify_mtu_ipv6(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 59edca1cc..ddbef56ac 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -14,9 +14,6 @@ # 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 copy import deepcopy from sys import exit from vyos.config import Config @@ -28,7 +25,6 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.ifconfig import MACVLANIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag @@ -47,19 +43,7 @@ def get_config(config=None): peth = get_interface_dict(conf, base) mode = leaf_node_changed(conf, ['mode']) - if mode: - peth.update({'mode_old' : mode}) - - # Check if source-interface is member of a bridge device - if 'source_interface' in peth: - bridge = is_member(conf, peth['source_interface'], 'bridge') - if bridge: - peth.update({'source_interface_is_bridge_member' : bridge}) - - # Check if we are a member of a bond device - bond = is_member(conf, peth['source_interface'], 'bonding') - if bond: - peth.update({'source_interface_is_bond_member' : bond}) + if mode: peth.update({'mode_old' : mode}) return peth @@ -72,16 +56,6 @@ def verify(peth): verify_vrf(peth) verify_address(peth) - if 'source_interface_is_bridge_member' in peth: - raise ConfigError( - 'Source interface "{source_interface}" can not be used as it is already a ' - 'member of bridge "{source_interface_is_bridge_member}"!'.format(**peth)) - - if 'source_interface_is_bond_member' in peth: - raise ConfigError( - 'Source interface "{source_interface}" can not be used as it is already a ' - 'member of bond "{source_interface_is_bond_member}"!'.format(**peth)) - # use common function to verify VLAN configuration verify_vlan_config(peth) return None @@ -101,9 +75,11 @@ def apply(peth): if 'mode_old' in peth: MACVLANIf(peth['ifname']).remove() - # MACVLAN interface needs to be created on-block instead of passing a ton - # of arguments, I just use a dict that is managed by vyos.ifconfig - conf = deepcopy(MACVLANIf.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = MACVLANIf.get_config() # Assign MACVLAN instance configuration parameters to config dict conf['source_interface'] = peth['source_interface'] diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 11d8d6edc..f1d885b15 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -22,10 +22,11 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.configdict import is_member from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf from vyos.ifconfig.afi import IP4, IP6 from vyos.configdict import list_diff -from vyos.validate import is_ipv4, is_ipv6, is_member +from vyos.validate import is_ipv4, is_ipv6 from vyos import ConfigError from vyos.dicts import FixedDict @@ -170,8 +171,8 @@ class ConfigurationState(object): """ >>> conf.get_values('addresses', 'address') will place a list of the new IP present in 'interface dummy dum1 address' - into the dictionnary entry "-add" (here 'addresses-add') using - Config.return_values and will add the the one which were removed in into + into the dictionnary entry "-add" (here 'addresses-add') using + Config.return_values and will add the the one which were removed in into the entry "-del" (here addresses-del') """ add_name = f'{name}-add' @@ -263,7 +264,7 @@ class ConfigurationState(object): d = d[lpath[-1]] # XXX: it should have provided me the content and not the key self._conf.set_level(l) - return d + return d def to_api(self): """ diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index bea3aa25b..002f40aef 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -17,13 +17,13 @@ import os from sys import exit -from copy import deepcopy from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_source_interface from vyos.ifconfig import VXLANIf, Interface from vyos import ConfigError @@ -73,11 +73,12 @@ def verify(vxlan): if 'source_interface' in vxlan: # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU # if our configured MTU is at least 50 bytes less - underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu()) - if underlay_mtu < (int(vxlan['mtu']) + 50): + lower_mtu = Interface(vxlan['source_interface']).get_mtu() + if lower_mtu < (int(vxlan['mtu']) + 50): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ f'MTU is to small ({underlay_mtu} bytes)') + verify_mtu_ipv6(vxlan) verify_address(vxlan) return None @@ -95,10 +96,11 @@ def apply(vxlan): v.remove() if 'deleted' not in vxlan: - # VXLAN interface needs to be created on-block - # instead of passing a ton of arguments, I just use a dict - # that is managed by vyos.ifconfig - conf = deepcopy(VXLANIf.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = VXLANIf.get_config() # Assign VXLAN instance configuration parameters to config dict for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']: diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index e7c22da1a..d5800264f 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -27,6 +27,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod from vyos import ConfigError @@ -71,6 +72,7 @@ def verify(wireguard): verify_bridge_delete(wireguard) return None + verify_mtu_ipv6(wireguard) verify_address(wireguard) verify_vrf(wireguard) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index c6c843e7b..f8520aecf 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -18,7 +18,6 @@ import os from sys import exit from re import findall -from copy import deepcopy from netaddr import EUI, mac_unix_expanded from vyos.config import Config @@ -233,13 +232,15 @@ def apply(wifi): if 'deleted' in wifi: WiFiIf(interface).remove() else: - # WiFi interface needs to be created on-block (e.g. mode or physical - # interface) instead of passing a ton of arguments, I just use a dict - # that is managed by vyos.ifconfig - conf = deepcopy(WiFiIf.get_config()) + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = WiFiIf.get_config() # Assign WiFi instance configuration parameters to config dict conf['phy'] = wifi['physical_device'] + conf['wds'] = 'on' if 'wds' in wifi else 'off' # Finally create the new interface w = WiFiIf(interface, **conf) diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index e9806ef47..117bf0274 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -22,7 +22,7 @@ from vyos.config import Config from vyos.configverify import verify_vrf from vyos.snmpv3_hashgen import plaintext_to_md5, plaintext_to_sha1, random from vyos.template import render -from vyos.util import call +from vyos.util import call, chmod_755 from vyos.validate import is_ipv4, is_addr_assigned from vyos.version import get_version_data from vyos import ConfigError, airbag diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 new file mode 100755 index 000000000..f866ca9a6 --- /dev/null +++ b/src/migration-scripts/interfaces/12-to-13 @@ -0,0 +1,71 @@ +#!/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/>. + +# - T2903: Change vif-s ethertype from numeric number to literal +# - 0x88a8 -> 802.1ad +# - 0x8100 -> 802.1q +# - T2905: Change WWAN "ondemand" node to "connect-on-demand" to have identical +# CLI nodes for both types of dialer interfaces + +from sys import exit, argv +from vyos.configtree import ConfigTree + +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) + + # + # T2903 + # + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + if not config.exists(['interfaces', type, interface, 'vif-s']): + continue + + for vif_s in config.list_nodes(['interfaces', type, interface, 'vif-s']): + base_path = ['interfaces', type, interface, 'vif-s', vif_s] + if config.exists(base_path + ['ethertype']): + protocol = '802.1ad' + tmp = config.return_value(base_path + ['ethertype']) + if tmp == '0x8100': + protocol = '802.1q' + + config.set(base_path + ['protocol'], value=protocol) + config.delete(base_path + ['ethertype']) + + # + # T2905 + # + wwan_base = ['interfaces', 'wirelessmodem'] + if config.exists(wwan_base): + for interface in config.list_nodes(wwan_base): + if config.exists(wwan_base + [interface, 'ondemand']): + config.rename(wwan_base + [interface, 'ondemand'], 'connect-on-demand') + + 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/10-to-11 b/src/migration-scripts/system/10-to-11 index 1a0233c7d..3c49f0d95 100755 --- a/src/migration-scripts/system/10-to-11 +++ b/src/migration-scripts/system/10-to-11 @@ -1,9 +1,7 @@ #!/usr/bin/env python3 -# Unclutter RADIUS configuration -# -# Move radius-server top level tag nodes to a regular node which allows us -# to specify additional general features for the RADIUS client. +# Operator accounts have been deprecated due to a security issue. Those accounts +# will be converted to regular admin accounts. import sys from vyos.configtree import ConfigTree @@ -18,54 +16,21 @@ with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) -cfg_base = ['system', 'login'] -if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])): - # Nothing to do +base_level = ['system', 'login', 'user'] + +if not config.exists(base_level): + # Nothing to do, which shouldn't happen anyway + # only if you wipe the config and reboot. sys.exit(0) else: - # - # Migrate "system login radius-source-address" to "system login radius" - # - if config.exists(cfg_base + ['radius-source-address']): - address = config.return_value(cfg_base + ['radius-source-address']) - # delete old configuration node - config.delete(cfg_base + ['radius-source-address']) - # write new configuration node - config.set(cfg_base + ['radius', 'source-address'], value=address) - - # - # Migrate "system login radius-server" tag node to new - # "system login radius server" tag node and also rename the "secret" node to "key" - # - for server in config.list_nodes(cfg_base + ['radius-server']): - base_server = cfg_base + ['radius-server', server] - # "key" node is mandatory - key = config.return_value(base_server + ['secret']) - config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) - - # "port" is optional - if config.exists(base_server + ['port']): - port = config.return_value(base_server + ['port']) - config.set(cfg_base + ['radius', 'server', server, 'port'], value=port) - - # "timeout is optional" - if config.exists(base_server + ['timeout']): - timeout = config.return_value(base_server + ['timeout']) - config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout) - - # format as tag node - config.set_tag(cfg_base + ['radius', 'server']) - - # delete old configuration node - config.delete(base_server) - - # delete top level tag node - if config.exists(cfg_base + ['radius-server']): - config.delete(cfg_base + ['radius-server']) + for user in config.list_nodes(base_level): + if config.exists(base_level + [user, 'level']): + if config.return_value(base_level + [user, 'level']) == 'operator': + config.set(base_level + [user, 'level'], value="admin", replace=True) try: - with open(file_name, 'w') as f: - f.write(config.to_string()) + open(file_name,'w').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/system/11-to-12 b/src/migration-scripts/system/11-to-12 index 0c92a0746..1a0233c7d 100755 --- a/src/migration-scripts/system/11-to-12 +++ b/src/migration-scripts/system/11-to-12 @@ -1,47 +1,71 @@ #!/usr/bin/env python3 -# converts 'set system syslog host <address>:<port>' -# to 'set system syslog host <address> port <port>' +# Unclutter RADIUS configuration +# +# Move radius-server top level tag nodes to a regular node which allows us +# to specify additional general features for the RADIUS client. import sys -import re - from vyos.configtree import ConfigTree if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(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_file = f.read() config = ConfigTree(config_file) -cbase = ['system', 'syslog', 'host'] - -if not config.exists(cbase): +cfg_base = ['system', 'login'] +if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])): + # Nothing to do sys.exit(0) +else: + # + # Migrate "system login radius-source-address" to "system login radius" + # + if config.exists(cfg_base + ['radius-source-address']): + address = config.return_value(cfg_base + ['radius-source-address']) + # delete old configuration node + config.delete(cfg_base + ['radius-source-address']) + # write new configuration node + config.set(cfg_base + ['radius', 'source-address'], value=address) + + # + # Migrate "system login radius-server" tag node to new + # "system login radius server" tag node and also rename the "secret" node to "key" + # + for server in config.list_nodes(cfg_base + ['radius-server']): + base_server = cfg_base + ['radius-server', server] + # "key" node is mandatory + key = config.return_value(base_server + ['secret']) + config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) + + # "port" is optional + if config.exists(base_server + ['port']): + port = config.return_value(base_server + ['port']) + config.set(cfg_base + ['radius', 'server', server, 'port'], value=port) + + # "timeout is optional" + if config.exists(base_server + ['timeout']): + timeout = config.return_value(base_server + ['timeout']) + config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout) + + # format as tag node + config.set_tag(cfg_base + ['radius', 'server']) + + # delete old configuration node + config.delete(base_server) -for host in config.list_nodes(cbase): - if re.search(':[0-9]{1,5}$',host): - h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0) - p = re.sub(':', '', re.search(':[0-9]+$', host).group(0)) - config.set(cbase + [h]) - config.set(cbase + [h, 'port'], value=p) - for fac in config.list_nodes(cbase + [host, 'facility']): - config.set(cbase + [h, 'facility', fac]) - config.set_tag(cbase + [h, 'facility']) - if config.exists(cbase + [host, 'facility', fac, 'protocol']): - proto = config.return_value(cbase + [host, 'facility', fac, 'protocol']) - config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto) - if config.exists(cbase + [host, 'facility', fac, 'level']): - lvl = config.return_value(cbase + [host, 'facility', fac, 'level']) - config.set(cbase + [h, 'facility', fac, 'level'], value=lvl) - config.delete(cbase + [host]) + # delete top level tag node + if config.exists(cfg_base + ['radius-server']): + config.delete(cfg_base + ['radius-server']) try: - open(file_name,'w').write(config.to_string()) + 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/system/12-to-13 b/src/migration-scripts/system/12-to-13 index 5b068f4fc..36311a19d 100755 --- a/src/migration-scripts/system/12-to-13 +++ b/src/migration-scripts/system/12-to-13 @@ -1,70 +1,47 @@ #!/usr/bin/env python3 -# Fixup non existent time-zones. Some systems have time-zone set to: Los* -# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA -# assigned time zones. In the past they have been silently remapped. -# -# Time to clean it up! -# -# Migrate all configured timezones to real IANA assigned timezones! +# converts 'set system syslog host <address>:<port>' +# to 'set system syslog host <address> port <port>' -import re import sys +import re from vyos.configtree import ConfigTree -from vyos.util import cmd - if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(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_file = f.read() config = ConfigTree(config_file) -tz_base = ['system', 'time-zone'] -if not config.exists(tz_base): - # Nothing to do - sys.exit(0) -else: - tz = config.return_value(tz_base) +cbase = ['system', 'syslog', 'host'] - # retrieve all valid timezones - try: - tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::') - except OSError: - tz_datas = '' - tz_data = tz_datas.split('\n') - - if re.match(r'[Ll][Oo][Ss].+', tz): - tz = 'America/Los_Angeles' - elif re.match(r'[Dd][Ee][Nn].+', tz): - tz = 'America/Denver' - elif re.match(r'[Hh][Oo][Nn][Oo].+', tz): - tz = 'Pacific/Honolulu' - elif re.match(r'[Nn][Ee][Ww].+', tz): - tz = 'America/New_York' - elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz): - tz = 'America/Chicago' - elif re.match(r'[Aa][Nn][Cc].+', tz): - tz = 'America/Anchorage' - elif re.match(r'[Pp][Hh][Oo].+', tz): - tz = 'America/Phoenix' - elif re.match(r'GMT(.+)?', tz): - tz = 'Etc/' + tz - elif tz not in tz_data: - # assign default UTC timezone - tz = 'UTC' - - # replace timezone data is required - config.set(tz_base, value=tz) +if not config.exists(cbase): + sys.exit(0) - 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) +for host in config.list_nodes(cbase): + if re.search(':[0-9]{1,5}$',host): + h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0) + p = re.sub(':', '', re.search(':[0-9]+$', host).group(0)) + config.set(cbase + [h]) + config.set(cbase + [h, 'port'], value=p) + for fac in config.list_nodes(cbase + [host, 'facility']): + config.set(cbase + [h, 'facility', fac]) + config.set_tag(cbase + [h, 'facility']) + if config.exists(cbase + [host, 'facility', fac, 'protocol']): + proto = config.return_value(cbase + [host, 'facility', fac, 'protocol']) + config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto) + if config.exists(cbase + [host, 'facility', fac, 'level']): + lvl = config.return_value(cbase + [host, 'facility', fac, 'level']) + config.set(cbase + [h, 'facility', fac, 'level'], value=lvl) + config.delete(cbase + [host]) + +try: + open(file_name,'w').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/system/13-to-14 b/src/migration-scripts/system/13-to-14 index c055dad1f..5b068f4fc 100755 --- a/src/migration-scripts/system/13-to-14 +++ b/src/migration-scripts/system/13-to-14 @@ -1,15 +1,19 @@ #!/usr/bin/env python3 + +# Fixup non existent time-zones. Some systems have time-zone set to: Los* +# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA +# assigned time zones. In the past they have been silently remapped. +# +# Time to clean it up! # -# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be -# blacklisted as it is required by e.g. WireGuard and thus will always be -# loaded. +# Migrate all configured timezones to real IANA assigned timezones! -import os +import re import sys -ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf' - from vyos.configtree import ConfigTree +from vyos.util import cmd + if (len(sys.argv) < 1): print("Must specify file name!") @@ -21,16 +25,42 @@ with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) -ip_base = ['system', 'ipv6'] -if not config.exists(ip_base): +tz_base = ['system', 'time-zone'] +if not config.exists(tz_base): # Nothing to do sys.exit(0) else: - # delete 'system ipv6 blacklist' node - if config.exists(ip_base + ['blacklist']): - config.delete(ip_base + ['blacklist']) - if os.path.isfile(ipv6_blacklist_file): - os.unlink(ipv6_blacklist_file) + tz = config.return_value(tz_base) + + # retrieve all valid timezones + try: + tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::') + except OSError: + tz_datas = '' + tz_data = tz_datas.split('\n') + + if re.match(r'[Ll][Oo][Ss].+', tz): + tz = 'America/Los_Angeles' + elif re.match(r'[Dd][Ee][Nn].+', tz): + tz = 'America/Denver' + elif re.match(r'[Hh][Oo][Nn][Oo].+', tz): + tz = 'Pacific/Honolulu' + elif re.match(r'[Nn][Ee][Ww].+', tz): + tz = 'America/New_York' + elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz): + tz = 'America/Chicago' + elif re.match(r'[Aa][Nn][Cc].+', tz): + tz = 'America/Anchorage' + elif re.match(r'[Pp][Hh][Oo].+', tz): + tz = 'America/Phoenix' + elif re.match(r'GMT(.+)?', tz): + tz = 'Etc/' + tz + elif tz not in tz_data: + # assign default UTC timezone + tz = 'UTC' + + # replace timezone data is required + config.set(tz_base, value=tz) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15 index 2491e3d0d..c055dad1f 100755 --- a/src/migration-scripts/system/14-to-15 +++ b/src/migration-scripts/system/14-to-15 @@ -1,10 +1,14 @@ #!/usr/bin/env python3 # -# Make 'system options reboot-on-panic' valueless +# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be +# blacklisted as it is required by e.g. WireGuard and thus will always be +# loaded. import os import sys +ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf' + from vyos.configtree import ConfigTree if (len(sys.argv) < 1): @@ -17,17 +21,16 @@ with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) -base = ['system', 'options'] -if not config.exists(base): +ip_base = ['system', 'ipv6'] +if not config.exists(ip_base): # Nothing to do sys.exit(0) else: - if config.exists(base + ['reboot-on-panic']): - reboot = config.return_value(base + ['reboot-on-panic']) - config.delete(base + ['reboot-on-panic']) - # create new valueless node if action was true - if reboot == "true": - config.set(base + ['reboot-on-panic']) + # delete 'system ipv6 blacklist' node + if config.exists(ip_base + ['blacklist']): + config.delete(ip_base + ['blacklist']) + if os.path.isfile(ipv6_blacklist_file): + os.unlink(ipv6_blacklist_file) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16 index e70893d55..2491e3d0d 100755 --- a/src/migration-scripts/system/15-to-16 +++ b/src/migration-scripts/system/15-to-16 @@ -1,24 +1,6 @@ #!/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/>. - -# * remove "system login user <user> group" node, Why should be add a user to a -# 3rd party group when the system is fully managed by CLI? -# * remove "system login user <user> level" node -# This is the only privilege level left and also the default, what is the -# sense in keeping this orphaned node? +# Make 'system options reboot-on-panic' valueless import os import sys @@ -35,17 +17,17 @@ with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) -base = ['system', 'login', 'user'] +base = ['system', 'options'] if not config.exists(base): # Nothing to do sys.exit(0) else: - for user in config.list_nodes(base): - if config.exists(base + [user, 'group']): - config.delete(base + [user, 'group']) - - if config.exists(base + [user, 'level']): - config.delete(base + [user, 'level']) + if config.exists(base + ['reboot-on-panic']): + reboot = config.return_value(base + ['reboot-on-panic']) + config.delete(base + ['reboot-on-panic']) + # create new valueless node if action was true + if reboot == "true": + config.set(base + ['reboot-on-panic']) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17 index 8f762c0e2..e70893d55 100755 --- a/src/migration-scripts/system/16-to-17 +++ b/src/migration-scripts/system/16-to-17 @@ -14,8 +14,11 @@ # 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 "system console netconsole" -# remove "system console device <device> modem" +# * remove "system login user <user> group" node, Why should be add a user to a +# 3rd party group when the system is fully managed by CLI? +# * remove "system login user <user> level" node +# This is the only privilege level left and also the default, what is the +# sense in keeping this orphaned node? import os import sys @@ -32,41 +35,17 @@ with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) -base = ['system', 'console'] +base = ['system', 'login', 'user'] if not config.exists(base): # Nothing to do sys.exit(0) else: - # remove "system console netconsole" (T2561) - if config.exists(base + ['netconsole']): - config.delete(base + ['netconsole']) + for user in config.list_nodes(base): + if config.exists(base + [user, 'group']): + config.delete(base + [user, 'group']) - if config.exists(base + ['device']): - for device in config.list_nodes(base + ['device']): - dev_path = base + ['device', device] - # remove "system console device <device> modem" (T2570) - if config.exists(dev_path + ['modem']): - config.delete(dev_path + ['modem']) - - # Only continue on USB based serial consoles - if not 'ttyUSB' in device: - continue - - # A serial console has been configured but it does no longer - # exist on the system - cleanup - if not os.path.exists(f'/dev/{device}'): - config.delete(dev_path) - continue - - # migrate from ttyUSB device to new device in /dev/serial/by-bus - for root, dirs, files in os.walk('/dev/serial/by-bus'): - for usb_device in files: - device_file = os.path.realpath(os.path.join(root, usb_device)) - # migrate to new USB device names (T2529) - if os.path.basename(device_file) == device: - config.copy(dev_path, base + ['device', usb_device]) - # Delete old USB node from config - config.delete(dev_path) + if config.exists(base + [user, 'level']): + config.delete(base + [user, 'level']) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18 index dd2abce00..8f762c0e2 100755 --- a/src/migration-scripts/system/17-to-18 +++ b/src/migration-scripts/system/17-to-18 @@ -13,93 +13,64 @@ # # 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 disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface> -# if disable-dhcp-nameservers is set, just remove it -# else retrieve all interface names that have configured dhcp(v6) address and -# add them to the new name-servers-dhcp node +# remove "system console netconsole" +# remove "system console device <device> modem" + +import os +import sys -from sys import argv, exit -from vyos.ifconfig import Interface from vyos.configtree import ConfigTree -if (len(argv) < 1): +if (len(sys.argv) < 1): print("Must specify file name!") - exit(1) + sys.exit(1) -file_name = argv[1] +file_name = sys.argv[1] with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) - -base = ['system'] +base = ['system', 'console'] if not config.exists(base): # Nothing to do - exit(0) - -if config.exists(base + ['disable-dhcp-nameservers']): - config.delete(base + ['disable-dhcp-nameservers']) + sys.exit(0) else: - dhcp_interfaces = [] - - # go through all interfaces searching for 'address dhcp(v6)?' - for sect in Interface.sections(): - sect_base = ['interfaces', sect] - - if not config.exists(sect_base): - continue - - for intf in config.list_nodes(sect_base): - intf_base = sect_base + [intf] - - # try without vlans - if config.exists(intf_base + ['address']): - for addr in config.return_values(intf_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(intf) - - # try vif - if config.exists(intf_base + ['vif']): - for vif in config.list_nodes(intf_base + ['vif']): - vif_base = intf_base + ['vif', vif] - if config.exists(vif_base + ['address']): - for addr in config.return_values(vif_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif}') - - # try vif-s - if config.exists(intf_base + ['vif-s']): - for vif_s in config.list_nodes(intf_base + ['vif-s']): - vif_s_base = intf_base + ['vif-s', vif_s] - if config.exists(vif_s_base + ['address']): - for addr in config.return_values(vif_s_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif_s}') - - # try vif-c - if config.exists(intf_base + ['vif-c', vif_c]): - for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]): - vif_c_base = vif_s_base + ['vif-c', vif_c] - if config.exists(vif_c_base + ['address']): - for addr in config.return_values(vif_c_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') - - # set new config nodes - for intf in dhcp_interfaces: - config.set(base + ['name-servers-dhcp'], value=intf, replace=False) - - # delete old node - config.delete(base + ['disable-dhcp-nameservers']) - -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) - -exit(0) + # remove "system console netconsole" (T2561) + if config.exists(base + ['netconsole']): + config.delete(base + ['netconsole']) + + if config.exists(base + ['device']): + for device in config.list_nodes(base + ['device']): + dev_path = base + ['device', device] + # remove "system console device <device> modem" (T2570) + if config.exists(dev_path + ['modem']): + config.delete(dev_path + ['modem']) + + # Only continue on USB based serial consoles + if not 'ttyUSB' in device: + continue + + # A serial console has been configured but it does no longer + # exist on the system - cleanup + if not os.path.exists(f'/dev/{device}'): + config.delete(dev_path) + continue + + # migrate from ttyUSB device to new device in /dev/serial/by-bus + for root, dirs, files in os.walk('/dev/serial/by-bus'): + for usb_device in files: + device_file = os.path.realpath(os.path.join(root, usb_device)) + # migrate to new USB device names (T2529) + if os.path.basename(device_file) == device: + config.copy(dev_path, base + ['device', usb_device]) + # Delete old USB node from config + config.delete(dev_path) + + 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/system/18-to-19 b/src/migration-scripts/system/18-to-19 new file mode 100755 index 000000000..dd2abce00 --- /dev/null +++ b/src/migration-scripts/system/18-to-19 @@ -0,0 +1,105 @@ +#!/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/>. +# + +# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface> +# if disable-dhcp-nameservers is set, just remove it +# else retrieve all interface names that have configured dhcp(v6) address and +# add them to the new name-servers-dhcp node + +from sys import argv, exit +from vyos.ifconfig import Interface +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() + +config = ConfigTree(config_file) + +base = ['system'] +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['disable-dhcp-nameservers']): + config.delete(base + ['disable-dhcp-nameservers']) +else: + dhcp_interfaces = [] + + # go through all interfaces searching for 'address dhcp(v6)?' + for sect in Interface.sections(): + sect_base = ['interfaces', sect] + + if not config.exists(sect_base): + continue + + for intf in config.list_nodes(sect_base): + intf_base = sect_base + [intf] + + # try without vlans + if config.exists(intf_base + ['address']): + for addr in config.return_values(intf_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(intf) + + # try vif + if config.exists(intf_base + ['vif']): + for vif in config.list_nodes(intf_base + ['vif']): + vif_base = intf_base + ['vif', vif] + if config.exists(vif_base + ['address']): + for addr in config.return_values(vif_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif}') + + # try vif-s + if config.exists(intf_base + ['vif-s']): + for vif_s in config.list_nodes(intf_base + ['vif-s']): + vif_s_base = intf_base + ['vif-s', vif_s] + if config.exists(vif_s_base + ['address']): + for addr in config.return_values(vif_s_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}') + + # try vif-c + if config.exists(intf_base + ['vif-c', vif_c]): + for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]): + vif_c_base = vif_s_base + ['vif-c', vif_c] + if config.exists(vif_c_base + ['address']): + for addr in config.return_values(vif_c_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') + + # set new config nodes + for intf in dhcp_interfaces: + config.set(base + ['name-servers-dhcp'], value=intf, replace=False) + + # delete old node + config.delete(base + ['disable-dhcp-nameservers']) + +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) + +exit(0) diff --git a/src/migration-scripts/system/9-to-10 b/src/migration-scripts/system/9-to-10 deleted file mode 100755 index 3c49f0d95..000000000 --- a/src/migration-scripts/system/9-to-10 +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -# Operator accounts have been deprecated due to a security issue. Those accounts -# will be converted to regular admin accounts. - -import sys -from vyos.configtree import ConfigTree - -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_level = ['system', 'login', 'user'] - -if not config.exists(base_level): - # Nothing to do, which shouldn't happen anyway - # only if you wipe the config and reboot. - sys.exit(0) -else: - for user in config.list_nodes(base_level): - if config.exists(base_level + [user, 'level']): - if config.return_value(base_level + [user, 'level']) == 'operator': - config.set(base_level + [user, 'level'], value="admin", replace=True) - - try: - open(file_name,'w').write(config.to_string()) - - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 0079f7e5c..59dbeda17 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -107,16 +107,17 @@ # ### forward_zones ## Additional zones added to pdns-recursor forward-zones-file. -## If recursion-desired is true, '+' will be prepended to the zone line. -## If addNTA is true, a NTA will be added via lua-config-file. +## If recursion_desired is true, '+' will be prepended to the zone line. +## If addnta is true, a NTA (Negative Trust Anchor) will be added via +## lua-config-file. # # { 'type': 'forward_zones', # 'op': 'add', # 'data': { # '<str zone>': { -# 'nslist': ['<str nameserver>', ...], -# 'addNTA': <bool>, -# 'recursion-desired': <bool> +# 'server': ['<str nameserver>', ...], +# 'addnta': <bool>, +# 'recursion_desired': <bool> # } # ... # } @@ -305,12 +306,12 @@ tag_regex_schema = op_type_schema.extend({ forward_zone_add_schema = op_type_schema.extend({ 'data': { str: { - 'nslist': [str], - 'addNTA': bool, - 'recursion-desired': bool + 'server': [str], + 'addnta': Any({}, None), + 'recursion_desired': Any({}, None), } } - }, required=True) + }, required=False) hosts_add_schema = op_type_schema.extend({ 'data': { @@ -586,7 +587,7 @@ if __name__ == '__main__': context = zmq.Context() socket = context.socket(zmq.REP) - + # Set the right permissions on the socket, then change it back o_mask = os.umask(0o007) socket.bind(SOCKET_PATH) diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client index 48ebc83f7..d4d38315a 100755 --- a/src/utils/vyos-hostsd-client +++ b/src/utils/vyos-hostsd-client @@ -99,9 +99,9 @@ try: raise ValueError("--nameservers is required for this operation") client.add_forward_zones( { args.add_forward_zone: { - 'nslist': args.nameservers, - 'addNTA': args.addnta, - 'recursion-desired': args.recursion_desired + 'server': args.nameservers, + 'addnta': args.addnta, + 'recursion_desired': args.recursion_desired } }) elif args.delete_forward_zones: |