diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/container.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/dns_dynamic.py | 36 | ||||
-rwxr-xr-x | src/conf_mode/service_ipoe-server.py | 102 | ||||
-rwxr-xr-x | src/conf_mode/service_pppoe-server.py | 18 | ||||
-rwxr-xr-x | src/conf_mode/vpn_l2tp.py | 47 | ||||
-rwxr-xr-x | src/conf_mode/vpn_pptp.py | 39 | ||||
-rwxr-xr-x | src/conf_mode/vpn_sstp.py | 13 |
7 files changed, 102 insertions, 155 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 53e25f422..59d11c5a3 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -355,7 +355,7 @@ def generate_run_arguments(name, container_config): else: ip_param += f' --ip {address}' - return f'{container_base_cmd} --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip() + return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip() def generate(container): # bail out early - looks like removal from running config diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index c4dcb76ed..99fa8feee 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os - +import re from sys import exit from vyos.base import Warning @@ -30,6 +30,9 @@ airbag.enable() config_file = r'/run/ddclient/ddclient.conf' systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' +# Dynamic interfaces that might not exist when the configuration is loaded +dynamic_interfaces = ('pppoe', 'sstpc') + # Protocols that require zone zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', 'nfsn', 'nsupdate'] @@ -86,17 +89,29 @@ def verify(dyndns): if field not in config: raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}') - # If dyndns address is an interface, ensure that it exists + # If dyndns address is an interface, ensure + # that the interface exists (or just warn if dynamic interface) # and that web-options are not set if config['address'] != 'web': # exclude check interface for dynamic interfaces - interface_filter = ('pppoe', 'sstpc') - if config['address'].startswith(interface_filter): - Warning(f'interface {config["address"]} does not exist!') + if config['address'].startswith(dynamic_interfaces): + Warning(f'Interface "{config["address"]}" does not exist yet and cannot ' + f'be used for Dynamic DNS service "{service}" until it is up!') else: verify_interface_exists(config['address']) if 'web_options' in config: - raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address') + raise ConfigError(f'"web-options" is applicable only when using HTTP(S) ' + f'web request to obtain the IP address') + + # Warn if using checkip.dyndns.org, as it does not support HTTPS + # See: https://github.com/ddclient/ddclient/issues/597 + if 'web_options' in config: + if 'url' not in config['web_options']: + raise ConfigError(f'"url" in "web-options" {error_msg_req} ' + f'with protocol "{config["protocol"]}"') + elif re.search("^(https?://)?checkip\.dyndns\.org", config['web_options']['url']): + Warning(f'"checkip.dyndns.org" does not support HTTPS requests for IP address ' + f'lookup. Please use a different IP address lookup service.') # RFC2136 uses 'key' instead of 'password' if config['protocol'] != 'nsupdate' and 'password' not in config: @@ -124,13 +139,16 @@ def verify(dyndns): if config['ip_version'] == 'both': if config['protocol'] not in dualstack_supported: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} with protocol "{config["protocol"]}"') + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'with protocol "{config["protocol"]}"') # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}" with protocol "{config["protocol"]}"') + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'for "{config["server"]}" with protocol "{config["protocol"]}"') if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): - raise ConfigError(f'"expiry-time" must be greater than "wait-time" for Dynamic DNS service "{service}"') + raise ConfigError(f'"expiry-time" must be greater than "wait-time" for ' + f'Dynamic DNS service "{service}"') return None diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index b70e32373..36f00dec5 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -15,17 +15,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import jmespath from sys import exit from vyos.config import Config from vyos.configdict import get_accel_dict -from vyos.configverify import verify_accel_ppp_base_service from vyos.configverify import verify_interface_exists from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import get_pools_in_order +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,87 +35,6 @@ ipoe_conf = '/run/accel-pppd/ipoe.conf' ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets' -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: - 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 ValueError( - f"Pool '{next_pool_name}' not defined in configuration data" - ) - - if not resolved_pools: - raise ValueError("Circular reference in configuration data") - - for pool in resolved_pools: - unresolved_pools.pop(pool) - - return pools - - def get_config(config=None): if config: conf = config @@ -128,18 +47,11 @@ def get_config(config=None): # retrieve common dictionary keys ipoe = get_accel_dict(conf, base, ipoe_chap_secrets) - if jmespath.search('client_ip_pool.name', ipoe): - dict_named_pools = jmespath.search('client_ip_pool.name', ipoe) + if dict_search('client_ip_pool', ipoe): # Multiple named pools require ordered values T5099 - ipoe['ordered_named_pools'] = get_pools_in_order(dict_named_pools) - # T5099 'next-pool' option - if jmespath.search('client_ip_pool.name.*.next_pool', ipoe): - for pool, pool_config in ipoe['client_ip_pool']['name'].items(): - if 'next_pool' in pool_config: - ipoe['first_named_pool'] = pool - ipoe['first_named_pool_subnet'] = pool_config - break + ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe)) + ipoe['server_type'] = 'ipoe' return ipoe @@ -156,9 +68,7 @@ def verify(ipoe): raise ConfigError('Option "client-subnet" incompatible with "vlan"!' 'Use "ipoe client-ip-pool" instead.') - #verify_accel_ppp_base_service(ipoe, local_users=False) - # IPoE server does not have 'gateway' option in the CLI - # we cannot use configverify.py verify_accel_ppp_base_service for ipoe-server + verify_accel_ppp_ip_pool(ipoe) if dict_search('authentication.mode', ipoe) == 'radius': if not dict_search('authentication.radius.server', ipoe): diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 87660c127..7c624f034 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -21,13 +21,16 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_accel_dict from vyos.configdict import is_node_changed -from vyos.configverify import verify_accel_ppp_base_service from vyos.configverify import verify_interface_exists from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError from vyos import airbag + airbag.enable() pppoe_conf = r'/run/accel-pppd/pppoe.conf' @@ -45,6 +48,10 @@ def get_config(config=None): # retrieve common dictionary keys pppoe = get_accel_dict(conf, base, pppoe_chap_secrets) + if dict_search('client_ip_pool', pppoe): + # Multiple named pools require ordered values T5099 + pppoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pppoe)) + # reload-or-restart does not implemented in accel-ppp # use this workaround until it will be implemented # https://phabricator.accel-ppp.org/T3 @@ -53,7 +60,7 @@ def get_config(config=None): is_node_changed(conf, base + ['interface'])] if any(conditions): pppoe.update({'restart_required': {}}) - + pppoe['server_type'] = 'pppoe' return pppoe def verify(pppoe): @@ -72,12 +79,7 @@ def verify(pppoe): for interface in pppoe['interface']: verify_interface_exists(interface) - # local ippool and gateway settings config checks - if not (dict_search('client_ip_pool.subnet', pppoe) or - (dict_search('client_ip_pool.name', pppoe) or - (dict_search('client_ip_pool.start', pppoe) and - dict_search('client_ip_pool.stop', pppoe)))): - print('Warning: No PPPoE client pool defined') + verify_accel_ppp_ip_pool(pppoe) if dict_search('authentication.radius.dynamic_author.server', pppoe): if not dict_search('authentication.radius.dynamic_author.key', pppoe): diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 6232ce64a..9a022d93c 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -21,15 +21,16 @@ from copy import deepcopy from stat import S_IRUSR, S_IWUSR, S_IRGRP from sys import exit -from ipaddress import ip_network - from vyos.config import Config from vyos.template import is_ipv4 from vyos.template import render from vyos.utils.process import call from vyos.utils.system import get_half_cpus +from vyos.utils.dict import dict_search from vyos.utils.network import check_port_availability from vyos.utils.network import is_listen_port_bind_service +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError from vyos import airbag @@ -43,7 +44,7 @@ default_config_data = { 'auth_ppp_mppe': 'prefer', 'auth_proto': ['auth_mschap_v2'], 'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template - 'client_ip_pool': None, + 'client_ip_pool': {}, 'client_ip_subnets': [], 'client_ipv6_pool': [], 'client_ipv6_pool_configured': False, @@ -246,13 +247,14 @@ def get_config(config=None): conf.set_level(base_path) if conf.exists(['client-ip-pool']): - if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']): - start = conf.return_value(['client-ip-pool', 'start']) - stop = conf.return_value(['client-ip-pool', 'stop']) - l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0) + for pool_name in conf.list_nodes(['client-ip-pool']): + l2tp['client_ip_pool'][pool_name] = {} + l2tp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range']) + l2tp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool']) - if conf.exists(['client-ip-pool', 'subnet']): - l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet']) + if dict_search('client_ip_pool', l2tp): + # Multiple named pools require ordered values T5099 + l2tp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', l2tp)) if conf.exists(['client-ipv6-pool', 'prefix']): l2tp['client_ipv6_pool_configured'] = True @@ -281,23 +283,15 @@ def get_config(config=None): l2tp['client_ipv6_delegate_prefix'].append(tmp) + if conf.exists(['default-pool']): + l2tp['default_pool'] = conf.return_value(['default-pool']) + if conf.exists(['mtu']): l2tp['mtu'] = conf.return_value(['mtu']) # gateway address if conf.exists(['gateway-address']): l2tp['gateway_address'] = conf.return_value(['gateway-address']) - else: - # calculate gw-ip-address - if conf.exists(['client-ip-pool', 'start']): - # use start ip as gw-ip-address - l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start']) - - elif conf.exists(['client-ip-pool', 'subnet']): - # use first ip address from first defined pool - subnet = conf.return_values(['client-ip-pool', 'subnet'])[0] - subnet = ip_network(subnet) - l2tp['gateway_address'] = str(list(subnet.hosts())[0]) # LNS secret if conf.exists(['lns', 'shared-secret']): @@ -330,9 +324,13 @@ def get_config(config=None): if conf.exists(['ppp-options', 'ipv6-peer-intf-id']): l2tp['ppp_ipv6_peer_intf_id'] = conf.return_value(['ppp-options', 'ipv6-peer-intf-id']) + l2tp['server_type'] = 'l2tp' return l2tp + + + def verify(l2tp): if not l2tp: return None @@ -366,10 +364,11 @@ def verify(l2tp): not is_listen_port_bind_service(int(port), 'accel-pppd'): raise ConfigError(f'"{proto}" port "{port}" is used by another service') - # check for the existence of a client ip pool - if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']): - raise ConfigError( - "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool") + if l2tp['auth_mode'] == 'local' or l2tp['auth_mode'] == 'noauth': + if not l2tp['client_ip_pool']: + raise ConfigError( + "L2TP local auth mode requires local client-ip-pool to be configured!") + verify_accel_ppp_ip_pool(l2tp) # check ipv6 if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']: diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index d542f57fe..6243c3ed3 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -21,10 +21,14 @@ from copy import deepcopy from stat import S_IRUSR, S_IWUSR, S_IRGRP from sys import exit + from vyos.config import Config from vyos.template import render from vyos.utils.system import get_half_cpus from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError from vyos import airbag @@ -54,7 +58,7 @@ default_pptp = { 'outside_addr': '', 'dnsv4': [], 'wins': [], - 'client_ip_pool': '', + 'client_ip_pool': {}, 'mtu': '1436', 'auth_proto' : ['auth_mschap_v2'], 'ppp_mppe' : 'prefer', @@ -205,22 +209,24 @@ def get_config(config=None): conf.set_level(base_path) if conf.exists(['client-ip-pool']): - if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']): - start = conf.return_value(['client-ip-pool', 'start']) - stop = conf.return_value(['client-ip-pool', 'stop']) - pptp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0) + for pool_name in conf.list_nodes(['client-ip-pool']): + pptp['client_ip_pool'][pool_name] = {} + pptp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range']) + pptp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool']) + + if dict_search('client_ip_pool', pptp): + # Multiple named pools require ordered values T5099 + pptp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pptp)) + + if conf.exists(['default-pool']): + pptp['default_pool'] = conf.return_value(['default-pool']) if conf.exists(['mtu']): pptp['mtu'] = conf.return_value(['mtu']) # gateway address if conf.exists(['gateway-address']): - pptp['gw_ip'] = conf.return_value(['gateway-address']) - else: - # calculate gw-ip-address - if conf.exists(['client-ip-pool', 'start']): - # use start ip as gw-ip-address - pptp['gateway_address'] = conf.return_value(['client-ip-pool', 'start']) + pptp['gateway_address'] = conf.return_value(['gateway-address']) if conf.exists(['authentication', 'require']): # clear default list content, now populate with actual CLI values @@ -238,6 +244,7 @@ def get_config(config=None): if conf.exists(['authentication', 'mppe']): pptp['ppp_mppe'] = conf.return_value(['authentication', 'mppe']) + pptp['server_type'] = 'pptp' return pptp @@ -248,21 +255,25 @@ def verify(pptp): if pptp['auth_mode'] == 'local': if not pptp['local_users']: raise ConfigError('PPTP local auth mode requires local users to be configured!') - for user in pptp['local_users']: username = user['name'] if not user['password']: raise ConfigError(f'Password required for local user "{username}"') - elif pptp['auth_mode'] == 'radius': if len(pptp['radius_server']) == 0: raise ConfigError('RADIUS authentication requires at least one server') - for radius in pptp['radius_server']: if not radius['key']: server = radius['server'] raise ConfigError(f'Missing RADIUS secret key for server "{ server }"') + if pptp['auth_mode'] == 'local' or pptp['auth_mode'] == 'noauth': + if not pptp['client_ip_pool']: + raise ConfigError( + "PPTP local auth mode requires local client-ip-pool to be configured!") + + verify_accel_ppp_ip_pool(pptp) + if len(pptp['dnsv4']) > 2: raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index e98d8385b..ac053cc76 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.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 @@ -21,13 +21,15 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_accel_dict from vyos.configdict import dict_merge -from vyos.configverify import verify_accel_ppp_base_service from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.utils.process import call from vyos.utils.network import check_port_availability from vyos.utils.dict import dict_search +from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_ip_pool +from vyos.accel_ppp_util import get_pools_in_order from vyos.utils.network import is_listen_port_bind_service from vyos.utils.file import write_file from vyos import ConfigError @@ -53,13 +55,17 @@ def get_config(config=None): # retrieve common dictionary keys sstp = get_accel_dict(conf, base, sstp_chap_secrets) + if dict_search('client_ip_pool', sstp): + # Multiple named pools require ordered values T5099 + sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp)) if sstp: sstp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - + sstp['server_type'] = 'sstp' return sstp + def verify(sstp): if not sstp: return None @@ -75,6 +81,7 @@ def verify(sstp): if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp: raise ConfigError('Client IP subnet required') + verify_accel_ppp_ip_pool(sstp) # # SSL certificate checks # |