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 */ | 
