diff --git a/README-sudocmd.md b/README-sudocmd.md new file mode 100644 index 0000000000000000000000000000000000000000..2842a2fc3ae38b8cd965e5b3c7bee3a7c8495021 --- /dev/null +++ b/README-sudocmd.md @@ -0,0 +1,95 @@ +Sudocmd module +================ + +Description +----------- + +The sudocmd module allows to ensure presence and absence of sudo command. + +The sudocmd module is as compatible as possible to the Ansible upstream `ipa_sudocmd` module. + + +Features +-------- +* Sudo command management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipa_sudocmd 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 sudocmd exists: + +```yaml +--- +- name: Playbook to handle sudocmd + hosts: ipaserver + become: true + + tasks: + # Ensure sudocmd is present + - ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: present +``` + +Example playbook to make sure sudocmd is absent: + +```yaml +--- +- name: Playbook to handle sudocmd + hosts: ipaserver + become: true + + tasks: + # Ensure sudocmd are absent + - ipahostgroup: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: absent +``` + +Variables +========= + +ipasudocmd +------- + +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` \| `sudocmd` | The sudo command strings. | yes +`description` | The command description string. | no +`nomembers` | Suppress processing of membership attributes. (bool) | no +`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no + + +Authors +======= + +Rafael Guterres Jeffman diff --git a/README.md b/README.md index d4bc464365262a32bf7b4ba3bfc2d16590810e45..aa07e288a7a6f6f8f7875bbf8925bfe72346c4ff 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Features * Modules for group management * Modules for host management * Modules for hostgroup management +* Modules for sudocmd management * Modules for topology management * Modules for user management @@ -391,6 +392,7 @@ Modules in plugin/modules * [ipagroup](README-group.md) * [ipahost](README-host.md) * [ipahostgroup](README-hostgroup.md) +* [ipasudocmd](README-sudocmd.md) * [ipatopologysegment](README-topology.md) * [ipatopologysuffix](README-topology.md) * [ipauser](README-user.md) diff --git a/playbooks/sudocmd/ensure-sudocmd-is-absent.yml b/playbooks/sudocmd/ensure-sudocmd-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..1b3bbf45e4a0fbe4a4b7f08bb2c40d035ccf4742 --- /dev/null +++ b/playbooks/sudocmd/ensure-sudocmd-is-absent.yml @@ -0,0 +1,11 @@ +--- +- name: Playbook to manage sudo command + hosts: ipaserver + become: true + + tasks: + # Ensure sudo command is absent + - ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: absent diff --git a/playbooks/sudocmd/ensure-sudocmd-is-present.yml b/playbooks/sudocmd/ensure-sudocmd-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..3aa0f47195f39501e2b339a76213d4d0073752aa --- /dev/null +++ b/playbooks/sudocmd/ensure-sudocmd-is-present.yml @@ -0,0 +1,11 @@ +--- +- name: Playbook to manage sudo command + hosts: ipaserver + become: true + + tasks: + # Ensure sudo command is present + - ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: present diff --git a/plugins/modules/ipasudocmd.py b/plugins/modules/ipasudocmd.py new file mode 100644 index 0000000000000000000000000000000000000000..8e25561e815cf20265d57472a0a02a194aa5d7e8 --- /dev/null +++ b/plugins/modules/ipasudocmd.py @@ -0,0 +1,211 @@ +#!/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: ipasudocmd +short description: Manage FreeIPA sudo command +description: Manage FreeIPA sudo command +options: + ipaadmin_principal: + description: The admin principal + default: admin + ipaadmin_password: + description: The admin password + required: false + name: + description: The sudo command + required: true + aliases: ["sudocmd"] + description: + description: The command description + required: false + state: + description: State to ensure + default: present + choices: ["present", "absent"] +author: + - Rafael Jeffman +""" + +EXAMPLES = """ +# Ensure sudocmd is present +- ipacommand: + ipaadmin_password: MyPassword123 + name: su + state: present + +# Ensure sudocmd is absent +- ipacommand: + ipaadmin_password: MyPassword123 + name: su + state: absent +""" + +RETURN = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ + temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa + + +def find_sudocmd(module, name): + _args = { + "all": True, + "sudocmd": to_text(name), + } + + _result = api_command(module, "sudocmd_find", to_text(name), _args) + + if len(_result["result"]) > 1: + module.fail_json( + msg="There is more than one sudocmd '%s'" % (name)) + elif len(_result["result"]) == 1: + return _result["result"][0] + else: + return None + + +def gen_args(description): + _args = {} + if description is not None: + _args["description"] = description + + 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=["sudocmd"], default=None, + required=True), + # present + description=dict(type="str", default=None), + # state + state=dict(type="str", default="present", + choices=["present", "absent"]), + ), + supports_check_mode=True, + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + ipaadmin_principal = ansible_module.params.get("ipaadmin_principal") + ipaadmin_password = ansible_module.params.get("ipaadmin_password") + names = ansible_module.params.get("name") + + # present + description = ansible_module.params.get("description") + # state + state = ansible_module.params.get("state") + + # Check parameters + if state == "absent": + invalid = ["description"] + 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)) + + # 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 hostgroup exists + res_find = find_sudocmd(ansible_module, name) + + # Create command + if state == "present": + # Generate args + args = gen_args(description) + if res_find is not None: + # For all settings in 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, "sudocmd_mod", args]) + else: + commands.append([name, "sudocmd_add", args]) + # Set res_find to empty dict for next step + res_find = {} + elif state == "absent": + if res_find is not None: + commands.append([name, "sudocmd_del", {}]) + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Execute commands + for name, command, args in commands: + try: + result = api_command(ansible_module, command, to_text(name), + args) + # Check if any changes were made by any command + if command == 'sudocmd_del': + changed |= "Deleted" in result['summary'] + elif command == 'sudocmd_add': + changed |= "Added" in result['summary'] + except Exception as e: + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, + str(e))) + + 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/sudocmd/test_sudocmd.yml b/tests/sudocmd/test_sudocmd.yml new file mode 100644 index 0000000000000000000000000000000000000000..97427869fc2b430f79b92138274465213f16e1f3 --- /dev/null +++ b/tests/sudocmd/test_sudocmd.yml @@ -0,0 +1,120 @@ +--- + +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - name: Ensure sudocmds are absent + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/bin/su + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: absent + + - name: Ensure sudocmd is present + ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: present + register: result + failed_when: not result.changed + + - name: Ensure sudocmd is present again + ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: present + register: result + failed_when: result.changed + + - name: Ensure sudocmd is absent + ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: absent + register: result + failed_when: not result.changed + + - name: Ensure sudocmd is absent again + ipasudocmd: + ipaadmin_password: MyPassword123 + name: /usr/bin/su + state: absent + register: result + failed_when: result.changed + + - name: Ensure multiple sudocmd are present + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: present + register: result + failed_when: not result.changed + + - name: Ensure multiple sudocmd are present again + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: present + register: result + failed_when: result.changed + + - name: Ensure multiple sudocmd are absent + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: absent + register: result + failed_when: not result.changed + + - name: Ensure multiple sudocmd are absent again + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: absent + register: result + failed_when: result.changed + - name: Ensure sudocmds are absent + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/bin/su + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: absent + + - name: Ensure sudocmds are absent + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + state: absent + + - name: Ensure sudocmds are present + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/iwlist + state: present + + - name: Ensure multiple sudocmd are absent when only one was present + ipasudocmd: + ipaadmin_password: MyPassword123 + name: + - /usr/sbin/ifconfig + - /usr/sbin/iwlist + state: absent + register: result + failed_when: not result.changed