diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/conntrack.py | 91 | ||||
| -rwxr-xr-x | src/conf_mode/high-availability.py | 20 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-dummy.py | 4 | ||||
| -rwxr-xr-x | src/conf_mode/nat.py | 2 | ||||
| -rw-r--r-- | src/etc/sysctl.d/30-vyos-router.conf | 4 | ||||
| -rw-r--r-- | src/etc/systemd/system/keepalived.service.d/override.conf | 14 | ||||
| -rwxr-xr-x | src/helpers/config_dependency.py | 58 | ||||
| -rwxr-xr-x | src/helpers/vyos-domain-resolver.py | 6 | ||||
| -rwxr-xr-x | src/helpers/vyos-save-config.py | 5 | ||||
| -rwxr-xr-x | src/init/vyos-router | 3 | ||||
| -rwxr-xr-x | src/migration-scripts/conntrack/3-to-4 | 50 | ||||
| -rwxr-xr-x | src/migration-scripts/system/13-to-14 | 2 | ||||
| -rwxr-xr-x | src/op_mode/ipsec.py | 39 | ||||
| -rwxr-xr-x | src/op_mode/otp.py | 124 | ||||
| -rw-r--r-- | src/pam-configs/radius | 3 | ||||
| -rw-r--r-- | src/tests/test_dependency_graph.py | 31 | 
16 files changed, 410 insertions, 46 deletions
| diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 9c43640a9..a0de914bc 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -24,7 +24,9 @@ from vyos.firewall import find_nftables_rule  from vyos.firewall import remove_nftables_rule  from vyos.utils.process import process_named_running  from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args  from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd  from vyos.utils.process import run  from vyos.template import render  from vyos import ConfigError @@ -62,6 +64,13 @@ module_map = {       },  } +valid_groups = [ +    'address_group', +    'domain_group', +    'network_group', +    'port_group' +] +  def resync_conntrackd():      tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')      if tmp > 0: @@ -78,15 +87,53 @@ def get_config(config=None):                                       get_first_key=True,                                       with_recursive_defaults=True) +    conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), +                                                 get_first_key=True, +                                                 no_tag_node_value_mangle=True) +      return conntrack  def verify(conntrack): -    if dict_search('ignore.rule', conntrack) != None: -        for rule, rule_config in conntrack['ignore']['rule'].items(): -            if dict_search('destination.port', rule_config) or \ -               dict_search('source.port', rule_config): -               if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: -                   raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') +    for inet in ['ipv4', 'ipv6']: +        if dict_search_args(conntrack, 'ignore', inet, 'rule') != None: +            for rule, rule_config in conntrack['ignore'][inet]['rule'].items(): +                if dict_search('destination.port', rule_config) or \ +                   dict_search('destination.group.port_group', rule_config) or \ +                   dict_search('source.port', rule_config) or \ +                   dict_search('source.group.port_group', rule_config): +                   if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: +                       raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') + +                for side in ['destination', 'source']: +                    if side in rule_config: +                        side_conf = rule_config[side] + +                        if 'group' in side_conf: +                            if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: +                                raise ConfigError('Only one address-group, network-group or domain-group can be specified') + +                            for group in valid_groups: +                                if group in side_conf['group']: +                                    group_name = side_conf['group'][group] +                                    error_group = group.replace("_", "-") + +                                    if group in ['address_group', 'network_group', 'domain_group']: +                                        if 'address' in side_conf: +                                            raise ConfigError(f'{error_group} and address cannot both be defined') + +                                    if group_name and group_name[0] == '!': +                                        group_name = group_name[1:] + +                                    if inet == 'ipv6': +                                        group = f'ipv6_{group}' + +                                    group_obj = dict_search_args(conntrack['firewall_group'], group, group_name) + +                                    if group_obj is None: +                                        raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') + +                                    if not group_obj: +                                        Warning(f'{error_group} "{group_name}" has no members!')      return None @@ -94,26 +141,18 @@ def generate(conntrack):      render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)      render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)      render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) - -    # dry-run newly generated configuration -    tmp = run(f'nft -c -f {nftables_ct_file}') -    if tmp > 0: -        if os.path.exists(nftables_ct_file): -            os.unlink(nftables_ct_file) -        raise ConfigError('Configuration file errors encountered!') -      return None -def find_nftables_ct_rule(rule): +def find_nftables_ct_rule(table, chain, rule):      helper_search = re.search('ct helper set "(\w+)"', rule)      if helper_search:          rule = helper_search[1] -    return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) +    return find_nftables_rule(table, chain, [rule]) -def find_remove_rule(rule): -    handle = find_nftables_ct_rule(rule) +def find_remove_rule(table, chain, rule): +    handle = find_nftables_ct_rule(table, chain, rule)      if handle: -        remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle) +        remove_nftables_rule(table, chain, handle)  def apply(conntrack):      # Depending on the enable/disable state of the ALG (Application Layer Gateway) @@ -127,18 +166,24 @@ def apply(conntrack):                          cmd(f'rmmod {mod}')              if 'nftables' in module_config:                  for rule in module_config['nftables']: -                    find_remove_rule(rule) +                    find_remove_rule('raw', 'VYOS_CT_HELPER', rule) +                    find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule)          else:              if 'ko' in module_config:                  for mod in module_config['ko']:                      cmd(f'modprobe {mod}')              if 'nftables' in module_config:                  for rule in module_config['nftables']: -                    if not find_nftables_ct_rule(rule): -                        cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}') +                    if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule): +                        cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}') + +                    if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule): +                        cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}')      # Load new nftables ruleset -    cmd(f'nft -f {nftables_ct_file}') +    install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') +    if install_result == 1: +        raise ConfigError(f'Failed to apply configuration: {output}')      if process_named_running('conntrackd'):          # Reload conntrack-sync daemon to fetch new sysctl values diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 0121df11c..70f43ab52 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>. +import os  import time  from sys import exit @@ -24,6 +25,7 @@ from ipaddress import IPv6Interface  from vyos.base import Warning  from vyos.config import Config +from vyos.configdict import leaf_node_changed  from vyos.ifconfig.vrrp import VRRP  from vyos.template import render  from vyos.template import is_ipv4 @@ -35,6 +37,9 @@ from vyos import airbag  airbag.enable() +systemd_override = r'/run/systemd/system/keepalived.service.d/10-override.conf' + +  def get_config(config=None):      if config:          conf = config @@ -54,6 +59,9 @@ def get_config(config=None):      if conf.exists(conntrack_path):          ha['conntrack_sync_group'] = conf.return_value(conntrack_path) +    if leaf_node_changed(conf, base + ['vrrp', 'disable-snmp']): +        ha.update({'restart_required': {}}) +      return ha  def verify(ha): @@ -164,19 +172,23 @@ def verify(ha):  def generate(ha):      if not ha or 'disable' in ha: +        if os.path.isfile(systemd_override): +            os.unlink(systemd_override)          return None      render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha) +    render(systemd_override, 'high-availability/10-override.conf.j2', ha)      return None  def apply(ha):      service_name = 'keepalived.service' +    call('systemctl daemon-reload')      if not ha or 'disable' in ha:          call(f'systemctl stop {service_name}')          return None      # Check if IPv6 address is tentative T5533 -    for group, group_config in ha['vrrp']['group'].items(): +    for group, group_config in ha.get('vrrp', {}).get('group', {}).items():          if 'hello_source_address' in group_config:              if is_ipv6(group_config['hello_source_address']):                  ipv6_address = group_config['hello_source_address'] @@ -187,7 +199,11 @@ def apply(ha):                      if is_ipv6_tentative(interface, ipv6_address):                          time.sleep(interval) -    call(f'systemctl reload-or-restart {service_name}') +    systemd_action = 'reload-or-restart' +    if 'restart_required' in ha: +        systemd_action = 'restart' + +    call(f'systemctl {systemd_action} {service_name}')      return None  if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index e771581e1..db768b94d 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2021 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 @@ -55,7 +55,7 @@ def generate(dummy):      return None  def apply(dummy): -    d = DummyIf(dummy['ifname']) +    d = DummyIf(**dummy)      # Remove dummy interface      if 'deleted' in dummy: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 9da7fbe80..08e96f10b 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -112,7 +112,7 @@ def verify_rule(config, err_msg, groups_dict):                          group_obj = dict_search_args(groups_dict, group, group_name)                          if group_obj is None: -                            raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') +                            raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule')                          if not group_obj:                              Warning(f'{error_group} "{group_name}" has no members!') diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index f5d84be4b..ad43390bb 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -110,3 +110,7 @@ net.ipv6.neigh.default.gc_thresh3 = 8192  # Enable global RFS (Receive Flow Steering) configuration. RFS is inactive  # until explicitly configured at the interface level  net.core.rps_sock_flow_entries = 32768 + +# Congestion control +net.core.default_qdisc=fq +net.ipv4.tcp_congestion_control=bbr diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf deleted file mode 100644 index d91a824b9..000000000 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -After=vyos-router.service -# Only start if there is our configuration file - remove Debian default -# config file from the condition list -ConditionFileNotEmpty= -ConditionFileNotEmpty=/run/keepalived/keepalived.conf - -[Service] -KillMode=process -Type=simple -# Read configuration variable file if it is present -ExecStart= -ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp -PIDFile=/run/keepalived/keepalived.pid diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py new file mode 100755 index 000000000..50c72956e --- /dev/null +++ b/src/helpers/config_dependency.py @@ -0,0 +1,58 @@ +#!/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 os +import sys +from argparse import ArgumentParser +from argparse import ArgumentTypeError + +try: +    from vyos.configdep import check_dependency_graph +    from vyos.defaults import directories +except ImportError: +    # allow running during addon package build +    _here = os.path.dirname(__file__) +    sys.path.append(os.path.join(_here, '../../python/vyos')) +    from configdep import check_dependency_graph +    from defaults import directories + +# addon packages will need to specify the dependency directory +dependency_dir = os.path.join(directories['data'], +                              'config-mode-dependencies') + +def path_exists(s): +    if not os.path.exists(s): +        raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory") +    return s + +def main(): +    parser = ArgumentParser(description='generate and save dict from xml defintions') +    parser.add_argument('--dependency-dir', type=path_exists, +                        default=dependency_dir, +                        help='location of vyos-1x dependency directory') +    parser.add_argument('--supplement', type=str, +                        help='supplemental dependency file') +    args = vars(parser.parse_args()) + +    if not check_dependency_graph(**args): +        sys.exit(1) + +    sys.exit(0) + +if __name__ == '__main__': +    main() diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index 7e2fe2462..eac3d37af 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -37,12 +37,14 @@ domain_state = {}  ipv4_tables = {      'ip vyos_mangle',      'ip vyos_filter', -    'ip vyos_nat' +    'ip vyos_nat', +    'ip raw'  }  ipv6_tables = {      'ip6 vyos_mangle', -    'ip6 vyos_filter' +    'ip6 vyos_filter', +    'ip6 raw'  }  def get_config(conf): diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py index 2812155e8..8af4a7916 100755 --- a/src/helpers/vyos-save-config.py +++ b/src/helpers/vyos-save-config.py @@ -44,7 +44,10 @@ ct = config.get_config_tree(effective=True)  write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name  with open(write_file, 'w') as f: -    f.write(ct.to_string()) +    # config_tree is None before boot configuration is complete; +    # automated saves should check boot_configuration_complete +    if ct is not None: +        f.write(ct.to_string())      f.write("\n")      f.write(system_footer()) diff --git a/src/init/vyos-router b/src/init/vyos-router index 96f163213..a5d1a31fa 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -335,6 +335,9 @@ start ()      nfct helper add rpc inet tcp      nfct helper add rpc inet udp      nfct helper add tns inet tcp +    nfct helper add rpc inet6 tcp +    nfct helper add rpc inet6 udp +    nfct helper add tns inet6 tcp      nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"      rm -f /etc/hostname diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4 new file mode 100755 index 000000000..e90c383af --- /dev/null +++ b/src/migration-scripts/conntrack/3-to-4 @@ -0,0 +1,50 @@ +#!/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/>. + +# Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4` + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['system', 'conntrack'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +if config.exists(base + ['ignore', 'rule']): +    config.set(base + ['ignore', 'ipv4']) +    config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule']) +    config.delete(base + ['ignore', 'rule']) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14 index 1fa781869..5b781158b 100755 --- a/src/migration-scripts/system/13-to-14 +++ b/src/migration-scripts/system/13-to-14 @@ -34,7 +34,7 @@ else:      # retrieve all valid timezones      try: -        tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::') +        tz_datas = cmd('timedatectl list-timezones')      except OSError:          tz_datas = ''      tz_data = tz_datas.split('\n') diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 57d3cfed9..44d41219e 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -779,6 +779,45 @@ def show_ra_summary(raw: bool):      return _get_formatted_output_ra_summary(list_sa) +# PSK block +def _get_raw_psk(): +    conf: ConfigTreeQuery = ConfigTreeQuery() +    config_path = ['vpn', 'ipsec', 'authentication', 'psk'] +    psk_config = conf.get_config_dict(config_path, key_mangling=('-', '_'), +                                       get_first_key=True, +                                       no_tag_node_value_mangle=True) + +    psk_list = [] +    for psk, psk_data in psk_config.items(): +        psk_data['psk'] = psk +        psk_list.append(psk_data) + +    return psk_list + + +def _get_formatted_psk(psk_list): +    headers = ["PSK", "Id", "Secret"] +    formatted_data = [] + +    for psk_data in psk_list: +        formatted_data.append([psk_data["psk"], "\n".join(psk_data["id"]), psk_data["secret"]]) + +    return tabulate(formatted_data, headers=headers) + + +def show_psk(raw: bool): +    config = ConfigTreeQuery() +    if not config.exists('vpn ipsec authentication psk'): +        raise vyos.opmode.UnconfiguredSubsystem('VPN ipsec psk authentication is not configured') + +    psk = _get_raw_psk() +    if raw: +        return psk +    return _get_formatted_psk(psk) + +# PSK block end + +  if __name__ == '__main__':      try:          res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py new file mode 100755 index 000000000..c472a1ed3 --- /dev/null +++ b/src/op_mode/otp.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library.  If not, see <http://www.gnu.org/licenses/>. + + +import sys +import os +import vyos.opmode +from jinja2 import Template +from vyos.configquery import ConfigTreeQuery +from vyos.xml import defaults +from vyos.configdict import dict_merge +from vyos.util import popen + + +users_otp_template = Template(""" +{% if info == "full" %} +# You can share it with the user, he just needs to scan the QR in his OTP app +# username: {{username}} +# OTP KEY: {{key_base32}} +# OTP URL: {{otp_url}} +{{qrcode}} +# To add this OTP key to configuration, run the following commands: +set system login user {{username}} authentication otp key '{{key_base32}}' +{% if rate_limit != "3" %} +set system login user {{username}} authentication otp rate-limit '{{rate_limit}}' +{% endif %} +{% if rate_time != "30" %} +set system login user {{username}} authentication otp rate-time '{{rate_time}}' +{% endif %} +{% if window_size != "3" %} +set system login user {{username}} authentication otp window-size '{{window_size}}' +{% endif %} +{% elif info == "key-b32" %} +# OTP key in Base32 for system user {{username}}: +{{key_base32}} +{% elif info == "qrcode" %} +# QR code for system user '{{username}}' +{{qrcode}} +{% elif info == "uri" %} +# URI for system user '{{username}}' +{{otp_url}} +{% endif %} +""", trim_blocks=True, lstrip_blocks=True) + + +def _check_uname_otp(username:str): +    """ +    Check if "username" exists and have an OTP key +    """ +    config = ConfigTreeQuery() +    base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key'] +    if not config.exists(base_key): +        return None +    return True + +def _get_login_otp(username: str, info:str): +    """ +    Retrieve user settings from configuration and set some defaults +    """ +    config = ConfigTreeQuery() +    base = ['system', 'login', 'user', username] +    if not config.exists(base): +        return None +    user_otp = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    # We have gathered the dict representation of the CLI, but there are default +    # options which we need to update into the dictionary retrived. +    default_values = defaults(['system', 'login', 'user']) +    user_otp = dict_merge(default_values, user_otp) +    result = user_otp['authentication']['otp'] +    # Filling in the system and default options +    result['info'] = info +    result['hostname'] = os.uname()[1] +    result['username'] = username +    result['key_base32'] = result['key'] +    result['otp_length'] = '6' +    result['interval'] = '30' +    result['token_type'] = 'hotp-time' +    if result['token_type'] == 'hotp-time': +        token_type_acrn = 'totp' +    result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\ +        result['hostname'],"?secret=",result['key_base32'],"&digits=",\ +        result['otp_length'],"&period=",result['interval']]) +    result['qrcode'],err = popen('qrencode -t ansiutf8', input=result['otp_url']) +    return result + +def show_login(raw: bool, username: str, info:str): +    ''' +    Display OTP parameters for <username> +    ''' +    check_otp = _check_uname_otp(username) +    if check_otp: +        user_otp_params = _get_login_otp(username, info) +    else: +        print(f'There is no such user ("{username}") with an OTP key configured') +        print('You can use the following command to generate a key for a user:\n') +        print(f'generate system login username {username} otp-key hotp-time') +        sys.exit(0) +    if raw: +        return user_otp_params +    return users_otp_template.render(user_otp_params) + + +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/pam-configs/radius b/src/pam-configs/radius index 08247f77c..eee9cb93e 100644 --- a/src/pam-configs/radius +++ b/src/pam-configs/radius @@ -3,15 +3,18 @@ Default: no  Priority: 257  Auth-Type: Primary  Auth: +    [default=ignore success=2] pam_succeed_if.so service = sudo      [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet      [authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so  Account-Type: Primary  Account: +    [default=ignore success=2] pam_succeed_if.so service = sudo      [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet      [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so  Session-Type: Additional  Session: +    [default=ignore success=2] pam_succeed_if.so service = sudo      [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet      [authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py new file mode 100644 index 000000000..f682e87bb --- /dev/null +++ b/src/tests/test_dependency_graph.py @@ -0,0 +1,31 @@ +#!/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 os +from vyos.configdep import check_dependency_graph + +_here = os.path.dirname(__file__) +ddir = os.path.join(_here, '../../data/config-mode-dependencies') + +from unittest import TestCase + +class TestDependencyGraph(TestCase): +    def setUp(self): +        pass + +    def test_acyclic(self): +        res = check_dependency_graph(dependency_dir=ddir) +        self.assertTrue(res) | 
