diff options
Diffstat (limited to 'src/conf_mode')
64 files changed, 463 insertions, 1235 deletions
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index ced5d212e..31c552f5a 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -24,7 +24,7 @@ from vyos.config import Config from vyos.configverify import verify_interface_exists from vyos.template import render from vyos.utils.process import call -from vyos.validate import is_afi_configured +from vyos.utils.network import is_afi_configured from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 2a77540f7..9c43640a9 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -20,7 +20,6 @@ import re from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.firewall import find_nftables_rule from vyos.firewall import remove_nftables_rule from vyos.utils.process import process_named_running @@ -28,7 +27,6 @@ from vyos.utils.dict import dict_search from vyos.utils.process import cmd from vyos.utils.process import run from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -77,16 +75,8 @@ def get_config(config=None): base = ['system', 'conntrack'] conntrack = 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) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'timeout' in default_values and 'custom' in default_values['timeout']: - del default_values['timeout']['custom'] - conntrack = dict_merge(default_values, conntrack) + get_first_key=True, + with_recursive_defaults=True) return conntrack diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py index a83c2274d..4fb2ce27f 100755 --- a/src/conf_mode/conntrack_sync.py +++ b/src/conf_mode/conntrack_sync.py @@ -18,7 +18,6 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_interface_exists from vyos.utils.dict import dict_search from vyos.utils.process import process_named_running @@ -27,8 +26,7 @@ from vyos.utils.process import call from vyos.utils.process import run from vyos.template import render from vyos.template import get_ipv4 -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -50,11 +48,7 @@ def get_config(config=None): return None conntrack = 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) - conntrack = dict_merge(default_values, conntrack) + get_first_key=True, with_defaults=True) conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize') conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 3378aac63..ed7cc809c 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -37,7 +37,7 @@ from vyos.template import inc_ip from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.template import render -from vyos.xml import defaults +from vyos.xml_ref import default_value from vyos import ConfigError from vyos import airbag airbag.enable() @@ -66,58 +66,26 @@ def get_config(config=None): base = ['container'] container = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=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) - # container base default values can not be merged here - remove and add them later - if 'name' in default_values: - del default_values['name'] - # registry will be handled below - if 'registry' in default_values: - del default_values['registry'] - container = dict_merge(default_values, container) - - # Merge per-container default values - if 'name' in container: - default_values = defaults(base + ['name']) - if 'port' in default_values: - del default_values['port'] - if 'volume' in default_values: - del default_values['volume'] - for name in container['name']: - container['name'][name] = dict_merge(default_values, container['name'][name]) - - # T5047: Any container related configuration changed? We only - # wan't to restart the required containers and not all of them ... - tmp = is_node_changed(conf, base + ['name', name]) - if tmp: - if 'container_restart' not in container: - container['container_restart'] = [name] - else: - container['container_restart'].append(name) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'port' in container['name'][name]: - for port in container['name'][name]['port']: - default_values_port = defaults(base + ['name', 'port']) - container['name'][name]['port'][port] = dict_merge( - default_values_port, container['name'][name]['port'][port]) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'volume' in container['name'][name]: - for volume in container['name'][name]['volume']: - default_values_volume = defaults(base + ['name', 'volume']) - container['name'][name]['volume'][volume] = dict_merge( - default_values_volume, container['name'][name]['volume'][volume]) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + for name in container.get('name', []): + # T5047: Any container related configuration changed? We only + # wan't to restart the required containers and not all of them ... + tmp = is_node_changed(conf, base + ['name', name]) + if tmp: + if 'container_restart' not in container: + container['container_restart'] = [name] + else: + container['container_restart'].append(name) # registry is a tagNode with default values - merge the list from # default_values['registry'] into the tagNode variables if 'registry' not in container: container.update({'registry' : {}}) - default_values = defaults(base) - for registry in default_values['registry'].split(): + default_values = default_value(base + ['registry']) + for registry in default_values: tmp = {registry : {}} container['registry'] = dict_merge(tmp, container['registry']) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index fd39bd9fe..37d708847 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -20,12 +20,10 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.base import Warning from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,11 +39,9 @@ def get_config(config=None): if not conf.exists(base): return None - relay = 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) - relay = dict_merge(default_values, relay) + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return relay diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index c29270367..280057f04 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -23,14 +23,12 @@ from netaddr import IPRange from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.dict import dict_search from vyos.utils.process import call from vyos.utils.process import run -from vyos.validate import is_subnet_connected -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_subnet_connected +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -109,19 +107,15 @@ def get_config(config=None): if not conf.exists(base): return None - dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # T2665: defaults include lease time per TAG node which need to be added to - # individual subnet definitions - default_values = defaults(base + ['shared-network-name', 'subnet']) + dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) if 'shared_network_name' in dhcp: for network, network_config in dhcp['shared_network_name'].items(): if 'subnet' in network_config: for subnet, subnet_config in network_config['subnet'].items(): - if 'lease' not in subnet_config: - dhcp['shared_network_name'][network]['subnet'][subnet] = dict_merge( - default_values, dhcp['shared_network_name'][network]['subnet'][subnet]) - # If exclude IP addresses are defined we need to slice them out of # the defined ranges if {'exclude', 'range'} <= set(subnet_config): diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index 0e7da6f89..6537ca3c2 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -19,14 +19,11 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.ifconfig import Interface from vyos.template import render from vyos.template import is_ipv6 from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.validate import is_ipv6_link_local -from vyos.xml import defaults +from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,11 +39,9 @@ def get_config(config=None): if not conf.exists(base): return None - relay = 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) - relay = dict_merge(default_values, relay) + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return relay diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index f89ad5b9c..427001609 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -25,7 +25,7 @@ from vyos.template import render from vyos.template import is_ipv6 from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.validate import is_subnet_connected +from vyos.utils.network import is_subnet_connected from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index d78eb70bc..ab80defe8 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -49,16 +47,10 @@ def get_config(config=None): if not conf.exists(base_level): return None - dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) - - if 'address' in dyndns: - for address in dyndns['address']: - # Apply service specific defaults (svc_type = ['rfc2136', 'service']) - for svc_type in dyndns['address'][address]: - default_values = defaults(base_level + ['address', svc_type]) - for svc_cfg in dyndns['address'][address][svc_type]: - dyndns['address'][address][svc_type][svc_cfg] = dict_merge( - default_values, dyndns['address'][address][svc_type][svc_cfg]) + dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) dyndns['config_file'] = config_file return dyndns diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 2d98bffe3..c186f47af 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -21,14 +21,12 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client from vyos.template import render from vyos.template import bracketize_ipv6 from vyos.utils.process import call from vyos.utils.permission import chown from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -52,31 +50,10 @@ def get_config(config=None): if not conf.exists(base): return None - dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrieved. - default_values = defaults(base) - # T2665 due to how defaults under tag nodes work, we must clear these out before we merge - del default_values['authoritative_domain'] - del default_values['name_server'] - del default_values['domain']['name_server'] - dns = dict_merge(default_values, dns) - - # T2665: we cleared default values for tag node 'name_server' above. - # We now need to add them back back in a granular way. - if 'name_server' in dns: - default_values = defaults(base + ['name-server']) - for server in dns['name_server']: - dns['name_server'][server] = dict_merge(default_values, dns['name_server'][server]) - - # T2665: we cleared default values for tag node 'domain' above. - # We now need to add them back back in a granular way. - if 'domain' in dns: - default_values = defaults(base + ['domain', 'name-server']) - for domain in dns['domain'].keys(): - for server in dns['domain'][domain]['name_server']: - dns['domain'][domain]['name_server'][server] = dict_merge( - default_values, dns['domain'][domain]['name_server'][server]) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # some additions to the default dictionary if 'system' in dns: @@ -109,9 +86,6 @@ def get_config(config=None): rdata = recorddata[rtype][subnode] if rtype in [ 'a', 'aaaa' ]: - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'address' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required') continue @@ -127,9 +101,6 @@ def get_config(config=None): 'value': address }) elif rtype in ['cname', 'ptr', 'ns']: - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'target' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required') continue @@ -141,18 +112,12 @@ def get_config(config=None): 'value': '{}.'.format(rdata['target']) }) elif rtype == 'mx': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['server'] - rdata = dict_merge(rdefaults, rdata) - if not 'server' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') continue for servername in rdata['server']: serverdata = rdata['server'][servername] - serverdefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'server']) # T2665 - serverdata = dict_merge(serverdefaults, serverdata) zone['records'].append({ 'name': subnode, 'type': rtype.upper(), @@ -160,9 +125,6 @@ def get_config(config=None): 'value': '{} {}.'.format(serverdata['priority'], servername) }) elif rtype == 'txt': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'value' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required') continue @@ -175,9 +137,6 @@ def get_config(config=None): 'value': "\"{}\"".format(value.replace("\"", "\\\"")) }) elif rtype == 'spf': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'value' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required') continue @@ -189,19 +148,12 @@ def get_config(config=None): 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) }) elif rtype == 'srv': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['entry'] - rdata = dict_merge(rdefaults, rdata) - if not 'entry' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required') continue for entryno in rdata['entry']: entrydata = rdata['entry'][entryno] - entrydefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'entry']) # T2665 - entrydata = dict_merge(entrydefaults, entrydata) - if not 'hostname' in entrydata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}') continue @@ -217,19 +169,12 @@ def get_config(config=None): 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) }) elif rtype == 'naptr': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['rule'] - rdata = dict_merge(rdefaults, rdata) - - if not 'rule' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required') continue for ruleno in rdata['rule']: ruledata = rdata['rule'][ruleno] - ruledefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'rule']) # T2665 - ruledata = dict_merge(ruledefaults, ruledata) flags = "" if 'lookup-srv' in ruledata: flags += "S" diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 07166d457..e946704b3 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -23,7 +23,6 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff from vyos.configdep import set_dependents, call_dependents @@ -37,7 +36,6 @@ from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import process_named_running from vyos.utils.process import rc_cmd -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -97,19 +95,22 @@ def geoip_updated(conf, firewall): updated = False for key, path in dict_search_recursive(firewall, 'geoip'): - set_name = f'GEOIP_CC_{path[1]}_{path[3]}' - if path[0] == 'name': + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): out['name'].append(set_name) - elif path[0] == 'ipv6_name': + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' out['ipv6_name'].append(set_name) + updated = True if 'delete' in node_diff: for key, path in dict_search_recursive(node_diff['delete'], 'geoip'): - set_name = f'GEOIP_CC_{path[1]}_{path[3]}' - if path[0] == 'name': + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): out['deleted_name'].append(set_name) - elif path[0] == 'ipv6-name': + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' out['deleted_ipv6_name'].append(set_name) updated = True @@ -125,54 +126,17 @@ def get_config(config=None): conf = Config() base = ['firewall'] - firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary retrived. - # XXX: T2665: we currently have no nice way for defaults under tag - # nodes, thus we load the defaults "by hand" - default_values = defaults(base) - for tmp in ['name', 'ipv6_name']: - if tmp in default_values: - del default_values[tmp] - - if 'zone' in default_values: - del default_values['zone'] - - firewall = dict_merge(default_values, firewall) - - # Merge in defaults for IPv4 ruleset - if 'name' in firewall: - default_values = defaults(base + ['name']) - for name in firewall['name']: - firewall['name'][name] = dict_merge(default_values, - firewall['name'][name]) - - # Merge in defaults for IPv6 ruleset - if 'ipv6_name' in firewall: - default_values = defaults(base + ['ipv6-name']) - for ipv6_name in firewall['ipv6_name']: - firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, - firewall['ipv6_name'][ipv6_name]) - - if 'zone' in firewall: - default_values = defaults(base + ['zone']) - for zone in firewall['zone']: - firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone]) + firewall = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) if firewall['group_resync']: # Update nat and policy-route as firewall groups were updated set_dependents('group_resync', conf) - if 'config_trap' in firewall and firewall['config_trap'] == 'enable': - diff = get_config_diff(conf) - firewall['trap_diff'] = diff.get_child_nodes_diff_str(base) - firewall['trap_targets'] = conf.get_config_dict(['service', 'snmp', 'trap-target'], - key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - firewall['geoip_updated'] = geoip_updated(conf, firewall) fqdn_config_parse(firewall) @@ -191,11 +155,11 @@ def verify_rule(firewall, rule_conf, ipv6): 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, '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') 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') + 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') if 'queue_options' in rule_conf: if 'queue' not in rule_conf['action']: @@ -312,10 +276,6 @@ def verify_nested_group(group_name, group, groups, seen): verify_nested_group(g, groups[g], groups, seen) def verify(firewall): - if 'config_trap' in firewall and firewall['config_trap'] == 'enable': - if not firewall['trap_targets']: - raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined') - if 'group' in firewall: for group_type in nested_group_types: if group_type in firewall['group']: @@ -323,95 +283,45 @@ def verify(firewall): for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) - for name in ['name', 'ipv6_name']: - if name in firewall: - for name_id, name_conf in firewall[name].items(): - if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: - raise ConfigError('default-action set to jump, but no default-jump-target specified') - if 'default_jump_target' in name_conf: - target = name_conf['default_jump_target'] - if 'jump' not in name_conf['default_action']: - 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, name): - raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system') - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, rule_conf, name == 'ipv6_name') - - if 'interface' in firewall: - for ifname, if_firewall in firewall['interface'].items(): - # verify ifname needs to be disabled, dynamic devices come up later - # verify_interface_exists(ifname) - - for direction in ['in', 'out', 'local']: - name = dict_search_args(if_firewall, direction, 'name') - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - - if name and dict_search_args(firewall, 'name', name) == None: - raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}') - - if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: - raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}') - - local_zone = False - zone_interfaces = [] - - if 'zone' in firewall: - for zone, zone_conf in firewall['zone'].items(): - if 'local_zone' not in zone_conf and 'interface' not in zone_conf: - raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') - - if 'local_zone' in zone_conf: - if local_zone: - raise ConfigError('There cannot be multiple local zones') - if 'interface' in zone_conf: - raise ConfigError('Local zone cannot have interfaces assigned') - if 'intra_zone_filtering' in zone_conf: - raise ConfigError('Local zone cannot use intra-zone-filtering') - local_zone = True - - if 'interface' in zone_conf: - found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] - - if found_duplicates: - raise ConfigError(f'Interfaces cannot be assigned to multiple zones') - - zone_interfaces += zone_conf['interface'] - - if 'intra_zone_filtering' in zone_conf: - intra_zone = zone_conf['intra_zone_filtering'] - - if len(intra_zone) > 1: - raise ConfigError('Only one intra-zone-filtering action must be specified') - - if 'firewall' in intra_zone: - v4_name = dict_search_args(intra_zone, 'firewall', 'name') - if v4_name and not dict_search_args(firewall, 'name', v4_name): - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') - if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - - if not v4_name and not v6_name: - raise ConfigError('No firewall names specified for intra-zone-filtering') - - if 'from' in zone_conf: - for from_zone, from_conf in zone_conf['from'].items(): - if from_zone not in firewall['zone']: - raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') - - v4_name = dict_search_args(from_conf, 'firewall', 'name') - if v4_name and not dict_search_args(firewall, 'name', v4_name): - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') - if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + if 'ipv4' in firewall: + for name in ['name','forward','input','output']: + if name in firewall['ipv4']: + for name_id, name_conf in firewall['ipv4'][name].items(): + if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in name_conf: + target = name_conf['default_jump_target'] + if 'jump' not in name_conf['default_action']: + 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') + + if 'rule' in name_conf: + for rule_id, rule_conf in name_conf['rule'].items(): + verify_rule(firewall, rule_conf, False) + + if 'ipv6' in firewall: + for name in ['name','forward','input','output']: + if name in firewall['ipv6']: + for name_id, name_conf in firewall['ipv6'][name].items(): + if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in name_conf: + target = name_conf['default_jump_target'] + if 'jump' not in name_conf['default_action']: + 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') + + if 'rule' in name_conf: + for rule_id, rule_conf in name_conf['rule'].items(): + verify_rule(firewall, rule_conf, True) return None @@ -419,19 +329,6 @@ def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True - if 'zone' in firewall: - for local_zone, local_zone_conf in firewall['zone'].items(): - if 'local_zone' not in local_zone_conf: - continue - - local_zone_conf['from_local'] = {} - - for zone, zone_conf in firewall['zone'].items(): - if zone == local_zone or 'from' not in zone_conf: - continue - if local_zone in zone_conf['from']: - local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] - render(nftables_conf, 'firewall/nftables.j2', firewall) return None @@ -440,9 +337,8 @@ def apply_sysfs(firewall): paths = glob(conf['sysfs']) value = None - if name in firewall: - conf_value = firewall[name] - + if name in firewall['global_options']: + conf_value = firewall['global_options'][name] if conf_value in conf: value = conf[conf_value] elif conf_value == 'enable': @@ -459,9 +355,6 @@ def post_apply_trap(firewall): if 'first_install' in firewall: return None - if 'config_trap' not in firewall or firewall['config_trap'] != 'enable': - return None - if not process_named_running('snmpd'): return None diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index bfe906c87..71acd69fa 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -22,14 +22,13 @@ from ipaddress import ip_address from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.config import config_dict_merge from vyos.configverify import verify_vrf from vyos.ifconfig import Section from vyos.template import render from vyos.utils.process import call from vyos.utils.process import cmd -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -128,30 +127,19 @@ def get_config(config=None): flow_accounting = 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) + # We have gathered the dict representation of the CLI, but there are + # default values which we need to conditionally update into the + # dictionary retrieved. + default_values = conf.get_config_defaults(**flow_accounting.kwargs, + recursive=True) - # delete individual flow type default - should only be added if user uses - # this feature + # delete individual flow type defaults - should only be added if user + # sets this feature for flow_type in ['sflow', 'netflow']: - if flow_type in default_values: + if flow_type not in flow_accounting and flow_type in default_values: del default_values[flow_type] - flow_accounting = dict_merge(default_values, flow_accounting) - for flow_type in ['sflow', 'netflow']: - if flow_type in flow_accounting: - default_values = defaults(base + [flow_type]) - # we need to merge individual server configurations - if 'server' in default_values: - del default_values['server'] - flow_accounting[flow_type] = dict_merge(default_values, flow_accounting[flow_type]) - - if 'server' in flow_accounting[flow_type]: - default_values = defaults(base + [flow_type, 'server']) - for server in flow_accounting[flow_type]['server']: - flow_accounting[flow_type]['server'][server] = dict_merge( - default_values,flow_accounting[flow_type]['server'][server]) + flow_accounting = config_dict_merge(default_values, flow_accounting) return flow_accounting diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 0cbd4c49c..626a3757e 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -14,7 +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 sys import exit from ipaddress import ip_interface @@ -23,14 +22,11 @@ from ipaddress import IPv6Interface from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.ifconfig.vrrp import VRRP from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.utils.process import call -from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,42 +38,12 @@ def get_config(config=None): conf = Config() base = ['high-availability'] - base_vrrp = ['high-availability', 'vrrp'] if not conf.exists(base): return None ha = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - if 'vrrp' in ha: - if dict_search('vrrp.global_parameters.garp', ha) != None: - default_values = defaults(base_vrrp + ['global-parameters', 'garp']) - ha['vrrp']['global_parameters']['garp'] = dict_merge( - default_values, ha['vrrp']['global_parameters']['garp']) - - if 'group' in ha['vrrp']: - default_values = defaults(base_vrrp + ['group']) - default_values_garp = defaults(base_vrrp + ['group', 'garp']) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'garp' in default_values: - del default_values['garp'] - for group in ha['vrrp']['group']: - ha['vrrp']['group'][group] = dict_merge(default_values, ha['vrrp']['group'][group]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'garp' in ha['vrrp']['group'][group]: - ha['vrrp']['group'][group]['garp'] = dict_merge( - default_values_garp, ha['vrrp']['group'][group]['garp']) - - # Merge per virtual-server default values - if 'virtual_server' in ha: - default_values = defaults(base + ['virtual-server']) - for vs in ha['virtual_server']: - ha['virtual_server'][vs] = dict_merge(default_values, ha['virtual_server'][vs]) + no_tag_node_value_mangle=True, + get_first_key=True, with_defaults=True) ## Get the sync group used for conntrack-sync conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'] @@ -112,7 +78,7 @@ def verify(ha): from vyos.utils.dict import check_mutually_exclusive_options try: check_mutually_exclusive_options(group_config["health_check"], health_check_types, required=True) - except ValueError as e: + except ValueError: Warning(f'Health check configuration for VRRP group "{group}" will remain unused ' \ f'until it has one of the following options: {health_check_types}') # XXX: health check has default options so we need to remove it diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 7bdf448a3..793a90d88 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -24,12 +24,9 @@ from copy import deepcopy import vyos.defaults from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdep import set_dependents, call_dependents from vyos.template import render -from vyos.utils.process import cmd from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -72,8 +69,9 @@ def get_config(config=None): return None api_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # One needs to 'flatten' the keys dict from the config into the # http-api.conf format for api_keys: @@ -93,8 +91,8 @@ def get_config(config=None): if 'api_keys' in api_dict: keys_added = True - if 'graphql' in api_dict: - api_dict = dict_merge(defaults(base), api_dict) + if api_dict.from_defaults(['graphql']): + del api_dict['graphql'] http_api.update(api_dict) diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 4ec2f1835..40db417dd 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -21,11 +21,9 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -39,16 +37,9 @@ def get_config(config=None): conf = Config() base = ['protocols', 'igmp-proxy'] - igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - if 'interface' in igmp_proxy: - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - default_values = defaults(base + ['interface']) - for interface in igmp_proxy['interface']: - igmp_proxy['interface'][interface] = dict_merge(default_values, - igmp_proxy['interface'][interface]) - + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_defaults=True) if conf.exists(['protocols', 'igmp']): igmp_proxy.update({'igmp_configured': ''}) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index c2a569fa9..0bd306ed0 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -36,8 +36,8 @@ from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf from vyos.ifconfig import Section from vyos.utils.dict import dict_search -from vyos.validate import has_address_configured -from vyos.validate import has_vrf_configured +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured from vyos import ConfigError from vyos import airbag airbag.enable() @@ -195,11 +195,11 @@ def verify(bond): raise ConfigError(error_msg + 'it does not exist!') if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(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: diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 087ead20a..c82f01e53 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -14,10 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os - from sys import exit -from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict @@ -25,16 +22,13 @@ from vyos.configdict import node_changed from vyos.configdict import is_member from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured -from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf -from vyos.validate import has_address_configured -from vyos.validate import has_vrf_configured -from vyos.xml import defaults +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured -from vyos.utils.process import cmd from vyos.utils.dict import dict_search from vyos import ConfigError @@ -61,22 +55,8 @@ def get_config(config=None): else: bridge.update({'member' : {'interface_remove' : tmp }}) - if dict_search('member.interface', bridge) != None: - # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: - # RuntimeError: dictionary changed size during iteration + if dict_search('member.interface', bridge) is not None: for interface in list(bridge['member']['interface']): - for key in ['cost', 'priority']: - if interface == key: - del bridge['member']['interface'][key] - continue - - # the default dictionary is not properly paged into the dict (see T2665) - # thus we will ammend it ourself - default_member_values = defaults(base + ['member', 'interface']) - for interface,interface_config in bridge['member']['interface'].items(): - bridge['member']['interface'][interface] = dict_merge( - default_member_values, bridge['member']['interface'][interface]) - # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') if tmp and bridge['ifname'] not in tmp: diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 6efeac302..e1db3206e 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -29,7 +29,7 @@ from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import L2TPv3If from vyos.utils.kernel import check_kmod -from vyos.validate import is_addr_assigned +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3bef9b8f6..26b217d98 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -61,7 +61,7 @@ from vyos.utils.kernel import unload_kmod from vyos.utils.process import call from vyos.utils.permission import chown from vyos.utils.process import cmd -from vyos.validate import is_addr_assigned +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -189,16 +189,7 @@ def verify_pki(openvpn): if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') - if mode == 'server' and 'dh_params' not in tls and not is_ec_private_key(pki, tls['certificate']): - raise ConfigError('Must specify "tls dh-params" when not using EC keys in server mode') - if 'dh_params' in tls: - if 'dh' not in pki: - raise ConfigError('There are no DH parameters in PKI configuration') - - if tls['dh_params'] not in pki['dh']: - raise ConfigError(f'Invalid dh-params on openvpn interface {interface}') - pki_dh = pki['dh'][tls['dh_params']] dh_params = load_dh_parameters(pki_dh['parameters']) dh_numbers = dh_params.parameter_numbers() @@ -207,6 +198,7 @@ def verify_pki(openvpn): if dh_bits < 2048: raise ConfigError(f'Minimum DH key-size is 2048 bits') + if 'auth_key' in tls or 'crypt_key' in tls: if not dict_search_args(pki, 'openvpn', 'shared_secret'): raise ConfigError('There are no openvpn shared-secrets in PKI configuration') @@ -495,9 +487,6 @@ def verify(openvpn): if openvpn['protocol'] == 'tcp-active': raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"') - if not dict_search('tls.dh_params', openvpn): - raise ConfigError('Must specify "tls dh-params" when "tls role" is "passive"') - if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']): if 'dh_params' in openvpn['tls']: print('Warning: using dh-params and EC keys simultaneously will ' \ diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 6a075970e..91aed9cc3 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -55,6 +55,9 @@ def get_config(config=None): tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) + tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key']) + if tmp: tunnel.update({'key_changed': {}}) + # We also need to inspect other configured tunnels as there are Kernel # restrictions where we need to comply. E.g. GRE tunnel key can't be used # twice, or with multiple GRE tunnels to the same location we must specify @@ -197,7 +200,8 @@ def apply(tunnel): remote = dict_search('linkinfo.info_data.remote', tmp) if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in - ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any']): + ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or + 'key_changed' in tunnel): if interface in interfaces(): tmp = Interface(interface) tmp.remove() diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index b1536148c..a3b0867e0 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -87,8 +87,8 @@ def verify(vxlan): raise ConfigError('Multicast VXLAN requires an underlaying interface') verify_source_interface(vxlan) - if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): - raise ConfigError('Group, remote or source-address must be configured') + if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): + raise ConfigError('Group, remote, source-address or source-interface must be configured') if 'vni' not in vxlan and 'external' not in vxlan: raise ConfigError( diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index a02baba82..446399255 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -29,10 +29,12 @@ from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import WireGuardIf from vyos.utils.kernel import check_kmod from vyos.utils.network import check_port_availability +from vyos.utils.network import is_wireguard_key_pair from vyos import ConfigError from vyos import airbag airbag.enable() + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -105,6 +107,9 @@ def verify(wireguard): if peer['public_key'] in public_keys: raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') + if 'disable' not in peer and is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): + raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') + public_keys.append(peer['public_key']) def apply(wireguard): diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 42326bea0..e49ad25ac 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -79,27 +79,9 @@ def get_config(config=None): ifname, wifi = get_interface_dict(conf, base) - # Cleanup "delete" default values when required user selectable values are - # not defined at all - tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'), - get_first_key=True) - if not (dict_search('security.wpa.passphrase', tmp) or - dict_search('security.wpa.radius', tmp)): - if 'deleted' not in wifi: - del wifi['security']['wpa'] - # if 'security' key is empty, drop it too - if len(wifi['security']) == 0: - del wifi['security'] - - # defaults include RADIUS server specifics per TAG node which need to be - # added to individual RADIUS servers instead - so we can simply delete them - if dict_search('security.wpa.radius.server.port', wifi) != None: - del wifi['security']['wpa']['radius']['server']['port'] - if not len(wifi['security']['wpa']['radius']['server']): - del wifi['security']['wpa']['radius'] - if not len(wifi['security']['wpa']): - del wifi['security']['wpa'] - if not len(wifi['security']): + if 'deleted' not in wifi: + # then get_interface_dict provides default keys + if wifi.from_defaults(['security']): # if not set by user del wifi['security'] if 'security' in wifi and 'wpa' in wifi['security']: @@ -120,14 +102,6 @@ def get_config(config=None): tmp = find_other_stations(conf, base, wifi['ifname']) if tmp: wifi['station_interfaces'] = tmp - # Add individual RADIUS server default values - if dict_search('security.wpa.radius.server', wifi): - default_values = defaults(base + ['security', 'wpa', 'radius', 'server']) - - for server in dict_search('security.wpa.radius.server', wifi): - wifi['security']['wpa']['radius']['server'][server] = dict_merge( - default_values, wifi['security']['wpa']['radius']['server'][server]) - return wifi def verify(wifi): diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index 0e5fc29d3..c2e87d171 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -20,13 +20,11 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.validate import is_addr_assigned -from vyos.validate import is_loopback_addr +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_loopback_addr from vyos.version import get_version_data from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos.template import render from vyos import ConfigError from vyos import airbag @@ -46,7 +44,9 @@ def get_config(config=None): return {} lldp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) if conf.exists(['service', 'snmp']): lldp['system_snmp_enabled'] = '' @@ -54,27 +54,12 @@ def get_config(config=None): version_data = get_version_data() lldp['version'] = version_data['version'] - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - # location coordinates have a default value - if 'interface' in lldp: - for interface, interface_config in lldp['interface'].items(): - default_values = defaults(base + ['interface']) - if dict_search('location.coordinate_based', interface_config) == None: - # no location specified - no need to add defaults - del default_values['location']['coordinate_based']['datum'] - del default_values['location']['coordinate_based']['altitude'] - - # cleanup default_values dictionary from inner to outer - # this might feel overkill here, but it does support easy extension - # in the future with additional default values - if len(default_values['location']['coordinate_based']) == 0: - del default_values['location']['coordinate_based'] - if len(default_values['location']) == 0: - del default_values['location'] - - lldp['interface'][interface] = dict_merge(default_values, - lldp['interface'][interface]) + # prune location information if not set by user + for interface in lldp.get('interface', []): + if lldp.from_defaults(['interface', interface, 'location']): + del lldp['interface'][interface]['location'] + elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): + del lldp['interface'][interface]['location']['coordinate_based'] return lldp diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py index 2fb0edf8e..8fe429653 100755 --- a/src/conf_mode/load-balancing-haproxy.py +++ b/src/conf_mode/load-balancing-haproxy.py @@ -20,14 +20,12 @@ from sys import exit from shutil import rmtree from vyos.config import Config -from vyos.configdict import dict_merge from vyos.utils.process import call from vyos.utils.network import check_port_availability from vyos.utils.network import is_listen_port_bind_service from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -54,18 +52,8 @@ def get_config(config=None): lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=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) - if 'backend' in default_values: - del default_values['backend'] if lb: - lb = dict_merge(default_values, lb) - - if 'backend' in lb: - for backend in lb['backend']: - default_balues_backend = defaults(base + ['backend']) - lb['backend'][backend] = dict_merge(default_balues_backend, lb['backend'][backend]) + lb = conf.merge_defaults(lb, recursive=True) return lb diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py index 3533a5a04..ad9c80d72 100755 --- a/src/conf_mode/load-balancing-wan.py +++ b/src/conf_mode/load-balancing-wan.py @@ -21,10 +21,8 @@ from shutil import rmtree from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.utils.process import cmd from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,48 +39,15 @@ def get_config(config=None): conf = Config() base = ['load-balancing', 'wan'] - lb = conf.get_config_dict(base, + lb = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True, - key_mangling=('-', '_'), - no_tag_node_value_mangle=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) - # lb base default values can not be merged here - remove and add them later - if 'interface_health' in default_values: - del default_values['interface_health'] - if 'rule' in default_values: - del default_values['rule'] - lb = dict_merge(default_values, lb) - - if 'interface_health' in lb: - for iface in lb.get('interface_health'): - default_values_iface = defaults(base + ['interface-health']) - if 'test' in default_values_iface: - del default_values_iface['test'] - lb['interface_health'][iface] = dict_merge( - default_values_iface, lb['interface_health'][iface]) - if 'test' in lb['interface_health'][iface]: - for node_test in lb['interface_health'][iface]['test']: - default_values_test = defaults(base + - ['interface-health', 'test']) - lb['interface_health'][iface]['test'][node_test] = dict_merge( - default_values_test, - lb['interface_health'][iface]['test'][node_test]) - - if 'rule' in lb: - for rule in lb.get('rule'): - default_values_rule = defaults(base + ['rule']) - if 'interface' in default_values_rule: - del default_values_rule['interface'] - lb['rule'][rule] = dict_merge(default_values_rule, lb['rule'][rule]) - if not conf.exists(base + ['rule', rule, 'limit']): - del lb['rule'][rule]['limit'] - if 'interface' in lb['rule'][rule]: - for iface in lb['rule'][rule]['interface']: - default_values_rule_iface = defaults(base + ['rule', 'interface']) - lb['rule'][rule]['interface'][iface] = dict_merge(default_values_rule_iface, lb['rule'][rule]['interface'][iface]) + with_recursive_defaults=True) + + # prune limit key if not set by user + for rule in lb.get('rule', []): + if lb.from_defaults(['rule', rule, 'limit']): + del lb['rule'][rule]['limit'] return lb diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index e19b12937..f9d711b36 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -25,7 +25,6 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.template import is_ip_network from vyos.utils.kernel import check_kmod @@ -33,8 +32,7 @@ from vyos.utils.dict import dict_search from vyos.utils.dict import dict_search_args from vyos.utils.process import cmd from vyos.utils.process import run -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -126,6 +124,18 @@ def verify_rule(config, err_msg, groups_dict): if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') + if 'load_balance' in config: + for item in ['source-port', 'destination-port']: + if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: + raise ConfigError('Protocol must be tcp or udp when specifying hash ports') + count = 0 + if 'backend' in config['load_balance']: + for member in config['load_balance']['backend']: + weight = config['load_balance']['backend'][member]['weight'] + count = count + int(weight) + if count != 100: + Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') + def get_config(config=None): if config: conf = config @@ -133,16 +143,9 @@ def get_config(config=None): conf = Config() base = ['nat'] - nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - for direction in ['source', 'destination', 'static']: - if direction in nat: - default_values = defaults(base + [direction, 'rule']) - for rule in dict_search(f'{direction}.rule', nat) or []: - nat[direction]['rule'][rule] = dict_merge(default_values, - nat[direction]['rule'][rule]) + nat = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # read in current nftable (once) for further processing tmp = cmd('nft -j list table raw') @@ -199,7 +202,7 @@ def verify(nat): Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config): - if 'exclude' not in config: + if 'exclude' not in config and 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') addr = dict_search('translation.address', config) @@ -211,7 +214,6 @@ def verify(nat): # common rule verification verify_rule(config, err_msg, nat['firewall_group']) - if dict_search('destination.rule', nat): for rule, config in dict_search('destination.rule', nat).items(): err_msg = f'Destination NAT configuration error in rule {rule}:' @@ -223,7 +225,7 @@ def verify(nat): Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config) and not dict_search('translation.redirect.port', config): - if 'exclude' not in config: + if 'exclude' not in config and 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') # common rule verification diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 25f625b84..4c12618bc 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -23,13 +23,11 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import cmd from vyos.utils.kernel import check_kmod from vyos.utils.dict import dict_search from vyos.template import is_ipv6 -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -60,16 +58,6 @@ def get_config(config=None): base = ['nat66'] nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - for direction in ['source', 'destination']: - if direction in nat: - default_values = defaults(base + [direction, 'rule']) - if 'rule' in nat[direction]: - for rule in nat[direction]['rule']: - nat[direction]['rule'][rule] = dict_merge(default_values, - nat[direction]['rule'][rule]) - # read in current nftable (once) for further processing tmp = cmd('nft -j list table ip6 raw') nftable_json = json.loads(tmp) diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index eb8cb3940..34ba2fe69 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -18,7 +18,6 @@ from sys import exit from vyos.config import Config from vyos.configdep import set_dependents, call_dependents -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.pki import is_ca_certificate from vyos.pki import load_certificate @@ -28,7 +27,6 @@ from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -113,8 +111,7 @@ def get_config(config=None): # We only merge on the defaults of there is a configuration at all if conf.exists(base): - default_values = defaults(base) - pki = dict_merge(default_values, pki) + pki = conf.merge_defaults(pki, recursive=True) # We need to get the entire system configuration to verify that we are not # deleting a certificate that is still referenced somewhere! diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index f5ac56f65..104711b55 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -19,13 +19,13 @@ import os from sys import exit from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -38,7 +38,8 @@ def get_config(config=None): else: conf = Config() base = ['protocols', 'babel'] - babel = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + babel = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) # FRR has VRF support for different routing daemons. As interfaces belong # to VRFs - or the global VRF, we need to check for changed interfaces so @@ -54,15 +55,13 @@ def get_config(config=None): return babel # 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) + # values which we need to update into the dictionary retrieved. + default_values = conf.get_config_defaults(base, key_mangling=('-', '_'), + get_first_key=True, + recursive=True) - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['interface'] - - # merge in remaining default values - babel = dict_merge(default_values, babel) + # merge in default values + babel = config_dict_merge(default_values, babel) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 0436abaf9..dab784662 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -17,12 +17,10 @@ import os from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.template import is_ipv6 from vyos.template import render_to_string -from vyos.validate import is_ipv6_link_local -from vyos.xml import defaults +from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError from vyos import frr from vyos import airbag @@ -41,18 +39,7 @@ def get_config(config=None): if not conf.exists(base): return bfd - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary retrived. - # XXX: T2665: we currently have no nice way for defaults under tag - # nodes, thus we load the defaults "by hand" - default_values = defaults(base + ['peer']) - if 'peer' in bfd: - for peer in bfd['peer']: - bfd['peer'][peer] = dict_merge(default_values, bfd['peer'][peer]) - - if 'profile' in bfd: - for profile in bfd['profile']: - bfd['profile'][profile] = dict_merge(default_values, bfd['profile'][profile]) + bfd = conf.merge_defaults(bfd, recursive=True) return bfd diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 7b9f15505..00015023c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -29,7 +29,7 @@ from vyos.template import is_interface from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_vrf -from vyos.validate import is_addr_assigned +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import frr from vyos import airbag diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py index faf56d741..e7e44db84 100755 --- a/src/conf_mode/protocols_failover.py +++ b/src/conf_mode/protocols_failover.py @@ -19,10 +19,8 @@ import json from pathlib import Path from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -42,15 +40,12 @@ def get_config(config=None): conf = Config() base = ['protocols', 'failover'] - failover = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + failover = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) # Set default values only if we set config - if failover.get('route'): - for route, route_config in failover.get('route').items(): - for next_hop, next_hop_config in route_config.get('next_hop').items(): - default_values = defaults(base + ['route']) - failover['route'][route]['next_hop'][next_hop] = dict_merge( - default_values['next_hop'], failover['route'][route]['next_hop'][next_hop]) + if failover.get('route') is not None: + failover = conf.merge_defaults(failover, recursive=True) return failover diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 4c637a99f..e00c58ee4 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -28,7 +28,6 @@ from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos.template import render_to_string -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -64,19 +63,14 @@ def get_config(config=None): if interfaces_removed: isis['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): isis.update({'deleted' : ''}) return isis - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) # merge in default values - isis = dict_merge(default_values, isis) + isis = conf.merge_defaults(isis, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -254,7 +248,7 @@ def apply(isis): if key not in isis: continue for interface in isis[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_isisd_config' in isis: frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index f2075d25b..cddd3765e 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,6 +20,7 @@ from sys import exit from sys import argv from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps @@ -29,7 +30,6 @@ from vyos.configverify import verify_access_list from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -65,17 +65,15 @@ def get_config(config=None): if interfaces_removed: ospf['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): ospf.update({'deleted' : ''}) return ospf # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) + default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True) # We have to cleanup the default dict, as default values could enable features # which are not explicitly enabled on the CLI. Example: default-information @@ -84,62 +82,27 @@ def get_config(config=None): # need to check this first and probably drop that key. if dict_search('default_information.originate', ospf) is None: del default_values['default_information'] - if dict_search('area.area_type.nssa', ospf) is None: - del default_values['area']['area_type']['nssa'] if 'mpls_te' not in ospf: del default_values['mpls_te'] if 'graceful_restart' not in ospf: del default_values['graceful_restart'] + for area_num in default_values.get('area', []): + if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: + del default_values['area'][area_num]['area_type']['nssa'] - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']: - # table is a tagNode thus we need to clean out all occurances for the - # default values and load them in later individually - if protocol == 'table': - del default_values['redistribute']['table'] - continue + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['neighbor'] - del default_values['area']['virtual_link'] - del default_values['interface'] - - # merge in remaining default values - ospf = dict_merge(default_values, ospf) - - if 'neighbor' in ospf: - default_values = defaults(base + ['neighbor']) - for neighbor in ospf['neighbor']: - ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor]) + for interface in ospf.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'hello_multiplier' in ospf['interface'][interface]: + del default_values['interface'][interface]['dead_interval'] - if 'area' in ospf: - default_values = defaults(base + ['area', 'virtual-link']) - for area, area_config in ospf['area'].items(): - if 'virtual_link' in area_config: - for virtual_link in area_config['virtual_link']: - ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( - default_values, ospf['area'][area]['virtual_link'][virtual_link]) - - if 'interface' in ospf: - for interface in ospf['interface']: - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - default_values = defaults(base + ['interface']) - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'hello_multiplier' in ospf['interface'][interface]: - del default_values['dead_interval'] - - ospf['interface'][interface] = dict_merge(default_values, - ospf['interface'][interface]) - - if 'redistribute' in ospf and 'table' in ospf['redistribute']: - default_values = defaults(base + ['redistribute', 'table']) - for table in ospf['redistribute']['table']: - ospf['redistribute']['table'][table] = dict_merge(default_values, - ospf['redistribute']['table'][table]) + ospf = config_dict_merge(default_values, ospf) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -287,7 +250,7 @@ def apply(ospf): if key not in ospf: continue for interface in ospf[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_ospfd_config' in ospf: frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index fbea51f56..5b1adce30 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -20,6 +20,7 @@ from sys import exit from sys import argv from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps @@ -29,7 +30,6 @@ from vyos.template import render_to_string from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -64,17 +64,16 @@ def get_config(config=None): if interfaces_removed: ospfv3['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): ospfv3.update({'deleted' : ''}) return ospfv3 # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) + default_values = conf.get_config_defaults(**ospfv3.kwargs, + recursive=True) # We have to cleanup the default dict, as default values could enable features # which are not explicitly enabled on the CLI. Example: default-information @@ -86,12 +85,10 @@ def get_config(config=None): if 'graceful_restart' not in ospfv3: del default_values['graceful_restart'] - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['interface'] + default_values.pop('interface', {}) # merge in remaining default values - ospfv3 = dict_merge(default_values, ospfv3) + ospfv3 = config_dict_merge(default_values, ospfv3) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -170,7 +167,7 @@ def apply(ospfv3): if key not in ospfv3: continue for interface in ospfv3[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in ospfv3: frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 5661dc377..bd47dfd00 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -25,7 +25,6 @@ from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -55,9 +54,7 @@ def get_config(config=None): # 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) - # merge in remaining default values - rip = dict_merge(default_values, rip) + rip = conf.merge_defaults(rip, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index e3c904e33..dd1550033 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -24,7 +24,6 @@ from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -45,9 +44,7 @@ def get_config(config=None): # 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) - # merge in remaining default values - ripng = dict_merge(default_values, ripng) + ripng = conf.merge_defaults(ripng, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 035b7db05..05e876f3b 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render_to_string from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -43,8 +41,7 @@ def get_config(config=None): # 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) - rpki = dict_merge(default_values, rpki) + rpki = conf.merge_defaults(rpki, recursive=True) return rpki diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index 53e9ff50d..5536adda2 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -38,7 +38,6 @@ from vyos.qos import TrafficShaper from vyos.qos import TrafficShaperHFSC from vyos.utils.process import call from vyos.utils.dict import dict_search_recursive -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -97,63 +96,32 @@ def get_config(config=None): type_node = path.split(" ")[1] # return only interface type node set_dependents(type_node, conf, ifname) - if 'policy' in qos: - for policy in qos['policy']: - # when calling defaults() we need to use the real CLI node, thus we - # need a hyphen - policy_hyphen = policy.replace('_', '-') - - if policy in ['random_detect']: - for rd_name, rd_config in qos['policy'][policy].items(): - # There are eight precedence levels - ensure all are present - # to be filled later down with the appropriate default values - default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {}, - '4' : {}, '5' : {}, '6' : {}, '7' : {} }} - qos['policy']['random_detect'][rd_name] = dict_merge( - default_precedence, qos['policy']['random_detect'][rd_name]) - - for p_name, p_config in qos['policy'][policy].items(): - default_values = defaults(base + ['policy', policy_hyphen]) - - if policy in ['priority_queue']: - if 'default' not in p_config: - raise ConfigError(f'QoS policy {p_name} misses "default" class!') - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'class' in default_values: - del default_values['class'] - if 'precedence' in default_values: - del default_values['precedence'] - - qos['policy'][policy][p_name] = dict_merge( - default_values, qos['policy'][policy][p_name]) - - # class is another tag node which requires individual handling - if 'class' in p_config: - default_values = defaults(base + ['policy', policy_hyphen, 'class']) - for p_class in p_config['class']: - qos['policy'][policy][p_name]['class'][p_class] = dict_merge( - default_values, qos['policy'][policy][p_name]['class'][p_class]) - - if 'precedence' in p_config: - default_values = defaults(base + ['policy', policy_hyphen, 'precedence']) - # precedence values are a bit more complex as they are calculated - # under specific circumstances - thus we need to iterate two times. - # first blend in the defaults from XML / CLI - for precedence in p_config['precedence']: - qos['policy'][policy][p_name]['precedence'][precedence] = dict_merge( - default_values, qos['policy'][policy][p_name]['precedence'][precedence]) - # second calculate defaults based on actual dictionary - for precedence in p_config['precedence']: - max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold']) - if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]: - qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str( - int((9 + int(precedence)) * max_thr) // 18); - - if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]: - qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \ - str(int(4 * max_thr)) + for policy in qos.get('policy', []): + if policy in ['random_detect']: + for rd_name in list(qos['policy'][policy]): + # There are eight precedence levels - ensure all are present + # to be filled later down with the appropriate default values + default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {}, + '4' : {}, '5' : {}, '6' : {}, '7' : {} }} + qos['policy']['random_detect'][rd_name] = dict_merge( + default_precedence, qos['policy']['random_detect'][rd_name]) + + qos = conf.merge_defaults(qos, recursive=True) + + for policy in qos.get('policy', []): + for p_name, p_config in qos['policy'][policy].items(): + if 'precedence' in p_config: + # precedence settings are a bit more complex as they are + # calculated under specific circumstances: + for precedence in p_config['precedence']: + max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold']) + if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str( + int((9 + int(precedence)) * max_thr) // 18); + + if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \ + str(int(4 * max_thr)) return qos @@ -202,7 +170,9 @@ def verify(qos): queue_lim = int(precedence_config['queue_limit']) if queue_lim < max_tr: raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!') - + if policy_type in ['priority_queue']: + if 'default' not in policy_config: + raise ConfigError(f'Policy {policy} misses "default" class!') if 'default' in policy_config: if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin']: raise ConfigError('Bandwidth not defined for default traffic!') diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 3ff7880b2..a8fce8e01 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -22,12 +22,10 @@ from urllib3 import PoolManager from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_interface_exists from vyos.template import render from vyos.utils.process import call from vyos.utils.permission import chown -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -55,8 +53,7 @@ def get_config(config=None): salt['id'] = gethostname() # 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) - salt = dict_merge(default_values, salt) + salt = conf.merge_defaults(salt, recursive=True) if not conf.exists(base): return None diff --git a/src/conf_mode/service_config_sync.py b/src/conf_mode/service_config_sync.py index 5cde735a1..4b8a7f6ee 100755 --- a/src/conf_mode/service_config_sync.py +++ b/src/conf_mode/service_config_sync.py @@ -19,8 +19,6 @@ import json from pathlib import Path from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -42,12 +40,8 @@ def get_config(config=None): base = ['service', 'config-sync'] if not conf.exists(base): return None - config = conf.get_config_dict(base, - get_first_key=True, - no_tag_node_value_mangle=True) - - default_values = defaults(base) - config = dict_merge(default_values, config) + config = conf.get_config_dict(base, get_first_key=True, + with_recursive_defaults=True) return config diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index 7eb41ea87..b112add3f 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -20,10 +20,8 @@ from sys import exit from psutil import process_iter from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError config_file = '/run/conserver/conserver.cf' @@ -49,11 +47,7 @@ def get_config(config=None): # 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 + ['device']) - if 'device' in proxy: - for device in proxy['device']: - tmp = dict_merge(default_values, proxy['device'][device]) - proxy['device'][device] = tmp + proxy = conf.merge_defaults(proxy, recursive=True) return proxy diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index f6b80552b..276a71fcb 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,11 +39,9 @@ def get_config(config=None): if not conf.exists(base): return None - fastnetmon = 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) - fastnetmon = dict_merge(default_values, fastnetmon) + fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return fastnetmon diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 0269bedd9..40eb13e23 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -22,7 +22,6 @@ from sys import exit from shutil import rmtree from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.ifconfig import Section @@ -30,7 +29,6 @@ from vyos.template import render from vyos.utils.process import call from vyos.utils.permission import chown from vyos.utils.process import cmd -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -83,8 +81,7 @@ def get_config(config=None): # 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) - monitoring = dict_merge(default_values, monitoring) + monitoring = conf.merge_defaults(monitoring, recursive=True) monitoring['custom_scripts_dir'] = custom_scripts_dir monitoring['hostname'] = get_hostname() diff --git a/src/conf_mode/service_monitoring_zabbix-agent.py b/src/conf_mode/service_monitoring_zabbix-agent.py new file mode 100755 index 000000000..98d8a32ca --- /dev/null +++ b/src/conf_mode/service_monitoring_zabbix-agent.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +service_name = 'zabbix-agent2' +service_conf = f'/run/zabbix/{service_name}.conf' +systemd_override = r'/run/systemd/system/zabbix-agent2.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'monitoring', 'zabbix-agent'] + + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + + # Cut the / from the end, /tmp/ => /tmp + if 'directory' in config and config['directory'].endswith('/'): + config['directory'] = config['directory'][:-1] + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if config is None: + return + + if 'server' not in config: + raise ConfigError('Server is required!') + + +def generate(config): + # bail out early - looks like removal from running config + if config is None: + # Remove old config and return + config_files = [service_conf, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Write configuration file + render(service_conf, 'zabbix-agent/zabbix-agent.conf.j2', config) + render(systemd_override, 'zabbix-agent/10-override.conf.j2', config) + + return None + + +def apply(config): + call('systemctl daemon-reload') + if config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index fe33c43ea..dbb47de4e 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,40 +33,9 @@ def get_config(config=None): else: conf = Config() base = ['service', 'router-advert'] - rtradv = 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_interface_values = defaults(base + ['interface']) - # we deal with prefix, route defaults later on - if 'prefix' in default_interface_values: - del default_interface_values['prefix'] - if 'route' in default_interface_values: - del default_interface_values['route'] - - default_prefix_values = defaults(base + ['interface', 'prefix']) - default_route_values = defaults(base + ['interface', 'route']) - - if 'interface' in rtradv: - for interface in rtradv['interface']: - rtradv['interface'][interface] = dict_merge( - default_interface_values, rtradv['interface'][interface]) - - if 'prefix' in rtradv['interface'][interface]: - for prefix in rtradv['interface'][interface]['prefix']: - rtradv['interface'][interface]['prefix'][prefix] = dict_merge( - default_prefix_values, rtradv['interface'][interface]['prefix'][prefix]) - - if 'route' in rtradv['interface'][interface]: - for route in rtradv['interface'][interface]['route']: - rtradv['interface'][interface]['route'][route] = dict_merge( - default_route_values, rtradv['interface'][interface]['route'][route]) - - if 'name_server' in rtradv['interface'][interface]: - # always use a list when dealing with nameservers - eases the template generation - if isinstance(rtradv['interface'][interface]['name_server'], str): - rtradv['interface'][interface]['name_server'] = [ - rtradv['interface'][interface]['name_server']] + rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return rtradv diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py index 54b72e029..ba5e645f0 100755 --- a/src/conf_mode/service_sla.py +++ b/src/conf_mode/service_sla.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -44,11 +42,9 @@ def get_config(config=None): if not conf.exists(base): return None - sla = 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) - sla = dict_merge(default_values, sla) + sla = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # Ignore default XML values if config doesn't exists # Delete key from dict diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index b37d502c2..cf26bf9ce 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -23,12 +23,10 @@ from ipaddress import IPv4Network from ipaddress import IPv6Network from vyos.config import Config -from vyos.configdict import dict_merge from vyos.utils.process import call from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -47,10 +45,7 @@ def get_config(config=None): if not upnpd: return None - if 'rule' in upnpd: - default_member_values = defaults(base + ['rule']) - for rule,rule_config in upnpd['rule'].items(): - upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) + upnpd = conf.merge_defaults(upnpd, recursive=True) uuidgen = uuid.uuid1() upnpd.update({'uuid': uuidgen}) diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py index bbdb756bd..12ae4135e 100755 --- a/src/conf_mode/service_webproxy.py +++ b/src/conf_mode/service_webproxy.py @@ -20,14 +20,13 @@ from shutil import rmtree from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.config import config_dict_merge from vyos.template import render from vyos.utils.process import call from vyos.utils.permission import chmod_755 from vyos.utils.dict import dict_search from vyos.utils.file import write_file -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos.base import Warning from vyos import ConfigError from vyos import airbag @@ -125,7 +124,8 @@ def get_config(config=None): 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) + default_values = conf.get_config_defaults(**proxy.kwargs, + recursive=True) # if no authentication method is supplied, no need to add defaults if not dict_search('authentication.method', proxy): @@ -138,16 +138,7 @@ def get_config(config=None): proxy['squidguard_conf'] = squidguard_config_file proxy['squidguard_db_dir'] = squidguard_db_dir - # XXX: T2665: blend in proper cache-peer default values later - default_values.pop('cache_peer') - proxy = dict_merge(default_values, proxy) - - # XXX: T2665: blend in proper cache-peer default values - if 'cache_peer' in proxy: - default_values = defaults(base + ['cache-peer']) - for peer in proxy['cache_peer']: - proxy['cache_peer'][peer] = dict_merge(default_values, - proxy['cache_peer'][peer]) + proxy = config_dict_merge(default_values, proxy) return proxy diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 0f0d97ac3..7882f8510 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -29,9 +29,8 @@ from vyos.template import render from vyos.utils.process import call from vyos.utils.permission import chmod_755 from vyos.utils.dict import dict_search -from vyos.validate import is_addr_assigned +from vyos.utils.network import is_addr_assigned from vyos.version import get_version_data -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -70,26 +69,9 @@ def get_config(config=None): # 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) - - # We can not merge defaults for tagNodes - those need to be blended in - # per tagNode instance - if 'listen_address' in default_values: - del default_values['listen_address'] - if 'community' in default_values: - del default_values['community'] - if 'trap_target' in default_values: - del default_values['trap_target'] - if 'v3' in default_values: - del default_values['v3'] - snmp = dict_merge(default_values, snmp) + snmp = conf.merge_defaults(snmp, recursive=True) if 'listen_address' in snmp: - default_values = defaults(base + ['listen-address']) - for address in snmp['listen_address']: - snmp['listen_address'][address] = dict_merge( - default_values, snmp['listen_address'][address]) - # Always listen on localhost if an explicit address has been configured # This is a safety measure to not end up with invalid listen addresses # that are not configured on this system. See https://vyos.dev/T850 @@ -101,41 +83,6 @@ def get_config(config=None): tmp = {'::1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) - if 'community' in snmp: - default_values = defaults(base + ['community']) - if 'network' in default_values: - # convert multiple default networks to list - default_values['network'] = default_values['network'].split() - for community in snmp['community']: - snmp['community'][community] = dict_merge( - default_values, snmp['community'][community]) - - if 'trap_target' in snmp: - default_values = defaults(base + ['trap-target']) - for trap in snmp['trap_target']: - snmp['trap_target'][trap] = dict_merge( - default_values, snmp['trap_target'][trap]) - - if 'v3' in snmp: - default_values = defaults(base + ['v3']) - # tagNodes need to be merged in individually later on - for tmp in ['user', 'group', 'trap_target']: - del default_values[tmp] - snmp['v3'] = dict_merge(default_values, snmp['v3']) - - for user_group in ['user', 'group']: - if user_group in snmp['v3']: - default_values = defaults(base + ['v3', user_group]) - for tmp in snmp['v3'][user_group]: - snmp['v3'][user_group][tmp] = dict_merge( - default_values, snmp['v3'][user_group][tmp]) - - if 'trap_target' in snmp['v3']: - default_values = defaults(base + ['v3', 'trap-target']) - for trap in snmp['v3']['trap_target']: - snmp['v3']['trap_target'][trap] = dict_merge( - default_values, snmp['v3']['trap_target'][trap]) - return snmp def verify(snmp): diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 3b63fcb7d..ee5e1eca2 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -21,12 +21,10 @@ from syslog import syslog from syslog import LOG_INFO from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.utils.process import call from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -57,8 +55,8 @@ def get_config(config=None): # 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) - ssh = dict_merge(default_values, ssh) + ssh = conf.merge_defaults(ssh, recursive=True) + # pass config file path - used in override template ssh['config_file'] = config_file diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index cca996e4f..63dff0e36 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -24,7 +24,6 @@ from vyos.utils.process import call from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.system import sysctl_write -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,11 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'ip'] - opt = 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) - opt = dict_merge(default_values, opt) + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # When working with FRR we need to know the corresponding address-family opt['afi'] = 'ip' diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 22210c27a..8a4df11fa 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -24,7 +24,6 @@ from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.system import sysctl_write from vyos.utils.file import write_file -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,12 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'ipv6'] - opt = 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) - opt = dict_merge(default_values, opt) + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # When working with FRR we need to know the corresponding address-family opt['afi'] = 'ipv6' diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index afd75913e..02c97afaa 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -24,7 +24,6 @@ from sys import exit from time import sleep from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.defaults import directories from vyos.template import render @@ -35,7 +34,6 @@ from vyos.utils.process import call from vyos.utils.process import rc_cmd from vyos.utils.process import run from vyos.utils.process import DEVNULL -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -54,7 +52,7 @@ MAX_USER_UID: int = 59999 # LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec MAX_RADIUS_TIMEOUT: int = 50 # MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout) -MAX_RADIUS_COUNT: int = 25 +MAX_RADIUS_COUNT: int = 8 # Maximum number of supported TACACS servers MAX_TACACS_COUNT: int = 8 @@ -93,7 +91,9 @@ def get_config(config=None): conf = Config() base = ['system', 'login'] login = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, get_first_key=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # users no longer existing in the running configuration need to be deleted local_users = get_local_users() @@ -101,27 +101,9 @@ def get_config(config=None): if 'user' in login: cli_users = list(login['user']) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - default_values = defaults(base + ['user']) - for user in login['user']: - login['user'][user] = dict_merge(default_values, login['user'][user]) - - # Add TACACS global defaults - if 'tacacs' in login: - default_values = defaults(base + ['tacacs']) - if 'server' in default_values: - del default_values['server'] - login['tacacs'] = dict_merge(default_values, login['tacacs']) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - for backend in ['radius', 'tacacs']: - default_values = defaults(base + [backend, 'server']) - for server in dict_search(f'{backend}.server', login) or []: - login[backend]['server'][server] = dict_merge(default_values, - login[backend]['server'][server]) - + # prune TACACS global defaults if not set by user + if login.from_defaults(['tacacs']): + del login['tacacs'] # create a list of all users, cli and users all_users = list(set(local_users + cli_users)) diff --git a/src/conf_mode/system-logs.py b/src/conf_mode/system-logs.py index 12145d641..8ad4875d4 100755 --- a/src/conf_mode/system-logs.py +++ b/src/conf_mode/system-logs.py @@ -19,11 +19,9 @@ from sys import exit from vyos import ConfigError from vyos import airbag from vyos.config import Config -from vyos.configdict import dict_merge from vyos.logger import syslog from vyos.template import render from vyos.utils.dict import dict_search -from vyos.xml import defaults airbag.enable() # path to logrotate configs @@ -38,11 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'logs'] - default_values = defaults(base) - logs_config = conf.get_config_dict(base, - key_mangling=('-', '_'), - get_first_key=True) - logs_config = dict_merge(default_values, logs_config) + logs_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return logs_config diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py index 5172b492e..d92121b3d 100755 --- a/src/conf_mode/system-option.py +++ b/src/conf_mode/system-option.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -21,14 +21,12 @@ from sys import exit from time import sleep from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_source_interface from vyos.template import render from vyos.utils.process import cmd from vyos.utils.process import is_systemd_service_running -from vyos.validate import is_addr_assigned -from vyos.validate import is_intf_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_intf_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -36,6 +34,11 @@ airbag.enable() curlrc_config = r'/etc/curlrc' ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' +time_format_to_locale = { + '12-hour': 'en_US.UTF-8', + '24-hour': 'en_GB.UTF-8' +} + def get_config(config=None): if config: @@ -43,12 +46,9 @@ def get_config(config=None): else: conf = Config() base = ['system', 'option'] - options = 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) - options = dict_merge(default_values, options) + options = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return options @@ -143,6 +143,11 @@ def apply(options): else: cmd('systemctl disable root-partition-auto-resize.service') + # Time format 12|24-hour + if 'time_format' in options: + time_format = time_format_to_locale.get(options['time_format']) + cmd(f'localectl set-locale LC_TIME={time_format}') + if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index 19c87bcee..07fbb0734 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -19,12 +19,10 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.utils.process import call from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -50,43 +48,9 @@ def get_config(config=None): tmp = is_node_changed(conf, base + ['vrf']) if tmp: syslog.update({'restart_required': {}}) - # 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) - # XXX: some syslog default values can not be merged here (originating from - # a tagNode - remove and add them later per individual tagNode instance - if 'console' in default_values: - del default_values['console'] - for entity in ['global', 'user', 'host', 'file']: - if entity in default_values: - del default_values[entity] - - syslog = dict_merge(default_values, syslog) - - # XXX: add defaults for "console" tree - if 'console' in syslog and 'facility' in syslog['console']: - default_values = defaults(base + ['console', 'facility']) - for facility in syslog['console']['facility']: - syslog['console']['facility'][facility] = dict_merge(default_values, - syslog['console']['facility'][facility]) - - # XXX: add defaults for "host" tree - for syslog_type in ['host', 'user', 'file']: - # Bail out early if there is nothing to do - if syslog_type not in syslog: - continue - - default_values_host = defaults(base + [syslog_type]) - if 'facility' in default_values_host: - del default_values_host['facility'] - - for tmp, tmp_config in syslog[syslog_type].items(): - syslog[syslog_type][tmp] = dict_merge(default_values_host, syslog[syslog_type][tmp]) - if 'facility' in tmp_config: - default_values_facility = defaults(base + [syslog_type, 'facility']) - for facility in tmp_config['facility']: - syslog[syslog_type][tmp]['facility'][facility] = dict_merge(default_values_facility, - syslog[syslog_type][tmp]['facility'][facility]) + syslog = conf.merge_defaults(syslog, recursive=True) + if syslog.from_defaults(['global']): + del syslog['global'] return syslog diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index 87d587959..ebf9a113b 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -19,12 +19,10 @@ import re from pathlib import Path from vyos.config import Config -from vyos.configdict import dict_merge from vyos.utils.process import call from vyos.utils.file import read_file from vyos.utils.file import write_file from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -45,16 +43,12 @@ def get_config(config=None): if 'device' not in console: return console - # convert CLI values to system values - default_values = defaults(base + ['device']) for device, device_config in console['device'].items(): if 'speed' not in device_config and device.startswith('hvc'): # XEN console has a different default console speed console['device'][device]['speed'] = 38400 - else: - # Merge in XML defaults - the proper way to do it - console['device'][device] = dict_merge(default_values, - console['device'][device]) + + console = conf.merge_defaults(console, recursive=True) return console diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py index 9e3d41100..2df1bbb7a 100755 --- a/src/conf_mode/system_sflow.py +++ b/src/conf_mode/system_sflow.py @@ -19,11 +19,9 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.utils.process import call -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,26 +40,9 @@ def get_config(config=None): if not conf.exists(base): return None - sflow = 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) - - sflow = dict_merge(default_values, sflow) - - # Ignore default XML values if config doesn't exists - # Delete key from dict - if 'port' in sflow['server']: - del sflow['server']['port'] - - # Set default values per server - if 'server' in sflow: - for server in sflow['server']: - default_values = defaults(base + ['server']) - sflow['server'][server] = dict_merge(default_values, sflow['server'][server]) + sflow = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return sflow diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index 2735772dc..3ad346e2e 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -24,14 +24,12 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.template import render from vyos.template import is_ipv4 from vyos.utils.process import call from vyos.utils.permission import chmod_755 -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -48,11 +46,9 @@ def get_config(config=None): if not conf.exists(base): return None - tftpd = 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) - tftpd = dict_merge(default_values, tftpd) + tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return tftpd def verify(tftpd): diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index b0825d0ee..fa271cbdb 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -27,7 +27,7 @@ from vyos.base import Warning from vyos.config import Config from vyos.configdict import leaf_node_changed from vyos.configverify import verify_interface_exists -from vyos.configdict import dict_merge +from vyos.defaults import directories from vyos.ifconfig import Interface from vyos.pki import encode_public_key from vyos.pki import load_private_key @@ -39,12 +39,11 @@ from vyos.template import ip_from_cidr from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.template import render -from vyos.validate import is_ipv6_link_local +from vyos.utils.network import is_ipv6_link_local from vyos.utils.dict import dict_search from vyos.utils.dict import dict_search_args from vyos.utils.process import call from vyos.utils.process import run -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -69,7 +68,6 @@ KEY_PATH = f'{swanctl_dir}/private/' CA_PATH = f'{swanctl_dir}/x509ca/' CRL_PATH = f'{swanctl_dir}/x509crl/' -DHCP_BASE = '/var/lib/dhcp/dhclient' DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_waiting' def get_config(config=None): @@ -84,88 +82,23 @@ def get_config(config=None): # retrieve common dictionary keys ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=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) - # XXX: T2665: we must safely remove default values for tag nodes, those are - # added in a more fine grained way later on - del default_values['esp_group'] - del default_values['ike_group'] - del default_values['remote_access'] - del default_values['site_to_site'] - ipsec = dict_merge(default_values, ipsec) - - if 'esp_group' in ipsec: - default_values = defaults(base + ['esp-group']) - for group in ipsec['esp_group']: - ipsec['esp_group'][group] = dict_merge(default_values, - ipsec['esp_group'][group]) - if 'ike_group' in ipsec: - default_values = defaults(base + ['ike-group']) - # proposal is a tag node which may come with individual defaults per node - if 'proposal' in default_values: - del default_values['proposal'] - - for group in ipsec['ike_group']: - ipsec['ike_group'][group] = dict_merge(default_values, - ipsec['ike_group'][group]) - - if 'proposal' in ipsec['ike_group'][group]: - default_values = defaults(base + ['ike-group', 'proposal']) - for proposal in ipsec['ike_group'][group]['proposal']: - ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values, - ipsec['ike_group'][group]['proposal'][proposal]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('remote_access.connection', ipsec): - default_values = defaults(base + ['remote-access', 'connection']) - for rw in ipsec['remote_access']['connection']: - ipsec['remote_access']['connection'][rw] = dict_merge(default_values, - ipsec['remote_access']['connection'][rw]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('remote_access.radius.server', ipsec): - # Fist handle the "base" stuff like RADIUS timeout - default_values = defaults(base + ['remote-access', 'radius']) - if 'server' in default_values: - del default_values['server'] - ipsec['remote_access']['radius'] = dict_merge(default_values, - ipsec['remote_access']['radius']) - - # Take care about individual RADIUS servers implemented as tagNodes - this - # requires special treatment - default_values = defaults(base + ['remote-access', 'radius', 'server']) - for server in ipsec['remote_access']['radius']['server']: - ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values, - ipsec['remote_access']['radius']['server'][server]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('site_to_site.peer', ipsec): - default_values = defaults(base + ['site-to-site', 'peer']) - for peer in ipsec['site_to_site']['peer']: - ipsec['site_to_site']['peer'][peer] = dict_merge(default_values, - ipsec['site_to_site']['peer'][peer]) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) if tmp: - ipsec['l2tp'] = tmp - l2tp_defaults = defaults(l2tp_base) - ipsec['l2tp'] = dict_merge(l2tp_defaults, ipsec['l2tp']) + ipsec['l2tp'] = conf.merge_defaults(tmp, recursive=True) ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address']) ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024' ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1' @@ -433,8 +366,9 @@ def verify(ipsec): dhcp_interface = peer_conf['dhcp_interface'] verify_interface_exists(dhcp_interface) + dhcp_base = directories['isc_dhclient_dir'] - if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'): + if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}") address = get_dhcp_address(dhcp_interface) diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index e82862fa3..a039172c4 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -19,7 +19,6 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render @@ -28,7 +27,6 @@ from vyos.utils.network import check_port_availability from vyos.utils.process import is_systemd_service_running from vyos.utils.network import is_listen_port_bind_service from vyos.utils.dict import dict_search -from vyos.xml import defaults from vyos import ConfigError from passlib.hash import sha512_crypt from time import sleep @@ -47,66 +45,6 @@ radius_servers = cfg_dir + '/radius_servers' def get_hash(password): return sha512_crypt.hash(password) - - -def _default_dict_cleanup(origin: dict, default_values: dict) -> dict: - """ - https://vyos.dev/T2665 - Clear unnecessary key values in merged config by dict_merge function - :param origin: config - :type origin: dict - :param default_values: default values - :type default_values: dict - :return: merged dict - :rtype: dict - """ - if 'mode' in origin["authentication"] and "local" in \ - origin["authentication"]["mode"]: - del origin['authentication']['local_users']['username']['otp'] - if not origin["authentication"]["local_users"]["username"]: - raise ConfigError( - 'Openconnect authentication mode local requires at least one user') - default_ocserv_usr_values = \ - default_values['authentication']['local_users']['username']['otp'] - for user, params in origin['authentication']['local_users'][ - 'username'].items(): - # Not every configuration requires OTP settings - if origin['authentication']['local_users']['username'][user].get( - 'otp'): - origin['authentication']['local_users']['username'][user][ - 'otp'] = dict_merge(default_ocserv_usr_values, - origin['authentication'][ - 'local_users']['username'][user][ - 'otp']) - - if 'mode' in origin["authentication"] and "radius" in \ - origin["authentication"]["mode"]: - del origin['authentication']['radius']['server']['port'] - if not origin["authentication"]['radius']['server']: - raise ConfigError( - 'Openconnect authentication mode radius requires at least one RADIUS server') - default_values_radius_port = \ - default_values['authentication']['radius']['server']['port'] - for server, params in origin['authentication']['radius'][ - 'server'].items(): - if 'port' not in params: - params['port'] = default_values_radius_port - - if 'mode' in origin["accounting"] and "radius" in \ - origin["accounting"]["mode"]: - del origin['accounting']['radius']['server']['port'] - if not origin["accounting"]['radius']['server']: - raise ConfigError( - 'Openconnect accounting mode radius requires at least one RADIUS server') - default_values_radius_port = \ - default_values['accounting']['radius']['server']['port'] - for server, params in origin['accounting']['radius'][ - 'server'].items(): - if 'port' not in params: - params['port'] = default_values_radius_port - return origin - - def get_config(config=None): if config: conf = config @@ -116,16 +54,14 @@ def get_config(config=None): if not conf.exists(base): return None - ocserv = 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) - ocserv = dict_merge(default_values, ocserv) - # workaround a "know limitation" - https://vyos.dev/T2665 - ocserv = _default_dict_cleanup(ocserv, default_values) + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + if ocserv: ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) return ocserv @@ -142,6 +78,8 @@ def verify(ocserv): # Check accounting if "accounting" in ocserv: if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]: + if not origin["accounting"]['radius']['server']: + raise ConfigError('Openconnect accounting mode radius requires at least one RADIUS server') if "authentication" not in ocserv or "mode" not in ocserv["authentication"]: raise ConfigError('Accounting depends on OpenConnect authentication configuration') elif "radius" not in ocserv["authentication"]["mode"]: @@ -150,9 +88,13 @@ def verify(ocserv): # Check authentication if "authentication" in ocserv: if "mode" in ocserv["authentication"]: - if "local" in ocserv["authentication"]["mode"]: - if "radius" in ocserv["authentication"]["mode"]: + if ("local" in ocserv["authentication"]["mode"] and + "radius" in ocserv["authentication"]["mode"]): raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration') + if "radius" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]['radius']['server']: + raise ConfigError('Openconnect authentication mode radius requires at least one RADIUS server') + if "local" in ocserv["authentication"]["mode"]: if not ocserv["authentication"]["local_users"]: raise ConfigError('openconnect mode local required at least one user') if not ocserv["authentication"]["local_users"]["username"]: diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 80ce1e8e3..82c2f236e 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -22,7 +22,6 @@ from re import search as re_search, MULTILINE as re_M from vyos.config import Config from vyos.configdep import set_dependents, call_dependents -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.ifconfig import Section from vyos.utils.boot import boot_configuration_complete @@ -31,7 +30,6 @@ from vyos.utils.process import rc_cmd from vyos.utils.system import sysctl_read from vyos.utils.system import sysctl_apply from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -94,28 +92,18 @@ def get_config(config=None): if not conf.exists(base): return {'removed_ifaces': removed_ifaces} - config = conf.get_config_dict(base, + config = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True, - key_mangling=('-', '_'), - no_tag_node_value_mangle=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) - if 'interface' in default_values: - del default_values['interface'] - config = dict_merge(default_values, config) + with_recursive_defaults=True) if 'interface' in config: for iface, iface_config in config['interface'].items(): - default_values_iface = defaults(base + ['interface']) - config['interface'][iface] = dict_merge(default_values_iface, config['interface'][iface]) # add an interface to a list of interfaces that need # to be reinitialized after the commit set_dependents('ethernet', conf, iface) - # Get PCI address auto - for iface, iface_config in config['interface'].items(): + # Get PCI address auto if iface_config['pci'] == 'auto': config['interface'][iface]['pci'] = _get_pci_address_by_interface(iface) |