diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/interfaces-wwan.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/policy.py | 132 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ospf.py | 52 | ||||
-rwxr-xr-x | src/conf_mode/service_console-server.py | 7 | ||||
-rwxr-xr-x | src/conf_mode/system-login.py | 8 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 6 |
6 files changed, 180 insertions, 27 deletions
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 97b3a6396..a14a992ae 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -116,7 +116,7 @@ def generate(wwan): # disconnect - e.g. happens during RF signal loss. The script watches every # WWAN interface - so there is only one instance. if not os.path.exists(cron_script): - write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n') return None diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 3008a20e0..a0d288e91 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -23,8 +23,42 @@ from vyos.util import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag + airbag.enable() + +def community_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in community and large community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ('replace' in actions or 'add' in actions): + return False + if 'replace' in actions and 'add' in actions: + return False + if ('delete' in actions) and ('none' in actions or 'replace' in actions): + return False + return True + + +def extcommunity_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in extended community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ( + 'rt' in actions or 'soo' in actions or 'bandwidth' in actions or 'bandwidth_non_transitive' in actions): + return False + if ('bandwidth_non_transitive' in actions) and ('bandwidth' not in actions): + return False + return True + def routing_policy_find(key, dictionary): # Recursively traverse a dictionary and extract the value assigned to # a given key as generator object. This is made for routing policies, @@ -46,6 +80,7 @@ def routing_policy_find(key, dictionary): for result in routing_policy_find(key, d): yield result + def get_config(config=None): if config: conf = config @@ -53,7 +88,8 @@ def get_config(config=None): conf = Config() base = ['policy'] - policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + policy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) # We also need some additional information from the config, prefix-lists @@ -67,12 +103,14 @@ def get_config(config=None): policy = dict_merge(tmp, policy) return policy + def verify(policy): if not policy: return None for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', 'extcommunity_list', 'large_community_list', + 'community_list', 'extcommunity_list', + 'large_community_list', 'prefix_list', 'prefix_list6', 'route_map']: # Bail out early and continue with next policy type if policy_type not in policy: @@ -97,15 +135,18 @@ def verify(policy): if 'source' not in rule_config: raise ConfigError(f'A source {mandatory_error}') - if int(instance) in range(100, 200) or int(instance) in range(2000, 2700): + if int(instance) in range(100, 200) or int( + instance) in range(2000, 2700): if 'destination' not in rule_config: - raise ConfigError(f'A destination {mandatory_error}') + raise ConfigError( + f'A destination {mandatory_error}') if policy_type == 'access_list6': if 'source' not in rule_config: raise ConfigError(f'A source {mandatory_error}') - if policy_type in ['as_path_list', 'community_list', 'extcommunity_list', + if policy_type in ['as_path_list', 'community_list', + 'extcommunity_list', 'large_community_list']: if 'regex' not in rule_config: raise ConfigError(f'A regex {mandatory_error}') @@ -115,10 +156,10 @@ def verify(policy): raise ConfigError(f'A prefix {mandatory_error}') if rule_config in entries: - raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!') + raise ConfigError( + f'Rule "{rule}" contains a duplicate prefix definition!') entries.append(rule_config) - # route-maps tend to be a bit more complex so they get their own verify() section if 'route_map' in policy: for route_map, route_map_config in policy['route_map'].items(): @@ -127,19 +168,23 @@ def verify(policy): for rule, rule_config in route_map_config['rule'].items(): # Specified community-list must exist - tmp = dict_search('match.community.community_list', rule_config) + tmp = dict_search('match.community.community_list', + rule_config) if tmp and tmp not in policy.get('community_list', []): raise ConfigError(f'community-list {tmp} does not exist!') # Specified extended community-list must exist tmp = dict_search('match.extcommunity', rule_config) if tmp and tmp not in policy.get('extcommunity_list', []): - raise ConfigError(f'extcommunity-list {tmp} does not exist!') + raise ConfigError( + f'extcommunity-list {tmp} does not exist!') # Specified large-community-list must exist - tmp = dict_search('match.large_community.large_community_list', rule_config) + tmp = dict_search('match.large_community.large_community_list', + rule_config) if tmp and tmp not in policy.get('large_community_list', []): - raise ConfigError(f'large-community-list {tmp} does not exist!') + raise ConfigError( + f'large-community-list {tmp} does not exist!') # Specified prefix-list must exist tmp = dict_search('match.ip.address.prefix_list', rule_config) @@ -147,49 +192,87 @@ def verify(policy): raise ConfigError(f'prefix-list {tmp} does not exist!') # Specified prefix-list must exist - tmp = dict_search('match.ipv6.address.prefix_list', rule_config) + tmp = dict_search('match.ipv6.address.prefix_list', + rule_config) if tmp and tmp not in policy.get('prefix_list6', []): raise ConfigError(f'prefix-list6 {tmp} does not exist!') - + # Specified access_list6 in nexthop must exist - tmp = dict_search('match.ipv6.nexthop.access_list', rule_config) + tmp = dict_search('match.ipv6.nexthop.access_list', + rule_config) if tmp and tmp not in policy.get('access_list6', []): raise ConfigError(f'access_list6 {tmp} does not exist!') # Specified prefix-list6 in nexthop must exist - tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config) + tmp = dict_search('match.ipv6.nexthop.prefix_list', + rule_config) if tmp and tmp not in policy.get('prefix_list6', []): raise ConfigError(f'prefix-list6 {tmp} does not exist!') + tmp = dict_search('set.community.delete', rule_config) + if tmp and tmp not in policy.get('community_list', []): + raise ConfigError(f'community-list {tmp} does not exist!') + + tmp = dict_search('set.large_community.delete', + rule_config) + if tmp and tmp not in policy.get('large_community_list', []): + raise ConfigError( + f'large-community-list {tmp} does not exist!') + + if 'set' in rule_config: + rule_action = rule_config['set'] + if 'community' in rule_action: + if not community_action_compatibility( + rule_action['community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in community') + if 'large_community' in rule_action: + if not community_action_compatibility( + rule_action['large_community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in large-community') + if 'extcommunity' in rule_action: + if not extcommunity_action_compatibility( + rule_action['extcommunity']): + raise ConfigError( + f'Unexpected combination between none, rt, soo, bandwidth, bandwidth-non-transitive in extended-community') # When routing protocols are active some use prefix-lists, route-maps etc. # to apply the systems routing policy to the learned or redistributed routes. # When the "routing policy" changes and policies, route-maps etc. are deleted, # it is our responsibility to verify that the policy can not be deleted if it # is used by any routing protocol if 'protocols' in policy: - for policy_type in ['access_list', 'access_list6', 'as_path_list', 'community_list', - 'extcommunity_list', 'large_community_list', 'prefix_list', 'route_map']: + for policy_type in ['access_list', 'access_list6', 'as_path_list', + 'community_list', + 'extcommunity_list', 'large_community_list', + 'prefix_list', 'route_map']: if policy_type in policy: - for policy_name in list(set(routing_policy_find(policy_type, policy['protocols']))): + for policy_name in list(set(routing_policy_find(policy_type, + policy[ + 'protocols']))): found = False if policy_name in policy[policy_type]: found = True # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related # list - we need to go the extra mile here and check both prefix-lists - if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in policy['prefix_list6']: + if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ + policy['prefix_list6']: found = True if not found: - tmp = policy_type.replace('_','-') - raise ConfigError(f'Can not delete {tmp} "{policy_name}", still in use!') + tmp = policy_type.replace('_', '-') + raise ConfigError( + f'Can not delete {tmp} "{policy_name}", still in use!') return None + def generate(policy): if not policy: return None policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy) return None + def apply(policy): bgp_daemon = 'bgpd' zebra_daemon = 'zebra' @@ -203,7 +286,8 @@ def apply(policy): frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') frr_cfg.modify_section(r'^bgp large-community-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) frr_cfg.commit_configuration(bgp_daemon) @@ -214,13 +298,15 @@ def apply(policy): frr_cfg.modify_section(r'^ipv6 access-list .*') frr_cfg.modify_section(r'^ip prefix-list .*') frr_cfg.modify_section(r'^ipv6 prefix-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) frr_cfg.commit_configuration(zebra_daemon) return None + if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 5b4874ba2..0582d32be 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -198,6 +198,58 @@ def verify(ospf): if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!') + # Segment routing checks + if dict_search('segment_routing.global_block', ospf): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf) + + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') + + # If segment routing global block low value is higher than the high value, throw error + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') + + if dict_search('segment_routing.local_block', ospf): + if dict_search('segment_routing.global_block', ospf) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') + + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf) + + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + if list(set(global_range) & set(local_range)): + raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\ + f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') + + # Check for a blank or invalid value per prefix + if dict_search('segment_routing.prefix', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + if prefix_config['index'].get('value') is None: + raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.') + + # Check for explicit-null and no-php-flag configured at the same time per prefix + if dict_search('segment_routing.prefix', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if 'index' in prefix_config: + if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']): + raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ + f'and no-php-flag configured at the same time.') + return None def generate(ospf): diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index a2e411e49..ee4fe42ab 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -61,6 +61,7 @@ def verify(proxy): if not proxy: return None + aliases = [] processes = process_iter(['name', 'cmdline']) if 'device' in proxy: for device, device_config in proxy['device'].items(): @@ -75,6 +76,12 @@ def verify(proxy): if 'ssh' in device_config and 'port' not in device_config['ssh']: raise ConfigError(f'Port "{device}" requires SSH port to be set!') + if 'alias' in device_config: + if device_config['alias'] in aliases: + raise ConfigError("Console aliases must be unique") + else: + aliases.append(device_config['alias']) + return None def generate(proxy): diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 3dcbc995c..dbd346fe4 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -40,6 +40,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +autologout_file = "/etc/profile.d/autologout.sh" radius_config_file = "/etc/pam_radius_auth.conf" def get_local_users(): @@ -203,6 +204,13 @@ def generate(login): if os.path.isfile(radius_config_file): os.unlink(radius_config_file) + if 'timeout' in login: + render(autologout_file, 'login/autologout.j2', login, + permission=0o755, user='root', group='root') + else: + if os.path.isfile(autologout_file): + os.unlink(autologout_file) + return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index c9061366d..77a425f8b 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -265,7 +265,7 @@ def verify(ipsec): ike = ra_conf['ike_group'] if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2': - raise ConfigError('IPSec remote-access connections requires IKEv2!') + raise ConfigError('IPsec remote-access connections requires IKEv2!') else: raise ConfigError(f"Missing ike-group on {name} remote-access config") @@ -308,10 +308,10 @@ def verify(ipsec): for pool in ra_conf['pool']: if pool == 'dhcp': if dict_search('remote_access.dhcp.server', ipsec) == None: - raise ConfigError('IPSec DHCP server is not configured!') + raise ConfigError('IPsec DHCP server is not configured!') elif pool == 'radius': if dict_search('remote_access.radius.server', ipsec) == None: - raise ConfigError('IPSec RADIUS server is not configured!') + raise ConfigError('IPsec RADIUS server is not configured!') if dict_search('authentication.client_mode', ra_conf) != 'eap-radius': raise ConfigError('RADIUS IP pool requires eap-radius client authentication!') |