diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/firewall.py | 70 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_geneve.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_l2tpv3.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_openvpn.py | 23 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_vti.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_vxlan.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_wireless.py | 75 | ||||
-rwxr-xr-x | src/conf_mode/service_snmp.py | 13 | ||||
-rwxr-xr-x | src/conf_mode/system_option.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 16 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 6 |
11 files changed, 163 insertions, 57 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index ec6b86ef2..352d5cbb1 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -128,7 +128,49 @@ def get_config(config=None): return firewall -def verify_rule(firewall, rule_conf, ipv6): +def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False): + targets_seen = [] + targets_pending = [jump_target] + + while targets_pending: + target = targets_pending.pop() + + if not ipv6: + if target not in dict_search_args(firewall, 'ipv4', 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + target_rules = dict_search_args(firewall, 'ipv4', 'name', target, 'rule') + else: + if target not in dict_search_args(firewall, 'ipv6', 'name'): + raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') + target_rules = dict_search_args(firewall, 'ipv6', 'name', target, 'rule') + + no_ipsec_in = root_chain in ('output', ) + + if target_rules: + for target_rule_conf in target_rules.values(): + # Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets: + if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \ + or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None): + if not ipv6: + raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall name {target} rules contain incompatible ipsec inbound matches') + else: + raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall ipv6 name {target} rules contain incompatible ipsec inbound matches') + # Make sure we're not looping back on ourselves somewhere: + if recursive and 'jump_target' in target_rule_conf: + child_target = target_rule_conf['jump_target'] + if child_target in targets_seen: + if not ipv6: + raise ConfigError(f'Loop detected in jump-targets, firewall name {target} refers to previously traversed name {child_target}') + else: + raise ConfigError(f'Loop detected in jump-targets, firewall ipv6 name {target} refers to previously traversed ipv6 name {child_target}') + targets_pending.append(child_target) + if len(targets_seen) == 7: + path_txt = ' -> '.join(targets_seen) + Warning(f'Deep nesting of jump targets has reached 8 levels deep, following the path {path_txt} -> {child_target}!') + + targets_seen.append(target) + +def verify_rule(firewall, chain_name, rule_conf, ipv6): if 'action' not in rule_conf: raise ConfigError('Rule action must be defined') @@ -139,12 +181,10 @@ def verify_rule(firewall, rule_conf, ipv6): if 'jump' not in rule_conf['action']: raise ConfigError('jump-target defined, but action jump needed and it is not defined') target = rule_conf['jump_target'] - if not ipv6: - if target not in dict_search_args(firewall, 'ipv4', 'name'): - raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + if chain_name != 'name': # This is a bit clumsy, but consolidates a chunk of code. + verify_jump_target(firewall, chain_name, target, ipv6, recursive=True) else: - if target not in dict_search_args(firewall, 'ipv6', 'name'): - raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') + verify_jump_target(firewall, chain_name, target, ipv6, recursive=False) if rule_conf['action'] == 'offload': if 'offload_target' not in rule_conf: @@ -185,8 +225,10 @@ def verify_rule(firewall, rule_conf, ipv6): raise ConfigError('Limit rate integer cannot be less than 1') if 'ipsec' in rule_conf: - if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']): - raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"') + if {'match_ipsec_in', 'match_none_in'} <= set(rule_conf['ipsec']): + raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') + if {'match_ipsec_out', 'match_none_out'} <= set(rule_conf['ipsec']): + raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') if 'recent' in rule_conf: if not {'count', 'time'} <= set(rule_conf['recent']): @@ -349,13 +391,11 @@ def verify(firewall): raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') if name_conf['default_jump_target'] == name_id: raise ConfigError(f'Loop detected on default-jump-target.') - ## Now need to check that default-jump-target exists (other firewall chain/name) - if target not in dict_search_args(firewall['ipv4'], 'name'): - raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + verify_jump_target(firewall, name, target, False, recursive=True) if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, rule_conf, False) + verify_rule(firewall, name, rule_conf, False) if 'ipv6' in firewall: for name in ['name','forward','input','output', 'prerouting']: @@ -369,13 +409,11 @@ def verify(firewall): raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') if name_conf['default_jump_target'] == name_id: raise ConfigError(f'Loop detected on default-jump-target.') - ## Now need to check that default-jump-target exists (other firewall chain/name) - if target not in dict_search_args(firewall['ipv6'], 'name'): - raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + verify_jump_target(firewall, name, target, True, recursive=True) if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, rule_conf, True) + verify_rule(firewall, name, rule_conf, True) #### ZONESSSS local_zone = False diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py index 769139e0f..007708d4a 100755 --- a/src/conf_mode/interfaces_geneve.py +++ b/src/conf_mode/interfaces_geneve.py @@ -24,6 +24,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf from vyos.ifconfig import GeneveIf from vyos.utils.network import interface_exists from vyos import ConfigError @@ -59,6 +60,7 @@ def verify(geneve): verify_mtu_ipv6(geneve) verify_address(geneve) + verify_vrf(geneve) verify_bond_bridge_member(geneve) verify_mirror_redirect(geneve) diff --git a/src/conf_mode/interfaces_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py index e25793543..b9f827bee 100755 --- a/src/conf_mode/interfaces_l2tpv3.py +++ b/src/conf_mode/interfaces_l2tpv3.py @@ -24,6 +24,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf from vyos.ifconfig import L2TPv3If from vyos.utils.kernel import check_kmod from vyos.utils.network import is_addr_assigned @@ -76,6 +77,7 @@ def verify(l2tpv3): verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) + verify_vrf(l2tpv3) verify_bond_bridge_member(l2tpv3) verify_mirror_redirect(l2tpv3) return None diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py index 0dc76b39a..a03bd5959 100755 --- a/src/conf_mode/interfaces_openvpn.py +++ b/src/conf_mode/interfaces_openvpn.py @@ -235,10 +235,6 @@ def verify_pki(openvpn): def verify(openvpn): if 'deleted' in openvpn: - # remove totp secrets file if totp is not configured - if os.path.isfile(otp_file.format(**openvpn)): - os.remove(otp_file.format(**openvpn)) - verify_bridge_delete(openvpn) return None @@ -326,8 +322,8 @@ def verify(openvpn): if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]: raise ConfigError('Must specify IPv4 "subnet-mask" for local-address') - if dict_search('encryption.ncp_ciphers', openvpn): - raise ConfigError('NCP ciphers can only be used in client or server mode') + if dict_search('encryption.data_ciphers', openvpn): + raise ConfigError('Cipher negotiation can only be used in client or server mode') else: # checks for client-server or site-to-site bridged @@ -524,7 +520,7 @@ def verify(openvpn): if dict_search('encryption.cipher', openvpn): raise ConfigError('"encryption cipher" option is deprecated for TLS mode. ' - 'Use "encryption ncp-ciphers" instead') + 'Use "encryption data-ciphers" instead') if dict_search('encryption.cipher', openvpn) == 'none': print('Warning: "encryption none" was specified!') @@ -635,9 +631,19 @@ def generate_pki_files(openvpn): def generate(openvpn): + if 'deleted' in openvpn: + # remove totp secrets file if totp is not configured + if os.path.isfile(otp_file.format(**openvpn)): + os.remove(otp_file.format(**openvpn)) + return None + + if 'disable' in openvpn: + return None + interface = openvpn['ifname'] directory = os.path.dirname(cfg_file.format(**openvpn)) openvpn['plugin_dir'] = '/usr/lib/openvpn' + # create base config directory on demand makedir(directory, user, group) # enforce proper permissions on /run/openvpn @@ -654,9 +660,6 @@ def generate(openvpn): if os.path.isdir(service_dir): rmtree(service_dir, ignore_errors=True) - if 'deleted' in openvpn or 'disable' in openvpn: - return None - # create client config directory on demand makedir(ccd_dir, user, group) diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py index e6a833df7..20629c6c1 100755 --- a/src/conf_mode/interfaces_vti.py +++ b/src/conf_mode/interfaces_vti.py @@ -19,6 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_mirror_redirect +from vyos.configverify import verify_vrf from vyos.ifconfig import VTIIf from vyos import ConfigError from vyos import airbag @@ -38,6 +39,7 @@ def get_config(config=None): return vti def verify(vti): + verify_vrf(vti) verify_mirror_redirect(vti) return None diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py index aca0a20e4..68646e8ff 100755 --- a/src/conf_mode/interfaces_vxlan.py +++ b/src/conf_mode/interfaces_vxlan.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos.configverify import verify_bond_bridge_member +from vyos.configverify import verify_vrf from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf from vyos.template import is_ipv6 @@ -216,6 +217,7 @@ def verify(vxlan): verify_mtu_ipv6(vxlan) verify_address(vxlan) + verify_vrf(vxlan) verify_bond_bridge_member(vxlan) verify_mirror_redirect(vxlan) diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py index 5fd7ab6e9..f35a250cb 100755 --- a/src/conf_mode/interfaces_wireless.py +++ b/src/conf_mode/interfaces_wireless.py @@ -19,6 +19,7 @@ import os from sys import exit from re import findall from netaddr import EUI, mac_unix_expanded +from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict @@ -34,6 +35,9 @@ from vyos.template import render from vyos.utils.dict import dict_search from vyos.utils.kernel import check_kmod from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import interface_exists from vyos import ConfigError from vyos import airbag airbag.enable() @@ -93,6 +97,11 @@ def get_config(config=None): if wifi.from_defaults(['security', 'wpa']): # if not set by user del wifi['security']['wpa'] + # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number + if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []): + wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] + del wifi['capabilities']['ht']['40mhz_incapable'] + if dict_search('security.wpa', wifi) != None: wpa_cipher = wifi['security']['wpa'].get('cipher') wpa_mode = wifi['security']['wpa'].get('mode') @@ -120,7 +129,7 @@ def get_config(config=None): tmp = find_other_stations(conf, base, wifi['ifname']) if tmp: wifi['station_interfaces'] = tmp - # used in hostapt.conf.j2 + # used in hostapd.conf.j2 wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) @@ -232,11 +241,6 @@ def verify(wifi): def generate(wifi): interface = wifi['ifname'] - # always stop hostapd service first before reconfiguring it - call(f'systemctl stop hostapd@{interface}.service') - # always stop wpa_supplicant service first before reconfiguring it - call(f'systemctl stop wpa_supplicant@{interface}.service') - # Delete config files if interface is removed if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): @@ -272,11 +276,6 @@ def generate(wifi): mac.dialect = mac_unix_expanded wifi['mac'] = str(mac) - # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number - if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []): - wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] - del wifi['capabilities']['ht']['40mhz_incapable'] - # render appropriate new config files depending on access-point or station mode if wifi['type'] == 'access-point': render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) @@ -290,23 +289,45 @@ def generate(wifi): def apply(wifi): interface = wifi['ifname'] + # From systemd source code: + # If there's a stop job queued before we enter the DEAD state, we shouldn't act on Restart=, + # in order to not undo what has already been enqueued. */ + # + # It was found that calling restart on hostapd will (4 out of 10 cases) deactivate + # the service instead of restarting it, when it was not yet properly stopped + # systemd[1]: hostapd@wlan1.service: Deactivated successfully. + # Thus kill all WIFI service and start them again after it's ensured nothing lives + call(f'systemctl stop hostapd@{interface}.service') + call(f'systemctl stop wpa_supplicant@{interface}.service') + if 'deleted' in wifi: - WiFiIf(interface).remove() - else: - # Finally create the new interface - w = WiFiIf(**wifi) - w.update(wifi) - - # Enable/Disable interface - interface is always placed in - # administrative down state in WiFiIf class - if 'disable' not in wifi: - # Physical interface is now configured. Proceed by starting hostapd or - # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['type'] == 'access-point': - call(f'systemctl start hostapd@{interface}.service') - - elif wifi['type'] == 'station': - call(f'systemctl start wpa_supplicant@{interface}.service') + WiFiIf(**wifi).remove() + return None + + while (is_systemd_service_running(f'hostapd@{interface}.service') or \ + is_systemd_service_active(f'hostapd@{interface}.service')): + sleep(0.250) # wait 250ms + + # Finally create the new interface + w = WiFiIf(**wifi) + w.update(wifi) + + # Enable/Disable interface - interface is always placed in + # administrative down state in WiFiIf class + if 'disable' not in wifi: + # Wait until interface was properly added to the Kernel + ii = 0 + while not (interface_exists(interface) and ii < 20): + sleep(0.250) # wait 250ms + ii += 1 + + # Physical interface is now configured. Proceed by starting hostapd or + # wpa_supplicant daemon. When type is monitor we can just skip this. + if wifi['type'] == 'access-point': + call(f'systemctl start hostapd@{interface}.service') + + elif wifi['type'] == 'station': + call(f'systemctl start wpa_supplicant@{interface}.service') return None diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py index 6f025cc23..c9c0ed9a0 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -41,6 +41,7 @@ config_file_client = r'/etc/snmp/snmp.conf' config_file_daemon = r'/etc/snmp/snmpd.conf' config_file_access = r'/usr/share/snmp/snmpd.conf' config_file_user = r'/var/lib/snmp/snmpd.conf' +default_script_dir = r'/config/user-data/' systemd_override = r'/run/systemd/system/snmpd.service.d/override.conf' systemd_service = 'snmpd.service' @@ -85,8 +86,20 @@ def get_config(config=None): tmp = {'::1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) + if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: + for key, val in snmp['script_extensions']['extension_name'].items(): + if 'script' not in val: + continue + script_path = val['script'] + # if script has not absolute path, use pre configured path + if not os.path.isabs(script_path): + script_path = os.path.join(default_script_dir, script_path) + + snmp['script_extensions']['extension_name'][key]['script'] = script_path + return snmp + def verify(snmp): if 'deleted' in snmp: return None diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py index 571ce55ec..180686924 100755 --- a/src/conf_mode/system_option.py +++ b/src/conf_mode/system_option.py @@ -31,6 +31,7 @@ from vyos.utils.process import cmd from vyos.utils.process import is_systemd_service_running from vyos.utils.network import is_addr_assigned from vyos.utils.network import is_intf_addr_assigned +from vyos.configdep import set_dependents, call_dependents from vyos import ConfigError from vyos import airbag airbag.enable() @@ -55,6 +56,12 @@ def get_config(config=None): get_first_key=True, with_recursive_defaults=True) + if 'performance' in options: + # Update IPv4 and IPv6 options after TuneD reapplies + # sysctl from config files + for protocol in ['ip', 'ipv6']: + set_dependents(protocol, conf) + return options def verify(options): @@ -145,6 +152,8 @@ def apply(options): else: cmd('systemctl stop tuned.service') + call_dependents() + # Keyboard layout - there will be always the default key inside the dict # but we check for key existence anyway if 'keyboard_layout' in options: diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index dc78c755e..cf82b767f 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -24,6 +24,7 @@ from time import sleep from vyos.base import Warning from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdep import set_dependents from vyos.configdep import call_dependents from vyos.configdict import leaf_node_changed @@ -86,9 +87,22 @@ def get_config(config=None): ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True, - with_recursive_defaults=True, with_pki=True) + # We have to cleanup the default dict, as default values could + # enable features which are not explicitly enabled on the + # CLI. E.g. dead-peer-detection defaults should not be injected + # unless the feature is explicitly opted in to by setting the + # top-level node + default_values = conf.get_config_defaults(**ipsec.kwargs, recursive=True) + + if 'ike_group' in ipsec: + for name, ike in ipsec['ike_group'].items(): + if 'dead_peer_detection' not in ike: + del default_values['ike_group'][name]['dead_peer_detection'] + + ipsec = config_dict_merge(default_values, ipsec) + ipsec['dhcp_interfaces'] = set() ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 8d8c234c0..184725573 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -26,7 +26,7 @@ from vyos.ifconfig import Interface from vyos.template import render from vyos.template import render_to_string from vyos.utils.dict import dict_search -from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vrf_tableid from vyos.utils.network import get_vrf_members from vyos.utils.network import interface_exists from vyos.utils.process import call @@ -160,8 +160,8 @@ def verify(vrf): # routing table id can't be changed - OS restriction if interface_exists(name): - tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name))) - if tmp and tmp != vrf_config['table']: + tmp = get_vrf_tableid(name) + if tmp and tmp != int(vrf_config['table']): raise ConfigError(f'VRF "{name}" table id modification not possible!') # VRF routing table ID must be unique on the system |