From 2f621608460a2a44b751f12c50ef9b29cc271e44 Mon Sep 17 00:00:00 2001 From: Rafael Guterres Jeffman <rjeffman@redhat.com> Date: Thu, 7 Nov 2019 00:30:51 -0300 Subject: [PATCH] New sudorule (Sudo Rule) management module There is a new sudorule (Sudo Rule) management module placed in the plugins folder: plugins/modules/ipasudorule.py The sudorule module allows to ensure presence and absence of Sudo Rules. Here is the documentation for the module: README-sudorule.md New example playbooks have been added: playbooks/sudorule/ensure-sudorule-host-member-is-absent.yml playbooks/sudorule/ensure-sudorule-host-member-is-present.yml playbooks/sudorule/ensure-sudorule-hostgroup-member-is-absent.yml playbooks/sudorule/ensure-sudorule-hostgroup-member-is-present.yml playbooks/sudorule/ensure-sudorule-is-absent.yml playbooks/sudorule/ensure-sudorule-is-disabled.yml playbooks/sudorule/ensure-sudorule-is-enabled.yml playbooks/sudorule/ensure-sudorule-is-present.yml playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml New tests added for the module: tests/hbacrule/test_sudorule.yml --- README-sudorule.md | 141 +++++ README.md | 2 + .../ensure-sudorule-host-member-is-absent.yml | 14 + ...ensure-sudorule-host-member-is-present.yml | 13 + ...re-sudorule-hostgroup-member-is-absent.yml | 14 + ...e-sudorule-hostgroup-member-is-present.yml | 13 + .../sudorule/ensure-sudorule-is-absent.yml | 11 + .../sudorule/ensure-sudorule-is-disabled.yml | 11 + .../sudorule/ensure-sudorule-is-enabled.yml | 11 + .../sudorule/ensure-sudorule-is-present.yml | 12 + .../ensure-sudorule-sudocmd-is-absent.yml | 15 + .../ensure-sudorule-sudocmd-is-present.yml | 14 + plugins/modules/ipasudorule.py | 564 ++++++++++++++++++ tests/sudorule/test_sudorule.yml | 279 +++++++++ 14 files changed, 1114 insertions(+) create mode 100644 README-sudorule.md create mode 100644 playbooks/sudorule/ensure-sudorule-host-member-is-absent.yml create mode 100644 playbooks/sudorule/ensure-sudorule-host-member-is-present.yml create mode 100644 playbooks/sudorule/ensure-sudorule-hostgroup-member-is-absent.yml create mode 100644 playbooks/sudorule/ensure-sudorule-hostgroup-member-is-present.yml create mode 100644 playbooks/sudorule/ensure-sudorule-is-absent.yml create mode 100644 playbooks/sudorule/ensure-sudorule-is-disabled.yml create mode 100644 playbooks/sudorule/ensure-sudorule-is-enabled.yml create mode 100644 playbooks/sudorule/ensure-sudorule-is-present.yml create mode 100644 playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml create mode 100644 playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml create mode 100644 plugins/modules/ipasudorule.py create mode 100644 tests/sudorule/test_sudorule.yml diff --git a/README-sudorule.md b/README-sudorule.md new file mode 100644 index 00000000..aca1b2d0 --- /dev/null +++ b/README-sudorule.md @@ -0,0 +1,141 @@ +Sudorule module +=============== + +Description +----------- + +The sudorule (Sudo Rule) module allows to ensure presence and absence of Sudo Rules and host, hostgroups, users, and user groups as members of Sudo Rule. + + +Features +-------- +* Sudo Rule management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipasudorule module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + + +Example playbook to make sure Sudo Rule is present: + +```yaml +--- +- name: Playbook to handle sudorules + hosts: ipaserver + become: true + + tasks: + # Ensure Sudo Rule is present + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +``` + + +Example playbook to make sure sudocmds are present in Sudo Rule: + +```yaml +--- +- name: Playbook to handle sudorules + hosts: ipaserver + become: true + + tasks: + # Ensure Sudo Rule is present + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + cmd: + - /sbin/ifconfig + action: member +``` + + +Example playbook to make sure sudocmds are not present in Sudo Rule: + +```yaml +--- +- name: Playbook to handle sudorules + hosts: ipaserver + become: true + + tasks: + # Ensure Sudo Rule is present + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + cmd: + - /sbin/ifconfig + action: member + state: absent +``` + +Example playbook to make sure Sudo Rule is absent: + +```yaml +--- +- name: Playbook to handle sudorules + hosts: ipaserver + become: true + + tasks: + # Ensure Sudo Rule is present + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +``` + + +Variables +========= + +ipasudorule +--------------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`name` \| `cn` | The list of sudorule name strings. | yes +`description` | The sudorule description string. | no +`usercategory` | User category the rule applies to. Choices: ["all"] | no +`hostcategory` | Host category the rule applies to. Choices: ["all"] | no +`cmdcategory` | Command category the rule applies to. Choices: ["all"] | no +`nomembers` | Suppress processing of membership attributes. (bool) | no +`host` | List of host name strings assigned to this sudorule. | no +`hostgroup` | List of host group name strings assigned to this sudorule. | no +`user` | List of user name strings assigned to this sudorule. | no +`group` | List of user group name strings assigned to this sudorule. | no +`cmd` | List of sudocmd name strings assigned to this sudorule. | no +`cmdgroup` | List of sudocmd group name strings assigned wto this sudorule. | no +`action` | Work on sudorule or member level. It can be on of `member` or `sudorule` and defaults to `sudorule`. | no +`state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | no + + +Authors +======= + +Rafael Jeffman diff --git a/README.md b/README.md index 65929b55..4957e86f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Features * Modules for pwpolicy management * Modules for sudocmd management * Modules for sudocmdgroup management +* Modules for sudorule management * Modules for topology management * Modules for user management @@ -403,6 +404,7 @@ Modules in plugin/modules * [ipapwpolicy](README-pwpolicy.md) * [ipasudocmd](README-sudocmd.md) * [ipasudocmdgroup](README-sudocmdgroup.md) +* [ipasudorule](README-sudorule.md) * [ipatopologysegment](README-topology.md) * [ipatopologysuffix](README-topology.md) * [ipauser](README-user.md) diff --git a/playbooks/sudorule/ensure-sudorule-host-member-is-absent.yml b/playbooks/sudorule/ensure-sudorule-host-member-is-absent.yml new file mode 100644 index 00000000..f74765c2 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-host-member-is-absent.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + # Ensure host server is absent in Sudo Rule + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + host: server + action: member + state: absent diff --git a/playbooks/sudorule/ensure-sudorule-host-member-is-present.yml b/playbooks/sudorule/ensure-sudorule-host-member-is-present.yml new file mode 100644 index 00000000..4ecf3f33 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-host-member-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + # Ensure host server is present in Sudo Rule + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + host: server + action: member diff --git a/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-absent.yml b/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-absent.yml new file mode 100644 index 00000000..301030f5 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-absent.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + # Ensure hostgroup cluster is absent in Sudo Rule + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + hostgroup: cluster + action: member + state: absent diff --git a/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-present.yml b/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-present.yml new file mode 100644 index 00000000..b4473b27 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-hostgroup-member-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + # Ensure hostgrep cluster is present in Sudo Rule + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + hostgroup: cluster + action: member diff --git a/playbooks/sudorule/ensure-sudorule-is-absent.yml b/playbooks/sudorule/ensure-sudorule-is-absent.yml new file mode 100644 index 00000000..4b87902c --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-is-absent.yml @@ -0,0 +1,11 @@ +--- +- name: Tests + hosts: ipaserver + become: true + + tasks: + # Ensure sudorule command is absent + - ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: absent diff --git a/playbooks/sudorule/ensure-sudorule-is-disabled.yml b/playbooks/sudorule/ensure-sudorule-is-disabled.yml new file mode 100644 index 00000000..90afbd24 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-is-disabled.yml @@ -0,0 +1,11 @@ +--- +- name: Tests + hosts: ipaserver + become: true + + tasks: + # Ensure sudorule command is disabled + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + state: disabled diff --git a/playbooks/sudorule/ensure-sudorule-is-enabled.yml b/playbooks/sudorule/ensure-sudorule-is-enabled.yml new file mode 100644 index 00000000..6618344c --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-is-enabled.yml @@ -0,0 +1,11 @@ +--- +- name: Tests + hosts: ipaserver + become: true + + tasks: + # Ensure sudorule command is enabled + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + state: enabled diff --git a/playbooks/sudorule/ensure-sudorule-is-present.yml b/playbooks/sudorule/ensure-sudorule-is-present.yml new file mode 100644 index 00000000..5b8f32bc --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-is-present.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + + tasks: + # Ensure sudorule command is present + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + description: A test sudo rule. + state: present diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml new file mode 100644 index 00000000..942d0b53 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml @@ -0,0 +1,15 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + state: absent diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml new file mode 100644 index 00000000..61fcbb0d --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member diff --git a/plugins/modules/ipasudorule.py b/plugins/modules/ipasudorule.py new file mode 100644 index 00000000..c21f247a --- /dev/null +++ b/plugins/modules/ipasudorule.py @@ -0,0 +1,564 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Rafael Guterres Jeffman <rjeffman@redhat.com> +# +# Copyright (C) 2019 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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/>. + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipasudorule +short description: Manage FreeIPA sudo rules +description: Manage FreeIPA sudo rules +options: + ipaadmin_principal: + description: The admin principal + default: admin + ipaadmin_password: + description: The admin password + required: false + name: + description: The sudorule name + required: true + aliases: ["cn"] + description: + description: The sudorule description + required: false + user: + description: List of users assigned to the sudo rule. + required: false + usercategory: + description: User category the sudo rule applies to + required: false + choices: ["all"] + usergroup: + description: List of user groups assigned to the sudo rule. + required: false + runasgroupcategory: + description: RunAs Group category applied to the sudo rule. + required: false + choices: ["all"] + runasusercategory: + description: RunAs User category applied to the sudorule. + required: false + choices: ["all"] + nomembers: + description: Suppress processing of membership attributes + required: false + type: bool + host: + description: List of host names assigned to this sudorule. + required: false + type: list + hostgroup: + description: List of host groups assigned to this sudorule. + required: false + type: list + hostcategory: + description: Host category the sudo rule applies to. + required: false + choices: ["all"] + cmd: + description: List of sudocmds assigned to this sudorule. + required: false + type: list + cmdgroup: + description: List of sudocmd groups assigned to this sudorule. + required: false + type: list + cmdcategory: + description: Cammand category the sudo rule applies to + required: false + choices: ["all"] + action: + description: Work on sudorule or member level + default: sudorule + choices: ["member", "sudorule"] + state: + description: State to ensure + default: present + choices: ["present", "absent", "enabled", "disabled"] +author: + - Rafael Jeffman +""" + +EXAMPLES = """ +# Ensure Sudo Rule tesrule1 is present +- ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + +# Ensure sudocmd is present in Sudo Rule +- ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + state: absent + +# Ensure host server is present in Sudo Rule +- ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + host: server + action: member + +# Ensure hostgroup cluster is present in Sudo Rule +- ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + hostgroup: cluster + action: member + +# Ensure sudo rule for usercategory "all" +- ipasudorule: + ipaadmin_password: MyPassword123 + name: allusers + usercategory: all + action: enabled + +# Ensure sudo rule for hostcategory "all" +- ipasudorule: + ipaadmin_password: MyPassword123 + name: allhosts + hostcategory: all + action: enabled + +# Ensure Sudo Rule tesrule1 is absent +- ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + state: absent +""" + +RETURN = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ + temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \ + module_params_get + + +def find_sudorule(module, name): + _args = { + "all": True, + "cn": name, + } + + _result = api_command(module, "sudorule_find", name, _args) + + if len(_result["result"]) > 1: + module.fail_json( + msg="There is more than one sudorule '%s'" % (name)) + elif len(_result["result"]) == 1: + return _result["result"][0] + else: + return None + + +def gen_args(ansible_module): + arglist = ['description', 'usercategory', 'hostcategory', 'cmdcategory', + 'runasusercategory', 'runasgroupcategory', 'nomembers'] + _args = {} + for arg in arglist: + value = module_params_get(ansible_module, arg) + if value is not None: + _args[arg] = value + + return _args + + +def main(): + ansible_module = AnsibleModule( + argument_spec=dict( + # general + ipaadmin_principal=dict(type="str", default="admin"), + ipaadmin_password=dict(type="str", required=False, no_log=True), + + name=dict(type="list", aliases=["cn"], default=None, + required=True), + # present + description=dict(required=False, type="str", default=None), + usercategory=dict(required=False, type="str", default=None, + choices=["all"]), + hostcategory=dict(required=False, type="str", default=None, + choices=["all"]), + nomembers=dict(required=False, type='bool', default=None), + host=dict(required=False, type='list', default=None), + hostgroup=dict(required=False, type='list', default=None), + user=dict(required=False, type='list', default=None), + group=dict(required=False, type='list', default=None), + cmd=dict(required=False, type="list", default=None), + cmdcategory=dict(required=False, type="str", default=None, + choices=["all"]), + runasusercategory=dict(required=False, type="str", default=None, + choices=["all"]), + runasgroupcategory=dict(required=False, type="str", default=None, + choices=["all"]), + action=dict(type="str", default="sudorule", + choices=["member", "sudorule"]), + # state + state=dict(type="str", default="present", + choices=["present", "absent", + "enabled", "disabled"]), + ), + supports_check_mode=True, + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + ipaadmin_principal = module_params_get(ansible_module, + "ipaadmin_principal") + ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password") + names = module_params_get(ansible_module, "name") + + # present + # The 'noqa' variables are not used here, but required for vars(). + # The use of 'noqa' ensures flake8 does not complain about them. + description = module_params_get(ansible_module, "description") # noqa + cmdcategory = module_params_get(ansible_module, 'cmdcategory') # noqa + usercategory = module_params_get(ansible_module, "usercategory") # noqa + hostcategory = module_params_get(ansible_module, "hostcategory") # noqa + runasusercategory = module_params_get(ansible_module, # noqa + "runasusercategory") + runasgroupcategory = module_params_get(ansible_module, # noqa + "runasgroupcategory") + hostcategory = module_params_get(ansible_module, "hostcategory") # noqa + nomembers = module_params_get(ansible_module, "nomembers") # noqa + host = module_params_get(ansible_module, "host") + hostgroup = module_params_get(ansible_module, "hostgroup") + user = module_params_get(ansible_module, "user") + group = module_params_get(ansible_module, "group") + cmd = module_params_get(ansible_module, 'cmd') + cmdgroup = module_params_get(ansible_module, 'cmdgroup') + action = module_params_get(ansible_module, "action") + + # state + state = module_params_get(ansible_module, "state") + + # Check parameters + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one sudorule can be added at a time.") + if action == "member": + invalid = ["description", "usercategory", "hostcategory", + "cmdcategory", "runasusercategory", + "runasgroupcategory", "nomembers"] + + for x in invalid: + if x in vars() and vars()[x] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with action " + "'%s'" % (x, action)) + + elif state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["description", "usercategory", "hostcategory", + "cmdcategory", "runasusercategory", + "runasgroupcategory", "nomembers"] + if action == "sudorule": + invalid.extend(["host", "hostgroup", "user", "group", + "cmd", "cmdgroup"]) + for x in invalid: + if vars()[x] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % + (x, state)) + + elif state in ["enabled", "disabled"]: + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + if action == "member": + ansible_module.fail_json( + msg="Action member can not be used with states enabled and " + "disabled") + invalid = ["description", "usercategory", "hostcategory", + "cmdcategory", "runasusercategory", "runasgroupcategory", + "nomembers", "nomembers", "host", "hostgroup", + "user", "group", "cmd", "cmdgroup"] + for x in invalid: + if vars()[x] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % + (x, state)) + else: + ansible_module.fail_json(msg="Invalid state '%s'" % state) + + # Init + + changed = False + exit_args = {} + ccache_dir = None + ccache_name = None + try: + if not valid_creds(ansible_module, ipaadmin_principal): + ccache_dir, ccache_name = temp_kinit(ipaadmin_principal, + ipaadmin_password) + api_connect() + + commands = [] + + for name in names: + # Make sure sudorule exists + res_find = find_sudorule(ansible_module, name) + + # Create command + if state == "present": + # Generate args + args = gen_args(ansible_module) + if action == "sudorule": + # Found the sudorule + if res_find is not None: + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify + if not compare_args_ipa(ansible_module, args, + res_find): + commands.append([name, "sudorule_mod", args]) + else: + commands.append([name, "sudorule_add", args]) + # Set res_find to empty dict for next step + res_find = {} + + # Generate addition and removal lists + host_add = list( + set(host or []) - + set(res_find.get("member_host", []))) + host_del = list( + set(res_find.get("member_host", [])) - + set(host or [])) + hostgroup_add = list( + set(hostgroup or []) - + set(res_find.get("member_hostgroup", []))) + hostgroup_del = list( + set(res_find.get("member_hostgroup", [])) - + set(hostgroup or [])) + + user_add = list( + set(user or []) - + set(res_find.get("member_user", []))) + user_del = list( + set(res_find.get("member_user", [])) - + set(user or [])) + group_add = list( + set(group or []) - + set(res_find.get("member_group", []))) + group_del = list( + set(res_find.get("member_group", [])) - + set(group or [])) + + cmd_add = list( + set(cmd or []) - + set(res_find.get("member_cmd", []))) + cmd_del = list( + set(res_find.get("member_cmd", [])) - + set(cmd or [])) + cmdgroup_add = list( + set(cmdgroup or []) - + set(res_find.get("member_cmdgroup", []))) + cmdgroup_del = list( + set(res_find.get("member_cmdgroup", [])) - + set(cmdgroup or [])) + + # Add hosts and hostgroups + if len(host_add) > 0 or len(hostgroup_add) > 0: + commands.append([name, "sudorule_add_host", + { + "host": host_add, + "hostgroup": hostgroup_add, + }]) + # Remove hosts and hostgroups + if len(host_del) > 0 or len(hostgroup_del) > 0: + commands.append([name, "sudorule_remove_host", + { + "host": host_del, + "hostgroup": hostgroup_del, + }]) + + # Add users and groups + if len(user_add) > 0 or len(group_add) > 0: + commands.append([name, "sudorule_add_user", + { + "user": user_add, + "group": group_add, + }]) + # Remove users and groups + if len(user_del) > 0 or len(group_del) > 0: + commands.append([name, "sudorule_remove_user", + { + "user": user_del, + "group": group_del, + }]) + + # Add commands + if len(cmd_add) > 0 or len(cmdgroup_add) > 0: + commands.append([name, "sudorule_add_allow_command", + { + "sudocmd": cmd_add, + "sudocmdgroup": cmdgroup_add, + }]) + + if len(cmd_del) > 0 or len(cmdgroup_del) > 0: + commands.append([name, "sudorule_add_deny_command", + { + "sudocmd": cmd_del, + "sudocmdgroup": cmdgroup_del + }]) + + elif action == "member": + if res_find is None: + ansible_module.fail_json(msg="No sudorule '%s'" % name) + + # Add hosts and hostgroups + if host is not None or hostgroup is not None: + commands.append([name, "sudorule_add_host", + { + "host": host, + "hostgroup": hostgroup, + }]) + + # Add users and groups + if user is not None or group is not None: + commands.append([name, "sudorule_add_user", + { + "user": user, + "group": group, + }]) + + # Add commands + if cmd is not None: + commands.append([name, "sudorule_add_allow_command", + { + "sudocmd": cmd, + }]) + + elif state == "absent": + if action == "sudorule": + if res_find is not None: + commands.append([name, "sudorule_del", {}]) + + elif action == "member": + if res_find is None: + ansible_module.fail_json(msg="No sudorule '%s'" % name) + + # Remove hosts and hostgroups + if host is not None or hostgroup is not None: + commands.append([name, "sudorule_remove_host", + { + "host": host, + "hostgroup": hostgroup, + }]) + + # Remove users and groups + if user is not None or group is not None: + commands.append([name, "sudorule_remove_user", + { + "user": user, + "group": group, + }]) + + # Remove commands + if cmd is not None: + commands.append([name, "sudorule_add_deny_command", + { + "sudocmd": cmd, + }]) + + elif state == "enabled": + if res_find is None: + ansible_module.fail_json(msg="No sudorule '%s'" % name) + # sudorule_enable is not failing on an enabled sudorule + # Therefore it is needed to have a look at the ipaenabledflag + # in res_find. + if "ipaenabledflag" not in res_find or \ + res_find["ipaenabledflag"][0] != "TRUE": + commands.append([name, "sudorule_enable", {}]) + + elif state == "disabled": + if res_find is None: + ansible_module.fail_json(msg="No sudorule '%s'" % name) + # sudorule_disable is not failing on an disabled sudorule + # Therefore it is needed to have a look at the ipaenabledflag + # in res_find. + if "ipaenabledflag" not in res_find or \ + res_find["ipaenabledflag"][0] != "FALSE": + commands.append([name, "sudorule_disable", {}]) + + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Execute commands + + errors = [] + for name, command, args in commands: + try: + result = api_command(ansible_module, command, name, + args) + + if "completed" in result: + if result["completed"] > 0: + changed = True + else: + changed = True + except Exception as e: + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, + str(e))) + # Get all errors + # All "already a member" and "not a member" failures in the + # result are ignored. All others are reported. + if "failed" in result and len(result["failed"]) > 0: + for item in result["failed"]: + failed_item = result["failed"][item] + for member_type in failed_item: + for member, failure in failed_item[member_type]: + if "already a member" in failure \ + or "not a member" in failure: + continue + errors.append("%s: %s %s: %s" % ( + command, member_type, member, failure)) + if len(errors) > 0: + ansible_module.fail_json(msg=", ".join(errors)) + + except Exception as e: + ansible_module.fail_json(msg=str(e)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/sudorule/test_sudorule.yml b/tests/sudorule/test_sudorule.yml new file mode 100644 index 00000000..88ed90ab --- /dev/null +++ b/tests/sudorule/test_sudorule.yml @@ -0,0 +1,279 @@ +--- + +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + + - name: Ensure hostgroup is present, with a host. + ipahostgroup: + ipaadmin_password: MyPassword123 + name: cluster + host: + - "{{ groups.ipaserver[0] }}" + + - name: Ensure some sudocmds are available + ipasudocmd: + ipaadmin_password: pass1234 + name: + - /sbin/ifconfig + - /usr/bin/vim + state: present + + - name: Ensure sudorules are absent + ipasudorule: + ipaadmin_password: pass1234 + name: + - testrule1 + - allusers + - allhosts + - allcommands + state: absent + + - name: Ensure sudorule is present + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present again + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + register: result + failed_when: result.changed + + - name: Ensure sudorule is present, runAsUserCategory. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + runAsUserCategory: all + register: result + failed_when: result.changed + + - name: Ensure sudorule is present, with usercategory 'all' + ipasudorule: + ipaadmin_password: pass1234 + name: allusers + usercategory: all + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present, with usercategory 'all', again + ipasudorule: + ipaadmin_password: pass1234 + name: allusers + usercategory: all + register: result + failed_when: result.changed + + - name: Ensure sudorule is present, with hostategory 'all' + ipasudorule: + ipaadmin_password: pass1234 + name: allhosts + hostcategory: all + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present, with hostategory 'all', again + ipasudorule: + ipaadmin_password: pass1234 + name: allhosts + hostcategory: all + register: result + failed_when: result.changed + + - name: Ensure sudorule is disabled + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: disabled + + - name: Ensure sudorule is disabled, again + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: disabled + register: result + failed_when: result.changed + + - name: Ensure sudorule is enabled + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: enabled + register: result + failed_when: not result.changed + + - name: Ensure sudorule is enabled, again + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: enabled + register: result + failed_when: result.changed + + - name: Ensure sudorule is present and some sudocmd are a member of it. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present and some sudocmd are a member of it, again. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + register: result + failed_when: result.changed + + - name: Ensure sudorule is present with cmdcategory 'all'. + ipasudorule: + ipaadmin_password: pass1234 + name: allcommands + cmdcategory: all + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present with cmdcategory 'all', again. + ipasudorule: + ipaadmin_password: pass1234 + name: allcommands + cmdcategory: all + register: result + failed_when: result.changed + + - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + host: "{{ groups.ipaserver[0] }}" + action: member + register: result + failed_when: not result.changed + + - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule, again. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + host: "{{ groups.ipaserver[0] }}" + action: member + register: result + failed_when: result.changed + + - name: Ensure hostgroup is present in sudorule. + ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + hostgroup: cluster + action: member + register: result + failed_when: not result.changed + + - name: Ensure hostgroup is present in sudorule, again. + ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 + hostgroup: cluster + action: member + register: result + failed_when: result.changed + + - name: Ensure sudorule sudocmds are absent + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudorule sudocmds are absent, again + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + cmd: + - /sbin/ifconfig + - /usr/bin/vim + action: member + state: absent + register: result + failed_when: result.changed + + - name: Ensure sudorule is absent + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudorule is absent, again. + ipasudorule: + ipaadmin_password: pass1234 + name: testrule1 + state: absent + register: result + failed_when: result.changed + + - name: Ensure sudorule allhosts is absent + ipasudorule: + ipaadmin_password: pass1234 + name: allhosts + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudorule allhosts is absent, again + ipasudorule: + ipaadmin_password: pass1234 + name: allhosts + state: absent + register: result + failed_when: result.changed + + - name: Ensure sudorule allusers is absent + ipasudorule: + ipaadmin_password: pass1234 + name: allusers + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudorule allusers is absent, again + ipasudorule: + ipaadmin_password: pass1234 + name: allusers + state: absent + register: result + failed_when: result.changed + + - name: Ensure sudorule allcommands is absent + ipasudorule: + ipaadmin_password: pass1234 + name: allcommands + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudorule allcommands is absent, again + ipasudorule: + ipaadmin_password: pass1234 + name: allcommands + state: absent + register: result + failed_when: result.changed -- GitLab