diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/dns_dynamic.py | 142 | ||||
-rwxr-xr-x | src/conf_mode/dynamic_dns.py | 156 | ||||
-rwxr-xr-x | src/conf_mode/flow_accounting_conf.py | 16 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-bridge.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/netns.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/service_router-advert.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/vpn_openconnect.py | 5 |
8 files changed, 164 insertions, 168 deletions
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py new file mode 100755 index 000000000..67134e681 --- /dev/null +++ b/src/conf_mode/dns_dynamic.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# 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 +# 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() + +config_file = r'/run/ddclient/ddclient.conf' +systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' + +# Protocols that require zone +zone_allowed = ['cloudflare', 'godaddy', 'hetzner', 'gandi', 'nfsn'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'dyndns2', 'freedns', 'njalla'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base_level = ['service', 'dns', 'dynamic'] + if not conf.exists(base_level): + return None + + dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) + + if 'address' in dyndns: + for address in dyndns['address']: + # Apply service specific defaults (svc_type = ['rfc2136', 'service']) + for svc_type in dyndns['address'][address]: + default_values = defaults(base_level + ['address', svc_type]) + for svc_cfg in dyndns['address'][address][svc_type]: + dyndns['address'][address][svc_type][svc_cfg] = dict_merge( + default_values, dyndns['address'][address][svc_type][svc_cfg]) + + dyndns['config_file'] = config_file + return dyndns + +def verify(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + return None + + for address in dyndns['address']: + # RFC2136 - configuration validation + if 'rfc2136' in dyndns['address'][address]: + for config in dyndns['address'][address]['rfc2136'].values(): + for field in ['host_name', 'zone', 'server', 'key']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 ' + f'based Dynamic DNS service on "{address}"') + + # Dynamic DNS service provider - configuration validation + if 'service' in dyndns['address'][address]: + for service, config in dyndns['address'][address]['service'].items(): + error_msg = f'is required for Dynamic DNS service "{service}" on "{address}"' + + for field in ['host_name', 'password', 'protocol']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg}') + + if config['protocol'] in zone_allowed and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg}') + + if config['protocol'] not in zone_allowed and 'zone' in config: + raise ConfigError(f'"{config["protocol"]}" does not support "zone"') + + if config['protocol'] not in username_unnecessary: + if 'username' not in config: + raise ConfigError(f'"username" {error_msg}') + + if config['ip_version'] == 'both': + if config['protocol'] not in dualstack_supported: + raise ConfigError(f'"{config["protocol"]}" does not support ' + f'both IPv4 and IPv6 at the same time') + # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) + if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] != 'members.dyndns.org': + raise ConfigError(f'"{config["protocol"]}" does not support ' + f'both IPv4 and IPv6 at the same time for "{config["server"]}"') + + return None + +def generate(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + return None + + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns) + render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) + return None + +def apply(dyndns): + systemd_service = 'ddclient.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl reload-or-restart {systemd_service}') + + 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/dynamic_dns.py b/src/conf_mode/dynamic_dns.py deleted file mode 100755 index 426e3d693..000000000 --- a/src/conf_mode/dynamic_dns.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 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() - -config_file = r'/run/ddclient/ddclient.conf' - -# Mapping of service name to service protocol -default_service_protocol = { - 'afraid': 'freedns', - 'changeip': 'changeip', - 'cloudflare': 'cloudflare', - 'dnspark': 'dnspark', - 'dslreports': 'dslreports1', - 'dyndns': 'dyndns2', - 'easydns': 'easydns', - 'namecheap': 'namecheap', - 'noip': 'noip', - 'sitelutions': 'sitelutions', - 'zoneedit': 'zoneedit1' -} - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base_level = ['service', 'dns', 'dynamic'] - if not conf.exists(base_level): - return None - - dyndns = conf.get_config_dict(base_level, 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. - for interface in dyndns['interface']: - if 'service' in dyndns['interface'][interface]: - # 'Autodetect' protocol used by DynDNS service - for service in dyndns['interface'][interface]['service']: - if service in default_service_protocol: - dyndns['interface'][interface]['service'][service].update( - {'protocol' : default_service_protocol.get(service)}) - else: - dyndns['interface'][interface]['service'][service].update( - {'custom': ''}) - - if 'rfc2136' in dyndns['interface'][interface]: - default_values = defaults(base_level + ['interface', 'rfc2136']) - for rfc2136 in dyndns['interface'][interface]['rfc2136']: - dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge( - default_values, dyndns['interface'][interface]['rfc2136'][rfc2136]) - - return dyndns - -def verify(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - # A 'node' corresponds to an interface - if 'interface' not in dyndns: - return None - - for interface in dyndns['interface']: - # RFC2136 - configuration validation - if 'rfc2136' in dyndns['interface'][interface]: - for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items(): - - for tmp in ['record', 'zone', 'server', 'key']: - if tmp not in config: - raise ConfigError(f'"{tmp}" required for rfc2136 based ' - f'DynDNS service on "{interface}"') - - if not os.path.isfile(config['key']): - raise ConfigError(f'"key"-file not found for rfc2136 based ' - f'DynDNS service on "{interface}"') - - # DynDNS service provider - configuration validation - if 'service' in dyndns['interface'][interface]: - for service, config in dyndns['interface'][interface]['service'].items(): - error_msg = f'required for DynDNS service "{service}" on "{interface}"' - if 'host_name' not in config: - raise ConfigError(f'"host-name" {error_msg}') - - if 'login' not in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare') - - if 'password' not in config: - raise ConfigError(f'"password" {error_msg}') - - if 'zone' in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"zone" option only supported with CloudFlare') - - if 'custom' in config: - if 'protocol' not in config: - raise ConfigError(f'"protocol" {error_msg}') - - if 'server' not in config: - raise ConfigError(f'"server" {error_msg}') - - return None - -def generate(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns) - return None - -def apply(dyndns): - if not dyndns: - call('systemctl stop ddclient.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart ddclient.service') - - 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/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index f67f1710e..c36d52e05 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.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 @@ -18,15 +18,13 @@ import os import re from sys import exit -import ipaddress - from ipaddress import ip_address from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf from vyos.ifconfig import Section -from vyos.ifconfig import Interface from vyos.template import render from vyos.util import call from vyos.util import cmd @@ -194,6 +192,7 @@ def verify(flow_config): sflow_collector_ipver = ip_address(server).version # check if vrf is defined for Sflow + verify_vrf(flow_config) sflow_vrf = None if 'vrf' in flow_config: sflow_vrf = flow_config['vrf'] @@ -211,7 +210,7 @@ def verify(flow_config): if not is_addr_assigned(tmp, sflow_vrf): raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') - # Check if configured netflow source-address exist in the system + # Check if configured sflow source-address exist in the system if 'source_address' in flow_config['sflow']: if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): tmp = flow_config['sflow']['source_address'] @@ -219,13 +218,18 @@ def verify(flow_config): # check NetFlow configuration if 'netflow' in flow_config: + # check if vrf is defined for netflow + netflow_vrf = None + if 'vrf' in flow_config: + netflow_vrf = flow_config['vrf'] + # check if at least one NetFlow collector is configured if NetFlow configuration is presented if 'server' not in flow_config['netflow']: raise ConfigError('You need to configure at least one NetFlow server!') # Check if configured netflow source-address exist in the system if 'source_address' in flow_config['netflow']: - if not is_addr_assigned(flow_config['netflow']['source_address']): + if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): tmp = flow_config['netflow']['source_address'] raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index b961408db..4da3b097f 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -131,11 +131,11 @@ def verify(bridge): raise ConfigError('Loopback interface "lo" can not be added to a bridge') if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(interface_config['is_bond_member'])) raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') if 'is_source_interface' in interface_config: diff --git a/src/conf_mode/netns.py b/src/conf_mode/netns.py index 0924eb616..20129ce65 100755 --- a/src/conf_mode/netns.py +++ b/src/conf_mode/netns.py @@ -82,7 +82,8 @@ def verify(netns): if 'name' in netns: for name, config in netns['name'].items(): - print(name) + # no tests (yet) + pass return None diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 1b8377a4a..1dd973d67 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -93,6 +93,10 @@ def verify(rtradv): if not (int(valid_lifetime) >= int(preferred_lifetime)): raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime') + if 'name_server' in interface_config: + if len(interface_config['name_server']) > 3: + raise ConfigError('No more then 3 IPv6 name-servers supported!') + if 'name_server_lifetime' in interface_config: # man page states: # The maximum duration how long the RDNSS entries are used for name diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 63887b278..b82d90e4d 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -455,7 +455,7 @@ def verify(ipsec): if dict_search('options.disable_route_autoinstall', ipsec) == None: - Warning('It\'s recommended to use ipsec vty with the next command\n[set vpn ipsec option disable-route-autoinstall]') + Warning('It\'s recommended to use ipsec vti with the next command\n[set vpn ipsec option disable-route-autoinstall]') if 'bind' in peer_conf['vti']: vti_interface = peer_conf['vti']['bind'] diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index 83021a3e6..3d5dc12a4 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -30,7 +30,7 @@ from vyos.util import is_listen_port_bind_service from vyos.util import dict_search from vyos.xml import defaults from vyos import ConfigError -from crypt import crypt, mksalt, METHOD_SHA512 +from passlib.hash import sha512_crypt from time import sleep from vyos import airbag @@ -45,7 +45,8 @@ radius_servers = cfg_dir + '/radius_servers' # Generate hash from user cleartext password def get_hash(password): - return crypt(password, mksalt(METHOD_SHA512)) + return sha512_crypt.hash(password) + def _default_dict_cleanup(origin: dict, default_values: dict) -> dict: |