diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/firewall.py | 28 | ||||
-rwxr-xr-x | src/conf_mode/pki.py | 129 | ||||
-rwxr-xr-x | src/conf_mode/policy.py | 10 | ||||
-rwxr-xr-x | src/conf_mode/protocols_eigrp.py | 123 | ||||
-rwxr-xr-x | src/conf_mode/protocols_rip.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/service_event_handler.py | 91 | ||||
-rwxr-xr-x | src/conf_mode/service_sla.py | 113 |
7 files changed, 480 insertions, 16 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 6924bf555..335098bf1 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -26,7 +26,13 @@ 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.firewall import get_ips_domains_dict +from vyos.firewall import nft_add_set_elements +from vyos.firewall import nft_flush_set +from vyos.firewall import nft_init_set +from vyos.firewall import nft_update_set_elements from vyos.template import render +from vyos.util import call from vyos.util import cmd from vyos.util import dict_search_args from vyos.util import process_named_running @@ -408,6 +414,26 @@ def apply(firewall): if install_result == 1: raise ConfigError('Failed to apply firewall') + # set fireall group domain-group xxx + if 'group' in firewall: + if 'domain_group' in firewall['group']: + # T970 Enable a resolver (systemd daemon) that checks + # domain-group addresses and update entries for domains by timeout + # If router loaded without internet connection or for synchronization + call('systemctl restart vyos-domain-group-resolve.service') + for group, group_config in firewall['group']['domain_group'].items(): + domains = [] + for address in group_config['address']: + domains.append(address) + # Add elements to domain-group, try to resolve domain => ip + # and add elements to nft set + ip_dict = get_ips_domains_dict(domains) + elements = sum(ip_dict.values(), []) + nft_init_set(group) + nft_add_set_elements(group, elements) + else: + call('systemctl stop vyos-domain-group-resolve.service') + if 'state_policy' in firewall and not state_policy_rule_exists(): for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']: cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY') diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index efa3578b4..29ed7b1b7 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -29,12 +29,60 @@ from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.util import ask_input +from vyos.util import call +from vyos.util import dict_search_args from vyos.util import dict_search_recursive from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() +# keys to recursively search for under specified path, script to call if update required +sync_search = [ + { + 'keys': ['certificate'], + 'path': ['service', 'https'], + 'script': '/usr/libexec/vyos/conf_mode/https.py' + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['interfaces', 'ethernet'], + 'script': '/usr/libexec/vyos/conf_mode/interfaces-ethernet.py' + }, + { + 'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'], + 'path': ['interfaces', 'openvpn'], + 'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py' + }, + { + 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'], + 'path': ['vpn', 'ipsec'], + 'script': '/usr/libexec/vyos/conf_mode/vpn_ipsec.py' + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['vpn', 'openconnect'], + 'script': '/usr/libexec/vyos/conf_mode/vpn_openconnect.py' + }, + { + 'keys': ['certificate', 'ca_certificate'], + 'path': ['vpn', 'sstp'], + 'script': '/usr/libexec/vyos/conf_mode/vpn_sstp.py' + } +] + +# key from other config nodes -> key in pki['changed'] and pki +sync_translate = { + 'certificate': 'certificate', + 'ca_certificate': 'ca', + 'dh_params': 'dh', + 'local_key': 'key_pair', + 'remote_key': 'key_pair', + 'shared_secret_key': 'openvpn', + 'auth_key': 'openvpn', + 'crypt_key': 'openvpn' +} + def get_config(config=None): if config: conf = config @@ -47,12 +95,21 @@ def get_config(config=None): no_tag_node_value_mangle=True) pki['changed'] = {} - tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_'), recursive=True) if tmp: pki['changed'].update({'ca' : tmp}) - tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_'), recursive=True) if tmp: pki['changed'].update({'certificate' : tmp}) + tmp = node_changed(conf, base + ['dh'], key_mangling=('-', '_'), recursive=True) + if tmp: pki['changed'].update({'dh' : tmp}) + + tmp = node_changed(conf, base + ['key-pair'], key_mangling=('-', '_'), recursive=True) + if tmp: pki['changed'].update({'key_pair' : tmp}) + + tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], key_mangling=('-', '_'), recursive=True) + if tmp: pki['changed'].update({'openvpn' : tmp}) + # We only merge on the defaults of there is a configuration at all if conf.exists(base): default_values = defaults(base) @@ -164,17 +221,30 @@ def verify(pki): if 'changed' in pki: # if the list is getting longer, we can move to a dict() and also embed the # search key as value from line 173 or 176 - for cert_type in ['ca', 'certificate']: - if not cert_type in pki['changed']: - continue - for certificate in pki['changed'][cert_type]: - if cert_type not in pki or certificate not in pki['changed'][cert_type]: - if cert_type == 'ca': - if certificate in dict_search_recursive(pki['system'], 'ca_certificate'): - raise ConfigError(f'CA certificate "{certificate}" is still in use!') - elif cert_type == 'certificate': - if certificate in dict_search_recursive(pki['system'], 'certificate'): - raise ConfigError(f'Certificate "{certificate}" is still in use!') + for search in sync_search: + for key in search['keys']: + changed_key = sync_translate[key] + + if changed_key not in pki['changed']: + continue + + for item_name in pki['changed'][changed_key]: + node_present = False + if changed_key == 'openvpn': + node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name) + else: + node_present = dict_search_args(pki, changed_key, item_name) + + if not node_present: + search_dict = dict_search_args(pki['system'], *search['path']) + + if not search_dict: + continue + + for found_name, found_path in dict_search_recursive(search_dict, key): + if found_name == item_name: + path_str = " ".join(search['path'] + found_path) + raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"') return None @@ -188,7 +258,38 @@ def apply(pki): if not pki: return None - # XXX: restart services if the content of a certificate changes + if 'changed' in pki: + for search in sync_search: + for key in search['keys']: + changed_key = sync_translate[key] + + if changed_key not in pki['changed']: + continue + + for item_name in pki['changed'][changed_key]: + node_present = False + if changed_key == 'openvpn': + node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name) + else: + node_present = dict_search_args(pki, changed_key, item_name) + + if node_present: + search_dict = dict_search_args(pki['system'], *search['path']) + + if not search_dict: + continue + + for found_name, found_path in dict_search_recursive(search_dict, key): + if found_name == item_name: + path_str = ' '.join(search['path'] + found_path) + print(f'pki: Updating config: {path_str} {found_name}') + + script = search['script'] + if found_path[0] == 'interfaces': + ifname = found_path[2] + call(f'VYOS_TAGNODE_VALUE={ifname} {script}') + else: + call(script) return None diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index ef6008140..3008a20e0 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -150,6 +150,16 @@ def verify(policy): tmp = dict_search('match.ipv6.address.prefix_list', rule_config) if tmp and tmp not in policy.get('prefix_list6', []): raise ConfigError(f'prefix-list6 {tmp} does not exist!') + + # Specified access_list6 in nexthop must exist + tmp = dict_search('match.ipv6.nexthop.access_list', rule_config) + if tmp and tmp not in policy.get('access_list6', []): + raise ConfigError(f'access_list6 {tmp} does not exist!') + + # Specified prefix-list6 in nexthop must exist + tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config) + if tmp and tmp not in policy.get('prefix_list6', []): + raise ConfigError(f'prefix-list6 {tmp} does not exist!') # When routing protocols are active some use prefix-lists, route-maps etc. # to apply the systems routing policy to the learned or redistributed routes. diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py new file mode 100755 index 000000000..c1a1a45e1 --- /dev/null +++ b/src/conf_mode/protocols_eigrp.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import exit +from sys import argv + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'eigrp'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path + eigrp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: eigrp.update({'vrf' : vrf}) + + if not conf.exists(base): + eigrp.update({'deleted' : ''}) + if not vrf: + # We are running in the default VRF context, thus we can not delete + # our main EIGRP instance if there are dependent EIGRP VRF instances. + eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + return eigrp + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify(). + # + # XXX: one MUST always call this without the key_mangling() option! See + # vyos.configverify.verify_common_route_maps() for more information. + tmp = conf.get_config_dict(['policy']) + # Merge policy dict into "regular" config dict + eigrp = dict_merge(tmp, eigrp) + + import pprint + pprint.pprint(eigrp) + return eigrp + +def verify(eigrp): + pass + +def generate(eigrp): + if not eigrp or 'deleted' in eigrp: + return None + + eigrp['protocol'] = 'eigrp' # required for frr/vrf.route-map.frr.j2 + eigrp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', eigrp) + eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) + +def apply(eigrp): + eigrp_daemon = 'eigrpd' + zebra_daemon = 'zebra' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'(\s+)?ip protocol eigrp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + if 'frr_zebra_config' in eigrp: + frr_cfg.add_before(frr.default_add_before, eigrp['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) + + # Generate empty helper string which can be ammended to FRR commands, it + # will be either empty (default VRF) or contain the "vrf <name" statement + vrf = '' + if 'vrf' in eigrp: + vrf = ' vrf ' + eigrp['vrf'] + + frr_cfg.load_configuration(eigrp_daemon) + frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) + if 'frr_eigrpd_config' in eigrp: + frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config']) + frr_cfg.commit_configuration(eigrp_daemon) + + return None + +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/protocols_rip.py b/src/conf_mode/protocols_rip.py index a76c1ce76..c78d90396 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 diff --git a/src/conf_mode/service_event_handler.py b/src/conf_mode/service_event_handler.py new file mode 100755 index 000000000..5440d1056 --- /dev/null +++ b/src/conf_mode/service_event_handler.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 json +from pathlib import Path + +from vyos.config import Config +from vyos.util import call, dict_search +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + +service_name = 'vyos-event-handler' +service_conf = Path(f'/run/{service_name}.conf') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'event-handler', 'event'] + config = conf.get_config_dict(base, + get_first_key=True, + no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + for name, event_config in config.items(): + if not dict_search('filter.pattern', event_config) or not dict_search( + 'script.path', event_config): + raise ConfigError( + 'Event-handler: both pattern and script path items are mandatory' + ) + + if dict_search('script.environment.message', event_config): + raise ConfigError( + 'Event-handler: "message" environment variable is reserved for log message text' + ) + + +def generate(config): + if not config: + # Remove old config and return + service_conf.unlink(missing_ok=True) + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + return None + + +def apply(config): + 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_sla.py b/src/conf_mode/service_sla.py new file mode 100755 index 000000000..e7c3ca59c --- /dev/null +++ b/src/conf_mode/service_sla.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +owamp_config_dir = '/etc/owamp-server' +owamp_config_file = f'{owamp_config_dir}/owamp-server.conf' +systemd_override_owamp = r'/etc/systemd/system/owamp-server.d/20-override.conf' + +twamp_config_dir = '/etc/twamp-server' +twamp_config_file = f'{twamp_config_dir}/twamp-server.conf' +systemd_override_twamp = r'/etc/systemd/system/twamp-server.d/20-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'sla'] + 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) + + # Ignore default XML values if config doesn't exists + # Delete key from dict + if not conf.exists(base + ['owamp-server']): + del sla['owamp_server'] + if not conf.exists(base + ['twamp-server']): + del sla['twamp_server'] + + return sla + +def verify(sla): + if not sla: + return None + +def generate(sla): + if not sla: + return None + + render(owamp_config_file, 'sla/owamp-server.conf.j2', sla) + render(systemd_override_owamp, 'sla/owamp-override.conf.j2', sla) + + render(twamp_config_file, 'sla/twamp-server.conf.j2', sla) + render(systemd_override_twamp, 'sla/twamp-override.conf.j2', sla) + + return None + +def apply(sla): + owamp_service = 'owamp-server.service' + twamp_service = 'twamp-server.service' + + call('systemctl daemon-reload') + + if not sla or 'owamp_server' not in sla: + call(f'systemctl stop {owamp_service}') + + if os.path.exists(owamp_config_file): + os.unlink(owamp_config_file) + + if not sla or 'twamp_server' not in sla: + call(f'systemctl stop {twamp_service}') + if os.path.exists(twamp_config_file): + os.unlink(twamp_config_file) + + if sla and 'owamp_server' in sla: + call(f'systemctl reload-or-restart {owamp_service}') + + if sla and 'twamp_server' in sla: + call(f'systemctl reload-or-restart {twamp_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |