From c792f817d6888272dbc4eb9bd984bed7e3abcb8b Mon Sep 17 00:00:00 2001 From: stalabi1 <54641848+stalabi1@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:25:20 -0700 Subject: [PATCH] Update AAA module (#382) * Update AAA module authentication implementation * Add fragment * Update new attribute with version_added * Update sonic_aaa.py * Update AAA module to align with SONiC functionality * Update playbook example * Revert * Address review comments and add name-service feature * Update aaa playbook example * Update get_replaced_config method * Address review comment * Update config description --- .../fragments/382-aaa-breaking-change.yaml | 2 + playbooks/common_examples/sonic_aaa.yaml | 37 +- .../network/sonic/argspec/aaa/aaa.py | 63 +- .../network/sonic/config/aaa/aaa.py | 548 ++++++++++++------ .../network/sonic/facts/aaa/aaa.py | 140 +++-- plugins/modules/sonic_aaa.py | 377 ++++++++---- .../roles/sonic_aaa/defaults/main.yml | 228 ++++++-- .../regression/roles/sonic_aaa/tasks/main.yml | 16 +- .../sonic_aaa/tasks/preparation_tests.yaml | 2 +- .../sonic_aaa/tasks/tasks_template_del.yaml | 21 - .../network/sonic/fixtures/sonic_aaa.yaml | 507 ++++++++++++++-- .../modules/network/sonic/test_sonic_aaa.py | 14 + 12 files changed, 1490 insertions(+), 465 deletions(-) create mode 100644 changelogs/fragments/382-aaa-breaking-change.yaml delete mode 100644 tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml diff --git a/changelogs/fragments/382-aaa-breaking-change.yaml b/changelogs/fragments/382-aaa-breaking-change.yaml new file mode 100644 index 000000000..96b0c64b7 --- /dev/null +++ b/changelogs/fragments/382-aaa-breaking-change.yaml @@ -0,0 +1,2 @@ +breaking_changes: + - sonic_aaa - Update AAA module to align with SONiC functionality (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/382) diff --git a/playbooks/common_examples/sonic_aaa.yaml b/playbooks/common_examples/sonic_aaa.yaml index 7d968b83c..41d434e1e 100644 --- a/playbooks/common_examples/sonic_aaa.yaml +++ b/playbooks/common_examples/sonic_aaa.yaml @@ -38,10 +38,39 @@ sonic_aaa: config: authentication: - data: - fail_through: true - group: tacacs+ - local: true + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local state: merged - name: Merge tacacs configurations sonic_tacacs_server: diff --git a/plugins/module_utils/network/sonic/argspec/aaa/aaa.py b/plugins/module_utils/network/sonic/argspec/aaa/aaa.py index a61b7307b..97dbdf198 100644 --- a/plugins/module_utils/network/sonic/argspec/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/argspec/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -42,16 +42,57 @@ def __init__(self, **kwargs): 'options': { 'authentication': { 'options': { - 'data': { - 'options': { - 'fail_through': {'type': 'bool'}, - 'group': { - 'choices': ['ldap', 'radius', 'tacacs+'], - 'type': 'str' - }, - 'local': {'type': 'bool'} - }, - 'type': 'dict' + 'auth_method': { + 'choices': ['ldap', 'local', 'radius', 'tacacs+'], + 'elements': 'str', + 'type': 'list' + }, + 'console_auth_local': {'type': 'bool'}, + 'failthrough': {'type': 'bool'}, + }, + 'type': 'dict' + }, + 'authorization': { + 'options': { + 'commands_auth_method': { + 'choices': ['local', 'tacacs+'], + 'elements': 'str', + 'type': 'list' + }, + 'login_auth_method': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' + } + }, + 'type': 'dict' + }, + 'name_service': { + 'options': { + 'group': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'netgroup': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' + }, + 'passwd': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'shadow': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'sudoers': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' } }, 'type': 'dict' diff --git a/plugins/module_utils/network/sonic/config/aaa/aaa.py b/plugins/module_utils/network/sonic/config/aaa/aaa.py index 036567f0a..b794a959c 100644 --- a/plugins/module_utils/network/sonic/config/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/config/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -13,6 +13,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) @@ -21,21 +22,21 @@ ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( - update_states, - get_diff, -) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( - utils, + remove_empties, + update_states ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( - to_request, - edit_config + edit_config, + to_request ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( get_new_config, get_formatted_config_diff ) +AAA_AUTHENTICATION_PATH = '/data/openconfig-system:system/aaa/authentication/config' +AAA_AUTHORIZATION_PATH = '/data/openconfig-system:system/aaa/authorization' +AAA_NAME_SERVICE_PATH = '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' PATCH = 'patch' DELETE = 'delete' @@ -66,7 +67,7 @@ def get_aaa_facts(self): facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) aaa_facts = facts['ansible_network_resources'].get('aaa') if not aaa_facts: - return [] + return {} return aaa_facts def execute_module(self): @@ -76,14 +77,17 @@ def execute_module(self): :returns: The result from module execution """ result = {'changed': False} - warnings = list() - commands = list() + warnings = [] + commands = [] existing_aaa_facts = self.get_aaa_facts() commands, requests = self.set_config(existing_aaa_facts) if commands and len(requests) > 0: if not self._module.check_mode: - self.edit_config(requests) + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) result['changed'] = True result['commands'] = commands @@ -98,6 +102,7 @@ def execute_module(self): if self._module.check_mode: result.pop('after', None) new_config = get_new_config(commands, existing_aaa_facts) + self.post_process_generated_config(new_config) result['after(generated)'] = new_config if self._module._diff: result['diff'] = get_formatted_config_diff(old_config, @@ -106,12 +111,6 @@ def execute_module(self): result['warnings'] = warnings return result - def edit_config(self, requests): - try: - response = edit_config(self._module, to_request(self._module, requests)) - except ConnectionError as exc: - self._module.fail_json(msg=str(exc), code=exc.code) - def set_config(self, existing_aaa_facts): """ Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) @@ -120,7 +119,7 @@ def set_config(self, existing_aaa_facts): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - want = self._module.params['config'] + want = remove_empties(self._module.params['config']) have = existing_aaa_facts resp = self.set_state(want, have) return to_list(resp) @@ -134,23 +133,20 @@ def set_state(self, want, have): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + commands = [] + requests = [] state = self._module.params['state'] - if not want: - want = {} - if not have: - have = {} - diff = self.get_diff_aaa(want, have) - if state == 'deleted': - commands = self._state_deleted(want, have) - elif state == 'merged': - commands = self._state_merged(diff) + if state == 'merged': + commands, requests = self._state_merged(diff) elif state == 'replaced': - commands = self._state_replaced(diff) + commands, requests = self._state_replaced(want, have, diff) elif state == 'overridden': - commands = self._state_overridden(want, have) - return commands + commands, requests = self._state_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + return commands, requests def _state_merged(self, diff): """ The command generator when state is merged @@ -159,40 +155,17 @@ def _state_merged(self, diff): :returns: the commands necessary to merge the provided into the current configuration """ - commands = [] - requests = [] - if diff: - requests = self.get_create_aaa_request(diff) - if len(requests) > 0: - commands = update_states(diff, "merged") - return commands, requests - - def _state_deleted(self, want, have): - """ The command generator when state is deleted + commands = diff + requests = self.get_modify_aaa_requests(commands) - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects - """ - commands = [] - requests = [] - if not want: - if have: - requests = self.get_delete_all_aaa_request(have) - if len(requests) > 0: - commands = update_states(have, "deleted") + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') else: - want = utils.remove_empties(want) - new_have = self.remove_default_entries(have) - d_diff = get_diff(want, new_have, is_skeleton=True) - diff_want = get_diff(want, d_diff, is_skeleton=True) - if diff_want: - requests = self.get_delete_all_aaa_request(diff_want) - if len(requests) > 0: - commands = update_states(diff_want, "deleted") + commands = [] + return commands, requests - def _state_replaced(self, diff): + def _state_replaced(self, want, have, diff): """ The command generator when state is merged :rtype: A list @@ -200,11 +173,25 @@ def _state_replaced(self, diff): the current configuration """ commands = [] + mod_commands = [] requests = [] - if diff: - requests = self.get_create_aaa_request(diff) - if len(requests) > 0: - commands = update_states(diff, "replaced") + replaced_config = self.get_replaced_config(want, have) + + if replaced_config: + is_delete_all = replaced_config == have + del_requests = self.get_delete_aaa_requests(replaced_config, have, is_delete_all) + requests.extend(del_requests) + commands.extend(update_states(replaced_config, 'deleted')) + mod_commands = want + else: + mod_commands = diff + + if mod_commands: + mod_requests = self.get_modify_aaa_requests(mod_commands) + if mod_requests: + requests.extend(mod_requests) + commands.extend(update_states(mod_commands, 'replaced')) + return commands, requests def _state_overridden(self, want, have): @@ -220,135 +207,344 @@ def _state_overridden(self, want, have): requests = [] if have and have != want: - del_requests = self.get_delete_all_aaa_request(have) + is_delete_all = True + del_requests = self.get_delete_aaa_requests(have, None, is_delete_all) requests.extend(del_requests) - commands.extend(update_states(have, "deleted")) + commands.extend(update_states(have, 'deleted')) have = [] if not have and want: mod_commands = want - mod_requests = self.get_create_aaa_request(mod_commands) + mod_requests = self.get_modify_aaa_requests(mod_commands) - if len(mod_requests) > 0: + if mod_requests: requests.extend(mod_requests) - commands.extend(update_states(mod_commands, "overridden")) + commands.extend(update_states(mod_commands, 'overridden')) return commands, requests - def get_create_aaa_request(self, commands): - requests = [] - aaa_path = 'data/openconfig-system:system/aaa' - method = PATCH - aaa_payload = self.build_create_aaa_payload(commands) - if aaa_payload: - request = {'path': aaa_path, 'method': method, 'data': aaa_payload} - requests.append(request) - return requests + def _state_deleted(self, want, have): + """ The command generator when state is deleted - def build_create_aaa_payload(self, commands): - payload = {} - auth_method_list = [] - if "authentication" in commands and commands["authentication"]: - payload = {"openconfig-system:aaa": {"authentication": {"config": {}}}} - if "local" in commands["authentication"]["data"] and commands["authentication"]["data"]["local"]: - auth_method_list.append('local') - if "group" in commands["authentication"]["data"] and commands["authentication"]["data"]["group"]: - auth_method = commands["authentication"]["data"]["group"] - auth_method_list.append(auth_method) - if auth_method_list: - cfg = {'authentication-method': auth_method_list} - payload['openconfig-system:aaa']['authentication']['config'].update(cfg) - if "fail_through" in commands["authentication"]["data"]: - cfg = {'failthrough': str(commands["authentication"]["data"]["fail_through"])} - payload['openconfig-system:aaa']['authentication']['config'].update(cfg) - return payload - - def remove_default_entries(self, data): - new_data = {} - if not data: - return new_data + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + is_delete_all = False + + if not want: + commands = deepcopy(have) + is_delete_all = True + else: + commands = deepcopy(want) + + requests = self.get_delete_aaa_requests(commands, have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') else: - new_data = {'authentication': {'data': {}}} - local = data['authentication']['data'].get('local', None) - if local is not None: - new_data["authentication"]["data"]["local"] = local - group = data['authentication']['data'].get('group', None) - if group is not None: - new_data["authentication"]["data"]["group"] = group - fail_through = data['authentication']['data'].get('fail_through', None) - if fail_through is not None: - new_data["authentication"]["data"]["fail_through"] = fail_through - return new_data - - def get_delete_all_aaa_request(self, have): + commands = [] + + return commands, requests + + def get_modify_aaa_requests(self, commands): requests = [] - if "authentication" in have and have["authentication"]: - if "local" in have["authentication"]["data"] or "group" in have["authentication"]["data"]: - request = self.get_authentication_method_delete_request() - requests.append(request) - if "fail_through" in have["authentication"]["data"]: - request = self.get_failthrough_delete_request() - requests.append(request) + + if commands: + # Authentication modification handling + authentication = commands.get('authentication') + if authentication: + authentication_cfg_dict = {} + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + if auth_method: + authentication_cfg_dict['authentication-method'] = auth_method + if console_auth_local is not None: + authentication_cfg_dict['console-authentication-local'] = console_auth_local + if failthrough is not None: + authentication_cfg_dict['failthrough'] = str(failthrough) + if authentication_cfg_dict: + payload = {'openconfig-system:config': authentication_cfg_dict} + requests.append({'path': AAA_AUTHENTICATION_PATH, 'method': PATCH, 'data': payload}) + + # Authorization modification handling + authorization = commands.get('authorization') + if authorization: + authorization_dict = {} + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + if commands_auth_method: + authorization_dict['openconfig-aaa-tacacsplus-ext:commands'] = {'config': {'authorization-method': commands_auth_method}} + if login_auth_method: + authorization_dict['openconfig-aaa-ext:login'] = {'config': {'authorization-method': login_auth_method}} + if authorization_dict: + payload = {'openconfig-system:authorization': authorization_dict} + requests.append({'path': AAA_AUTHORIZATION_PATH, 'method': PATCH, 'data': payload}) + + # Name-service modification handling + name_service = commands.get('name_service') + if name_service: + name_service_cfg_dict = {} + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + if group: + name_service_cfg_dict['group-method'] = group + if netgroup: + name_service_cfg_dict['netgroup-method'] = netgroup + if passwd: + name_service_cfg_dict['passwd-method'] = passwd + if shadow: + name_service_cfg_dict['shadow-method'] = shadow + if sudoers: + name_service_cfg_dict['sudoers-method'] = sudoers + if name_service_cfg_dict: + payload = {'openconfig-aaa-ext:config': name_service_cfg_dict} + requests.append({'path': AAA_NAME_SERVICE_PATH, 'method': PATCH, 'data': payload}) + return requests - def get_authentication_method_delete_request(self): - path = 'data/openconfig-system:system/aaa/authentication/config/authentication-method' - method = DELETE - request = {'path': path, 'method': method} - return request + def get_delete_aaa_requests(self, commands, have, is_delete_all): + requests = [] - def get_failthrough_delete_request(self): - path = 'data/openconfig-system:system/aaa/authentication/config/failthrough' - method = DELETE - request = {'path': path, 'method': method} + if not commands: + return requests + + if is_delete_all: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, None)) + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, None)) + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, None)) + return requests + + config_dict = {} + # Authentication deletion handling + authentication = commands.get('authentication') + if authentication: + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + cfg_authentication = have.get('authentication') + if cfg_authentication: + authentication_dict = {} + cfg_auth_method = cfg_authentication.get('auth_method') + cfg_console_auth_local = cfg_authentication.get('console_auth_local') + cfg_failthrough = cfg_authentication.get('failthrough') + + # Current SONiC behavior doesn't support single list item deletion + if auth_method and auth_method == cfg_auth_method: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'authentication-method')) + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None and console_auth_local == cfg_console_auth_local: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'console-authentication-local')) + authentication_dict['console_auth_local'] = console_auth_local + if failthrough is not None and failthrough == cfg_failthrough: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'failthrough')) + authentication_dict['failthrough'] = failthrough + if authentication_dict: + config_dict['authentication'] = authentication_dict + + # Authorization deletion handling + authorization = commands.get('authorization') + if authorization: + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + cfg_authorization = have.get('authorization') + if cfg_authorization: + authorization_dict = {} + cfg_commands_auth_method = cfg_authorization.get('commands_auth_method') + cfg_login_auth_method = cfg_authorization.get('login_auth_method') + + # Current SONiC behavior doesn't support single list item deletion + if commands_auth_method and commands_auth_method == cfg_commands_auth_method: + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, + 'openconfig-aaa-tacacsplus-ext:commands/config/authorization-method')) + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method and login_auth_method == cfg_login_auth_method: + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, 'openconfig-aaa-ext:login/config/authorization-method')) + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + config_dict['authorization'] = authorization_dict + + # Name-service deletion handling + name_service = commands.get('name_service') + if name_service: + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + cfg_name_service = have.get('name_service') + if cfg_name_service: + name_service_dict = {} + cfg_group = cfg_name_service.get('group') + cfg_netgroup = cfg_name_service.get('netgroup') + cfg_passwd = cfg_name_service.get('passwd') + cfg_shadow = cfg_name_service.get('shadow') + cfg_sudoers = cfg_name_service.get('sudoers') + + # Current SONiC behavior doesn't support single list item deletion + if group and group == cfg_group: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'group-method')) + name_service_dict['group'] = group + if netgroup and netgroup == cfg_netgroup: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'netgroup-method')) + name_service_dict['netgroup'] = netgroup + if passwd and passwd == cfg_passwd: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'passwd-method')) + name_service_dict['passwd'] = passwd + if shadow and shadow == cfg_shadow: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'shadow-method')) + name_service_dict['shadow'] = shadow + if sudoers and sudoers == cfg_sudoers: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'sudoers-method')) + name_service_dict['sudoers'] = sudoers + if name_service_dict: + config_dict['name_service'] = name_service_dict + commands = config_dict + + return requests + + def get_delete_request(self, url, attr): + if attr: + url += '/%s' % (attr) + request = {'path': url, 'method': DELETE} return request - # Current SONiC code behavior for patch overwrites the OC authentication-method leaf-list - # This function serves as a workaround for the issue, allowing the user to append to the - # OC authentication-method leaf-list. def get_diff_aaa(self, want, have): - diff_cfg = {} - diff_authentication = {} - diff_data = {} + """AAA module requires custom diff method due to overwritting of list in SONiC""" + if not have: + return want - authentication = want.get('authentication', None) + cfg_dict = {} + # Authentication diff handling + authentication = want.get('authentication') if authentication: - data = authentication.get('data', None) - if data: - fail_through = data.get('fail_through', None) - local = data.get('local', None) - group = data.get('group', None) - - cfg_authentication = have.get('authentication', None) - if cfg_authentication: - cfg_data = cfg_authentication.get('data', None) - if cfg_data: - cfg_fail_through = cfg_data.get('fail_through', None) - cfg_local = cfg_data.get('local', None) - cfg_group = cfg_data.get('group', None) - - if fail_through is not None and fail_through != cfg_fail_through: - diff_data['fail_through'] = fail_through - if local and local != cfg_local: - diff_data['local'] = local - if group and group != cfg_group: - diff_data['group'] = group - - diff_local = diff_data.get('local', None) - diff_group = diff_data.get('group', None) - if diff_local and not diff_group and cfg_group: - diff_data['group'] = cfg_group - if diff_group and not diff_local and cfg_local: - diff_data['local'] = cfg_local - else: - if fail_through is not None: - diff_data['fail_through'] = fail_through - if local: - diff_data['local'] = local - if group: - diff_data['group'] = group - if diff_data: - diff_authentication['data'] = diff_data - diff_cfg['authentication'] = diff_authentication - - return diff_cfg + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + cfg_authentication = have.get('authentication') + if cfg_authentication: + authentication_dict = {} + cfg_auth_method = cfg_authentication.get('auth_method') + cfg_console_auth_local = cfg_authentication.get('console_auth_local') + cfg_failthrough = cfg_authentication.get('failthrough') + + if auth_method and auth_method != cfg_auth_method: + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None and console_auth_local != cfg_console_auth_local: + authentication_dict['console_auth_local'] = console_auth_local + if failthrough is not None and failthrough != cfg_failthrough: + authentication_dict['failthrough'] = failthrough + if authentication_dict: + cfg_dict['authentication'] = authentication_dict + else: + cfg_dict['authentication'] = authentication + + # Authorization diff handling + authorization = want.get('authorization') + if authorization: + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + cfg_authorization = have.get('authorization') + if cfg_authorization: + authorization_dict = {} + cfg_commands_auth_method = cfg_authorization.get('commands_auth_method') + cfg_login_auth_method = cfg_authorization.get('login_auth_method') + + if commands_auth_method and commands_auth_method != cfg_commands_auth_method: + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method and login_auth_method != cfg_login_auth_method: + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + cfg_dict['authorization'] = authorization_dict + else: + cfg_dict['authorization'] = authorization + + # Name-service diff handling + name_service = want.get('name_service') + if name_service: + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + cfg_name_service = have.get('name_service') + if cfg_name_service: + name_service_dict = {} + cfg_group = cfg_name_service.get('group') + cfg_netgroup = cfg_name_service.get('netgroup') + cfg_passwd = cfg_name_service.get('passwd') + cfg_shadow = cfg_name_service.get('shadow') + cfg_sudoers = cfg_name_service.get('sudoers') + + if group and group != cfg_group: + name_service_dict['group'] = group + if netgroup and netgroup != cfg_netgroup: + name_service_dict['netgroup'] = netgroup + if passwd and passwd != cfg_passwd: + name_service_dict['passwd'] = passwd + if shadow and shadow != cfg_shadow: + name_service_dict['shadow'] = shadow + if sudoers and sudoers != cfg_sudoers: + name_service_dict['sudoers'] = sudoers + if name_service_dict: + cfg_dict['name_service'] = name_service_dict + else: + cfg_dict['name_service'] = name_service + + return cfg_dict + + def get_replaced_config(self, want, have): + config_dict = {} + authentication = want.get('authentication') + authorization = want.get('authorization') + name_service = want.get('name_service') + cfg_authentication = have.get('authentication') + cfg_authorization = have.get('authorization') + cfg_name_service = have.get('name_service') + + if authentication and cfg_authentication and authentication != cfg_authentication: + config_dict['authentication'] = cfg_authentication + if authorization and cfg_authorization and authorization != cfg_authorization: + config_dict['authorization'] = cfg_authorization + if name_service and cfg_name_service and name_service != cfg_name_service: + config_dict['name_service'] = cfg_name_service + + return config_dict + + def post_process_generated_config(self, data): + if data: + authentication = data.get('authentication') + authorization = data.get('authorization') + name_service = data.get('name_service') + + if authentication: + if 'auth_method' in authentication and not authentication['auth_method']: + data['authentication'].pop('auth_method') + if not data['authentication']: + data.pop('authentication') + if authorization: + if 'commands_auth_method' in authorization and not authorization['commands_auth_method']: + data['authorization'].pop('commands_auth_method') + if 'login_auth_method' in authorization and not authorization['login_auth_method']: + data['authorization'].pop('login_auth_method') + if not data['authorization']: + data.pop('authorization') + if name_service: + for method in ('group', 'netgroup', 'passwd', 'shadow', 'sudoers'): + if method in name_service and not name_service[method]: + data['name_service'].pop(method) + if not data['name_service']: + data.pop('name_service') diff --git a/plugins/module_utils/network/sonic/facts/aaa/aaa.py b/plugins/module_utils/network/sonic/facts/aaa/aaa.py index 541a5805e..8a2b6a3ae 100644 --- a/plugins/module_utils/network/sonic/facts/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/facts/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -9,7 +9,6 @@ for a given resource, parsed, and the facts tree is populated based on the configuration. """ - from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -18,13 +17,16 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.aaa.aaa import AaaArgs from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( to_request, edit_config ) -from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.aaa.aaa import AaaArgs -GET = "get" +AAA_PATH = '/data/openconfig-system:system/aaa' class AaaFacts(object): @@ -45,20 +47,6 @@ def __init__(self, module, subspec='config', options='options'): self.generated_spec = utils.generate_dict(facts_argument_spec) - def get_aaa(self): - """Get aaa details available in chassis""" - request = [{"path": "data/openconfig-system:system/aaa", "method": GET}] - try: - response = edit_config(self._module, to_request(self._module, request)) - except ConnectionError as exc: - self._module.fail_json(msg=str(exc), code=exc.code) - data = {} - if ('openconfig-system:aaa' in response[0][1]): - if ('authentication' in response[0][1]['openconfig-system:aaa']): - if ('config' in response[0][1]['openconfig-system:aaa']['authentication']): - data = response[0][1]['openconfig-system:aaa']['authentication']['config'] - return data - def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for aaa :param connection: the device connection @@ -67,44 +55,94 @@ def populate_facts(self, connection, ansible_facts, data=None): :rtype: dictionary :returns: facts """ - if not data: - data = self.get_aaa() objs = [] - objs = self.render_config(self.generated_spec, data) + + if not data: + data = self.update_aaa(self._module) + objs = data facts = {} if objs: params = utils.validate_config(self.argument_spec, {'config': objs}) - facts['aaa'] = params['config'] - + facts['aaa'] = remove_empties(params['config']) ansible_facts['ansible_network_resources'].update(facts) return ansible_facts - def render_config(self, spec, conf): - """ - Render config as dictionary structure and delete keys - from spec for null values + def get_config(self, module, path, key_name): + """Retrieve OC configuration from device""" + cfg = None + get_path = '%s/%s' % (AAA_PATH, path) + request = {'path': get_path, 'method': 'get'} - :param spec: The facts tree, generated from the argspec - :param conf: The configuration - :rtype: dictionary - :returns: The generated config - """ - config = self.parse_sonic_aaa(spec, conf) - return config - - def parse_sonic_aaa(self, spec, conf): - config = deepcopy(spec) - if conf: - temp = {} - if ('authentication-method' in conf) and (conf['authentication-method']): - if 'local' in conf['authentication-method']: - temp['local'] = True - choices = ['tacacs+', 'ldap', 'radius'] - for i, word in enumerate(conf['authentication-method']): - if word in choices: - temp['group'] = conf['authentication-method'][i] - if ('failthrough' in conf): - temp['fail_through'] = conf['failthrough'] - if temp: - config['authentication']['data'] = temp - return utils.remove_empties(config) + try: + response = edit_config(module, to_request(module, request)) + if key_name in response[0][1]: + cfg = response[0][1].get(key_name) + except Exception as exc: + # Avoid raising error when there is no configuration + if 'Resource not found' in str(exc): + pass + else: + module.fail_json(msg=str(exc), code=exc.code) + + return cfg + + def update_aaa(self, module): + """Transform OC configuration to Ansible argspec""" + config_dict = {} + bool_dict = {'True': True, 'False': False} + + # Authentication configuration handling + authentication_cfg = self.get_config(module, 'authentication/config', 'openconfig-system:config') + if authentication_cfg: + authentication_dict = {} + auth_method = authentication_cfg.get('authentication-method') + console_auth_local = authentication_cfg.get('console-authentication-local') + failthrough = authentication_cfg.get('failthrough') + + if auth_method: + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None: + authentication_dict['console_auth_local'] = console_auth_local + if failthrough: + authentication_dict['failthrough'] = bool_dict[failthrough] + if authentication_dict: + config_dict['authentication'] = authentication_dict + + # Authorization configuration handling + authorization_dict = {} + commands_auth_method = self.get_config(module, 'authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method', + 'openconfig-aaa-tacacsplus-ext:authorization-method') + login_auth_method = self.get_config(module, 'authorization/openconfig-aaa-ext:login/config/authorization-method', + 'openconfig-aaa-ext:authorization-method') + + if commands_auth_method: + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method: + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + config_dict['authorization'] = authorization_dict + + # Name-service configuration handling + name_service_cfg = self.get_config(module, 'openconfig-aaa-ext:name-service/config', 'openconfig-aaa-ext:config') + if name_service_cfg: + name_service_dict = {} + group = name_service_cfg.get('group-method') + netgroup = name_service_cfg.get('netgroup-method') + passwd = name_service_cfg.get('passwd-method') + shadow = name_service_cfg.get('shadow-method') + sudoers = name_service_cfg.get('sudoers-method') + + if group: + name_service_dict['group'] = group + if netgroup: + name_service_dict['netgroup'] = netgroup + if passwd: + name_service_dict['passwd'] = passwd + if shadow: + name_service_dict['shadow'] = shadow + if sudoers: + name_service_dict['sudoers'] = sudoers + if name_service_dict: + config_dict['name_service'] = name_service_dict + + return config_dict diff --git a/plugins/modules/sonic_aaa.py b/plugins/modules/sonic_aaa.py index c17c0f711..129c63337 100644 --- a/plugins/modules/sonic_aaa.py +++ b/plugins/modules/sonic_aaa.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -35,224 +35,397 @@ module: sonic_aaa version_added: 1.1.0 notes: -- Tested against Enterprise SONiC Distribution by Dell Technologies. -- Supports C(check_mode). -author: Abirami N (@abirami-n) -short_description: Manage AAA and its parameters +- Tested against Enterprise SONiC Distribution by Dell Technologies +- Supports C(check_mode) +author: Shade Talabi (@stalabi1) +short_description: Manage AAA configuration on SONiC description: - - This module is used for configuration management of aaa parameters on devices running Enterprise SONiC. + - This module provides configuration management of AAA for devices running SONiC. options: config: description: - - Specifies the aaa related configurations + - AAA configuration + - For all lists in the module, the list items should be specified in order of desired priority. + - List items specified first have the highest priority. type: dict suboptions: authentication: description: - - Specifies the configurations required for aaa authentication + - AAA authentication configuration type: dict + version_added: 3.0.0 suboptions: - data: + auth_method: description: - - Specifies the data required for aaa authentication - type: dict - suboptions: - fail_through: - description: - - Specifies the state of failthrough - type: bool - local: - description: - - Enable or Disable local authentication - type: bool - group: - description: - - Specifies the method of aaa authentication - type: str - choices: - - ldap - - radius - - tacacs+ - + - Specifies the order of the methods in which to authenticate login + type: list + elements: str + choices: ['ldap', 'local', 'radius', 'tacacs+'] + console_auth_local : + description: + Enable/disable local authentication on console + type: bool + failthrough: + description: + - Enable/disable failthrough + type: bool + authorization: + description: + - AAA authorization configuration + type: dict + version_added: 3.0.0 + suboptions: + commands_auth_method: + description: + - Specifies the order of the methods in which to authorize commands + type: list + elements: str + choices: ['local', 'tacacs+'] + login_auth_method: + description: + - Specifies the order of the methods in which to authorize login + type: list + elements: str + choices: ['ldap', 'local'] + name_service: + description: + - AAA name-service configuration + type: dict + version_added: 3.0.0 + suboptions: + group: + description: + - Name-service source for group method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + netgroup: + description: + - Name-service source for netgroup method + type: list + elements: str + choices: ['ldap', 'local'] + passwd: + description: + - Name-service source for passwd method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + shadow: + description: + - Name-service source for shadow method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + sudoers: + description: + - Name-service source for sudoers method + type: list + elements: str + choices: ['ldap', 'local'] state: description: - - Specifies the operation to be performed on the aaa parameters configured on the device. - - In case of merged, the input configuration will be merged with the existing aaa configuration on the device. - - In case of deleted the existing aaa configuration will be removed from the device. - - In case of replaced, the existing aaa configuration will be replaced with provided configuration. - - In case of overridden, the existing aaa configuration will be overridden with the provided configuration. - default: merged + - The state of the configuration after module completion choices: ['merged', 'deleted', 'overridden', 'replaced'] + default: merged type: str """ EXAMPLES = """ -# Using deleted +# Using Merged # # Before state: # ------------- # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : local +# sonic# show aaa +# (No AAA configuration present) -- name: Delete aaa configurations +- name: Merge AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - local: True - state: deleted + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + netgroup: + - local + passwd: + - login + shadow: + - ldap + sudoers: + - local + state: merged # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -# Using deleted +# Using Replaced # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : local +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Delete aaa configurations +- name: Replace AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: - state: deleted + authentication: + console_auth_local: True + failthrough: False + authorization: + commands_auth_method: + - local + name_service: + group: + - ldap + state: replaced # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : +# failthrough : False # login-method : +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap -# Using merged +# Using Overridden # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Merge aaa configurations +- name: Override AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - local: true - fail_through: true - state: merged + auth_method: + - tacacs+ + console_auth_local: True + failthrough: True + state: overridden # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : local +# login-method : tacacs+ +# console authentication : local -# Using replaced +# Using Deleted # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : local, radius +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Replace aaa configurations +- name: Delete AAA individual attributes dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - group: ldap - fail_through: true - state: replaced + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + netgroup: + - local + passwd: + - login + shadow: + - ldap + sudoers: + - local + state: deleted # After state: # ------------ # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : local, ldap +# sonic# show aaa +# (No AAA configuration present) -# Using overridden +# Using Deleted # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : local, radius +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Override aaa configurations +- name: Delete all AAA configuration dellemc.enterprise_sonic.sonic_aaa: - config: - authentication: - data: - group: tacacs+ - fail_through: true - state: overridden + config: {} + state: deleted # After state: # ------------ # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : tacacs+ - +# sonic# show aaa +# (No AAA configuration present) """ RETURN = """ before: - description: The configuration prior to the model invocation. + description: The configuration prior to the module invocation. returned: always type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. after: - description: The resulting configuration model invocation. + description: The resulting configuration module invocation. returned: when changed type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. after(generated): - description: The generated configuration model invocation. + description: The generated configuration module invocation. returned: when C(check_mode) type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. commands: description: The set of commands pushed to the remote device. returned: always diff --git a/tests/regression/roles/sonic_aaa/defaults/main.yml b/tests/regression/roles/sonic_aaa/defaults/main.yml index d69780c24..f87a39824 100644 --- a/tests/regression/roles/sonic_aaa/defaults/main.yml +++ b/tests/regression/roles/sonic_aaa/defaults/main.yml @@ -1,73 +1,213 @@ --- ansible_connection: httpapi module_name: aaa + tests: - name: test_case_01 - description: aaa properties + description: Initial AAA configuration state: merged input: authentication: - data: - fail_through: true - group: tacacs+ - local: true - - # This configuration will lock out admin user - # - name: test_case_02 - # description: Update created aaa properties - # state: merged - # input: - # authentication: - # data: - # fail_through: false + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - - name: test_case_03 - description: Update aaa properties - change group + - name: test_case_02 + description: Modify AAA configuration state: merged input: authentication: - data: - fail_through: true - group: radius + auth_method: + - local + - tacacs+ + - ldap + - radius + console_auth_local: False + failthrough: False + authorization: + commands_auth_method: + - tacacs+ + - local + login_auth_method: + - ldap + - local + name_service: + group: + - local + - login + netgroup: + - ldap + passwd: + - login + shadow: + - login + - local + - ldap + sudoers: + - local - - name: test_case_04 - description: Replace aaa properties + - name: test_case_03 + description: Replace AAA configuration state: replaced input: authentication: - data: - fail_through: false - group: ldap + console_auth_local: True + authorization: + login_auth_method: + - local + name_service: + group: + - login - - name: test_case_05 - description: Override aaa properties + + - name: test_case_04 + description: Override AAA configuration state: overridden input: authentication: - data: - fail_through: true - group: radius - local: true + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - - name: test_case_06 - description: Delete aaa properties + - name: test_case_05 + description: Delete AAA individual attributes state: deleted input: authentication: - data: - group: radius + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - - name: test_case_07 - description: aaa properties - state: merged + - name: test_case_06 + description: Add AAA configuration for delete all + state: merged input: authentication: - data: - fail_through: true - group: radius - local: true + auth_method: + - local + - tacacs+ + - ldap + - radius + console_auth_local: False + failthrough: False + authorization: + commands_auth_method: + - tacacs+ + - local + login_auth_method: + - ldap + - local + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local -test_delete_all: - - name: del_all_test_case_01 - description: Delete aaa properties + - name: test_case_07 + description: Delete all AAA configuratiom state: deleted + input: {} diff --git a/tests/regression/roles/sonic_aaa/tasks/main.yml b/tests/regression/roles/sonic_aaa/tasks/main.yml index fdcaa9a43..0678e3732 100644 --- a/tests/regression/roles/sonic_aaa/tasks/main.yml +++ b/tests/regression/roles/sonic_aaa/tasks/main.yml @@ -1,17 +1,11 @@ -- debug: msg="sonic_aaa Test started ..." +- debug: msg="sonic_aaa test started ..." -- name: Preparations test +- set_fact: + base_cfg_path: "{{ playbook_dir + '/roles/' + role_name + '/' + 'templates/' }}" + +- name: Preparation tests include_tasks: preparation_tests.yaml - name: "Test {{ module_name }} started ..." include_tasks: tasks_template.yaml loop: "{{ tests }}" - -- name: "test_delete_all {{ module_name }} stated ..." - include_tasks: tasks_template_del.yaml - loop: "{{ test_delete_all }}" - when: test_delete_all is defined - -- name: Display all variables/facts known for a host - debug: - var: hostvars[inventory_hostname].ansible_facts.test_reports diff --git a/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml index e8a964f3f..ee5f19794 100644 --- a/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml +++ b/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml @@ -1,4 +1,4 @@ -- name: Deletes old radius server configurations +- name: Delete existing AAA configuration sonic_aaa: config: {} state: deleted diff --git a/tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml b/tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml deleted file mode 100644 index b50cc26cd..000000000 --- a/tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml +++ /dev/null @@ -1,21 +0,0 @@ -- name: "{{ item.name}} , {{ item.description}}" - sonic_aaa: - state: "{{ item.state }}" - config: - register: action_task_output - ignore_errors: yes - -- import_role: - name: common - tasks_from: action.facts.report.yaml - -- name: "{{ item.name}} , {{ item.description}} Idempotent" - sonic_aaa: - state: "{{ item.state }}" - config: - register: idempotent_task_output - ignore_errors: yes - -- import_role: - name: common - tasks_from: idempotent.facts.report.yaml diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml index e7ca4429a..9d44f4f4a 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml @@ -2,77 +2,496 @@ merged_01: module_args: config: - authentication: - data: - local: true - fail_through: true - group: radius + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' response: code: 200 - - path: "data/sonic-system-aaa:sonic-system-aaa" + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + expected_config_requests: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: + config: + authorization-method: + - local + - tacacs+ + openconfig-aaa-ext:login: + config: + authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local + +replaced_01: + module_args: + config: + authentication: + console_auth_local: True + authorization: + commands_auth_method: + - local + name_service: + group: + - local + state: replaced + existing_aaa_config: + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' response: code: 200 value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa" - method: "patch" + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' data: - openconfig-system:aaa: - authentication: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + console-authentication-local: True + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: config: - authentication-method: + authorization-method: - local - - radius - failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - local + +overridden_01: + module_args: + config: + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local + state: overridden + existing_aaa_config: + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + console-authentication-local: False + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: + value: + openconfig-aaa-ext:config: + group-method: + - local + expected_config_requests: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: + config: + authorization-method: + - local + - tacacs+ + openconfig-aaa-ext:login: + config: + authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local + deleted_01: module_args: + config: + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local state: deleted existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' response: code: 200 value: - openconfig-system:aaa: - authentication: - config: - authentication-method: - - radius - - local - failthrough: true + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa/authentication/config/authentication-method" - method: "delete" + - path: '/data/openconfig-system:system/aaa/authentication/config/authentication-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config/console-authentication-local' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config/failthrough' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + method: 'delete' data: - - path: "data/openconfig-system:system/aaa/authentication/config/failthrough" - method: "delete" + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/group-method' + method: 'delete' data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/netgroup-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/passwd-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/shadow-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/sudoers-method' + method: 'delete' + data: + deleted_02: module_args: - config: - authentication: - data: - local: true - fail_through: true - group: radius + config: {} state: deleted existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' response: code: 200 value: - openconfig-system:aaa: - authentication: - config: - authentication-method: - - radius - - local - failthrough: true + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa/authentication/config/authentication-method" - method: "delete" + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' data: - - path: "data/openconfig-system:system/aaa/authentication/config/failthrough" - method: "delete" + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' data: diff --git a/tests/unit/modules/network/sonic/test_sonic_aaa.py b/tests/unit/modules/network/sonic/test_sonic_aaa.py index de747c2c8..fb1296059 100644 --- a/tests/unit/modules/network/sonic/test_sonic_aaa.py +++ b/tests/unit/modules/network/sonic/test_sonic_aaa.py @@ -54,6 +54,20 @@ def test_sonic_aaa_merged_01(self): result = self.execute_module(changed=True) self.validate_config_requests() + def test_sonic_aaa_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_aaa_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_aaa_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_aaa_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + def test_sonic_aaa_deleted_01(self): set_module_args(self.fixture_data['deleted_01']['module_args']) self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_aaa_config'])