diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2 index 7fe4b17bf..290e6235d 100644 --- a/data/templates/accel-ppp/pptp.config.j2 +++ b/data/templates/accel-ppp/pptp.config.j2 @@ -1,83 +1,75 @@ ### generated by accel_pptp.py ### [modules] log_syslog pptp shaper {# Common authentication backend definitions #} {% include 'accel-ppp/config_modules_auth_mode.j2' %} ippool {# Common IPv6 definitions #} {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} -{% if authentication.require is vyos_defined %} -{% if authentication.require == 'chap' %} -auth_chap_md5 -{% elif authentication.require == 'mschap' %} -auth_mschap_v1 -{% else %} -auth_{{ authentication.require.replace('-', '_') }} -{% endif %} -{% endif %} +{% include 'accel-ppp/config_modules_auth_protocols.j2' %} [core] thread-count={{ thread_count }} [common] {% if max_concurrent_sessions is vyos_defined %} max-starting={{ max_concurrent_sessions }} {% endif %} [log] syslog=accel-pptp,daemon copy=1 level=5 {# Common DNS name-server definition #} {% include 'accel-ppp/config_name_server.j2' %} {% if wins_server is vyos_defined %} [wins] {% for server in wins_server %} wins{{ loop.index }}={{ server }} {% endfor %} {% endif %} [pptp] ifname=pptp%d {% if outside_address is vyos_defined %} bind={{ outside_address }} {% endif %} verbose=1 ppp-max-mtu={{ mtu }} mppe={{ authentication.mppe }} echo-interval=10 echo-failure=3 {% if default_pool is vyos_defined %} ip-pool={{ default_pool }} {% endif %} {% if default_ipv6_pool is vyos_defined %} ipv6-pool={{ default_ipv6_pool }} ipv6-pool-delegate={{ default_ipv6_pool }} {% endif %} [client-ip-range] 0.0.0.0/0 {# Common IP pool definitions #} {% include 'accel-ppp/config_ip_pool.j2' %} {# Common IPv6 pool definitions #} {% include 'accel-ppp/config_ipv6_pool.j2' %} {# Common ppp-options definitions #} {% include 'accel-ppp/ppp-options.j2' %} {# Common chap-secrets and RADIUS server/option definitions #} {% include 'accel-ppp/config_chap_secrets_radius.j2' %} {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} [cli] tcp=127.0.0.1:2003 diff --git a/interface-definitions/include/version/pptp-version.xml.i b/interface-definitions/include/version/pptp-version.xml.i index 3e1482ecc..a877d77ff 100644 --- a/interface-definitions/include/version/pptp-version.xml.i +++ b/interface-definitions/include/version/pptp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/pptp-version.xml.i --> -<syntaxVersion component='pptp' version='4'></syntaxVersion> +<syntaxVersion component='pptp' version='5'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index eeec2aeef..23d6e54d1 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -1,191 +1,191 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="service"> <children> <node name="ipoe-server" owner="${vyos_conf_scripts_dir}/service_ipoe-server.py"> <properties> <help>Internet Protocol over Ethernet (IPoE) Server</help> <priority>900</priority> </properties> <children> + <node name="authentication"> + <properties> + <help>Client authentication methods</help> + </properties> + <children> + #include <include/accel-ppp/auth-mode.xml.i> + <tagNode name="interface"> + <properties> + <help>Network interface for client MAC addresses</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + <tagNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + <children> + <node name="rate-limit"> + <properties> + <help>Upload/Download speed limits</help> + </properties> + <children> + <leafNode name="upload"> + <properties> + <help>Upload bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="download"> + <properties> + <help>Download bandwidth limit in kbits/sec</help> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="vlan"> + <properties> + <help>VLAN monitor for automatic creation of VLAN interfaces</help> + <valueHelp> + <format>u32:1-4094</format> + <description>Client VLAN id</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4094"/> + </constraint> + <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> <tagNode name="interface"> <properties> <help>Interface to listen dhcp or unclassified packets</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> <children> <leafNode name="mode"> <properties> <help>Client connectivity mode</help> <completionHelp> <list>l2 l3</list> </completionHelp> <valueHelp> <format>l2</format> <description>Client located on same interface as server</description> </valueHelp> <valueHelp> <format>l3</format> <description>Client located behind a router</description> </valueHelp> <constraint> <regex>(l2|l3)</regex> </constraint> </properties> <defaultValue>l2</defaultValue> </leafNode> <leafNode name="network"> <properties> <help>Enables clients to share the same network or each client has its own vlan</help> <completionHelp> <list>shared vlan</list> </completionHelp> <constraint> <regex>(shared|vlan)</regex> </constraint> <valueHelp> <format>shared</format> <description>Multiple clients share the same network</description> </valueHelp> <valueHelp> <format>vlan</format> <description>One VLAN per client</description> </valueHelp> </properties> <defaultValue>shared</defaultValue> </leafNode> <leafNode name="client-subnet"> <properties> <help>Client address pool</help> <valueHelp> <format>ipv4net</format> <description>IPv4 address and prefix length</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> </constraint> </properties> </leafNode> <node name="external-dhcp"> <properties> <help>DHCP requests will be forwarded</help> </properties> <children> <leafNode name="dhcp-relay"> <properties> <help>DHCP Server the request will be redirected to.</help> <valueHelp> <format>ipv4</format> <description>IPv4 address of the DHCP Server</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> <leafNode name="giaddr"> <properties> <help>Relay Agent IPv4 Address</help> <valueHelp> <format>ipv4</format> <description>Gateway IP address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> </children> </node> #include <include/accel-ppp/vlan.xml.i> </children> </tagNode> #include <include/accel-ppp/max-concurrent-sessions.xml.i> #include <include/name-server-ipv4-ipv6.xml.i> #include <include/accel-ppp/client-ip-pool.xml.i> #include <include/accel-ppp/gateway-address-multi.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> - <node name="authentication"> - <properties> - <help>Client authentication methods</help> - </properties> - <children> - #include <include/accel-ppp/auth-mode.xml.i> - <tagNode name="interface"> - <properties> - <help>Network interface for client MAC addresses</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - </properties> - <children> - <tagNode name="mac"> - <properties> - <help>Media Access Control (MAC) address</help> - <valueHelp> - <format>macaddr</format> - <description>Hardware (MAC) address</description> - </valueHelp> - <constraint> - <validator name="mac-address"/> - </constraint> - </properties> - <children> - <node name="rate-limit"> - <properties> - <help>Upload/Download speed limits</help> - </properties> - <children> - <leafNode name="upload"> - <properties> - <help>Upload bandwidth limit in kbits/sec</help> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> - </leafNode> - <leafNode name="download"> - <properties> - <help>Download bandwidth limit in kbits/sec</help> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> - </leafNode> - </children> - </node> - <leafNode name="vlan"> - <properties> - <help>VLAN monitor for automatic creation of VLAN interfaces</help> - <valueHelp> - <format>u32:1-4094</format> - <description>Client VLAN id</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-4094"/> - </constraint> - <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage> - </properties> - </leafNode> - </children> - </tagNode> - </children> - </tagNode> - <node name="radius"> - <children> - #include <include/accel-ppp/radius-additions-rate-limit.xml.i> - </children> - </node> - #include <include/radius-auth-server-ipv4.xml.i> - #include <include/accel-ppp/radius-additions.xml.i> - </children> - </node> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> </children> </node> </children> </node> </interfaceDefinition> diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in index 942690bca..6148e3269 100644 --- a/interface-definitions/vpn_l2tp.xml.in +++ b/interface-definitions/vpn_l2tp.xml.in @@ -1,146 +1,146 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="vpn"> <children> <node name="l2tp" owner="${vyos_conf_scripts_dir}/vpn_l2tp.py"> <properties> <help>L2TP Virtual Private Network (VPN)</help> <priority>902</priority> </properties> <children> <node name="remote-access"> <properties> <help>Remote access L2TP VPN</help> </properties> <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access L2TP VPN</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> #include <include/accel-ppp/max-concurrent-sessions.xml.i> #include <include/accel-ppp/mtu-128-16384.xml.i> <leafNode name="mtu"> <defaultValue>1436</defaultValue> </leafNode> <leafNode name="outside-address"> <properties> <help>External IP address to which VPN clients will connect</help> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> #include <include/accel-ppp/gateway-address.xml.i> #include <include/name-server-ipv4-ipv6.xml.i> <node name="lns"> <properties> <help>L2TP Network Server (LNS)</help> </properties> <children> <leafNode name="shared-secret"> <properties> <help>Tunnel password used to authenticate the client (LAC)</help> </properties> </leafNode> <leafNode name="host-name"> <properties> <help>Sent to the client (LAC) in the Host-Name attribute</help> <constraint> #include <include/constraint/host-name.xml.i> </constraint> <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> </properties> </leafNode> </children> </node> <node name="ipsec-settings"> <properties> <help>Internet Protocol Security (IPsec) for remote access L2TP VPN</help> </properties> <children> <node name="authentication"> <properties> <help>IPsec authentication settings</help> </properties> <children> <leafNode name="mode"> <properties> <help>Authentication mode for IPsec</help> <valueHelp> <format>pre-shared-secret</format> <description>Use pre-shared secret for IPsec authentication</description> </valueHelp> <valueHelp> <format>x509</format> <description>Use X.509 certificate for IPsec authentication</description> </valueHelp> <constraint> <regex>(pre-shared-secret|x509)</regex> </constraint> <completionHelp> <list>pre-shared-secret x509</list> </completionHelp> </properties> </leafNode> #include <include/ipsec/authentication-pre-shared-secret.xml.i> #include <include/ipsec/authentication-x509.xml.i> </children> </node> <leafNode name="ike-lifetime"> <properties> <help>IKE lifetime</help> <valueHelp> <format>u32:30-86400</format> <description>IKE lifetime in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 30-86400"/> </constraint> </properties> <defaultValue>3600</defaultValue> </leafNode> <leafNode name="lifetime"> <properties> <help>ESP lifetime</help> <valueHelp> <format>u32:30-86400</format> <description>IKE lifetime in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 30-86400"/> </constraint> </properties> <defaultValue>3600</defaultValue> </leafNode> #include <include/ipsec/esp-group.xml.i> #include <include/ipsec/ike-group.xml.i> </children> </node> #include <include/accel-ppp/wins-server.xml.i> #include <include/accel-ppp/client-ip-pool.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> #include <include/generic-description.xml.i> #include <include/dhcp-interface.xml.i> - <node name="authentication"> - <properties> - <help>Authentication for remote access L2TP VPN</help> - </properties> - <children> - #include <include/accel-ppp/auth-protocols.xml.i> - #include <include/accel-ppp/auth-mode.xml.i> - #include <include/accel-ppp/auth-local-users.xml.i> - #include <include/radius-auth-server-ipv4.xml.i> - #include <include/accel-ppp/radius-additions.xml.i> - <node name="radius"> - <children> - #include <include/accel-ppp/radius-additions-rate-limit.xml.i> - </children> - </node> - </children> - </node> #include <include/accel-ppp/ppp-options.xml.i> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in index d23086c02..2e2a3bec4 100644 --- a/interface-definitions/vpn_pptp.xml.in +++ b/interface-definitions/vpn_pptp.xml.in @@ -1,122 +1,60 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="vpn"> <children> <node name="pptp" owner="${vyos_conf_scripts_dir}/vpn_pptp.py"> <properties> <help>Point to Point Tunneling Protocol (PPTP) Virtual Private Network (VPN)</help> <priority>901</priority> </properties> <children> <node name="remote-access"> <properties> <help>Remote access PPTP VPN</help> </properties> <children> + <node name="authentication"> + <properties> + <help>Authentication for remote access PPTP VPN</help> + </properties> + <children> + #include <include/accel-ppp/auth-local-users.xml.i> + #include <include/accel-ppp/auth-mode.xml.i> + #include <include/accel-ppp/auth-protocols.xml.i> + #include <include/radius-auth-server-ipv4.xml.i> + #include <include/accel-ppp/radius-additions.xml.i> + <node name="radius"> + <children> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> + </children> + </node> + </children> + </node> #include <include/accel-ppp/max-concurrent-sessions.xml.i> #include <include/accel-ppp/mtu-128-16384.xml.i> <leafNode name="mtu"> <defaultValue>1436</defaultValue> </leafNode> <leafNode name="outside-address"> <properties> <help>External IP address to which VPN clients will connect</help> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> #include <include/accel-ppp/gateway-address.xml.i> #include <include/name-server-ipv4-ipv6.xml.i> #include <include/accel-ppp/wins-server.xml.i> #include <include/accel-ppp/client-ip-pool.xml.i> - <node name="authentication"> - <properties> - <help>Authentication for remote access PPTP VPN</help> - </properties> - <children> - <leafNode name="require"> - <properties> - <help>Authentication protocol for remote access peer PPTP VPN</help> - <completionHelp> - <list>pap chap mschap mschap-v2</list> - </completionHelp> - <valueHelp> - <format>pap</format> - <description>Require the peer to authenticate itself using PAP [Password Authentication Protocol].</description> - </valueHelp> - <valueHelp> - <format>chap</format> - <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description> - </valueHelp> - <valueHelp> - <format>mschap</format> - <description>Require the peer to authenticate itself using CHAP [Challenge Handshake Authentication Protocol].</description> - </valueHelp> - <valueHelp> - <format>mschap-v2</format> - <description>Require the peer to authenticate itself using MS-CHAPv2 [Microsoft Challenge Handshake Authentication Protocol, Version 2].</description> - </valueHelp> - <constraint> - <regex>(pap|chap|mschap|mschap-v2)</regex> - </constraint> - </properties> - <defaultValue>mschap-v2</defaultValue> - </leafNode> - #include <include/accel-ppp/auth-mode.xml.i> - <node name="local-users"> - <properties> - <help>Local user authentication for remote access PPTP VPN</help> - </properties> - <children> - <tagNode name="username"> - <properties> - <help>User name for authentication</help> - </properties> - <children> - #include <include/generic-disable-node.xml.i> - <leafNode name="password"> - <properties> - <help>Password for authentication</help> - </properties> - </leafNode> - <leafNode name="static-ip"> - <properties> - <help>Static client IP address</help> - </properties> - <defaultValue>*</defaultValue> - </leafNode> - </children> - </tagNode> - </children> - </node> - <node name="radius"> - <children> - #include <include/accel-ppp/radius-additions-rate-limit.xml.i> - </children> - </node> - #include <include/radius-auth-server-ipv4.xml.i> - #include <include/accel-ppp/radius-additions.xml.i> - <node name="radius"> - <children> - <leafNode name="timeout"> - <defaultValue>30</defaultValue> - </leafNode> - <leafNode name="acct-timeout"> - <defaultValue>30</defaultValue> - </leafNode> - </children> - </node> - </children> - </node> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> #include <include/accel-ppp/ppp-options.xml.i> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py index d60402e48..bd0c46a19 100644 --- a/python/vyos/accel_ppp_util.py +++ b/python/vyos/accel_ppp_util.py @@ -1,202 +1,206 @@ # Copyright 2023-2024 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.base import Warning 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 dict_search('authentication.radius.dynamic_author.server', config): + if not dict_search('authentication.radius.dynamic_author.key', config): + raise ConfigError('DAE/CoA server key required!') + 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" ) 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 and client-ipv6-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): Warning("IPv4 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') if 'client_ipv6_pool' in vpn_config: for ipv6_pool, ipv6_pool_config in vpn_config['client_ipv6_pool'].items(): if 'delegate' in ipv6_pool_config and 'prefix' not in ipv6_pool_config: raise ConfigError( f'IPv6 delegate-prefix requires IPv6 prefix to be configured in "{ipv6_pool}"!') if dict_search('authentication.mode', vpn_config) in ['local', 'noauth']: if not dict_search('client_ip_pool', vpn_config) and not dict_search( 'client_ipv6_pool', vpn_config): raise ConfigError( "Local auth mode requires local client-ip-pool or client-ipv6-pool to be configured!") if dict_search('client_ip_pool', vpn_config) and not dict_search( 'default_pool', vpn_config): Warning("'default-pool' is not defined") if dict_search('client_ipv6_pool', vpn_config) and not dict_search( 'default_ipv6_pool', vpn_config): Warning("'default-ipv6-pool' is not defined") diff --git a/smoketest/scripts/cli/test_vpn_l2tp.py b/smoketest/scripts/cli/test_vpn_l2tp.py index e253f0e49..c3b5b500d 100755 --- a/smoketest/scripts/cli/test_vpn_l2tp.py +++ b/smoketest/scripts/cli/test_vpn_l2tp.py @@ -1,100 +1,100 @@ #!/usr/bin/env python3 # # Copyright (C) 2023-2024 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 re import unittest from base_accel_ppp_test import BasicAccelPPPTest from configparser import ConfigParser from vyos.utils.process import cmd class TestVPNL2TPServer(BasicAccelPPPTest.TestCase): @classmethod def setUpClass(cls): cls._base_path = ['vpn', 'l2tp', 'remote-access'] cls._config_file = '/run/accel-pppd/l2tp.conf' cls._chap_secrets = '/run/accel-pppd/l2tp.chap-secrets' cls._protocol_section = 'l2tp' # call base-classes classmethod super(TestVPNL2TPServer, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestVPNL2TPServer, cls).tearDownClass() def basic_protocol_specific_config(self): pass def test_l2tp_server_authentication_protocols(self): - # Test configuration of local authentication for PPPoE server + # Test configuration of local authentication protocols self.basic_config() # explicitly test mschap-v2 - no special reason self.set( ['authentication', 'protocols', 'mschap-v2']) # commit changes self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True) conf.read(self._config_file) self.assertEqual(conf['modules']['auth_mschap_v2'], None) def test_vpn_l2tp_dependence_ipsec_swanctl(self): # Test config vpn for tasks T3843 and T5926 base_path = ['vpn', 'l2tp', 'remote-access'] # make precondition self.cli_set(['interfaces', 'dummy', 'dum0', 'address', '203.0.113.1/32']) self.cli_set(['vpn', 'ipsec', 'interface', 'dum0']) self.cli_commit() # check ipsec apply to swanctl self.assertEqual('', cmd('echo vyos | sudo -S swanctl -L ')) self.cli_set(base_path + ['authentication', 'local-users', 'username', 'foo', 'password', 'bar']) self.cli_set(base_path + ['authentication', 'mode', 'local']) self.cli_set(base_path + ['authentication', 'protocols', 'chap']) self.cli_set(base_path + ['client-ip-pool', 'first', 'range', '10.200.100.100-10.200.100.110']) self.cli_set(base_path + ['description', 'VPN - REMOTE']) self.cli_set(base_path + ['name-server', '1.1.1.1']) self.cli_set(base_path + ['ipsec-settings', 'authentication', 'mode', 'pre-shared-secret']) self.cli_set(base_path + ['ipsec-settings', 'authentication', 'pre-shared-secret', 'SeCret']) self.cli_set(base_path + ['ipsec-settings', 'ike-lifetime', '8600']) self.cli_set(base_path + ['ipsec-settings', 'lifetime', '3600']) self.cli_set(base_path + ['outside-address', '203.0.113.1']) self.cli_set(base_path + ['gateway-address', '203.0.113.1']) self.cli_commit() # check l2tp apply to swanctl self.assertTrue('l2tp_remote_access:' in cmd('echo vyos | sudo -S swanctl -L ')) self.cli_delete(['vpn', 'l2tp']) self.cli_commit() # check l2tp apply to swanctl after delete config self.assertEqual('', cmd('echo vyos | sudo -S swanctl -L ')) # need to correct tearDown test self.basic_config() self.cli_set(base_path + ['authentication', 'protocols', 'chap']) self.cli_commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_pptp.py b/smoketest/scripts/cli/test_vpn_pptp.py index 40dcb7f80..ac46d210d 100755 --- a/smoketest/scripts/cli/test_vpn_pptp.py +++ b/smoketest/scripts/cli/test_vpn_pptp.py @@ -1,204 +1,44 @@ #!/usr/bin/env python3 # # Copyright (C) 2023-2024 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 re import unittest from configparser import ConfigParser from vyos.utils.process import cmd from base_accel_ppp_test import BasicAccelPPPTest from vyos.template import is_ipv4 class TestVPNPPTPServer(BasicAccelPPPTest.TestCase): @classmethod def setUpClass(cls): cls._base_path = ['vpn', 'pptp', 'remote-access'] cls._config_file = '/run/accel-pppd/pptp.conf' cls._chap_secrets = '/run/accel-pppd/pptp.chap-secrets' cls._protocol_section = 'pptp' # call base-classes classmethod super(TestVPNPPTPServer, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestVPNPPTPServer, cls).tearDownClass() def basic_protocol_specific_config(self): pass - def test_accel_local_authentication(self): - # Test configuration of local authentication - self.basic_config() - - # upload / download limit - user = "test" - password = "test2" - static_ip = "100.100.100.101" - upload = "5000" - download = "10000" - - self.set( - [ - "authentication", - "local-users", - "username", - user, - "password", - password, - ] - ) - self.set( - [ - "authentication", - "local-users", - "username", - user, - "static-ip", - static_ip, - ] - ) - - # commit changes - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) - conf.read(self._config_file) - - # check proper path to chap-secrets file - self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets) - - # basic verification - self.verify(conf) - - # check local users - tmp = cmd(f"sudo cat {self._chap_secrets}") - regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s" - tmp = re.findall(regex, tmp) - self.assertTrue(tmp) - - # Check local-users default value(s) - self.delete(["authentication", "local-users", "username", user, "static-ip"]) - # commit changes - self.cli_commit() - - # check local users - tmp = cmd(f"sudo cat {self._chap_secrets}") - regex = f"{user}\s+\*\s+{password}\s+\*\s" - tmp = re.findall(regex, tmp) - self.assertTrue(tmp) - - def test_accel_radius_authentication(self): - # Test configuration of RADIUS authentication for PPPoE server - self.basic_config() - - radius_server = "192.0.2.22" - radius_key = "secretVyOS" - radius_port = "2000" - radius_port_acc = "3000" - - self.set(["authentication", "mode", "radius"]) - self.set( - ["authentication", "radius", "server", radius_server, "key", radius_key] - ) - self.set( - [ - "authentication", - "radius", - "server", - radius_server, - "port", - radius_port, - ] - ) - self.set( - [ - "authentication", - "radius", - "server", - radius_server, - "acct-port", - radius_port_acc, - ] - ) - - nas_id = "VyOS-PPPoE" - nas_ip = "7.7.7.7" - self.set(["authentication", "radius", "nas-identifier", nas_id]) - self.set(["authentication", "radius", "nas-ip-address", nas_ip]) - - source_address = "1.2.3.4" - self.set(["authentication", "radius", "source-address", source_address]) - - # commit changes - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) - conf.read(self._config_file) - - # basic verification - self.verify(conf) - - # check auth - self.assertTrue(conf["radius"].getboolean("verbose")) - self.assertEqual(conf["radius"]["acct-timeout"], "30") - self.assertEqual(conf["radius"]["timeout"], "30") - self.assertEqual(conf["radius"]["max-try"], "3") - - self.assertEqual(conf["radius"]["nas-identifier"], nas_id) - self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip) - self.assertEqual(conf["radius"]["bind"], source_address) - - server = conf["radius"]["server"].split(",") - self.assertEqual(radius_server, server[0]) - self.assertEqual(radius_key, server[1]) - self.assertEqual(f"auth-port={radius_port}", server[2]) - self.assertEqual(f"acct-port={radius_port_acc}", server[3]) - self.assertEqual(f"req-limit=0", server[4]) - self.assertEqual(f"fail-time=0", server[5]) - - # - # Disable Radius Accounting - # - self.delete(["authentication", "radius", "server", radius_server, "acct-port"]) - self.set( - [ - "authentication", - "radius", - "server", - radius_server, - "disable-accounting", - ] - ) - - # commit changes - self.cli_commit() - - conf.read(self._config_file) - - server = conf["radius"]["server"].split(",") - self.assertEqual(radius_server, server[0]) - self.assertEqual(radius_key, server[1]) - self.assertEqual(f"auth-port={radius_port}", server[2]) - self.assertEqual(f"acct-port=0", server[3]) - self.assertEqual(f"req-limit=0", server[4]) - self.assertEqual(f"fail-time=0", server[5]) - - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 6df6f3dc7..5f72b983c 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -1,118 +1,110 @@ #!/usr/bin/env python3 # # Copyright (C) 2018-2024 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 get_accel_dict 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.accel_ppp_util import verify_accel_ppp_base_service from vyos import ConfigError from vyos import airbag airbag.enable() ipoe_conf = '/run/accel-pppd/ipoe.conf' ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets' def get_config(config=None): if config: conf = config else: conf = Config() base = ['service', 'ipoe-server'] if not conf.exists(base): return None # retrieve common dictionary keys ipoe = get_accel_dict(conf, base, ipoe_chap_secrets) if dict_search('client_ip_pool', ipoe): # Multiple named pools require ordered values T5099 ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe)) ipoe['server_type'] = 'ipoe' return ipoe def verify(ipoe): if not ipoe: return None if 'interface' not in ipoe: raise ConfigError('No IPoE interface configured') for interface, iface_config in ipoe['interface'].items(): verify_interface_exists(interface) if 'client_subnet' in iface_config and 'vlan' in iface_config: raise ConfigError('Option "client-subnet" incompatible with "vlan"!' 'Use "ipoe client-ip-pool" instead.') + verify_accel_ppp_base_service(ipoe, local_users=False) verify_accel_ppp_ip_pool(ipoe) - if dict_search('authentication.mode', ipoe) == 'radius': - if not dict_search('authentication.radius.server', ipoe): - raise ConfigError('RADIUS authentication requires at least one server') - - for server in dict_search('authentication.radius.server', ipoe): - radius_config = ipoe['authentication']['radius']['server'][server] - if 'key' not in radius_config: - raise ConfigError(f'Missing RADIUS secret key for server "{server}"') - - return None def generate(ipoe): if not ipoe: return None render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe) if dict_search('authentication.mode', ipoe) == 'local': render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe, permission=0o640) return None def apply(ipoe): systemd_service = 'accel-ppp@ipoe.service' if ipoe == None: call(f'systemctl stop {systemd_service}') for file in [ipoe_conf, ipoe_chap_secrets]: if os.path.exists(file): os.unlink(file) return None call(f'systemctl reload-or-restart {systemd_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_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 31299a15c..c2dfbdb44 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -1,126 +1,120 @@ #!/usr/bin/env python3 # # Copyright (C) 2018-2024 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 get_accel_dict from vyos.configdict import is_node_changed 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' pppoe_chap_secrets = r'/run/accel-pppd/pppoe.chap-secrets' def get_config(config=None): if config: conf = config else: conf = Config() base = ['service', 'pppoe-server'] if not conf.exists(base): return 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 conditions = [is_node_changed(conf, base + ['client-ip-pool']), is_node_changed(conf, base + ['client-ipv6-pool']), is_node_changed(conf, base + ['interface'])] if any(conditions): pppoe.update({'restart_required': {}}) pppoe['server_type'] = 'pppoe' return pppoe def verify(pppoe): if not pppoe: return None verify_accel_ppp_base_service(pppoe) + verify_accel_ppp_ip_pool(pppoe) if 'wins_server' in pppoe and len(pppoe['wins_server']) > 2: raise ConfigError('Not more then two WINS name-servers can be configured') if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') # Check is interface exists in the system for interface in pppoe['interface']: verify_interface_exists(interface) - 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): - raise ConfigError('DA/CoE server key required!') - - return None def generate(pppoe): if not pppoe: return None render(pppoe_conf, 'accel-ppp/pppoe.config.j2', pppoe) if dict_search('authentication.mode', pppoe) == 'local': render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', pppoe, permission=0o640) return None def apply(pppoe): systemd_service = 'accel-ppp@pppoe.service' if not pppoe: call(f'systemctl stop {systemd_service}') for file in [pppoe_conf, pppoe_chap_secrets]: if os.path.exists(file): os.unlink(file) return None if 'restart_required' in pppoe: call(f'systemctl restart {systemd_service}') else: call(f'systemctl reload-or-restart {systemd_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/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 4ca717814..266381754 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -1,116 +1,109 @@ #!/usr/bin/env python3 # # Copyright (C) 2019-2024 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.configdep import call_dependents, set_dependents from vyos.configdict import get_accel_dict 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.base import Warning from vyos import ConfigError from vyos import airbag airbag.enable() l2tp_conf = '/run/accel-pppd/l2tp.conf' l2tp_chap_secrets = '/run/accel-pppd/l2tp.chap-secrets' def get_config(config=None): if config: conf = config else: conf = Config() base = ['vpn', 'l2tp', 'remote-access'] set_dependents('ipsec', conf) if not conf.exists(base): return None # retrieve common dictionary keys l2tp = get_accel_dict(conf, base, l2tp_chap_secrets) 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)) l2tp['server_type'] = 'l2tp' return l2tp def verify(l2tp): if not l2tp: return None verify_accel_ppp_base_service(l2tp) - - if dict_search('authentication.radius.dynamic_author.server', l2tp): - if not dict_search('authentication.radius.dynamic_author.key', l2tp): - raise ConfigError('DA/CoE server key required!') - verify_accel_ppp_ip_pool(l2tp) - if 'wins_server' in l2tp and len(l2tp['wins_server']) > 2: raise ConfigError( 'Not more then two WINS name-servers can be configured') return None def generate(l2tp): if not l2tp: return None render(l2tp_conf, 'accel-ppp/l2tp.config.j2', l2tp) if dict_search('authentication.mode', l2tp) == 'local': render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2', l2tp, permission=0o640) return None def apply(l2tp): if not l2tp: call('systemctl stop accel-ppp@l2tp.service') for file in [l2tp_chap_secrets, l2tp_conf]: if os.path.exists(file): os.unlink(file) else: call('systemctl restart accel-ppp@l2tp.service') call_dependents() 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/migration-scripts/pptp/4-to-5 b/src/migration-scripts/pptp/4-to-5 new file mode 100755 index 000000000..d4b3f9a14 --- /dev/null +++ b/src/migration-scripts/pptp/4-to-5 @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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/>. + +# - Move 'require' from 'protocols' in 'authentication' node +# - Migrate to new default values in radius timeout and acct-timeout + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'pptp', 'remote-access'] + +if not config.exists(base): + exit(0) + +#migrate require to protocols +require_path = base + ['authentication', 'require'] +if config.exists(require_path): + protocols = list(config.return_values(require_path)) + for protocol in protocols: + config.set(base + ['authentication', 'protocols'], value=protocol, + replace=False) + config.delete(require_path) +else: + config.set(base + ['authentication', 'protocols'], value='mschap-v2') + +radius_path = base + ['authentication', 'radius'] +if config.exists(radius_path): + if not config.exists(radius_path + ['timeout']): + config.set(radius_path + ['timeout'], value=3) + if not config.exists(radius_path + ['acct-timeout']): + config.set(radius_path + ['acct-timeout'], value=3) + + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1)