diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/accel_ppp_util.py | 193 | ||||
-rw-r--r-- | python/vyos/configdiff.py | 24 | ||||
-rw-r--r-- | python/vyos/configsession.py | 6 | ||||
-rw-r--r-- | python/vyos/configverify.py | 66 | ||||
-rw-r--r-- | python/vyos/remote.py | 30 | ||||
-rw-r--r-- | python/vyos/system/disk.py | 11 | ||||
-rw-r--r-- | python/vyos/system/image.py | 5 | ||||
-rw-r--r-- | python/vyos/template.py | 29 | ||||
-rw-r--r-- | python/vyos/utils/network.py | 17 |
9 files changed, 282 insertions, 99 deletions
diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py new file mode 100644 index 000000000..757d447a2 --- /dev/null +++ b/python/vyos/accel_ppp_util.py @@ -0,0 +1,193 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +# The sole purpose of this module is to hold common functions used in +# all kinds of implementations to verify the CLI configuration. +# It is started by migrating the interfaces to the new get_config_dict() +# approach which will lead to a lot of code that can be reused. + +# NOTE: imports should be as local as possible to the function which +# makes use of it! + +from vyos import ConfigError +from vyos.utils.dict import dict_search + + +def get_pools_in_order(data: dict) -> list: + """Return a list of dictionaries representing pool data in the order + in which they should be allocated. Pool must be defined before we can + use it with 'next-pool' option. + + Args: + data: A dictionary of pool data, where the keys are pool names and the + values are dictionaries containing the 'subnet' key and the optional + 'next_pool' key. + + Returns: + list: A list of dictionaries + + Raises: + ValueError: If a 'next_pool' key references a pool name that + has not been defined. + ValueError: If a circular reference is found in the 'next_pool' keys. + + Example: + config_data = { + ... 'first-pool': { + ... 'next_pool': 'second-pool', + ... 'subnet': '192.0.2.0/25' + ... }, + ... 'second-pool': { + ... 'next_pool': 'third-pool', + ... 'subnet': '203.0.113.0/25' + ... }, + ... 'third-pool': { + ... 'subnet': '198.51.100.0/24' + ... }, + ... 'foo': { + ... 'subnet': '100.64.0.0/24', + ... 'next_pool': 'second-pool' + ... } + ... } + + % get_pools_in_order(config_data) + [{'third-pool': {'subnet': '198.51.100.0/24'}}, + {'second-pool': {'next_pool': 'third-pool', 'subnet': '203.0.113.0/25'}}, + {'first-pool': {'next_pool': 'second-pool', 'subnet': '192.0.2.0/25'}}, + {'foo': {'next_pool': 'second-pool', 'subnet': '100.64.0.0/24'}}] + """ + pools = [] + unresolved_pools = {} + + for pool, pool_config in data.items(): + if "next_pool" not in pool_config or not pool_config["next_pool"]: + pools.insert(0, {pool: pool_config}) + else: + unresolved_pools[pool] = pool_config + + while unresolved_pools: + resolved_pools = [] + + for pool, pool_config in unresolved_pools.items(): + next_pool_name = pool_config["next_pool"] + + if any(p for p in pools if next_pool_name in p): + index = next( + (i for i, p in enumerate(pools) if next_pool_name in p), None + ) + pools.insert(index + 1, {pool: pool_config}) + resolved_pools.append(pool) + elif next_pool_name in unresolved_pools: + # next pool not yet resolved + pass + else: + raise ConfigError( + f"Pool '{next_pool_name}' not defined in configuration data" + ) + + if not resolved_pools: + raise ConfigError("Circular reference in configuration data") + + for pool in resolved_pools: + unresolved_pools.pop(pool) + + return pools + + +def verify_accel_ppp_base_service(config, local_users=True): + """ + Common helper function which must be used by all Accel-PPP services based + on get_config_dict() + """ + # vertify auth settings + if local_users and dict_search("authentication.mode", config) == "local": + if ( + dict_search("authentication.local_users", config) is None + or dict_search("authentication.local_users", config) == {} + ): + raise ConfigError( + "Authentication mode local requires local users to be configured!" + ) + + for user in dict_search("authentication.local_users.username", config): + user_config = config["authentication"]["local_users"]["username"][user] + + if "password" not in user_config: + raise ConfigError(f'Password required for local user "{user}"') + + if "rate_limit" in user_config: + # if up/download is set, check that both have a value + if not {"upload", "download"} <= set(user_config["rate_limit"]): + raise ConfigError( + f'User "{user}" has rate-limit configured for only one ' + "direction but both upload and download must be given!" + ) + + elif dict_search("authentication.mode", config) == "radius": + if not dict_search("authentication.radius.server", config): + raise ConfigError("RADIUS authentication requires at least one server") + + for server in dict_search("authentication.radius.server", config): + radius_config = config["authentication"]["radius"]["server"][server] + if "key" not in radius_config: + raise ConfigError(f'Missing RADIUS secret key for server "{server}"') + + if "name_server_ipv4" in config: + if len(config["name_server_ipv4"]) > 2: + raise ConfigError( + "Not more then two IPv4 DNS name-servers " "can be configured" + ) + + if "name_server_ipv6" in config: + if len(config["name_server_ipv6"]) > 3: + raise ConfigError( + "Not more then three IPv6 DNS name-servers " "can be configured" + ) + + if "client_ipv6_pool" in config: + ipv6_pool = config["client_ipv6_pool"] + if "delegate" in ipv6_pool: + if "prefix" not in ipv6_pool: + raise ConfigError( + 'IPv6 "delegate" also requires "prefix" to be defined!' + ) + + for delegate in ipv6_pool["delegate"]: + if "delegation_prefix" not in ipv6_pool["delegate"][delegate]: + raise ConfigError("delegation-prefix length required!") + + +def verify_accel_ppp_ip_pool(vpn_config): + """ + Common helper function which must be used by Accel-PPP + services (pptp, l2tp, sstp, pppoe) to verify client-ip-pool + """ + if dict_search("client_ip_pool", vpn_config): + for pool_name, pool_config in vpn_config["client_ip_pool"].items(): + next_pool = dict_search(f"next_pool", pool_config) + if next_pool: + if next_pool not in vpn_config["client_ip_pool"]: + raise ConfigError(f'Next pool "{next_pool}" does not exist') + if not dict_search(f"range", pool_config): + raise ConfigError( + f'Pool "{pool_name}" does not contain range but next-pool exists' + ) + + if not dict_search("gateway_address", vpn_config): + raise ConfigError("Server requires gateway-address to be configured!") + default_pool = dict_search("default_pool", vpn_config) + if default_pool: + if default_pool not in dict_search("client_ip_pool", vpn_config): + raise ConfigError(f'Default pool "{default_pool}" does not exists') diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index 1ec2dfafe..03b06c6d9 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -165,6 +165,30 @@ class ConfigDiff(object): return True return False + def node_changed_presence(self, path=[]) -> bool: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + path = self._make_path(path) + before = self._diff_tree.left.exists(path) + after = self._diff_tree.right.exists(path) + return (before and not after) or (not before and after) + + def node_changed_children(self, path=[]) -> list: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + path = self._make_path(path) + add = self._diff_tree.add + sub = self._diff_tree.sub + children = set() + if add.exists(path): + children.update(add.list_nodes(path)) + if sub.exists(path): + children.update(sub.list_nodes(path)) + + return list(children) + def get_child_nodes_diff_str(self, path=[]): ret = {'add': {}, 'change': {}, 'delete': {}} diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 9802ebae4..90842b749 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -30,8 +30,10 @@ SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] -INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] -REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] +INSTALL_IMAGE = ['/usr/libexec/vyos/op_mode/image_installer.py', + '--action', 'add', '--no-prompt', '--image-path'] +REMOVE_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py', + '--action', 'delete', '--no-prompt', '--image-name'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 52f9238b8..27055c863 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -385,72 +385,6 @@ def verify_vlan_config(config): verify_mtu_parent(c_vlan, config) verify_mtu_parent(c_vlan, s_vlan) -def verify_accel_ppp_base_service(config, local_users=True): - """ - Common helper function which must be used by all Accel-PPP services based - on get_config_dict() - """ - # vertify auth settings - if local_users and dict_search('authentication.mode', config) == 'local': - if (dict_search(f'authentication.local_users', config) is None or - dict_search(f'authentication.local_users', config) == {}): - raise ConfigError( - 'Authentication mode local requires local users to be configured!') - - for user in dict_search('authentication.local_users.username', config): - user_config = config['authentication']['local_users']['username'][user] - - if 'password' not in user_config: - raise ConfigError(f'Password required for local user "{user}"') - - if 'rate_limit' in user_config: - # if up/download is set, check that both have a value - if not {'upload', 'download'} <= set(user_config['rate_limit']): - raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \ - 'direction but both upload and download must be given!') - - elif dict_search('authentication.mode', config) == 'radius': - if not dict_search('authentication.radius.server', config): - raise ConfigError('RADIUS authentication requires at least one server') - - for server in dict_search('authentication.radius.server', config): - radius_config = config['authentication']['radius']['server'][server] - if 'key' not in radius_config: - raise ConfigError(f'Missing RADIUS secret key for server "{server}"') - - # Check global gateway or gateway in named pool - gateway = False - if 'gateway_address' in config: - gateway = True - else: - if 'client_ip_pool' in config: - if dict_search_recursive(config, 'gateway_address', ['client_ip_pool', 'name']): - for _, v in config['client_ip_pool']['name'].items(): - if 'gateway_address' in v: - gateway = True - break - if not gateway: - raise ConfigError('Server requires gateway-address to be configured!') - - if 'name_server_ipv4' in config: - if len(config['name_server_ipv4']) > 2: - raise ConfigError('Not more then two IPv4 DNS name-servers ' \ - 'can be configured') - - if 'name_server_ipv6' in config: - if len(config['name_server_ipv6']) > 3: - raise ConfigError('Not more then three IPv6 DNS name-servers ' \ - 'can be configured') - - if 'client_ipv6_pool' in config: - ipv6_pool = config['client_ipv6_pool'] - if 'delegate' in ipv6_pool: - if 'prefix' not in ipv6_pool: - raise ConfigError('IPv6 "delegate" also requires "prefix" to be defined!') - - for delegate in ipv6_pool['delegate']: - if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]: - raise ConfigError('delegation-prefix length required!') def verify_diffie_hellman_length(file, min_keysize): """ Verify Diffie-Hellamn keypair length given via file. It must be greater diff --git a/python/vyos/remote.py b/python/vyos/remote.py index 8b90e4530..fec44b571 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -46,17 +46,6 @@ from vyos.version import get_version CHUNK_SIZE = 8192 -@contextmanager -def umask(mask: int): - """ - Context manager that temporarily sets the process umask. - """ - oldmask = os.umask(mask) - try: - yield - finally: - os.umask(oldmask) - class InteractivePolicy(MissingHostKeyPolicy): """ Paramiko policy for interactively querying the user on whether to proceed @@ -88,6 +77,17 @@ class SourceAdapter(HTTPAdapter): num_pools=connections, maxsize=maxsize, block=block, source_address=self._source_pair) +@contextmanager +def umask(mask: int): + """ + Context manager that temporarily sets the process umask. + """ + import os + oldmask = os.umask(mask) + try: + yield + finally: + os.umask(oldmask) def check_storage(path, size): """ @@ -436,8 +436,8 @@ def urlc(urlstring, *args, **kwargs): except KeyError: raise ValueError(f'Unsupported URL scheme: "{scheme}"') -def download(local_path, urlstring, progressbar=False, raise_error=False, check_space=False, - source_host='', source_port=0, timeout=10.0): +def download(local_path, urlstring, progressbar=False, check_space=False, + source_host='', source_port=0, timeout=10.0, raise_error=False): try: progressbar = progressbar and is_interactive() urlc(urlstring, progressbar, check_space, source_host, source_port, timeout).download(local_path) @@ -448,14 +448,12 @@ def download(local_path, urlstring, progressbar=False, raise_error=False, check_ except KeyboardInterrupt: print_error('\nDownload aborted by user.') -def upload(local_path, urlstring, progressbar=False, raise_error=False, +def upload(local_path, urlstring, progressbar=False, source_host='', source_port=0, timeout=10.0): try: progressbar = progressbar and is_interactive() urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path) except Exception as err: - if raise_error: - raise print_error(f'Unable to upload "{urlstring}": {err}') except KeyboardInterrupt: print_error('\nUpload aborted by user.') diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py index 49e6b5c5e..f8e0fd1bf 100644 --- a/python/vyos/system/disk.py +++ b/python/vyos/system/disk.py @@ -150,7 +150,7 @@ def filesystem_create(partition: str, fstype: str) -> None: def partition_mount(partition: str, path: str, fsype: str = '', - overlay_params: dict[str, str] = {}) -> None: + overlay_params: dict[str, str] = {}) -> bool: """Mount a partition into a path Args: @@ -159,6 +159,9 @@ def partition_mount(partition: str, fsype (str): optionally, set fstype ('squashfs', 'overlay', 'iso9660') overlay_params (dict): optionally, set overlay parameters. Defaults to None. + + Returns: + bool: True on success """ if fsype in ['squashfs', 'iso9660']: command: str = f'mount -o loop,ro -t {fsype} {partition} {path}' @@ -171,7 +174,11 @@ def partition_mount(partition: str, else: command = f'mount {partition} {path}' - run(command) + rc = run(command) + if rc == 0: + return True + + return False def partition_umount(partition: str = '', path: str = '') -> None: diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py index 6c4e3bba5..c03ce02d5 100644 --- a/python/vyos/system/image.py +++ b/python/vyos/system/image.py @@ -261,3 +261,8 @@ def is_live_boot() -> bool: if boot_type == 'live': return True return False + +def is_running_as_container() -> bool: + if Path('/.dockerenv').exists(): + return True + return False diff --git a/python/vyos/template.py b/python/vyos/template.py index f79c7545e..2d4beeec2 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -664,8 +664,8 @@ def nat_static_rule(rule_conf, rule_id, nat_type): from vyos.nat import parse_nat_static_rule return parse_nat_static_rule(rule_conf, rule_id, nat_type) -@register_filter('conntrack_ignore_rule') -def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): +@register_filter('conntrack_rule') +def conntrack_rule(rule_conf, rule_id, action, ipv6=False): ip_prefix = 'ip6' if ipv6 else 'ip' def_suffix = '6' if ipv6 else '' output = [] @@ -676,11 +676,15 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): output.append(f'iifname {ifname}') if 'protocol' in rule_conf: - proto = rule_conf['protocol'] + if action != 'timeout': + proto = rule_conf['protocol'] + else: + for protocol, protocol_config in rule_conf['protocol'].items(): + proto = protocol output.append(f'meta l4proto {proto}') tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if tcp_flags: + if tcp_flags and action != 'timeout': from vyos.firewall import parse_tcp_flags output.append(parse_tcp_flags(tcp_flags)) @@ -743,11 +747,24 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): output.append(f'{proto} {prefix}port {operator} @P_{group_name}') - output.append('counter notrack') - output.append(f'comment "ignore-{rule_id}"') + if action == 'ignore': + output.append('counter notrack') + output.append(f'comment "ignore-{rule_id}"') + else: + output.append(f'counter ct timeout set ct-timeout-{rule_id}') + output.append(f'comment "timeout-{rule_id}"') return " ".join(output) +@register_filter('conntrack_ct_policy') +def conntrack_ct_policy(protocol_conf): + output = [] + for item in protocol_conf: + item_value = protocol_conf[item] + output.append(f'{item}: {item_value}') + + return ", ".join(output) + @register_filter('range_to_regex') def range_to_regex(num_range): """Convert range of numbers or list of ranges diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 6a5de5423..2a0808fca 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -61,14 +61,17 @@ def get_vrf_members(vrf: str) -> list: """ import json from vyos.utils.process import cmd - if not interface_exists(vrf): - raise ValueError(f'VRF "{vrf}" does not exist!') - output = cmd(f'ip --json --brief link show master {vrf}') - answer = json.loads(output) interfaces = [] - for data in answer: - if 'ifname' in data: - interfaces.append(data.get('ifname')) + try: + if not interface_exists(vrf): + raise ValueError(f'VRF "{vrf}" does not exist!') + output = cmd(f'ip --json --brief link show vrf {vrf}') + answer = json.loads(output) + for data in answer: + if 'ifname' in data: + interfaces.append(data.get('ifname')) + except: + pass return interfaces def get_interface_vrf(interface): |