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) |