diff options
Diffstat (limited to 'src')
45 files changed, 653 insertions, 229 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 30016b865..4f93c93a1 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -84,16 +84,16 @@ def get_config(config=None): # tagNodes in place, it is better to blend in the defaults manually. if 'port' in container['name'][name]: for port in container['name'][name]['port']: - default_values = defaults(base + ['name', 'port']) + default_values_port = defaults(base + ['name', 'port']) container['name'][name]['port'][port] = dict_merge( - default_values, container['name'][name]['port'][port]) + default_values_port, container['name'][name]['port'][port]) # XXX: T2665: we can not safely rely on the defaults() when there are # tagNodes in place, it is better to blend in the defaults manually. if 'volume' in container['name'][name]: for volume in container['name'][name]['volume']: - default_values = defaults(base + ['name', 'volume']) + default_values_volume = defaults(base + ['name', 'volume']) container['name'][name]['volume'][volume] = dict_merge( - default_values, container['name'][name]['volume'][volume]) + default_values_volume, container['name'][name]['volume'][volume]) # Delete container network, delete containers tmp = node_changed(conf, base + ['network']) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index 4de2ca2f3..7e702a446 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -18,9 +18,11 @@ import os from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render +from vyos.base import Warning from vyos.util import call from vyos.util import dict_search from vyos.xml import defaults @@ -59,6 +61,19 @@ def verify(relay): raise ConfigError('No DHCP relay server(s) configured.\n' \ 'At least one DHCP relay server required.') + if 'interface' in relay: + Warning('DHCP relay interface is DEPRECATED - please use upstream-interface and listen-interface instead!') + if 'upstream_interface' in relay or 'listen_interface' in relay: + raise ConfigError('<interface> configuration is not compatible with upstream/listen interface') + else: + Warning('<interface> is going to be deprecated.\n' \ + 'Please use <listen-interface> and <upstream-interface>') + + if 'upstream_interface' in relay and 'listen_interface' not in relay: + raise ConfigError('No listen-interface configured') + if 'listen_interface' in relay and 'upstream_interface' not in relay: + raise ConfigError('No upstream-interface configured') + return None def generate(relay): diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 52b682d6d..39c87478f 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -283,7 +283,7 @@ def generate(dhcp): if not dhcp or 'disable' in dhcp: return None - # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw + # Please see: https://vyos.dev/T1129 for quoting of the raw # parameters we can pass to ISC DHCPd tmp_file = '/tmp/dhcpd.conf' render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp, diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 6328294c1..7e801eb26 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -79,9 +79,10 @@ def get_config(config=None): # http-api.conf format for api_keys: if 'keys' in api_dict: api_dict['api_keys'] = [] - for el in list(api_dict['keys']['id']): - key = api_dict['keys']['id'][el]['key'] - api_dict['api_keys'].append({'id': el, 'key': key}) + for el in list(api_dict['keys'].get('id', {})): + key = api_dict['keys']['id'][el].get('key', '') + if key: + api_dict['api_keys'].append({'id': el, 'key': key}) del api_dict['keys'] # Do we run inside a VRF context? diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 8155f36c2..13d84a6fe 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -645,7 +645,7 @@ def generate(openvpn): user=user, group=group) # we need to support quoting of raw parameters from OpenVPN CLI - # see https://phabricator.vyos.net/T1632 + # see https://vyos.dev/T1632 render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index a14a992ae..9ca495476 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -171,7 +171,7 @@ def apply(wwan): options = f'ip-type={ip_type},apn=' + wwan['apn'] if 'authentication' in wwan: - options += ',user={user},password={password}'.format(**wwan['authentication']) + options += ',user={username},password={password}'.format(**wwan['authentication']) command = f'{base_cmd} --simple-connect="{options}"' call(command, stdout=DEVNULL) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index c410258ee..4f05957fa 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -235,6 +235,11 @@ def verify(bgp): raise ConfigError(f'Specified peer-group "{peer_group}" for '\ f'neighbor "{neighbor}" does not exist!') + if 'local_role' in peer_config: + #Ensure Local Role has only one value. + if len(peer_config['local_role']) > 1: + raise ConfigError(f'Only one local role can be specified for peer "{peer}"!') + if 'local_as' in peer_config: if len(peer_config['local_as']) > 1: raise ConfigError(f'Only one local-as number can be specified for peer "{peer}"!') diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index 0418e8d82..dca713283 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 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 @@ -14,12 +14,14 @@ # 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 netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configverify import verify_interface_exists from vyos.qos import CAKE from vyos.qos import DropTail from vyos.qos import FairQueue @@ -194,8 +196,6 @@ def verify(qos): # we should check interface ingress/egress configuration after verifying that # the policy name is used only once - this makes the logic easier! for interface, interface_config in qos['interface'].items(): - verify_interface_exists(interface) - for direction in ['egress', 'ingress']: # bail out early if shaper for given direction is not used at all if direction not in interface_config: @@ -229,6 +229,13 @@ def apply(qos): return None for interface, interface_config in qos['interface'].items(): + if not os.path.exists(f'/sys/class/net/{interface}'): + # When shaper is bound to a dialup (e.g. PPPoE) interface it is + # possible that it is yet not availbale when to QoS code runs. + # Skip the configuration and inform the user + Warning(f'Interface "{interface}" does not exist!') + continue + for direction in ['egress', 'ingress']: # bail out early if shaper for given direction is not used at all if direction not in interface_config: diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index ab2ccf99e..9b7c04eb0 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -92,7 +92,7 @@ def get_config(config=None): # Always listen on localhost if an explicit address has been configured # This is a safety measure to not end up with invalid listen addresses - # that are not configured on this system. See https://phabricator.vyos.net/T850 + # that are not configured on this system. See https://vyos.dev/T850 if '127.0.0.1' not in snmp['listen_address']: tmp = {'127.0.0.1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) @@ -103,6 +103,9 @@ def get_config(config=None): if 'community' in snmp: default_values = defaults(base + ['community']) + if 'network' in default_values: + # convert multiple default networks to list + default_values['network'] = default_values['network'].split() for community in snmp['community']: snmp['community'][community] = dict_merge( default_values, snmp['community'][community]) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index e26b81e3d..0a4a88bf8 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -16,18 +16,17 @@ import os -from crypt import crypt -from crypt import METHOD_SHA512 +from passlib.hosts import linux_context from psutil import users from pwd import getpwall from pwd import getpwnam -from spwd import getspnam from sys import exit from time import sleep from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf +from vyos.defaults import directories from vyos.template import render from vyos.template import is_ipv4 from vyos.util import cmd @@ -43,6 +42,11 @@ airbag.enable() autologout_file = "/etc/profile.d/autologout.sh" radius_config_file = "/etc/pam_radius_auth.conf" +# LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec +MAX_RADIUS_TIMEOUT: int = 50 +# MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout) +MAX_RADIUS_COUNT: int = 25 + def get_local_users(): """Return list of dynamically allocated users (see Debian Policy Manual)""" local_users = [] @@ -54,6 +58,13 @@ def get_local_users(): return local_users +def get_shadow_password(username): + with open('/etc/shadow') as f: + for user in f.readlines(): + items = user.split(":") + if username == items[0]: + return items[1] + return None def get_config(config=None): if config: @@ -118,18 +129,27 @@ def verify(login): if 'radius' in login: if 'server' not in login['radius']: raise ConfigError('No RADIUS server defined!') - + sum_timeout: int = 0 + radius_servers_count: int = 0 fail = True for server, server_config in dict_search('radius.server', login).items(): if 'key' not in server_config: raise ConfigError(f'RADIUS server "{server}" requires key!') - - if 'disabled' not in server_config: + if 'disable' not in server_config: + sum_timeout += int(server_config['timeout']) + radius_servers_count += 1 fail = False - continue + if fail: raise ConfigError('All RADIUS servers are disabled') + if radius_servers_count > MAX_RADIUS_COUNT: + raise ConfigError('Number of RADIUS servers more than 25 ') + + if sum_timeout > MAX_RADIUS_TIMEOUT: + raise ConfigError('Sum of RADIUS servers timeouts ' + 'has to be less or eq 50 sec') + verify_vrf(login['radius']) if 'source_address' in login['radius']: @@ -153,13 +173,13 @@ def generate(login): for user, user_config in login['user'].items(): tmp = dict_search('authentication.plaintext_password', user_config) if tmp: - encrypted_password = crypt(tmp, METHOD_SHA512) + encrypted_password = linux_context.hash(tmp) login['user'][user]['authentication']['encrypted_password'] = encrypted_password del login['user'][user]['authentication']['plaintext_password'] # remove old plaintext password and set new encrypted password env = os.environ.copy() - env['vyos_libexec_dir'] = '/usr/libexec/vyos' + env['vyos_libexec_dir'] = directories['base'] # Set default commands for re-adding user with encrypted password del_user_plain = f"system login user '{user}' authentication plaintext-password" @@ -186,7 +206,7 @@ def generate(login): call(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env) else: try: - if getspnam(user).sp_pwdp == dict_search('authentication.encrypted_password', user_config): + if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config): # If the current encrypted bassword matches the encrypted password # from the config - do not update it. This will remove the encrypted # value from the system logs. diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index ce4f13d27..d207c63df 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -53,8 +53,6 @@ dhcp_wait_attempts = 2 dhcp_wait_sleep = 1 swanctl_dir = '/etc/swanctl' -ipsec_conf = '/etc/ipsec.conf' -ipsec_secrets = '/etc/ipsec.secrets' charon_conf = '/etc/strongswan.d/charon.conf' charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf' charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf' @@ -542,8 +540,7 @@ def generate(ipsec): cleanup_pki_files() if not ipsec: - for config_file in [ipsec_conf, ipsec_secrets, charon_dhcp_conf, - charon_radius_conf, interface_conf, swanctl_conf]: + for config_file in [charon_dhcp_conf, charon_radius_conf, interface_conf, swanctl_conf]: if os.path.isfile(config_file): os.unlink(config_file) render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes}) @@ -618,8 +615,6 @@ def generate(ipsec): if id: ipsec['authentication']['psk'][psk]['id'].append(id) - render(ipsec_conf, 'ipsec/ipsec.conf.j2', ipsec) - render(ipsec_secrets, 'ipsec/ipsec.secrets.j2', ipsec) render(charon_conf, 'ipsec/charon.j2', ipsec) render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.j2', ipsec) render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.j2', ipsec) @@ -634,25 +629,12 @@ def resync_nhrp(ipsec): if tmp > 0: print('ERROR: failed to reapply NHRP settings!') -def wait_for_vici_socket(timeout=5, sleep_interval=0.1): - start_time = time() - test_command = f'sudo socat -u OPEN:/dev/null UNIX-CONNECT:{vici_socket}' - while True: - if (start_time + timeout) < time(): - return None - result = run(test_command) - if result == 0: - return True - sleep(sleep_interval) - def apply(ipsec): - systemd_service = 'strongswan-starter.service' + systemd_service = 'strongswan.service' if not ipsec: call(f'systemctl stop {systemd_service}') else: call(f'systemctl reload-or-restart {systemd_service}') - if wait_for_vici_socket(): - call('sudo swanctl -q') resync_nhrp(ipsec) diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index 63ffe2a41..68da70d7d 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.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 @@ -47,9 +47,9 @@ def get_hash(password): return crypt(password, mksalt(METHOD_SHA512)) -def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict: +def _default_dict_cleanup(origin: dict, default_values: dict) -> dict: """ - https://phabricator.vyos.net/T2665 + https://vyos.dev/T2665 Clear unnecessary key values in merged config by dict_merge function :param origin: config :type origin: dict @@ -63,7 +63,7 @@ def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict: del origin['authentication']['local_users']['username']['otp'] if not origin["authentication"]["local_users"]["username"]: raise ConfigError( - 'Openconnect mode local required at least one user') + 'Openconnect authentication mode local requires at least one user') default_ocserv_usr_values = \ default_values['authentication']['local_users']['username']['otp'] for user, params in origin['authentication']['local_users'][ @@ -82,7 +82,7 @@ def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict: del origin['authentication']['radius']['server']['port'] if not origin["authentication"]['radius']['server']: raise ConfigError( - 'Openconnect authentication mode radius required at least one radius server') + 'Openconnect authentication mode radius requires at least one RADIUS server') default_values_radius_port = \ default_values['authentication']['radius']['server']['port'] for server, params in origin['authentication']['radius'][ @@ -95,7 +95,7 @@ def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict: del origin['accounting']['radius']['server']['port'] if not origin["accounting"]['radius']['server']: raise ConfigError( - 'Openconnect accounting mode radius required at least one radius server') + 'Openconnect accounting mode radius requires at least one RADIUS server') default_values_radius_port = \ default_values['accounting']['radius']['server']['port'] for server, params in origin['accounting']['radius'][ @@ -105,8 +105,11 @@ def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict: return origin -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['vpn', 'openconnect'] if not conf.exists(base): return None @@ -116,8 +119,8 @@ def get_config(): # options which we need to update into the dictionary retrived. default_values = defaults(base) ocserv = dict_merge(default_values, ocserv) - # workaround a "know limitation" - https://phabricator.vyos.net/T2665 - ocserv = T2665_default_dict_cleanup(ocserv, default_values) + # workaround a "know limitation" - https://vyos.dev/T2665 + ocserv = _default_dict_cleanup(ocserv, default_values) if ocserv: ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) diff --git a/src/etc/commit/post-hooks.d/00vyos-sync b/src/etc/commit/post-hooks.d/00vyos-sync new file mode 100755 index 000000000..8ec732df0 --- /dev/null +++ b/src/etc/commit/post-hooks.d/00vyos-sync @@ -0,0 +1,7 @@ +#!/bin/sh +# When power is lost right after a commit modified files, the +# system can be corrupted and e.g. login is no longer possible. +# Always sync files to the backend storage after a commit. +# https://vyos.dev/T4975 +sync + diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks new file mode 100644 index 000000000..b4b4d516d --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/99-run-user-hooks @@ -0,0 +1,5 @@ +#!/bin/bash +DHCP_PRE_HOOKS="/config/scripts/dhcp-client/pre-hooks.d/" +if [ -d "${DHCP_PRE_HOOKS}" ] ; then + run-parts "${DHCP_PRE_HOOKS}" +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/99-run-user-hooks b/src/etc/dhcp/dhclient-exit-hooks.d/99-run-user-hooks new file mode 100755 index 000000000..442419d79 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/99-run-user-hooks @@ -0,0 +1,5 @@ +#!/bin/bash +DHCP_POST_HOOKS="/config/scripts/dhcp-client/post-hooks.d/" +if [ -d "${DHCP_POST_HOOKS}" ] ; then + run-parts "${DHCP_POST_HOOKS}" +fi diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index 4880605d6..f5d84be4b 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -19,7 +19,7 @@ kernel.core_pattern=/var/core/core-%e-%p-%t # arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it. net.ipv4.conf.all.arp_filter=0 -# https://phabricator.vyos.net/T300 +# https://vyos.dev/T300 net.ipv4.conf.all.arp_ignore=0 net.ipv4.conf.all.arp_announce=2 diff --git a/src/etc/systemd/system/ddclient.service.d/override.conf b/src/etc/systemd/system/ddclient.service.d/override.conf index d9c9963b0..09d929d39 100644 --- a/src/etc/systemd/system/ddclient.service.d/override.conf +++ b/src/etc/systemd/system/ddclient.service.d/override.conf @@ -8,4 +8,4 @@ WorkingDirectory=/run/ddclient PIDFile= PIDFile=/run/ddclient/ddclient.pid ExecStart= -ExecStart=/usr/sbin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf +ExecStart=/usr/bin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 index ee4d6b82c..c7f324661 100755 --- a/src/migration-scripts/interfaces/0-to-1 +++ b/src/migration-scripts/interfaces/0-to-1 @@ -3,7 +3,7 @@ # Change syntax of bridge interface # - move interface based bridge-group to actual bridge (de-nest) # - make stp and igmp-snooping nodes valueless -# https://phabricator.vyos.net/T1556 +# https://vyos.dev/T1556 import sys from vyos.configtree import ConfigTree diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 index 050137318..c75404d85 100755 --- a/src/migration-scripts/interfaces/1-to-2 +++ b/src/migration-scripts/interfaces/1-to-2 @@ -2,7 +2,7 @@ # Change syntax of bond interface # - move interface based bond-group to actual bond (de-nest) -# https://phabricator.vyos.net/T1614 +# https://vyos.dev/T1614 import sys from vyos.configtree import ConfigTree @@ -40,7 +40,7 @@ else: # some combinations were allowed in the past from a CLI perspective # but the kernel overwrote them - remove from CLI to not confuse the users. # In addition new consitency checks are in place so users can't repeat the - # mistake. One of those nice issues is https://phabricator.vyos.net/T532 + # mistake. One of those nice issues is https://vyos.dev/T532 for bond in config.list_nodes(base): if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']): mode = config.return_value(base + [bond, 'mode']) diff --git a/src/migration-scripts/interfaces/16-to-17 b/src/migration-scripts/interfaces/16-to-17 index a6b4c7663..d123be06f 100755 --- a/src/migration-scripts/interfaces/16-to-17 +++ b/src/migration-scripts/interfaces/16-to-17 @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # Command line migration of port mirroring -# https://phabricator.vyos.net/T3089 +# https://vyos.dev/T3089 import sys from vyos.configtree import ConfigTree diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3 index a63a54cdf..68d41de39 100755 --- a/src/migration-scripts/interfaces/2-to-3 +++ b/src/migration-scripts/interfaces/2-to-3 @@ -2,7 +2,7 @@ # Change syntax of openvpn encryption settings # - move cipher from encryption to encryption cipher -# https://phabricator.vyos.net/T1704 +# https://vyos.dev/T1704 import sys from vyos.configtree import ConfigTree diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 0bd858760..cb1c36882 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS -# CLI. See https://phabricator.vyos.net/T3619#102254 for all the details. +# CLI. See https://vyos.dev/T3619#102254 for all the details. # T3787: Remove deprecated UDP fragmentation offloading option from sys import argv diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 new file mode 100755 index 000000000..949cc55b6 --- /dev/null +++ b/src/migration-scripts/interfaces/26-to-27 @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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/>. + +# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node +# to "authentication username" + +from sys import argv + +from vyos.ethtool import Ethtool +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + 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) + +for type in ['pppoe', 'sstpc-client', 'wwam']: + base = ['interfaces', type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + auth_base = base + [interface, 'authentication', 'user'] + if config.exists(auth_base): + config.rename(auth_base, 'username') + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28 new file mode 100755 index 000000000..6225d6414 --- /dev/null +++ b/src/migration-scripts/interfaces/27-to-28 @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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/>. + +# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast" +# valueless node. + +from sys import argv + +from vyos.ethtool import Ethtool +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['interfaces', 'tunnel'] +config = ConfigTree(config_file) + +if not config.exists(base): + exit(0) + +for ifname in config.list_nodes(base): + print(ifname) + multicast_base = base + [ifname, 'multicast'] + if config.exists(multicast_base): + tmp = config.return_value(multicast_base) + print(tmp) + # Delete old Config node + config.delete(multicast_base) + if tmp == 'enable': + config.set(base + [ifname, 'enable-multicast']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/migration-scripts/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5 index 2a42c60ff..f645c5aeb 100755 --- a/src/migration-scripts/interfaces/4-to-5 +++ b/src/migration-scripts/interfaces/4-to-5 @@ -50,7 +50,7 @@ def migrate_dialer(config, tree, intf): # Remove IPv6 router-advert nodes as this makes no sense on a # client diale rinterface to send RAs back into the network - # https://phabricator.vyos.net/T2055 + # https://vyos.dev/T2055 ipv6_ra = pppoe_base + ['ipv6', 'router-advert'] if config.exists(ipv6_ra): config.delete(ipv6_ra) diff --git a/src/migration-scripts/ipsec/11-to-12 b/src/migration-scripts/ipsec/11-to-12 new file mode 100755 index 000000000..8bbde5efa --- /dev/null +++ b/src/migration-scripts/ipsec/11-to-12 @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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/>. + +# Remove legacy ipsec.conf and ipsec.secrets - Not supported with swanctl + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['vpn', 'ipsec'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['include-ipsec-conf']): + config.delete(base + ['include-ipsec-conf']) + +if config.exists(base + ['include-ipsec-secrets']): + config.delete(base + ['include-ipsec-secrets']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 index 41026cbd6..14d3a6e0a 100755 --- a/src/migration-scripts/qos/1-to-2 +++ b/src/migration-scripts/qos/1-to-2 @@ -118,6 +118,28 @@ if config.exists(netem_base): if config.exists(netem_base + [policy_name, 'burst']): config.delete(netem_base + [policy_name, 'burst']) +# Change bandwidth unit MBit -> mbit as tc only supports mbit +base = ['qos', 'policy'] +if config.exists(base): + for policy_type in config.list_nodes(base): + for policy in config.list_nodes(base + [policy_type]): + policy_base = base + [policy_type, policy] + if config.exists(policy_base + ['bandwidth']): + tmp = config.return_value(policy_base + ['bandwidth']) + config.set(policy_base + ['bandwidth'], value=tmp.lower()) + + if config.exists(policy_base + ['class']): + for cls in config.list_nodes(policy_base + ['class']): + cls_base = policy_base + ['class', cls] + if config.exists(cls_base + ['bandwidth']): + tmp = config.return_value(cls_base + ['bandwidth']) + config.set(cls_base + ['bandwidth'], value=tmp.lower()) + + if config.exists(policy_base + ['default', 'bandwidth']): + if config.exists(policy_base + ['default', 'bandwidth']): + tmp = config.return_value(policy_base + ['default', 'bandwidth']) + config.set(policy_base + ['default', 'bandwidth'], value=tmp.lower()) + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1 index a836f7011..096ba779d 100755 --- a/src/migration-scripts/snmp/0-to-1 +++ b/src/migration-scripts/snmp/0-to-1 @@ -33,18 +33,18 @@ if not config.exists(config_base): # Nothing to do sys.exit(0) else: - # we no longer support a per trap target engine ID (https://phabricator.vyos.net/T818) + # we no longer support a per trap target engine ID (https://vyos.dev/T818) if config.exists(config_base + ['v3', 'trap-target']): for target in config.list_nodes(config_base + ['v3', 'trap-target']): config.delete(config_base + ['v3', 'trap-target', target, 'engineid']) - # we no longer support a per user engine ID (https://phabricator.vyos.net/T818) + # we no longer support a per user engine ID (https://vyos.dev/T818) if config.exists(config_base + ['v3', 'user']): for user in config.list_nodes(config_base + ['v3', 'user']): config.delete(config_base + ['v3', 'user', user, 'engineid']) # we drop TSM support as there seem to be no users and this code is untested - # https://phabricator.vyos.net/T1769 + # https://vyos.dev/T1769 if config.exists(config_base + ['v3', 'tsm']): config.delete(config_base + ['v3', 'tsm']) diff --git a/src/op_mode/accelppp.py b/src/op_mode/accelppp.py index 2fd045dc3..87a25bb96 100755 --- a/src/op_mode/accelppp.py +++ b/src/op_mode/accelppp.py @@ -27,29 +27,51 @@ from vyos.util import rc_cmd accel_dict = { 'ipoe': { 'port': 2002, - 'path': 'service ipoe-server' + 'path': 'service ipoe-server', + 'base_path': 'service ipoe-server' }, 'pppoe': { 'port': 2001, - 'path': 'service pppoe-server' + 'path': 'service pppoe-server', + 'base_path': 'service pppoe-server' }, 'pptp': { 'port': 2003, - 'path': 'vpn pptp' + 'path': 'vpn pptp', + 'base_path': 'vpn pptp' }, 'l2tp': { 'port': 2004, - 'path': 'vpn l2tp' + 'path': 'vpn l2tp', + 'base_path': 'vpn l2tp remote-access' }, 'sstp': { 'port': 2005, - 'path': 'vpn sstp' + 'path': 'vpn sstp', + 'base_path': 'vpn sstp' } } -def _get_raw_statistics(accel_output, pattern): - return vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':') +def _get_config_settings(protocol): + '''Get config dict from VyOS configuration''' + conf = ConfigTreeQuery() + base_path = accel_dict[protocol]['base_path'] + data = conf.get_config_dict(base_path, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + if conf.exists(f'{base_path} authentication local-users'): + # Delete sensitive data + del data['authentication']['local_users'] + return {'config_option': data} + + +def _get_raw_statistics(accel_output, pattern, protocol): + return { + **vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':'), + **_get_config_settings(protocol) + } def _get_raw_sessions(port): @@ -103,7 +125,7 @@ def show_statistics(raw: bool, protocol: str): rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat') if raw: - return _get_raw_statistics(output, pattern) + return _get_raw_statistics(output, pattern, protocol) return output diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py index f071ae350..8ba55c901 100755 --- a/src/op_mode/generate_public_key_command.py +++ b/src/op_mode/generate_public_key_command.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -19,28 +19,51 @@ import sys import urllib.parse import vyos.remote +from vyos.template import generate_uuid4 -def get_key(path): + +def get_key(path) -> list: + """Get public keys from a local file or remote URL + + Args: + path: Path to the public keys file + + Returns: list of public keys split by new line + + """ url = urllib.parse.urlparse(path) if url.scheme == 'file' or url.scheme == '': with open(os.path.expanduser(path), 'r') as f: key_string = f.read() else: key_string = vyos.remote.get_remote_config(path) - return key_string.split() - -try: - username = sys.argv[1] - algorithm, key, identifier = get_key(sys.argv[2]) -except Exception as e: - print("Failed to retrieve the public key: {}".format(e)) - sys.exit(1) - -print('# To add this key as an embedded key, run the following commands:') -print('configure') -print(f'set system login user {username} authentication public-keys {identifier} key {key}') -print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}') -print('commit') -print('save') -print('exit') + return key_string.split('\n') + + +if __name__ == "__main__": + first_loop = True + + for k in get_key(sys.argv[2]): + k = k.split() + # Skip empty list entry + if k == []: + continue + + try: + username = sys.argv[1] + # Github keys don't have identifier for example 'vyos@localhost' + # 'ssh-rsa AAAA... vyos@localhost' + # Generate uuid4 identifier + identifier = f'github@{generate_uuid4("")}' if sys.argv[2].startswith('https://github.com') else k[2] + algorithm, key = k[0], k[1] + except Exception as e: + print("Failed to retrieve the public key: {}".format(e)) + sys.exit(1) + + if first_loop: + print('# To add this key as an embedded key, run the following commands:') + print('configure') + print(f'set system login user {username} authentication public-keys {identifier} key {key}') + print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}') + first_loop = False diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index f6417764a..8e76f4cc0 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -14,25 +14,19 @@ # 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 import re import sys import typing -from collections import OrderedDict from hurry import filesize from re import split as re_split from tabulate import tabulate -from subprocess import TimeoutExpired -from vyos.util import call from vyos.util import convert_data from vyos.util import seconds_to_human import vyos.opmode - - -SWANCTL_CONF = '/etc/swanctl/swanctl.conf' +import vyos.ipsec def _convert(text): @@ -43,22 +37,13 @@ def _alphanum_key(key): return [_convert(c) for c in re_split('([0-9]+)', str(key))] -def _get_vici_sas(): - from vici import Session as vici_session - - try: - session = vici_session() - except Exception: - raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized") - sas = list(session.list_sas()) - return sas - - def _get_raw_data_sas(): - get_sas = _get_vici_sas() - sas = convert_data(get_sas) - return sas - + try: + get_sas = vyos.ipsec.get_vici_sas() + sas = convert_data(get_sas) + return sas + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.UnconfiguredSubsystem(err) def _get_formatted_output_sas(sas): sa_data = [] @@ -139,22 +124,14 @@ def _get_formatted_output_sas(sas): # Connections block -def _get_vici_connections(): - from vici import Session as vici_session - - try: - session = vici_session() - except Exception: - raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized") - connections = list(session.list_conns()) - return connections - def _get_convert_data_connections(): - get_connections = _get_vici_connections() - connections = convert_data(get_connections) - return connections - + try: + get_connections = vyos.ipsec.get_vici_connections() + connections = convert_data(get_connections) + return connections + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.UnconfiguredSubsystem(err) def _get_parent_sa_proposal(connection_name: str, data: list) -> dict: """Get parent SA proposals by connection name @@ -239,7 +216,8 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str, # Get all child SA states # there can be multiple SAs per tunnel child_sa_states = [ - v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name + v['state'] for k, v in child_sas.items() if + v['name'] == tunnel_name ] return 'up' if 'INSTALLED' in child_sa_states else child_sa @@ -406,39 +384,46 @@ def _get_formatted_output_conections(data): # Connections block end -def get_peer_connections(peer, tunnel): - search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*' - matches = [] - if not os.path.exists(SWANCTL_CONF): - raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized") - suffix = None if tunnel is None else (f'tunnel-{tunnel}' if - tunnel.isnumeric() else tunnel) - with open(SWANCTL_CONF, 'r') as f: - for line in f.readlines(): - result = re.match(search, line) - if result: - if tunnel is None: - matches.append(result[1]) - else: - if result[2] == suffix: - matches.append(result[1]) - return matches - - -def reset_peer(peer: str, tunnel:typing.Optional[str]): - conns = get_peer_connections(peer, tunnel) - - if not conns: - raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting') - - for conn in conns: - try: - call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10) - call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10) - except TimeoutExpired as e: - raise vyos.opmode.InternalError(f'Timed out while resetting {conn}') - - print('Peer reset result: success') +def _get_childsa_id_list(ike_sas: list) -> list: + """ + Generate list of CHILD SA ids based on list of OrderingDict + wich is returned by vici + :param ike_sas: list of IKE SAs generated by vici + :type ike_sas: list + :return: list of IKE SAs ids + :rtype: list + """ + list_childsa_id: list = [] + for ike in ike_sas: + for ike_sa in ike.values(): + for child_sa in ike_sa['child-sas'].values(): + list_childsa_id.append(child_sa['uniqueid'].decode('ascii')) + return list_childsa_id + + +def reset_peer(peer: str, tunnel: typing.Optional[str] = None): + # Convert tunnel to Strongwan format of CHILD_SA + if tunnel: + if tunnel.isnumeric(): + tunnel = f'{peer}-tunnel-{tunnel}' + elif tunnel == 'vti': + tunnel = f'{peer}-vti' + try: + sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel) + + if not sa_list: + raise vyos.opmode.IncorrectValue('Peer not found, aborting') + if tunnel and sa_list: + childsa_id_list: list = _get_childsa_id_list(sa_list) + if not childsa_id_list: + raise vyos.opmode.IncorrectValue( + 'Peer or tunnel(s) not found, aborting') + vyos.ipsec.terminate_vici_by_name(peer, tunnel) + print('Peer reset result: success') + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.IncorrectValue(err) def show_sa(raw: bool): diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py new file mode 100755 index 000000000..5ff91a59c --- /dev/null +++ b/src/op_mode/nhrp.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 sys +import tabulate +import vyos.opmode + +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import colon_separated_to_dict + + +def _get_formatted_output(output_dict: dict) -> str: + """ + Create formatted table for CLI output + :param output_dict: dictionary for API + :type output_dict: dict + :return: tabulate string + :rtype: str + """ + print(f"Status: {output_dict['Status']}") + output: str = tabulate.tabulate(output_dict['routes'], headers='keys', + numalign="left") + return output + + +def _get_formatted_dict(output_string: str) -> dict: + """ + Format string returned from CMD to API list + :param output_string: String received by CMD + :type output_string: str + :return: dictionary for API + :rtype: dict + """ + formatted_dict: dict = { + 'Status': '', + 'routes': [] + } + output_list: list = output_string.split('\n\n') + for list_a in output_list: + output_dict = colon_separated_to_dict(list_a, True) + if 'Status' in output_dict: + formatted_dict['Status'] = output_dict['Status'] + else: + formatted_dict['routes'].append(output_dict) + return formatted_dict + + +def show_interface(raw: bool): + """ + Command 'show nhrp interface' + :param raw: if API + :type raw: bool + """ + if not process_named_running('opennhrp'): + raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') + interface_string: str = cmd('sudo opennhrpctl interface show') + interface_dict: dict = _get_formatted_dict(interface_string) + if raw: + return interface_dict + else: + return _get_formatted_output(interface_dict) + + +def show_tunnel(raw: bool): + """ + Command 'show nhrp tunnel' + :param raw: if API + :type raw: bool + """ + if not process_named_running('opennhrp'): + raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') + tunnel_string: str = cmd('sudo opennhrpctl show') + tunnel_dict: list = _get_formatted_dict(tunnel_string) + if raw: + return tunnel_dict + else: + return _get_formatted_output(tunnel_dict) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py index 3797a7153..79130c7c0 100755 --- a/src/op_mode/openvpn.py +++ b/src/op_mode/openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -153,6 +153,8 @@ def _get_raw_data(mode: str) -> dict: d = data[intf] d['local_host'] = conf_dict[intf].get('local-host', '') d['local_port'] = conf_dict[intf].get('local-port', '') + if conf.exists(f'interfaces openvpn {intf} server client'): + d['configured_clients'] = conf.list_nodes(f'interfaces openvpn {intf} server client') if mode in ['client', 'site-to-site']: for client in d['clients']: if 'shared-secret-key-file' in list(conf_dict[intf]): @@ -171,8 +173,8 @@ def _format_openvpn(data: dict) -> str: 'TX bytes', 'RX bytes', 'Connected Since'] out = '' - data_out = [] for intf in list(data): + data_out = [] l_host = data[intf]['local_host'] l_port = data[intf]['local_port'] for client in list(data[intf]['clients']): @@ -190,7 +192,9 @@ def _format_openvpn(data: dict) -> str: data_out.append([name, remote, tunnel, local, tx_bytes, rx_bytes, online_since]) - out += tabulate(data_out, headers) + if data_out: + out += tabulate(data_out, headers) + out += "\n" return out diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py index ae532ccc9..88982c50b 100755 --- a/src/op_mode/show_openconnect_otp.py +++ b/src/op_mode/show_openconnect_otp.py @@ -46,7 +46,7 @@ def get_otp_ocserv(username): # options which we need to update into the dictionary retrived. default_values = defaults(base) ocserv = dict_merge(default_values, ocserv) - # workaround a "know limitation" - https://phabricator.vyos.net/T2665 + # workaround a "know limitation" - https://vyos.dev/T2665 del ocserv['authentication']['local_users']['username']['otp'] if not ocserv["authentication"]["local_users"]["username"]: return None diff --git a/src/services/api/graphql/generate/config_session_function.py b/src/services/api/graphql/generate/config_session_function.py index fc0dd7a87..20fc7cc1d 100644 --- a/src/services/api/graphql/generate/config_session_function.py +++ b/src/services/api/graphql/generate/config_session_function.py @@ -8,8 +8,12 @@ def show_config(path: list[str], configFormat: typing.Optional[str]): def show(path: list[str]): pass +def show_user_info(user: str): + pass + queries = {'show_config': show_config, - 'show': show} + 'show': show, + 'show_user_info': show_user_info} def save_config_file(fileName: typing.Optional[str]): pass diff --git a/src/services/api/graphql/graphql/auth_token_mutation.py b/src/services/api/graphql/graphql/auth_token_mutation.py index 21ac40094..603a13758 100644 --- a/src/services/api/graphql/graphql/auth_token_mutation.py +++ b/src/services/api/graphql/graphql/auth_token_mutation.py @@ -20,6 +20,7 @@ from ariadne import ObjectType, UnionType from graphql import GraphQLResolveInfo from .. libs.token_auth import generate_token +from .. session.session import get_user_info from .. import state auth_token_mutation = ObjectType("Mutation") @@ -36,13 +37,24 @@ def auth_token_resolver(obj: Any, info: GraphQLResolveInfo, data: Dict): datetime.timedelta(seconds=exp_interval)) res = generate_token(user, passwd, secret, expiration) - if res: + try: + res |= get_user_info(user) + except ValueError: + # non-existent user already caught + pass + if 'token' in res: data['result'] = res return { "success": True, "data": data } + if 'errors' in res: + return { + "success": False, + "errors": res['errors'] + } + return { "success": False, "errors": ['token generation failed'] diff --git a/src/services/api/graphql/libs/token_auth.py b/src/services/api/graphql/libs/token_auth.py index 2100eba7f..8585485c9 100644 --- a/src/services/api/graphql/libs/token_auth.py +++ b/src/services/api/graphql/libs/token_auth.py @@ -29,14 +29,13 @@ def generate_token(user: str, passwd: str, secret: str, exp: int) -> dict: payload_data = {'iss': user, 'sub': user_id, 'exp': exp} secret = state.settings.get('secret') if secret is None: - return { - "success": False, - "errors": ['failed secret generation'] - } + return {"errors": ['missing secret']} token = jwt.encode(payload=payload_data, key=secret, algorithm="HS256") users |= {user_id: user} return {'token': token} + else: + return {"errors": ['failed pam authentication']} def get_user_context(request): context = {} diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py index 0b77b1433..3c5a062b6 100644 --- a/src/services/api/graphql/session/session.py +++ b/src/services/api/graphql/session/session.py @@ -29,6 +29,28 @@ from api.graphql.libs.op_mode import normalize_output op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') +def get_config_dict(path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False, + no_tag_node_value_mangle=False): + config = Config() + return config.get_config_dict(path=path, effective=effective, + key_mangling=key_mangling, + get_first_key=get_first_key, + no_multi_convert=no_multi_convert, + no_tag_node_value_mangle=no_tag_node_value_mangle) + +def get_user_info(user): + user_info = {} + info = get_config_dict(['system', 'login', 'user', user], + get_first_key=True) + if not info: + raise ValueError("No such user") + + user_info['user'] = user + user_info['full_name'] = info.get('full-name', '') + + return user_info + class Session: """ Wrapper for calling configsession functions based on GraphQL requests. @@ -116,6 +138,19 @@ class Session: return res + def show_user_info(self): + session = self._session + data = self._data + + user_info = {} + user = data['user'] + try: + user_info = get_user_info(user) + except Exception as error: + raise error + + return user_info + def system_status(self): import api.graphql.session.composite.system_status as system_status diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index f59e089ae..cd73f38ec 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -425,7 +425,7 @@ async def validation_exception_handler(request, exc): return error(400, str(exc.errors()[0])) @app.post('/configure') -def configure_op(data: Union[ConfigureModel, ConfigureListModel]): +async def configure_op(data: Union[ConfigureModel, ConfigureListModel]): session = app.state.vyos_session env = session.get_session_env() config = vyos.config.Config(session_env=env) @@ -494,7 +494,7 @@ def configure_op(data: Union[ConfigureModel, ConfigureListModel]): return success(None) @app.post("/retrieve") -def retrieve_op(data: RetrieveModel): +async def retrieve_op(data: RetrieveModel): session = app.state.vyos_session env = session.get_session_env() config = vyos.config.Config(session_env=env) diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py index ad7e053db..6fb43ece2 100644 --- a/src/tests/test_configverify.py +++ b/src/tests/test_configverify.py @@ -27,11 +27,6 @@ class TestDictSearch(TestCase): def test_dh_key_none(self): self.assertFalse(verify_diffie_hellman_length('/tmp/non_existing_file', '1024')) - def test_dh_key_256(self): - key_len = '256' - cmd(f'openssl dhparam -out {dh_file} {key_len}') - self.assertTrue(verify_diffie_hellman_length(dh_file, key_len)) - def test_dh_key_512(self): key_len = '512' cmd(f'openssl dhparam -out {dh_file} {key_len}') diff --git a/src/validators/timezone b/src/validators/timezone index baf5abca2..107571181 100755 --- a/src/validators/timezone +++ b/src/validators/timezone @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -25,7 +25,7 @@ if __name__ == '__main__': parser.add_argument("--validate", action="store", required=True, help="Check if timezone is valid") args = parser.parse_args() - tz_data = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::') + tz_data = cmd('timedatectl list-timezones') tz_data = tz_data.split('\n') if args.validate not in tz_data: diff --git a/src/xdp/common/common_libbpf.c b/src/xdp/common/common_libbpf.c index 5788ecd9e..443ca4c66 100644 --- a/src/xdp/common/common_libbpf.c +++ b/src/xdp/common/common_libbpf.c @@ -24,10 +24,6 @@ static inline bool IS_ERR_OR_NULL(const void *ptr) int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr, struct bpf_object **pobj, int *prog_fd) { - struct bpf_object_open_attr open_attr = { - .file = attr->file, - .prog_type = attr->prog_type, - }; struct bpf_program *prog, *first_prog = NULL; enum bpf_attach_type expected_attach_type; enum bpf_prog_type prog_type; @@ -41,10 +37,13 @@ int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr, if (!attr->file) return -EINVAL; + obj = bpf_object__open_file(attr->file, NULL); - obj = bpf_object__open_xattr(&open_attr); - if (IS_ERR_OR_NULL(obj)) - return -ENOENT; + if (libbpf_get_error(obj)) + return -EINVAL; + + prog = bpf_object__next_program(obj, NULL); + bpf_program__set_type(prog, attr->prog_type); bpf_object__for_each_program(prog, obj) { /* @@ -82,7 +81,7 @@ int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr, bpf_map__for_each(map, obj) { const char* mapname = bpf_map__name(map); - if (!bpf_map__is_offload_neutral(map)) + if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY) bpf_map__set_ifindex(map, attr->ifindex); /* Was: map->map_ifindex = attr->ifindex; */ diff --git a/src/xdp/common/common_user_bpf_xdp.c b/src/xdp/common/common_user_bpf_xdp.c index faf7f4f91..524f08c9d 100644 --- a/src/xdp/common/common_user_bpf_xdp.c +++ b/src/xdp/common/common_user_bpf_xdp.c @@ -21,7 +21,7 @@ int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd) int err; /* libbpf provide the XDP net_device link-level hook attach helper */ - err = bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags); + err = bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL); if (err == -EEXIST && !(xdp_flags & XDP_FLAGS_UPDATE_IF_NOEXIST)) { /* Force mode didn't work, probably because a program of the * opposite type is loaded. Let's unload that and try loading @@ -32,9 +32,9 @@ int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd) xdp_flags &= ~XDP_FLAGS_MODES; xdp_flags |= (old_flags & XDP_FLAGS_SKB_MODE) ? XDP_FLAGS_DRV_MODE : XDP_FLAGS_SKB_MODE; - err = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags); + err = bpf_xdp_detach(ifindex, xdp_flags, NULL); if (!err) - err = bpf_set_link_xdp_fd(ifindex, prog_fd, old_flags); + err = bpf_xdp_attach(ifindex, prog_fd, old_flags, NULL); } if (err < 0) { fprintf(stderr, "ERR: " @@ -65,7 +65,7 @@ int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id) __u32 curr_prog_id; int err; - err = bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags); + err = bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id); if (err) { fprintf(stderr, "ERR: get link xdp id failed (err=%d): %s\n", -err, strerror(-err)); @@ -86,7 +86,7 @@ int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id) return EXIT_FAIL; } - if ((err = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags)) < 0) { + if ((err = bpf_xdp_detach(ifindex, xdp_flags, NULL)) < 0) { fprintf(stderr, "ERR: %s() link set xdp failed (err=%d): %s\n", __func__, err, strerror(-err)); return EXIT_FAIL_XDP; @@ -109,22 +109,28 @@ struct bpf_object *load_bpf_object_file(const char *filename, int ifindex) * hardware offloading XDP programs (note this sets libbpf * bpf_program->prog_ifindex and foreach bpf_map->map_ifindex). */ - struct bpf_prog_load_attr prog_load_attr = { - .prog_type = BPF_PROG_TYPE_XDP, - .ifindex = ifindex, - }; - prog_load_attr.file = filename; + struct bpf_program *prog; + obj = bpf_object__open_file(filename, NULL); + + if (libbpf_get_error(obj)) + return NULL; + + prog = bpf_object__next_program(obj, NULL); + bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); + bpf_program__set_ifindex(prog, ifindex); /* Use libbpf for extracting BPF byte-code from BPF-ELF object, and * loading this into the kernel via bpf-syscall */ - err = bpf_prog_load_xattr(&prog_load_attr, &obj, &first_prog_fd); + err = bpf_object__load(obj); if (err) { fprintf(stderr, "ERR: loading BPF-OBJ file(%s) (%d): %s\n", filename, err, strerror(-err)); return NULL; } + first_prog_fd = bpf_program__fd(prog); + /* Notice how a pointer to a libbpf bpf_object is returned */ return obj; } @@ -136,12 +142,15 @@ static struct bpf_object *open_bpf_object(const char *file, int ifindex) struct bpf_map *map; struct bpf_program *prog, *first_prog = NULL; - struct bpf_object_open_attr open_attr = { - .file = file, - .prog_type = BPF_PROG_TYPE_XDP, - }; + obj = bpf_object__open_file(file, NULL); - obj = bpf_object__open_xattr(&open_attr); + if (libbpf_get_error(obj)) + return NULL; + + prog = bpf_object__next_program(obj, NULL); + bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); + + err = bpf_object__load(obj); if (IS_ERR_OR_NULL(obj)) { err = -PTR_ERR(obj); fprintf(stderr, "ERR: opening BPF-OBJ file(%s) (%d): %s\n", @@ -157,7 +166,7 @@ static struct bpf_object *open_bpf_object(const char *file, int ifindex) } bpf_object__for_each_map(map, obj) { - if (!bpf_map__is_offload_neutral(map)) + if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY) bpf_map__set_ifindex(map, ifindex); } @@ -264,10 +273,10 @@ struct bpf_object *load_bpf_and_xdp_attach(struct config *cfg) if (cfg->progsec[0]) /* Find a matching BPF prog section name */ - bpf_prog = bpf_object__find_program_by_title(bpf_obj, cfg->progsec); + bpf_prog = bpf_object__find_program_by_name(bpf_obj, cfg->progsec); else /* Find the first program */ - bpf_prog = bpf_program__next(NULL, bpf_obj); + bpf_prog = bpf_object__next_program(bpf_obj, NULL); if (!bpf_prog) { fprintf(stderr, "ERR: couldn't find a program in ELF section '%s'\n", cfg->progsec); diff --git a/src/xdp/common/xdp_stats_kern.h b/src/xdp/common/xdp_stats_kern.h index 4e08551a0..c061a149d 100644 --- a/src/xdp/common/xdp_stats_kern.h +++ b/src/xdp/common/xdp_stats_kern.h @@ -13,12 +13,12 @@ #endif /* Keeps stats per (enum) xdp_action */ -struct bpf_map_def SEC("maps") xdp_stats_map = { - .type = BPF_MAP_TYPE_PERCPU_ARRAY, - .key_size = sizeof(__u32), - .value_size = sizeof(struct datarec), - .max_entries = XDP_ACTION_MAX, -}; +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, struct datarec); + __uint(max_entries, XDP_ACTION_MAX); +} xdp_stats_map SEC(".maps"); static __always_inline __u32 xdp_stats_record_action(struct xdp_md *ctx, __u32 action) diff --git a/src/xdp/xdp_prog_kern.c b/src/xdp/xdp_prog_kern.c index a1eb395af..59308325d 100644 --- a/src/xdp/xdp_prog_kern.c +++ b/src/xdp/xdp_prog_kern.c @@ -16,19 +16,19 @@ #define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n)) #endif -struct bpf_map_def SEC("maps") tx_port = { - .type = BPF_MAP_TYPE_DEVMAP, - .key_size = sizeof(int), - .value_size = sizeof(int), - .max_entries = 256, -}; - -struct bpf_map_def SEC("maps") redirect_params = { - .type = BPF_MAP_TYPE_HASH, - .key_size = ETH_ALEN, - .value_size = ETH_ALEN, - .max_entries = 1, -}; +struct { + __uint(type, BPF_MAP_TYPE_DEVMAP); + __type(key, int); + __type(value, int); + __uint(max_entries, 256); +} tx_port SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, ETH_ALEN); + __type(value, ETH_ALEN); + __uint(max_entries, 1); +} redirect_params SEC(".maps"); static __always_inline __u16 csum_fold_helper(__u32 csum) { @@ -208,8 +208,12 @@ out: return xdp_stats_record_action(ctx, action); } +#ifndef AF_INET #define AF_INET 2 +#endif +#ifndef AF_INET6 #define AF_INET6 10 +#endif #define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF) /* from include/net/ip.h */ |