diff --git a/.ansible-freeipa.metadata b/.ansible-freeipa.metadata new file mode 100644 index 0000000..d534268 --- /dev/null +++ b/.ansible-freeipa.metadata @@ -0,0 +1 @@ +583ac570c030eb68a2026a506054f2f93587beb4 SOURCES/ansible-freeipa-0.1.8.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b19cd6f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/ansible-freeipa-0.1.8.tar.gz diff --git a/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch b/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch new file mode 100644 index 0000000..f5c5147 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch @@ -0,0 +1,1129 @@ +From dc0a5585fb036fbeba2200564e26c478465afeec Mon Sep 17 00:00:00 2001 +From: Rafael Guterres Jeffman +Date: Tue, 31 Dec 2019 11:04:49 -0300 +Subject: [PATCH] Add missing attributes to ipasudorule. + +This patch adds the following attributes to ipasudorule: + + - order + - sudooption + - runasuser + - runasgroup + +It also fixes behavior of sudocmd assigned to the the sudorule, with the +adittion of the attributes: + + - allow_sudocmds + - deny_sudocmds + - allow_sudocmdgroups + - deny_sudocmdgroups + +README-sudorule and tests have been updated to comply with the changes. +--- + README-sudorule.md | 14 +- + ...sure-sudorule-does-not-have-sudooption.yml | 14 + + .../ensure-sudorule-has-sudooption.yml | 13 + + .../ensure-sudorule-is-present-with-order.yml | 12 + + .../sudorule/ensure-sudorule-is-present.yml | 2 + + .../ensure-sudorule-runasuser-is-absent.yml | 14 + + .../ensure-sudorule-runasuser-is-present.yml | 13 + + .../ensure-sudorule-sudocmd-is-absent.yml | 7 +- + .../ensure-sudorule-sudocmd-is-present.yml | 7 +- + plugins/modules/ipasudorule.py | 353 +++++++++++++----- + tests/sudorule/test_sudorule.yml | 204 +++++++--- + 11 files changed, 504 insertions(+), 149 deletions(-) + create mode 100644 playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml + create mode 100644 playbooks/sudorule/ensure-sudorule-has-sudooption.yml + create mode 100644 playbooks/sudorule/ensure-sudorule-is-present-with-order.yml + create mode 100644 playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml + create mode 100644 playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml + +diff --git a/README-sudorule.md b/README-sudorule.md +index bb3498b..50c73ad 100644 +--- a/README-sudorule.md ++++ b/README-sudorule.md +@@ -68,7 +68,7 @@ Example playbook to make sure sudocmds are present in Sudo Rule: + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /sbin/ifconfig + action: member + ``` +@@ -87,7 +87,7 @@ Example playbook to make sure sudocmds are not present in Sudo Rule: + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /sbin/ifconfig + action: member + state: absent +@@ -130,8 +130,14 @@ Variable | Description | Required + `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 ++`allow_sudocmd` | List of sudocmd name strings assigned to the allow group of this sudorule. | no ++`deny_sudocmd` | List of sudocmd name strings assigned to the deny group of this sudorule. | no ++`allow_sudocmdgroup` | List of sudocmd groups name strings assigned to the allow group of this sudorule. | no ++`deny_sudocmdgroup` | List of sudocmd groups name strings assigned to the deny group of this sudorule. | no ++`sudooption` \| `option` | List of options to the sudorule | no ++`order` | Integer to order the sudorule | no ++`runasuser` | List of users for Sudo to execute as. | no ++`runasgroup` | List of groups for Sudo to execute as. | 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 + +diff --git a/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml b/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml +new file mode 100644 +index 0000000..1307044 +--- /dev/null ++++ b/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml +@@ -0,0 +1,14 @@ ++--- ++- name: Tests ++ hosts: ipaserver ++ become: true ++ gather_facts: false ++ ++ tasks: ++ # Ensure sudooption is absent in sudorule ++ - ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ sudooption: "!root" ++ action: member ++ state: absent +diff --git a/playbooks/sudorule/ensure-sudorule-has-sudooption.yml b/playbooks/sudorule/ensure-sudorule-has-sudooption.yml +new file mode 100644 +index 0000000..1f32b9a +--- /dev/null ++++ b/playbooks/sudorule/ensure-sudorule-has-sudooption.yml +@@ -0,0 +1,13 @@ ++--- ++- name: Tests ++ hosts: ipaserver ++ become: true ++ gather_facts: false ++ ++ tasks: ++ # Ensure sudooption is present in sudorule ++ - ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ sudooption: "!root" ++ action: member +diff --git a/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml b/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml +new file mode 100644 +index 0000000..9a3c2b2 +--- /dev/null ++++ b/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml +@@ -0,0 +1,12 @@ ++--- ++- name: Tests ++ hosts: ipaserver ++ become: true ++ gather_facts: false ++ ++ tasks: ++ # Ensure sudorule is present with the given order. ++ - ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ order: 2 +diff --git a/playbooks/sudorule/ensure-sudorule-is-present.yml b/playbooks/sudorule/ensure-sudorule-is-present.yml +index 5b8f32b..89041af 100644 +--- a/playbooks/sudorule/ensure-sudorule-is-present.yml ++++ b/playbooks/sudorule/ensure-sudorule-is-present.yml +@@ -9,4 +9,6 @@ + ipaadmin_password: MyPassword123 + name: testrule1 + description: A test sudo rule. ++ allow_sudocmd: /bin/ls ++ deny_sudocmd: /bin/vim + state: present +diff --git a/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml b/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml +new file mode 100644 +index 0000000..56612f1 +--- /dev/null ++++ b/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml +@@ -0,0 +1,14 @@ ++--- ++- name: Tests ++ hosts: ipaserver ++ become: true ++ gather_facts: false ++ ++ tasks: ++ # Ensure sudorule is present with the given order. ++ - ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ runasuser: admin ++ action: member ++ state: absent +diff --git a/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml b/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml +new file mode 100644 +index 0000000..8af49b9 +--- /dev/null ++++ b/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml +@@ -0,0 +1,13 @@ ++--- ++- name: Tests ++ hosts: ipaserver ++ become: true ++ gather_facts: false ++ ++ tasks: ++ # Ensure sudorule is present with the given order. ++ - ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ runasuser: admin ++ action: member +diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml +index 942d0b5..328242a 100644 +--- a/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml ++++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml +@@ -8,8 +8,13 @@ + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /sbin/ifconfig ++ deny_sudocmd: + - /usr/bin/vim ++ allow_sudocmdgroup: ++ - devops ++ deny_sudocmdgroup: ++ - users + action: member + state: absent +diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml +index 61fcbb0..55acd61 100644 +--- a/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml ++++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml +@@ -8,7 +8,12 @@ + - ipasudorule: + ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /sbin/ifconfig ++ deny_sudocmd: + - /usr/bin/vim ++ allow_sudocmdgroup: ++ - devops ++ deny_sudocmdgroup: ++ - users + action: member +diff --git a/plugins/modules/ipasudorule.py b/plugins/modules/ipasudorule.py +index c21f247..285a946 100644 +--- a/plugins/modules/ipasudorule.py ++++ b/plugins/modules/ipasudorule.py +@@ -79,18 +79,43 @@ + description: Host category the sudo rule applies to. + required: false + choices: ["all"] +- cmd: +- description: List of sudocmds assigned to this sudorule. ++ allow_sudocmd: ++ description: List of allowed sudocmds assigned to this sudorule. + required: false + type: list +- cmdgroup: +- description: List of sudocmd groups assigned to this sudorule. ++ allow_sudocmdgroup: ++ description: List of allowed sudocmd groups assigned to this sudorule. ++ required: false ++ type: list ++ deny_sudocmd: ++ description: List of denied sudocmds assigned to this sudorule. ++ required: false ++ type: list ++ deny_sudocmdgroup: ++ description: List of denied sudocmd groups assigned to this sudorule. + required: false + type: list + cmdcategory: +- description: Cammand category the sudo rule applies to ++ description: Command category the sudo rule applies to + required: false + choices: ["all"] ++ order: ++ description: Order to apply this rule. ++ required: false ++ type: int ++ sudooption: ++ description: ++ required: false ++ type: list ++ aliases: ["options"] ++ runasuser: ++ description: List of users for Sudo to execute as. ++ required: false ++ type: list ++ runasgroup: ++ description: List of groups for Sudo to execute as. ++ required: false ++ type: list + action: + description: Work on sudorule or member level + default: sudorule +@@ -111,13 +136,13 @@ + + # Ensure sudocmd is present in Sudo Rule + - ipasudorule: +- ipaadmin_password: pass1234 +- name: testrule1 +- cmd: +- - /sbin/ifconfig +- - /usr/bin/vim +- action: member +- state: absent ++ ipaadmin_password: pass1234 ++ name: testrule1 ++ allow_sudocmd: ++ - /sbin/ifconfig ++ - /usr/bin/vim ++ action: member ++ state: absent + + # Ensure host server is present in Sudo Rule + - ipasudorule: +@@ -160,7 +185,7 @@ + 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 ++ module_params_get, gen_add_del_lists + + + def find_sudorule(module, name): +@@ -180,14 +205,26 @@ def find_sudorule(module, name): + return None + + +-def gen_args(ansible_module): +- arglist = ['description', 'usercategory', 'hostcategory', 'cmdcategory', +- 'runasusercategory', 'runasgroupcategory', 'nomembers'] ++def gen_args(description, usercat, hostcat, cmdcat, runasusercat, ++ runasgroupcat, order, nomembers): + _args = {} +- for arg in arglist: +- value = module_params_get(ansible_module, arg) +- if value is not None: +- _args[arg] = value ++ ++ if description is not None: ++ _args['description'] = description ++ if usercat is not None: ++ _args['usercategory'] = usercat ++ if hostcat is not None: ++ _args['hostcategory'] = hostcat ++ if cmdcat is not None: ++ _args['cmdcategory'] = cmdcat ++ if runasusercat is not None: ++ _args['ipasudorunasusercategory'] = runasusercat ++ if runasgroupcat is not None: ++ _args['ipasudorunasgroupcategory'] = runasgroupcat ++ if order is not None: ++ _args['sudoorder'] = order ++ if nomembers is not None: ++ _args['nomembers'] = nomembers + + return _args + +@@ -212,13 +249,21 @@ def main(): + 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), ++ allow_sudocmd=dict(required=False, type="list", default=None), ++ deny_sudocmd=dict(required=False, type="list", default=None), ++ allow_sudocmdgroup=dict(required=False, type="list", default=None), ++ deny_sudocmdgroup=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"]), ++ runasuser=dict(required=False, type="list", default=None), ++ runasgroup=dict(required=False, type="list", default=None), ++ order=dict(type="int", required=False, aliases=['sudoorder']), ++ sudooption=dict(required=False, type='list', default=None, ++ aliases=["options"]), + action=dict(type="str", default="sudorule", + choices=["member", "sudorule"]), + # state +@@ -256,8 +301,16 @@ def main(): + 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') ++ allow_sudocmd = module_params_get(ansible_module, 'allow_sudocmd') ++ allow_sudocmdgroup = module_params_get(ansible_module, ++ 'allow_sudocmdgroup') ++ deny_sudocmd = module_params_get(ansible_module, 'deny_sudocmd') ++ deny_sudocmdgroup = module_params_get(ansible_module, ++ 'deny_sudocmdgroup') ++ sudooption = module_params_get(ansible_module, "sudooption") ++ order = module_params_get(ansible_module, "order") ++ runasuser = module_params_get(ansible_module, "runasuser") ++ runasgroup = module_params_get(ansible_module, "runasgroup") + action = module_params_get(ansible_module, "action") + + # state +@@ -272,28 +325,30 @@ def main(): + if action == "member": + invalid = ["description", "usercategory", "hostcategory", + "cmdcategory", "runasusercategory", +- "runasgroupcategory", "nomembers"] ++ "runasgroupcategory", "order", "nomembers"] + +- for x in invalid: +- if x in vars() and vars()[x] is not None: ++ for arg in invalid: ++ if arg in vars() and vars()[arg] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with action " +- "'%s'" % (x, action)) ++ "'%s'" % (arg, action)) + + elif state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["description", "usercategory", "hostcategory", + "cmdcategory", "runasusercategory", +- "runasgroupcategory", "nomembers"] ++ "runasgroupcategory", "nomembers", "order"] + if action == "sudorule": + invalid.extend(["host", "hostgroup", "user", "group", +- "cmd", "cmdgroup"]) +- for x in invalid: +- if vars()[x] is not None: ++ "runasuser", "runasgroup", "allow_sudocmd", ++ "allow_sudocmdgroup", "deny_sudocmd", ++ "deny_sudocmdgroup", "sudooption"]) ++ for arg in invalid: ++ if vars()[arg] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % +- (x, state)) ++ (arg, state)) + + elif state in ["enabled", "disabled"]: + if len(names) < 1: +@@ -305,12 +360,14 @@ def main(): + 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: ++ "user", "group", "allow_sudocmd", "allow_sudocmdgroup", ++ "deny_sudocmd", "deny_sudocmdgroup", "runasuser", ++ "runasgroup", "order", "sudooption"] ++ for arg in invalid: ++ if vars()[arg] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % +- (x, state)) ++ (arg, state)) + else: + ansible_module.fail_json(msg="Invalid state '%s'" % state) + +@@ -335,7 +392,9 @@ def main(): + # Create command + if state == "present": + # Generate args +- args = gen_args(ansible_module) ++ args = gen_args(description, usercategory, hostcategory, ++ cmdcategory, runasusercategory, ++ runasgroupcategory, order, nomembers) + if action == "sudorule": + # Found the sudorule + if res_find is not None: +@@ -351,44 +410,42 @@ def main(): + 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 [])) ++ host_add, host_del = gen_add_del_lists( ++ host, res_find.get('member_host', [])) ++ ++ hostgroup_add, hostgroup_del = gen_add_del_lists( ++ hostgroup, res_find.get('member_hostgroup', [])) ++ ++ user_add, user_del = gen_add_del_lists( ++ user, res_find.get('member_user', [])) ++ ++ group_add, group_del = gen_add_del_lists( ++ group, res_find.get('member_group', [])) ++ ++ allow_cmd_add, allow_cmd_del = gen_add_del_lists( ++ allow_sudocmd, ++ res_find.get('memberallowcmd_sudocmd', [])) ++ ++ allow_cmdgroup_add, allow_cmdgroup_del = gen_add_del_lists( ++ allow_sudocmdgroup, ++ res_find.get('memberallowcmd_sudocmdgroup', [])) ++ ++ deny_cmd_add, deny_cmd_del = gen_add_del_lists( ++ deny_sudocmd, ++ res_find.get('memberdenycmd_sudocmd', [])) ++ ++ deny_cmdgroup_add, deny_cmdgroup_del = gen_add_del_lists( ++ deny_sudocmdgroup, ++ res_find.get('memberdenycmd_sudocmdgroup', [])) ++ ++ sudooption_add, sudooption_del = gen_add_del_lists( ++ sudooption, res_find.get('ipasudoopt', [])) ++ ++ runasuser_add, runasuser_del = gen_add_del_lists( ++ runasuser, res_find.get('ipasudorunas_user', [])) ++ ++ runasgroup_add, runasgroup_del = gen_add_del_lists( ++ runasgroup, res_find.get('ipasudorunas_group', [])) + + # Add hosts and hostgroups + if len(host_add) > 0 or len(hostgroup_add) > 0: +@@ -420,20 +477,59 @@ def main(): + "group": group_del, + }]) + +- # Add commands +- if len(cmd_add) > 0 or len(cmdgroup_add) > 0: ++ # Add commands allowed ++ if len(allow_cmd_add) > 0 or len(allow_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: ++ {"sudocmd": allow_cmd_add, ++ "sudocmdgroup": allow_cmdgroup_add, ++ }]) ++ ++ if len(allow_cmd_del) > 0 or len(allow_cmdgroup_del) > 0: ++ commands.append([name, "sudorule_remove_allow_command", ++ {"sudocmd": allow_cmd_del, ++ "sudocmdgroup": allow_cmdgroup_del ++ }]) ++ ++ # Add commands denied ++ if len(deny_cmd_add) > 0 or len(deny_cmdgroup_add) > 0: + commands.append([name, "sudorule_add_deny_command", +- { +- "sudocmd": cmd_del, +- "sudocmdgroup": cmdgroup_del +- }]) ++ {"sudocmd": deny_cmd_add, ++ "sudocmdgroup": deny_cmdgroup_add, ++ }]) ++ ++ if len(deny_cmd_del) > 0 or len(deny_cmdgroup_del) > 0: ++ commands.append([name, "sudorule_remove_deny_command", ++ {"sudocmd": deny_cmd_del, ++ "sudocmdgroup": deny_cmdgroup_del ++ }]) ++ ++ # Add RunAS Users ++ if len(runasuser_add) > 0: ++ commands.append([name, "sudorule_add_runasuser", ++ {"user": runasuser_add}]) ++ # Remove RunAS Users ++ if len(runasuser_del) > 0: ++ commands.append([name, "sudorule_remove_runasuser", ++ {"user": runasuser_del}]) ++ ++ # Add RunAS Groups ++ if len(runasgroup_add) > 0: ++ commands.append([name, "sudorule_add_runasgroup", ++ {"group": runasgroup_add}]) ++ # Remove RunAS Groups ++ if len(runasgroup_del) > 0: ++ commands.append([name, "sudorule_remove_runasgroup", ++ {"group": runasgroup_del}]) ++ ++ # Add sudo options ++ for sudoopt in sudooption_add: ++ commands.append([name, "sudorule_add_option", ++ {"ipasudoopt": sudoopt}]) ++ ++ # Remove sudo options ++ for sudoopt in sudooption_del: ++ commands.append([name, "sudorule_remove_option", ++ {"ipasudoopt": sudoopt}]) + + elif action == "member": + if res_find is None: +@@ -456,11 +552,38 @@ def main(): + }]) + + # Add commands +- if cmd is not None: ++ if allow_sudocmd is not None \ ++ or allow_sudocmdgroup is not None: + commands.append([name, "sudorule_add_allow_command", +- { +- "sudocmd": cmd, +- }]) ++ {"sudocmd": allow_sudocmd, ++ "sudocmdgroup": allow_sudocmdgroup, ++ }]) ++ ++ # Add commands ++ if deny_sudocmd is not None \ ++ or deny_sudocmdgroup is not None: ++ commands.append([name, "sudorule_add_deny_command", ++ {"sudocmd": deny_sudocmd, ++ "sudocmdgroup": deny_sudocmdgroup, ++ }]) ++ ++ # Add RunAS Users ++ if runasuser is not None: ++ commands.append([name, "sudorule_add_runasuser", ++ {"user": runasuser}]) ++ ++ # Add RunAS Groups ++ if runasgroup is not None: ++ commands.append([name, "sudorule_add_runasgroup", ++ {"group": runasgroup}]) ++ ++ # Add options ++ if sudooption is not None: ++ existing_opts = res_find.get('ipasudoopt', []) ++ for sudoopt in sudooption: ++ if sudoopt not in existing_opts: ++ commands.append([name, "sudorule_add_option", ++ {"ipasudoopt": sudoopt}]) + + elif state == "absent": + if action == "sudorule": +@@ -487,12 +610,40 @@ def main(): + "group": group, + }]) + +- # Remove commands +- if cmd is not None: +- commands.append([name, "sudorule_add_deny_command", +- { +- "sudocmd": cmd, +- }]) ++ # Remove allow commands ++ if allow_sudocmd is not None \ ++ or allow_sudocmdgroup is not None: ++ commands.append([name, "sudorule_remove_allow_command", ++ {"sudocmd": allow_sudocmd, ++ "sudocmdgroup": allow_sudocmdgroup ++ }]) ++ ++ # Remove deny commands ++ if deny_sudocmd is not None \ ++ or deny_sudocmdgroup is not None: ++ commands.append([name, "sudorule_remove_deny_command", ++ {"sudocmd": deny_sudocmd, ++ "sudocmdgroup": deny_sudocmdgroup ++ }]) ++ ++ # Remove RunAS Users ++ if runasuser is not None: ++ commands.append([name, "sudorule_remove_runasuser", ++ {"user": runasuser}]) ++ ++ # Remove RunAS Groups ++ if runasgroup is not None: ++ commands.append([name, "sudorule_remove_runasgroup", ++ {"group": runasgroup}]) ++ ++ # Remove options ++ if sudooption is not None: ++ existing_opts = res_find.get('ipasudoopt', []) ++ for sudoopt in sudooption: ++ if sudoopt in existing_opts: ++ commands.append([name, ++ "sudorule_remove_option", ++ {"ipasudoopt": sudoopt}]) + + elif state == "enabled": + if res_find is None: +@@ -530,9 +681,9 @@ def main(): + changed = True + else: + changed = True +- except Exception as e: ++ except Exception as ex: + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, +- str(e))) ++ str(ex))) + # Get all errors + # All "already a member" and "not a member" failures in the + # result are ignored. All others are reported. +@@ -549,8 +700,8 @@ def main(): + if len(errors) > 0: + ansible_module.fail_json(msg=", ".join(errors)) + +- except Exception as e: +- ansible_module.fail_json(msg=str(e)) ++ except Exception as ex: ++ ansible_module.fail_json(msg=str(ex)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) +diff --git a/tests/sudorule/test_sudorule.yml b/tests/sudorule/test_sudorule.yml +index 88ed90a..25090bb 100644 +--- a/tests/sudorule/test_sudorule.yml ++++ b/tests/sudorule/test_sudorule.yml +@@ -16,15 +16,22 @@ + + - name: Ensure some sudocmds are available + ipasudocmd: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: + - /sbin/ifconfig + - /usr/bin/vim + state: present + ++ - name: Ensure sudocmdgroup is available ++ ipasudocmdgroup: ++ ipaadmin_password: MyPassword123 ++ name: test_sudorule ++ sudocmd: /usr/bin/vim ++ state: present ++ + - name: Ensure sudorules are absent + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: + - testrule1 + - allusers +@@ -34,21 +41,21 @@ + + - name: Ensure sudorule is present + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + register: result + failed_when: not result.changed + + - name: Ensure sudorule is present again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + register: result + failed_when: result.changed + + - name: Ensure sudorule is present, runAsUserCategory. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + runAsUserCategory: all + register: result +@@ -56,7 +63,7 @@ + + - name: Ensure sudorule is present, with usercategory 'all' + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allusers + usercategory: all + register: result +@@ -64,7 +71,7 @@ + + - name: Ensure sudorule is present, with usercategory 'all', again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allusers + usercategory: all + register: result +@@ -72,7 +79,7 @@ + + - name: Ensure sudorule is present, with hostategory 'all' + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allhosts + hostcategory: all + register: result +@@ -80,7 +87,7 @@ + + - name: Ensure sudorule is present, with hostategory 'all', again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allhosts + hostcategory: all + register: result +@@ -88,13 +95,13 @@ + + - name: Ensure sudorule is disabled + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: disabled + + - name: Ensure sudorule is disabled, again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: disabled + register: result +@@ -102,7 +109,7 @@ + + - name: Ensure sudorule is enabled + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: enabled + register: result +@@ -110,37 +117,77 @@ + + - name: Ensure sudorule is enabled, again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: enabled + register: result + failed_when: result.changed + +- - name: Ensure sudorule is present and some sudocmd are a member of it. ++ - name: Ensure sudorule is present and some sudocmd are allowed. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /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. ++ - name: Ensure sudorule is present and some sudocmd are allowed, again. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: ++ allow_sudocmd: + - /sbin/ifconfig ++ action: member ++ register: result ++ failed_when: result.changed ++ ++ - name: Ensure sudorule is present and some sudocmd are denyed. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmd: ++ - /usr/bin/vim ++ action: member ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure sudorule is present and some sudocmd are denyed, again. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmd: + - /usr/bin/vim + action: member + register: result + failed_when: result.changed + ++ - name: Ensure sudorule is present and, sudocmds are absent. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ allow_sudocmd: /sbin/ifconfig ++ deny_sudocmd: /usr/bin/vim ++ action: member ++ state: absent ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure sudorule is present and, sudocmds are absent, again. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ allow_sudocmd: /sbin/ifconfig ++ deny_sudocmd: /usr/bin/vim ++ action: member ++ state: absent ++ register: result ++ failed_when: result.changed ++ + - name: Ensure sudorule is present with cmdcategory 'all'. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allcommands + cmdcategory: all + register: result +@@ -148,7 +195,7 @@ + + - name: Ensure sudorule is present with cmdcategory 'all', again. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allcommands + cmdcategory: all + register: result +@@ -156,7 +203,7 @@ + + - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + host: "{{ groups.ipaserver[0] }}" + action: member +@@ -165,7 +212,7 @@ + + - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule, again. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + host: "{{ groups.ipaserver[0] }}" + action: member +@@ -190,25 +237,77 @@ + register: result + failed_when: result.changed + +- - name: Ensure sudorule sudocmds are absent ++ - name: Ensure sudorule is present, with an allow_sudocmdgroup. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: +- - /sbin/ifconfig +- - /usr/bin/vim ++ allow_sudocmdgroup: test_sudorule ++ state: present ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure sudorule is present, with an allow_sudocmdgroup, again. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ allow_sudocmdgroup: test_sudorule ++ state: present ++ register: result ++ failed_when: result.changed ++ ++ - name: Ensure sudorule is present, but allow_sudocmdgroup is absent. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ allow_sudocmdgroup: test_sudorule + action: member + state: absent + register: result + failed_when: not result.changed + +- - name: Ensure sudorule sudocmds are absent, again ++ - name: Ensure sudorule is present, but allow_sudocmdgroup is absent. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 +- cmd: +- - /sbin/ifconfig +- - /usr/bin/vim ++ allow_sudocmdgroup: test_sudorule ++ action: member ++ state: absent ++ register: result ++ failed_when: result.changed ++ ++ - name: Ensure sudorule is present, with an deny_sudocmdgroup. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmdgroup: test_sudorule ++ state: present ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure sudorule is present, with an deny_sudocmdgroup, again. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmdgroup: test_sudorule ++ state: present ++ register: result ++ failed_when: result.changed ++ ++ - name: Ensure sudorule is present, but deny_sudocmdgroup is absent. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmdgroup: test_sudorule ++ action: member ++ state: absent ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure sudorule is present, but deny_sudocmdgroup is absent, again. ++ ipasudorule: ++ ipaadmin_password: MyPassword123 ++ name: testrule1 ++ deny_sudocmdgroup: test_sudorule + action: member + state: absent + register: result +@@ -216,7 +315,7 @@ + + - name: Ensure sudorule is absent + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: absent + register: result +@@ -224,7 +323,7 @@ + + - name: Ensure sudorule is absent, again. + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: testrule1 + state: absent + register: result +@@ -232,7 +331,7 @@ + + - name: Ensure sudorule allhosts is absent + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allhosts + state: absent + register: result +@@ -240,7 +339,7 @@ + + - name: Ensure sudorule allhosts is absent, again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allhosts + state: absent + register: result +@@ -248,7 +347,7 @@ + + - name: Ensure sudorule allusers is absent + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allusers + state: absent + register: result +@@ -256,7 +355,7 @@ + + - name: Ensure sudorule allusers is absent, again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allusers + state: absent + register: result +@@ -264,7 +363,7 @@ + + - name: Ensure sudorule allcommands is absent + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allcommands + state: absent + register: result +@@ -272,8 +371,29 @@ + + - name: Ensure sudorule allcommands is absent, again + ipasudorule: +- ipaadmin_password: pass1234 ++ ipaadmin_password: MyPassword123 + name: allcommands + state: absent + register: result + failed_when: result.changed ++ ++ # cleanup ++ - name : Ensure sudocmdgroup is absent ++ ipasudocmdgroup: ++ ipaadmin_password: MyPassword123 ++ name: test_sudorule ++ state: absent ++ ++ - name: Ensure hostgroup is absent. ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: cluster ++ state: absent ++ ++ - name: Ensure sudocmds are absent ++ ipasudocmd: ++ ipaadmin_password: MyPassword123 ++ name: ++ - /sbin/ifconfig ++ - /usr/bin/vim ++ state: absent diff --git a/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch b/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch new file mode 100644 index 0000000..767f487 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch @@ -0,0 +1,177 @@ +From 3780a9a00e77ae0fd2944b36adad446d094fc90f Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Tue, 11 Feb 2020 10:34:39 +0100 +Subject: [PATCH] ansible_freeipa_module: Fix comparison of bool parameters in + compare_args_ipa + +Bool types are not iterable. Therefore the comparison using sets was failing +with a TypeError. This prevented to change the bool parameters for hosts. + +A test for the host module has been added to verify that the bool parameters +can be modified. + +New test: + + tests/host/test_host_bool_params.yml + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1784514 +--- + .../module_utils/ansible_freeipa_module.py | 18 ++- + tests/host/test_host_bool_params.yml | 119 ++++++++++++++++++ + 2 files changed, 133 insertions(+), 4 deletions(-) + create mode 100644 tests/host/test_host_bool_params.yml + +diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py +index 8154a12..9e97b88 100644 +--- a/plugins/module_utils/ansible_freeipa_module.py ++++ b/plugins/module_utils/ansible_freeipa_module.py +@@ -222,10 +222,20 @@ def compare_args_ipa(module, args, ipa): + arg = [to_text(_arg) for _arg in arg] + if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int): + arg = [to_text(_arg) for _arg in arg] +- # module.warn("%s <=> %s" % (arg, ipa_arg)) +- if set(arg) != set(ipa_arg): +- # module.warn("DIFFERENT") +- return False ++ # module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg))) ++ try: ++ arg_set = set(arg) ++ ipa_arg_set = set(ipa_arg) ++ except TypeError: ++ if arg != ipa_arg: ++ # module.warn("%s != %s" % (repr(arg), repr(ipa_arg))) ++ return False ++ else: ++ if arg_set != ipa_arg_set: ++ # module.warn("%s != %s" % (repr(arg), repr(ipa_arg))) ++ return False ++ ++ # module.warn("%s == %s" % (repr(arg), repr(ipa_arg))) + + return True + +diff --git a/tests/host/test_host_bool_params.yml b/tests/host/test_host_bool_params.yml +new file mode 100644 +index 0000000..824ea99 +--- /dev/null ++++ b/tests/host/test_host_bool_params.yml +@@ -0,0 +1,119 @@ ++--- ++- name: Test host bool parameters ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ - name: Get Domain from server name ++ set_fact: ++ ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}" ++ when: ipaserver_domain is not defined ++ ++ - name: Set host1_fqdn .. host6_fqdn ++ set_fact: ++ host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" ++ ++ - name: Host absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ host1_fqdn }}" ++ update_dns: yes ++ state: absent ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ force: yes ++ requires_pre_auth: yes ++ ok_as_delegate: yes ++ ok_to_auth_as_delegate: yes ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ requires_pre_auth: yes ++ ok_as_delegate: yes ++ ok_to_auth_as_delegate: yes ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate set to no ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ requires_pre_auth: no ++ ok_as_delegate: no ++ ok_to_auth_as_delegate: no ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate set to no again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ requires_pre_auth: no ++ ok_as_delegate: no ++ ok_to_auth_as_delegate: no ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ requires_pre_auth: yes ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with requires_pre_auth again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ requires_pre_auth: yes ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with ok_as_delegate ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ok_as_delegate: yes ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with ok_as_delegate again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ok_as_delegate: yes ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with ok_to_auth_as_delegate ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ok_to_auth_as_delegate: yes ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present with ok_to_auth_as_delegate again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ok_to_auth_as_delegate: yes ++ register: result ++ failed_when: result.changed ++ ++ - name: Host absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ host1_fqdn }}" ++ update_dns: yes ++ state: absent diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch b/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch new file mode 100644 index 0000000..9e04d44 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch @@ -0,0 +1,838 @@ +From 3865ce657e3ea1b621aa054c792201aedfde2d11 Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Fri, 7 Feb 2020 10:11:38 +0100 +Subject: [PATCH] ipahbacrule: Fix handing of members with action hbacrule + +Changing members (host, hostgroup, hbacsvc, hbacsvcgroup, user, group) with +action hbacrule was not working due to the use of the wrong parameter +prefix. This has been fixed and the old members are removed correctly now. + +The test script has been reworked completely to verify the fix. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1787996 +--- + plugins/modules/ipahbacrule.py | 24 +- + tests/hbacrule/test_hbacrule.yml | 549 +++++++++++++++++++++++-------- + 2 files changed, 432 insertions(+), 141 deletions(-) + +diff --git a/plugins/modules/ipahbacrule.py b/plugins/modules/ipahbacrule.py +index 385876b..82340c2 100644 +--- a/plugins/modules/ipahbacrule.py ++++ b/plugins/modules/ipahbacrule.py +@@ -344,41 +344,41 @@ def main(): + # Generate addition and removal lists + host_add = list( + set(host or []) - +- set(res_find.get("member_host", []))) ++ set(res_find.get("memberhost_host", []))) + host_del = list( +- set(res_find.get("member_host", [])) - ++ set(res_find.get("memberhost_host", [])) - + set(host or [])) + hostgroup_add = list( + set(hostgroup or []) - +- set(res_find.get("member_hostgroup", []))) ++ set(res_find.get("memberhost_hostgroup", []))) + hostgroup_del = list( +- set(res_find.get("member_hostgroup", [])) - ++ set(res_find.get("memberhost_hostgroup", [])) - + set(hostgroup or [])) + + hbacsvc_add = list( + set(hbacsvc or []) - +- set(res_find.get("member_hbacsvc", []))) ++ set(res_find.get("memberservice_hbacsvc", []))) + hbacsvc_del = list( +- set(res_find.get("member_hbacsvc", [])) - ++ set(res_find.get("memberservice_hbacsvc", [])) - + set(hbacsvc or [])) + hbacsvcgroup_add = list( + set(hbacsvcgroup or []) - +- set(res_find.get("member_hbacsvcgroup", []))) ++ set(res_find.get("memberservice_hbacsvcgroup", []))) + hbacsvcgroup_del = list( +- set(res_find.get("member_hbacsvcgroup", [])) - ++ set(res_find.get("memberservice_hbacsvcgroup", [])) - + set(hbacsvcgroup or [])) + + user_add = list( + set(user or []) - +- set(res_find.get("member_user", []))) ++ set(res_find.get("memberuser_user", []))) + user_del = list( +- set(res_find.get("member_user", [])) - ++ set(res_find.get("memberuser_user", [])) - + set(user or [])) + group_add = list( + set(group or []) - +- set(res_find.get("member_group", []))) ++ set(res_find.get("memberuser_group", []))) + group_del = list( +- set(res_find.get("member_group", [])) - ++ set(res_find.get("memberuser_group", [])) - + set(group or [])) + + # Add hosts and hostgroups +diff --git a/tests/hbacrule/test_hbacrule.yml b/tests/hbacrule/test_hbacrule.yml +index a5615cc..38858d3 100644 +--- a/tests/hbacrule/test_hbacrule.yml ++++ b/tests/hbacrule/test_hbacrule.yml +@@ -1,338 +1,629 @@ + --- +-- name: Tests ++- name: Playbook to handle hbacrules + hosts: ipaserver + become: true +- gather_facts: false + + tasks: +- - name: Ensure HBAC Rule allhosts is absent +- ipahbacrule: ++ - name: Get Domain from server name ++ set_fact: ++ ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}" ++ when: ipaserver_domain is not defined ++ ++ # CLEANUP TEST ITEMS ++ ++ - name: Ensure test hosts are absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ state: absent ++ ++ - name: Ensure test hostgroups are absent ++ ipahostgroup: + ipaadmin_password: MyPassword123 +- name: allhosts,sshd-pinky,loginRule ++ name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04 + state: absent + +- - name: User pinky absent ++ - name: Ensure test users are absent + ipauser: + ipaadmin_password: MyPassword123 +- name: pinky ++ name: testuser01,testuser02,testuser03,testuser04 + state: absent + +- - name: User group login absent ++ - name: Ensure test user groups are absent + ipagroup: + ipaadmin_password: MyPassword123 +- name: login ++ name: testgroup01,testgroup02,testgroup03,testgroup04 ++ state: absent ++ ++ - name: Ensure test HBAC Services are absent ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04 ++ state: absent ++ ++ - name: Ensure test HBAC Service Groups are absent ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04 + state: absent + +- - name: User pinky present ++ # CREATE TEST ITEMS ++ ++ - name: Ensure hosts "{{ 'host[1..4].' + ipaserver_domain }}" are present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ 'testhost01.' + ipaserver_domain }}" ++ force: yes ++ - name: "{{ 'testhost02.' + ipaserver_domain }}" ++ force: yes ++ - name: "{{ 'testhost03.' + ipaserver_domain }}" ++ force: yes ++ - name: "{{ 'testhost04.' + ipaserver_domain }}" ++ force: yes ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure host-group testhostgroup01 is present ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhostgroup01 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure host-group testhostgroup02 is present ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhostgroup02 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure host-group testhostgroup03 is present ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhostgroup03 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure host-group testhostgroup04 is present ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhostgroup04 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure testusers are present + ipauser: + ipaadmin_password: MyPassword123 +- name: pinky +- uid: 10001 +- gid: 100 +- phone: "+555123457" +- email: pinky@acme.com +- principalexpiration: "20220119235959" +- #passwordexpiration: "2022-01-19 23:59:59" +- first: pinky +- last: Acme ++ users: ++ - name: testuser01 ++ first: test ++ last: user01 ++ - name: testuser02 ++ first: test ++ last: user02 ++ - name: testuser03 ++ first: test ++ last: user03 ++ - name: testuser04 ++ first: test ++ last: user04 + register: result + failed_when: not result.changed + +- - name: User group login present ++ - name: Ensure user group testgroup01 is present + ipagroup: + ipaadmin_password: MyPassword123 +- name: login ++ name: testgroup01 + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule allhosts is present +- ipahbacrule: ++ - name: Ensure user group testgroup02 is present ++ ipagroup: + ipaadmin_password: MyPassword123 +- name: allhosts +- usercategory: all ++ name: testgroup02 + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule allhosts is present again +- ipahbacrule: ++ - name: Ensure user group testgroup03 is present ++ ipagroup: + ipaadmin_password: MyPassword123 +- name: allhosts +- usercategory: all ++ name: testgroup03 + register: result +- failed_when: result.changed ++ failed_when: not result.changed + +- - name: Ensure host "{{ groups.ipaserver[0] }}" is present in HBAC Rule allhosts ++ - name: Ensure user group testgroup04 is present ++ ipagroup: ++ ipaadmin_password: MyPassword123 ++ name: testgroup04 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service testhbacsvc01 is present ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc01 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service testhbacsvc02 is present ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc02 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service testhbacsvc03 is present ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc03 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service testhbacsvc04 is present ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc04 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service Group testhbacsvcgroup01 is present ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup01 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service Group testhbacsvcgroup02 is present ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup02 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service Group testhbacsvcgroup03 is present ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup03 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC Service Group testhbacsvcgroup04 is present ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup04 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure test HBAC rule hbacrule01 is absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts +- host: "{{ groups.ipaserver[0] }}" +- action: member ++ name: hbacrule01 ++ state: absent ++ ++ # ENSURE HBACRULE ++ ++ - name: Ensure HBAC rule hbacrule01 is present ++ ipahbacrule: ++ ipaadmin_password: MyPassword123 ++ name: hbacrule01 + register: result + failed_when: not result.changed + +- - name: Ensure host "{{ groups.ipaserver[0] }}" is present in HBAC Rule allhosts again ++ - name: Ensure HBAC rule hbacrule01 is present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts +- host: "{{ groups.ipaserver[0] }}" +- action: member ++ name: hbacrule01 + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule sshd-pinky is present ++ # CHANGE HBACRULE WITH ALL MEMBERS ++ ++ - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hostcategory: all ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ hostgroup: testhostgroup01,testhostgroup02 ++ user: testuser01,testuser02 ++ group: testgroup01,testgroup02 ++ hbacsvc: testhbacsvc01,testhbacsvc02 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule sshd-pinky is present again ++ - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hostcategory: all ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ hostgroup: testhostgroup01,testhostgroup02 ++ user: testuser01,testuser02 ++ group: testgroup01,testgroup02 ++ hbacsvc: testhbacsvc01,testhbacsvc02 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + register: result + failed_when: result.changed + +- - name: Ensure user pinky is present in HBAC Rule sshd-pinky ++ # REMOVE MEMBERS ONE BY ONE ++ ++ - name: Ensure test HBAC rule hbacrule01 host members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- user: pinky ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ state: absent + action: member + register: result + failed_when: not result.changed + +- - name: Ensure user pinky is present in HBAC Rule sshd-pinky again ++ - name: Ensure test HBAC rule hbacrule01 host members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- user: pinky ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ state: absent + action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC service sshd is present in HBAC Rule sshd-pinky ++ - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hbacsvc: sshd ++ name: hbacrule01 ++ hostgroup: testhostgroup01,testhostgroup02 ++ state: absent + action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC service sshd is present in HBAC Rule sshd-pinky again ++ - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hbacsvc: sshd ++ name: hbacrule01 ++ hostgroup: testhostgroup01,testhostgroup02 ++ state: absent + action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule loginRule is present with HBAC service sshd ++ - name: Ensure test HBAC rule hbacrule01 user members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- group: login ++ name: hbacrule01 ++ user: testuser01,testuser02 ++ state: absent ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule loginRule is present with HBAC service sshd again ++ - name: Ensure test HBAC rule hbacrule01 user members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- group: login ++ name: hbacrule01 ++ user: testuser01,testuser02 ++ state: absent ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure user pinky is present in HBAC Rule loginRule ++ - name: Ensure test HBAC rule hbacrule01 user group members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- user: pinky ++ name: hbacrule01 ++ group: testgroup01,testgroup02 ++ state: absent + action: member + register: result + failed_when: not result.changed + +- - name: Ensure user pinky is present in HBAC Rule loginRule again ++ - name: Ensure test HBAC rule hbacrule01 user group members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- user: pinky ++ name: hbacrule01 ++ group: testgroup01,testgroup02 ++ state: absent + action: member + register: result + failed_when: result.changed + +- - name: Ensure user pinky is absent in HBAC Rule loginRule ++ - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- user: pinky +- action: member ++ name: hbacrule01 ++ hbacsvc: testhbacsvc01,testhbacsvc02 + state: absent ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure user pinky is absent in HBAC Rule loginRule again ++ - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule +- user: pinky +- action: member ++ name: hbacrule01 ++ hbacsvc: testhbacsvc01,testhbacsvc02 + state: absent ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule loginRule is absent ++ - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule ++ name: hbacrule01 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + state: absent ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule loginRule is absent again ++ - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: loginRule ++ name: hbacrule01 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + state: absent ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC service sshd is absent in HBAC Rule sshd-pinky ++ # ADD MEMBERS BACK ++ ++ - name: Ensure test HBAC rule hbacrule01 host members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hbacsvc: sshd ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" + action: member +- state: absent + register: result + failed_when: not result.changed + +- - name: Ensure HBAC service sshd is absent in HBAC Rule sshd-pinky again ++ - name: Ensure test HBAC rule hbacrule01 host members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- hbacsvc: sshd ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" + action: member +- state: absent + register: result + failed_when: result.changed + +- - name: Ensure user pinky is absent in HBAC Rule sshd-pinky ++ - name: Ensure test HBAC rule hbacrule01 hostgroup members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- user: pinky ++ name: hbacrule01 ++ hostgroup: testhostgroup01,testhostgroup02 + action: member +- state: absent + register: result + failed_when: not result.changed + +- - name: Ensure user pinky is absent in HBAC Rule sshd-pinky again ++ - name: Ensure test HBAC rule hbacrule01 hostgroup members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- user: pinky ++ name: hbacrule01 ++ hostgroup: testhostgroup01,testhostgroup02 + action: member +- state: absent + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule sshd-pinky is disabled ++ - name: Ensure test HBAC rule hbacrule01 user members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: disabled ++ name: hbacrule01 ++ user: testuser01,testuser02 ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule sshd-pinky is disabled again ++ - name: Ensure test HBAC rule hbacrule01 user members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: disabled ++ name: hbacrule01 ++ user: testuser01,testuser02 ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule sshd-pinky is enabled ++ - name: Ensure test HBAC rule hbacrule01 user group members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: enabled ++ name: hbacrule01 ++ group: testgroup01,testgroup02 ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule sshd-pinky is enabled again ++ - name: Ensure test HBAC rule hbacrule01 user group members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: enabled ++ name: hbacrule01 ++ group: testgroup01,testgroup02 ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule sshd-pinky is absent ++ - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: absent ++ name: hbacrule01 ++ hbacsvc: testhbacsvc01,testhbacsvc02 ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule sshd-pinky is absent again ++ - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: sshd-pinky +- state: absent ++ name: hbacrule01 ++ hbacsvc: testhbacsvc01,testhbacsvc02 ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure host "{{ groups.ipaserver[0] }}" is absent in HBAC Rule allhosts ++ - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts +- host: "{{ groups.ipaserver[0] }}" ++ name: hbacrule01 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + action: member +- state: absent + register: result + failed_when: not result.changed + +- - name: Ensure host "{{ groups.ipaserver[0] }}" is absent in HBAC Rule allhosts again ++ - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts +- host: "{{ groups.ipaserver[0] }}" ++ name: hbacrule01 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + action: member ++ register: result ++ failed_when: result.changed ++ ++ # CHANGE TO DIFFERENT MEMBERS ++ ++ - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups ++ ipahbacrule: ++ ipaadmin_password: MyPassword123 ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ hostgroup: testhostgroup03,testhostgroup04 ++ user: testuser03,testuser04 ++ group: testgroup03,testgroup04 ++ hbacsvc: testhbacsvc03,testhbacsvc04 ++ hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again ++ ipahbacrule: ++ ipaadmin_password: MyPassword123 ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ hostgroup: testhostgroup03,testhostgroup04 ++ user: testuser03,testuser04 ++ group: testgroup03,testgroup04 ++ hbacsvc: testhbacsvc03,testhbacsvc04 ++ hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04 ++ register: result ++ failed_when: result.changed ++ ++ # ENSURE OLD TEST MEMBERS ARE ABSENT ++ ++ - name: Ensure HBAC rule hbacrule01 members (same) are present ++ ipahbacrule: ++ ipaadmin_password: MyPassword123 ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ hostgroup: testhostgroup01,testhostgroup02 ++ user: testuser01,testuser02 ++ group: testgroup01,testgroup02 ++ hbacsvc: testhbacsvc01,testhbacsvc02 ++ hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02 + state: absent ++ action: member + register: result + failed_when: result.changed + +- - name: Ensure HBAC Rule allhosts is absent ++ # ENSURE NEW TEST MEMBERS ARE ABSENT ++ ++ - name: Ensure HBAC rule hbacrule01 members are absent + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ hostgroup: testhostgroup03,testhostgroup04 ++ user: testuser03,testuser04 ++ group: testgroup03,testgroup04 ++ hbacsvc: testhbacsvc03,testhbacsvc04 ++ hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04 + state: absent ++ action: member + register: result + failed_when: not result.changed + +- - name: Ensure HBAC Rule allhosts is absent again ++ - name: Ensure HBAC rule hbacrule01 members are absent again + ipahbacrule: + ipaadmin_password: MyPassword123 +- name: allhosts ++ name: hbacrule01 ++ host: ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ hostgroup: testhostgroup03,testhostgroup04 ++ user: testuser03,testuser04 ++ group: testgroup03,testgroup04 ++ hbacsvc: testhbacsvc03,testhbacsvc04 ++ hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04 + state: absent ++ action: member + register: result + failed_when: result.changed + +- - name: User pinky absent ++ # CLEANUP TEST ITEMS ++ ++ - name: Ensure test HBAC rule hbacrule01 is absent ++ ipahbacrule: ++ ipaadmin_password: MyPassword123 ++ name: hbacrule01 ++ state: absent ++ ++ - name: Ensure test hosts are absent ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ 'testhost01.' + ipaserver_domain }}" ++ - "{{ 'testhost02.' + ipaserver_domain }}" ++ - "{{ 'testhost03.' + ipaserver_domain }}" ++ - "{{ 'testhost04.' + ipaserver_domain }}" ++ state: absent ++ ++ - name: Ensure test hostgroups are absent ++ ipahostgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04 ++ state: absent ++ ++ - name: Ensure test users are absent + ipauser: + ipaadmin_password: MyPassword123 +- name: pinky ++ name: testuser01,testuser02,testuser03,testuser04 + state: absent + +- - name: User group login absent ++ - name: Ensure test user groups are absent + ipagroup: + ipaadmin_password: MyPassword123 +- name: login ++ name: testgroup01,testgroup02,testgroup03,testgroup04 ++ state: absent ++ ++ - name: Ensure test HBAC Services are absent ++ ipahbacsvc: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04 ++ state: absent ++ ++ - name: Ensure test HBAC Service Groups are absent ++ ipahbacsvcgroup: ++ ipaadmin_password: MyPassword123 ++ name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04 + state: absent diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch new file mode 100644 index 0000000..512a213 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch @@ -0,0 +1,106 @@ +From 22d8784da29dcfede0744ef6b691b4506eae5deb Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Thu, 20 Feb 2020 12:58:11 +0100 +Subject: [PATCH] ipahost: Do not fail on missing DNS or zone when no IP + address given + +If no IP address is given and either DNS is not configured or if the zone is +not found then ipahost may not fail in dnsrecord_find. + +The error happened for example by ensuring the absence of a host that is not +part of the domain or for a host that has been added with force and is using +a domain that is not served by the DNS server in the domain. It also +happened if there was no DNS server in the domain at all. + +A new test case has been added to test_host_ipaddresses.yml + +The fix requires ipalib_errors provided by ansible_freeipa_module. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1804838 +--- + plugins/modules/ipahost.py | 17 +++++++++++++++-- + tests/host/test_host_ipaddresses.yml | 9 +++++++++ + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py +index 558560e..062f768 100644 +--- a/plugins/modules/ipahost.py ++++ b/plugins/modules/ipahost.py +@@ -409,7 +409,7 @@ + from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ + temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \ + module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \ +- is_ipv4_addr, is_ipv6_addr ++ is_ipv4_addr, is_ipv6_addr, ipalib_errors + import six + + +@@ -871,7 +871,20 @@ def main(): + + # Make sure host exists + res_find = find_host(ansible_module, name) +- res_find_dnsrecord = find_dnsrecord(ansible_module, name) ++ try: ++ res_find_dnsrecord = find_dnsrecord(ansible_module, name) ++ except ipalib_errors.NotFound as e: ++ msg = str(e) ++ if ip_address is None and \ ++ ("DNS is not configured" in msg or \ ++ "DNS zone not found" in msg): ++ # IP address(es) not given and no DNS support in IPA ++ # -> Ignore failure ++ # IP address(es) not given and DNS zone is not found ++ # -> Ignore failure ++ res_find_dnsrecord = None ++ else: ++ ansible_module.fail_json(msg="%s: %s" % (host, msg)) + + # Create command + if state == "present": +diff --git a/tests/host/test_host_ipaddresses.yml b/tests/host/test_host_ipaddresses.yml +index 0a97dd5..136a610 100644 +--- a/tests/host/test_host_ipaddresses.yml ++++ b/tests/host/test_host_ipaddresses.yml +@@ -301,6 +301,15 @@ + register: result + failed_when: result.changed + ++ - name: Absent host01.ihavenodns.info test ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: host01.ihavenodns.info ++ state: absent ++ register: result ++ failed_when: result.changed ++ + - name: Host absent + ipahost: + ipaadmin_password: MyPassword123 +From 4d94cb09a9fb09dd2576223b9be7f77d515202fb Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Thu, 20 Feb 2020 12:54:32 +0100 +Subject: [PATCH] ansible_freeipa_module: Import ipalib.errors as ipalib_errors + +For beeing able to catch ipalib.errors.NotFound errors in ipahost it is +needed to import ipalib.errors. ipalib.errors is now imported as +ipalib_errors to not have name conflicts with the errors list used in some +of the modules. + +Related: https://bugzilla.redhat.com/show_bug.cgi?id=1804838 +--- + plugins/module_utils/ansible_freeipa_module.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py +index 6acdbef..5066de3 100644 +--- a/plugins/module_utils/ansible_freeipa_module.py ++++ b/plugins/module_utils/ansible_freeipa_module.py +@@ -28,6 +28,7 @@ + import gssapi + from datetime import datetime + from ipalib import api ++from ipalib import errors as ipalib_errors + from ipalib.config import Env + from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT + try: diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch new file mode 100644 index 0000000..aea3a62 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch @@ -0,0 +1,51 @@ +From 24515e40ad289552d45bddd33c7a0dda93117a7f Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Wed, 18 Dec 2019 12:28:03 +0100 +Subject: [PATCH] ipahost: Enhanced failure msg for member params used without + member action + +The failure message if member parameters like certificate, managedby_host, +principal, allow_create_keytab_* and allow_retrieve_keytab_* are used +without member action for state absent has been enhanced to propose the +member action. +--- + plugins/modules/ipahost.py | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py +index ec5e196..8ee9532 100644 +--- a/plugins/modules/ipahost.py ++++ b/plugins/modules/ipahost.py +@@ -511,19 +511,25 @@ def check_parameters( + "userclass", "auth_ind", "requires_pre_auth", + "ok_as_delegate", "ok_to_auth_as_delegate", "force", + "reverse", "ip_address", "update_password"] ++ for x in invalid: ++ if vars()[x] is not None: ++ module.fail_json( ++ msg="Argument '%s' can not be used with state '%s'" % ++ (x, state)) + if action == "host": +- invalid.extend([ ++ invalid = [ + "certificate", "managedby_host", "principal", + "allow_create_keytab_user", "allow_create_keytab_group", + "allow_create_keytab_host", "allow_create_keytab_hostgroup", + "allow_retrieve_keytab_user", "allow_retrieve_keytab_group", + "allow_retrieve_keytab_host", +- "allow_retrieve_keytab_hostgroup"]) +- for x in invalid: +- if vars()[x] is not None: +- module.fail_json( +- msg="Argument '%s' can not be used with state '%s'" % +- (x, state)) ++ "allow_retrieve_keytab_hostgroup" ++ ] ++ for x in invalid: ++ if vars()[x] is not None: ++ module.fail_json( ++ msg="Argument '%s' can only be used with action " ++ "'member' for state '%s'" % (x, state)) + + + def main(): diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch new file mode 100644 index 0000000..fa2dadd --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch @@ -0,0 +1,57 @@ +From 0816b0773b1535780c7c3e5f05bda39434ab6bac Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Fri, 14 Feb 2020 13:21:54 +0100 +Subject: [PATCH] ipahost: Fail on action member for new hosts, fix + dnsrecord_add reverse flag + +The check to make sure that member can not be used on non existing hosts +has bee missing. Also the reverse flag for the dnsrecord_add call was None +if the varaible was not set. + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1803026 +--- + plugins/modules/ipahost.py | 23 +++++++++++++++-------- + 1 file changed, 15 insertions(+), 8 deletions(-) + +diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py +index a5fd482..558560e 100644 +--- a/plugins/modules/ipahost.py ++++ b/plugins/modules/ipahost.py +@@ -1005,6 +1005,11 @@ def main(): + dnsrecord_args.get("aaaarecord"), + _dnsrec.get("aaaarecord")) + ++ else: ++ if res_find is None: ++ ansible_module.fail_json( ++ msg="No host '%s'" % name) ++ + if action != "host" or (action == "host" and res_find is None): + certificate_add = certificate or [] + certificate_del = [] +@@ -1178,15 +1183,17 @@ def main(): + domain_name = name[name.find(".")+1:] + host_name = name[:name.find(".")] + ++ _args = {"idnsname": host_name} ++ if reverse is not None: ++ _args["a_extra_create_reverse"] = reverse ++ _args["aaaa_extra_create_reverse"] = reverse ++ if len(dnsrecord_a_add) > 0: ++ _args["arecord"] = dnsrecord_a_add ++ if len(dnsrecord_aaaa_add) > 0: ++ _args["aaaarecord"] = dnsrecord_aaaa_add ++ + commands.append([domain_name, +- "dnsrecord_add", +- { +- "idnsname": host_name, +- "arecord": dnsrecord_a_add, +- "a_extra_create_reverse": reverse, +- "aaaarecord": dnsrecord_aaaa_add, +- "aaaa_extra_create_reverse": reverse +- }]) ++ "dnsrecord_add", _args]) + + if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0: + domain_name = name[name.find(".")+1:] diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch new file mode 100644 index 0000000..592da6e --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch @@ -0,0 +1,78 @@ +From b6100f0c19e2caf73ab70bbc572d3e47e6066b48 Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Tue, 17 Dec 2019 14:04:43 +0100 +Subject: [PATCH] ipahost: Fix choices of auth_ind parameter, allow to reset + parameter + +The choices for the auth_ind parameter have been wrong. The choices are now +['radius', 'otp', 'pkinit', 'hardened', '']. The empty string has been added +to be able to rest auth_ind for the host entry. +--- + README-host.md | 2 +- + plugins/modules/ipahost.py | 15 ++++++++++++--- + 2 files changed, 13 insertions(+), 4 deletions(-) + +diff --git a/README-host.md b/README-host.md +index edec8d9..be5ad79 100644 +--- a/README-host.md ++++ b/README-host.md +@@ -280,7 +280,7 @@ Variable | Description | Required + `mac_address` \| `macaddress` | List of hardware MAC addresses. | no + `sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no + `userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no +-`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened"] | no ++`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened", ""] | no + `requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no + `ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no + `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no +diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py +index ec5e196..b130395 100644 +--- a/plugins/modules/ipahost.py ++++ b/plugins/modules/ipahost.py +@@ -147,9 +147,10 @@ + Defines a whitelist for Authentication Indicators. Use 'otp' to allow + OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA + authentications. Other values may be used for custom configurations. ++ Use empty string to reset auth_ind to the initial value. + type: list + aliases: ["krbprincipalauthind"] +- choices: ["radius", "otp", "pkinit", "hardened"] ++ choices: ["radius", "otp", "pkinit", "hardened", ""] + required: false + requires_pre_auth: + description: Pre-authentication is required for the service +@@ -277,9 +278,10 @@ + Defines a whitelist for Authentication Indicators. Use 'otp' to allow + OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA + authentications. Other values may be used for custom configurations. ++ Use empty string to reset auth_ind to the initial value. + type: list + aliases: ["krbprincipalauthind"] +- choices: ["radius", "otp", "pkinit", "hardened"] ++ choices: ["radius", "otp", "pkinit", "hardened", ""] + required: false + requires_pre_auth: + description: Pre-authentication is required for the service +@@ -590,7 +592,7 @@ def main(): + default=None), + auth_ind=dict(type='list', aliases=["krbprincipalauthind"], + default=None, +- choices=['password', 'radius', 'otp']), ++ choices=['radius', 'otp', 'pkinit', 'hardened', '']), + requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"], + default=None), + ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"], +@@ -835,6 +837,13 @@ def main(): + if x in args: + del args[x] + ++ # Ignore auth_ind if it is empty (for resetting) ++ # and not set in for the host ++ if "krbprincipalauthind" not in res_find and \ ++ "krbprincipalauthind" in args and \ ++ args["krbprincipalauthind"] == ['']: ++ del args["krbprincipalauthind"] ++ + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify diff --git a/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch b/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch new file mode 100644 index 0000000..bde4624 --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch @@ -0,0 +1,179 @@ +From 4dd1d25eacd1481be0a881a017144ff4d3396ccd Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Thu, 6 Feb 2020 15:38:00 +0100 +Subject: [PATCH] ipapwpolicy: Use global_policy if name is not set + +If the name is not set, the policy global_policy is now used. It was needed +before to explicitly name the global_policy. Also a check has been added +to fail early if global_policy is used with state absent. + +The README for pwpolicy has been extended with an example for global_policy +and also the description of the name variable. + +The test has also been extended to check a change of maxlife for +global_policy and that global_policy can not be used with state: absent + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1797532 +--- + README-pwpolicy.md | 19 +++++++++++-- + plugins/modules/ipapwpolicy.py | 9 ++++-- + tests/pwpolicy/test_pwpolicy.yml | 49 ++++++++++++++++++++++++++++++++ + 3 files changed, 73 insertions(+), 4 deletions(-) + +diff --git a/README-pwpolicy.md b/README-pwpolicy.md +index 16306b7..847b32d 100644 +--- a/README-pwpolicy.md ++++ b/README-pwpolicy.md +@@ -56,7 +56,7 @@ Example playbook to ensure presence of pwpolicies for exisiting group ops: + maxfail: 3 + ``` + +-Example playbook to ensure absence of pwpolicies for group ops ++Example playbook to ensure absence of pwpolicies for group ops: + + ```yaml + --- +@@ -72,6 +72,21 @@ Example playbook to ensure absence of pwpolicies for group ops + state: absent + ``` + ++Example playbook to ensure maxlife is set to 49 in global policy: ++ ++```yaml ++--- ++- name: Playbook to handle pwpolicies ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ # Ensure absence of pwpolicies for group ops ++ - ipapwpolicy: ++ ipaadmin_password: MyPassword123 ++ maxlife: 49 ++``` ++ + + Variables + ========= +@@ -83,7 +98,7 @@ 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 pwpolicy name strings. | no ++`name` \| `cn` | The list of pwpolicy name strings. If name is not given, `global_policy` will be used automatically. | no + `maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int) | no + `minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int) | no + `history` \| `krbpwdhistorylength` | Password history size. (int) | no +diff --git a/plugins/modules/ipapwpolicy.py b/plugins/modules/ipapwpolicy.py +index 9437b59..f168703 100644 +--- a/plugins/modules/ipapwpolicy.py ++++ b/plugins/modules/ipapwpolicy.py +@@ -167,7 +167,7 @@ def main(): + ipaadmin_password=dict(type="str", required=False, no_log=True), + + name=dict(type="list", aliases=["cn"], default=None, +- required=True), ++ required=False), + # present + + maxlife=dict(type="int", aliases=["krbmaxpwdlife"], default=None), +@@ -218,6 +218,9 @@ def main(): + + # Check parameters + ++ if names is None: ++ names = ["global_policy"] ++ + if state == "present": + if len(names) != 1: + ansible_module.fail_json( +@@ -225,8 +228,10 @@ def main(): + + if state == "absent": + if len(names) < 1: ++ ansible_module.fail_json(msg="No name given.") ++ if "global_policy" in names: + ansible_module.fail_json( +- msg="No name given.") ++ msg="'global_policy' can not be made absent.") + invalid = ["maxlife", "minlife", "history", "minclasses", + "minlength", "priority", "maxfail", "failinterval", + "lockouttime"] +diff --git a/tests/pwpolicy/test_pwpolicy.yml b/tests/pwpolicy/test_pwpolicy.yml +index 5c69345..f93f275 100644 +--- a/tests/pwpolicy/test_pwpolicy.yml ++++ b/tests/pwpolicy/test_pwpolicy.yml +@@ -5,10 +5,30 @@ + gather_facts: false + + tasks: ++ - name: Ensure maxlife of 90 for global_policy ++ ipapwpolicy: ++ ipaadmin_password: SomeADMINpassword ++ maxlife: 90 ++ ++ - name: Ensure absence of group ops ++ ipagroup: ++ ipaadmin_password: SomeADMINpassword ++ name: ops ++ state: absent ++ ++ - name: Ensure absence of pwpolicies for group ops ++ ipapwpolicy: ++ ipaadmin_password: SomeADMINpassword ++ name: ops ++ state: absent ++ + - name: Ensure presence of group ops + ipagroup: + ipaadmin_password: SomeADMINpassword + name: ops ++ state: present ++ register: result ++ failed_when: not result.changed + + - name: Ensure presence of pwpolicies for group ops + ipapwpolicy: +@@ -42,6 +62,28 @@ + register: result + failed_when: result.changed + ++ - name: Ensure maxlife of 49 for global_policy ++ ipapwpolicy: ++ ipaadmin_password: SomeADMINpassword ++ maxlife: 49 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Ensure maxlife of 49 for global_policy again ++ ipapwpolicy: ++ ipaadmin_password: SomeADMINpassword ++ maxlife: 49 ++ register: result ++ failed_when: result.changed ++ ++ - name: Ensure absence of pwpoliciy global_policy will fail ++ ipapwpolicy: ++ ipaadmin_password: SomeADMINpassword ++ state: absent ++ register: result ++ ignore_errors: True ++ failed_when: result is defined and result ++ + - name: Ensure absence of pwpolicies for group ops + ipapwpolicy: + ipaadmin_password: SomeADMINpassword +@@ -50,6 +92,13 @@ + register: result + failed_when: not result.changed + ++ - name: Ensure maxlife of 90 for global_policy ++ ipapwpolicy: ++ ipaadmin_password: MyPassword123 ++ maxlife: 90 ++ register: result ++ failed_when: not result.changed ++ + - name: Ensure absence of pwpolicies for group ops + ipapwpolicy: + ipaadmin_password: SomeADMINpassword diff --git a/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch b/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch new file mode 100644 index 0000000..cb0a7ef --- /dev/null +++ b/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch @@ -0,0 +1,116 @@ +From 36c1c837086c42049f09cf689a1ebd61627abae0 Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Tue, 17 Dec 2019 15:30:45 +0100 +Subject: [PATCH] ipauser: Allow reset of userauthtype, do not depend on + first,last for mod + +It was not possible to reset the userauthtype. The empty string has been +added to userauthtype for this. + +Also ipauser will only depend on given first and last name if the user +does not exist yet. For the update operation these parameters are not +needed anymore. +--- + README-user.md | 2 +- + plugins/modules/ipauser.py | 38 ++++++++++++++++++++++++++------------ + 2 files changed, 27 insertions(+), 13 deletions(-) + +diff --git a/README-user.md b/README-user.md +index 56772a7..991121c 100644 +--- a/README-user.md ++++ b/README-user.md +@@ -408,7 +408,7 @@ Variable | Description | Required + `manager` | List of manager user names. | no + `carlicense` | List of car licenses. | no + `sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no +-`userauthtype` | List of supported user authentication types. Choices: `password`, `radius` and `otp` | no ++`userauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp` and ``. Use empty string to reset userauthtype to the initial value. | no + `userclass` | User category. (semantics placed on this attribute are for local interpretation). | no + `radius` | RADIUS proxy configuration | no + `radiususer` | RADIUS proxy username | no +diff --git a/plugins/modules/ipauser.py b/plugins/modules/ipauser.py +index ac45295..36e8bae 100644 +--- a/plugins/modules/ipauser.py ++++ b/plugins/modules/ipauser.py +@@ -153,9 +153,12 @@ + required: false + aliases: ["ipasshpubkey"] + userauthtype: +- description: List of supported user authentication types +- choices=['password', 'radius', 'otp'] ++ description: ++ List of supported user authentication types ++ Use empty string to reset userauthtype to the initial value. ++ choices=['password', 'radius', 'otp', ''] + required: false ++ aliases: ["ipauserauthtype"] + userclass: + description: + - User category +@@ -310,9 +313,12 @@ + required: false + aliases: ["ipasshpubkey"] + userauthtype: +- description: List of supported user authentication types +- choices=['password', 'radius', 'otp'] ++ description: ++ List of supported user authentication types ++ Use empty string to reset userauthtype to the initial value. ++ choices=['password', 'radius', 'otp', ''] + required: false ++ aliases: ["ipauserauthtype"] + userclass: + description: + - User category +@@ -701,7 +707,7 @@ def main(): + default=None), + userauthtype=dict(type='list', aliases=["ipauserauthtype"], + default=None, +- choices=['password', 'radius', 'otp']), ++ choices=['password', 'radius', 'otp', '']), + userclass=dict(type="list", aliases=["class"], + default=None), + radius=dict(type="str", aliases=["ipatokenradiusconfiglink"], +@@ -845,13 +851,6 @@ def main(): + if names is not None and len(names) != 1: + ansible_module.fail_json( + msg="Only one user can be added at a time using name.") +- if action != "member": +- # Only check first and last here if names is set +- if names is not None: +- if first is None: +- ansible_module.fail_json(msg="First name is needed") +- if last is None: +- ansible_module.fail_json(msg="Last name is needed") + + check_parameters( + ansible_module, state, action, +@@ -1011,6 +1010,13 @@ def main(): + if "noprivate" in args: + del args["noprivate"] + ++ # Ignore userauthtype if it is empty (for resetting) ++ # and not set in for the user ++ if "ipauserauthtype" not in res_find and \ ++ "ipauserauthtype" in args and \ ++ args["ipauserauthtype"] == ['']: ++ del args["ipauserauthtype"] ++ + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify +@@ -1019,6 +1025,14 @@ def main(): + commands.append([name, "user_mod", args]) + + else: ++ # Make sure we have a first and last name ++ if first is None: ++ ansible_module.fail_json( ++ msg="First name is needed") ++ if last is None: ++ ansible_module.fail_json( ++ msg="Last name is needed") ++ + commands.append([name, "user_add", args]) + + # Handle members: principal, manager, certificate and diff --git a/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch b/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch new file mode 100644 index 0000000..4f2093d --- /dev/null +++ b/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch @@ -0,0 +1,915 @@ +From 167c76311da72c2bfabf4b2bce9e128c11d519d0 Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Wed, 12 Feb 2020 16:54:13 +0100 +Subject: [PATCH] ipahost: Add support for several IP addresses and also to + change them + +ipahost was so far ignoring IP addresses when the host already existed. +This happened because host_mod is not providing functionality to do this. +Now ipaddress is a list and it is possible to ensure a host with several +IP addresses (these can be IPv4 and IPv6). Also it is possible to ensure +presence and absence of IP addresses for an exising host using action +member. + +There are no IP address conclict checks as this would lead into issues with +updating an existing host that already is using a duplicate IP address for +example for round-robin (RR). Also this might lead into issues with ensuring +a new host with several IP addresses in this case. Also to ensure a list of +hosts with changing the IP address of one host to another in the list would +result in issues here. + +New example playbooks have been added: + + playbooks/host/host-present-with-several-ip-addresses.yml + playbooks/host/host-member-ipaddresses-absent.yml + playbooks/host/host-member-ipaddresses-present.yml + +A new test has been added for verification: + + tests/host/test_host_ipaddresses.yml + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1783976 + https://bugzilla.redhat.com/show_bug.cgi?id=1783979 +--- + README-host.md | 79 ++++- + .../host/host-member-ipaddresses-absent.yml | 17 + + .../host/host-member-ipaddresses-present.yml | 16 + + ...host-present-with-several-ip-addresses.yml | 24 ++ + .../module_utils/ansible_freeipa_module.py | 23 ++ + plugins/modules/ipahost.py | 179 +++++++--- + tests/host/test_host_ipaddresses.yml | 312 ++++++++++++++++++ + 7 files changed, 600 insertions(+), 50 deletions(-) + create mode 100644 playbooks/host/host-member-ipaddresses-absent.yml + create mode 100644 playbooks/host/host-member-ipaddresses-present.yml + create mode 100644 playbooks/host/host-present-with-several-ip-addresses.yml + create mode 100644 tests/host/test_host_ipaddresses.yml + +diff --git a/README-host.md b/README-host.md +index be5ad79..ecc59a9 100644 +--- a/README-host.md ++++ b/README-host.md +@@ -65,6 +65,79 @@ Example playbook to ensure host presence: + - "52:54:00:BD:97:1E" + state: present + ``` ++Compared to `ipa host-add` command no IP address conflict check is done as the ipahost module supports to have several IPv4 and IPv6 addresses for a host. ++ ++ ++Example playbook to ensure host presence with several IP addresses: ++ ++```yaml ++--- ++- name: Playbook to handle hosts ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ # Ensure host is present ++ - ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ description: Example host ++ ip_address: ++ - 192.168.0.123 ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b3 ++ - fe80::20c:29ff:fe02:a1b4 ++ locality: Lab ++ ns_host_location: Lab ++ ns_os_version: CentOS 7 ++ ns_hardware_platform: Lenovo T61 ++ mac_address: ++ - "08:00:27:E3:B1:2D" ++ - "52:54:00:BD:97:1E" ++ state: present ++``` ++ ++ ++Example playbook to ensure IP addresses are present for a host: ++ ++```yaml ++--- ++- name: Playbook to handle hosts ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ # Ensure host is present ++ - ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ ip_address: ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member ++ state: present ++``` ++ ++ ++Example playbook to ensure IP addresses are absent for a host: ++ ++```yaml ++--- ++- name: Playbook to handle hosts ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ # Ensure host is present ++ - ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ ip_address: ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member ++ state: absent ++``` + + + Example playbook to ensure host presence without DNS: +@@ -215,7 +288,7 @@ Example playbook to disable a host: + update_dns: yes + state: disabled + ``` +-`update_dns` controls if the DNS entries will be updated. ++`update_dns` controls if the DNS entries will be updated in this case. For `state` present it is controlling the update of the DNS SSHFP records, but not the the other DNS records. + + + Example playbook to ensure a host is absent: +@@ -286,8 +359,8 @@ Variable | Description | Required + `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no + `force` | Force host name even if not in DNS. | no + `reverse` | Reverse DNS detection. | no +-`ip_address` \| `ipaddress` | The host IP address. | no +-`update_dns` | Update DNS entries. | no ++`ip_address` \| `ipaddress` | The host IP address list. It can contain IPv4 and IPv6 addresses. No conflict check for IP addresses is done. | no ++`update_dns` | For existing hosts: DNS SSHFP records are updated with `state` present and all DNS entries for a host removed with `state` absent. | no + + + Return Values +diff --git a/playbooks/host/host-member-ipaddresses-absent.yml b/playbooks/host/host-member-ipaddresses-absent.yml +new file mode 100644 +index 0000000..2466dbd +--- /dev/null ++++ b/playbooks/host/host-member-ipaddresses-absent.yml +@@ -0,0 +1,17 @@ ++--- ++- name: Host member IP addresses absent ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ - name: Ensure host01.example.com IP addresses absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ ip_address: ++ - 192.168.0.123 ++ - fe80::20c:29ff:fe02:a1b3 ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member ++ state: absent +diff --git a/playbooks/host/host-member-ipaddresses-present.yml b/playbooks/host/host-member-ipaddresses-present.yml +new file mode 100644 +index 0000000..f473993 +--- /dev/null ++++ b/playbooks/host/host-member-ipaddresses-present.yml +@@ -0,0 +1,16 @@ ++--- ++- name: Host member IP addresses present ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ - name: Ensure host01.example.com IP addresses present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ ip_address: ++ - 192.168.0.123 ++ - fe80::20c:29ff:fe02:a1b3 ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member +diff --git a/playbooks/host/host-present-with-several-ip-addresses.yml b/playbooks/host/host-present-with-several-ip-addresses.yml +new file mode 100644 +index 0000000..4956562 +--- /dev/null ++++ b/playbooks/host/host-present-with-several-ip-addresses.yml +@@ -0,0 +1,24 @@ ++--- ++- name: Host present with several IP addresses ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ - name: Ensure host is present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: host01.example.com ++ description: Example host ++ ip_address: ++ - 192.168.0.123 ++ - fe80::20c:29ff:fe02:a1b3 ++ - 192.168.0.124 ++ - fe80::20c:29ff:fe02:a1b4 ++ locality: Lab ++ ns_host_location: Lab ++ ns_os_version: CentOS 7 ++ ns_hardware_platform: Lenovo T61 ++ mac_address: ++ - "08:00:27:E3:B1:2D" ++ - "52:54:00:BD:97:1E" ++ state: present +diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py +index 9e97b88..6acdbef 100644 +--- a/plugins/module_utils/ansible_freeipa_module.py ++++ b/plugins/module_utils/ansible_freeipa_module.py +@@ -42,6 +42,7 @@ + from ipalib.x509 import Encoding + except ImportError: + from cryptography.hazmat.primitives.serialization import Encoding ++import socket + import base64 + import six + +@@ -285,3 +286,25 @@ def encode_certificate(cert): + if not six.PY2: + encoded = encoded.decode('ascii') + return encoded ++ ++ ++def is_ipv4_addr(ipaddr): ++ """ ++ Test if figen IP address is a valid IPv4 address ++ """ ++ try: ++ socket.inet_pton(socket.AF_INET, ipaddr) ++ except socket.error: ++ return False ++ return True ++ ++ ++def is_ipv6_addr(ipaddr): ++ """ ++ Test if figen IP address is a valid IPv6 address ++ """ ++ try: ++ socket.inet_pton(socket.AF_INET6, ipaddr) ++ except socket.error: ++ return False ++ return True +diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py +index dba4181..a5fd482 100644 +--- a/plugins/modules/ipahost.py ++++ b/plugins/modules/ipahost.py +@@ -176,11 +176,16 @@ + default: true + required: false + ip_address: +- description: The host IP address ++ description: ++ The host IP address list (IPv4 and IPv6). No IP address conflict ++ check will be done. + aliases: ["ipaddress"] + required: false + update_dns: +- description: Update DNS entries ++ description: ++ Controls the update of the DNS SSHFP records for existing hosts and ++ the removal of all DNS entries if a host gets removed with state ++ absent. + required: false + description: + description: The host description +@@ -306,11 +311,16 @@ + default: true + required: false + ip_address: +- description: The host IP address ++ description: ++ The host IP address list (IPv4 and IPv6). No IP address conflict ++ check will be done. + aliases: ["ipaddress"] + required: false + update_dns: +- description: Update DNS entries ++ description: ++ Controls the update of the DNS SSHFP records for existing hosts and ++ the removal of all DNS entries if a host gets removed with state ++ absent. + required: false + update_password: + description: +@@ -398,7 +408,8 @@ + 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, \ +- module_params_get, gen_add_del_lists, encode_certificate, api_get_realm ++ module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \ ++ is_ipv4_addr, is_ipv6_addr + import six + + +@@ -428,6 +439,32 @@ def find_host(module, name): + return None + + ++def find_dnsrecord(module, name): ++ domain_name = name[name.find(".")+1:] ++ host_name = name[:name.find(".")] ++ ++ _args = { ++ "all": True, ++ "idnsname": to_text(host_name), ++ } ++ ++ _result = api_command(module, "dnsrecord_find", to_text(domain_name), ++ _args) ++ ++ if len(_result["result"]) > 1: ++ module.fail_json( ++ msg="There is more than one host '%s'" % (name)) ++ elif len(_result["result"]) == 1: ++ _res = _result["result"][0] ++ certs = _res.get("usercertificate") ++ if certs is not None: ++ _res["usercertificate"] = [encode_certificate(cert) for ++ cert in certs] ++ return _res ++ else: ++ return None ++ ++ + def show_host(module, name): + _result = api_command(module, "host_show", to_text(name), {}) + return _result["result"] +@@ -470,16 +507,34 @@ def gen_args(description, locality, location, platform, os, password, random, + _args["ipakrboktoauthasdelegate"] = ok_to_auth_as_delegate + if force is not None: + _args["force"] = force +- if reverse is not None: +- _args["no_reverse"] = not reverse + if ip_address is not None: +- _args["ip_address"] = ip_address ++ # IP addresses are handed extra, therefore it is needed to set ++ # the force option here to make sure that host-add is able to ++ # add a host without IP address. ++ _args["force"] = True + if update_dns is not None: + _args["updatedns"] = update_dns + + return _args + + ++def gen_dnsrecord_args(module, ip_address, reverse): ++ _args = {} ++ if reverse is not None: ++ _args["a_extra_create_reverse"] = reverse ++ _args["aaaa_extra_create_reverse"] = reverse ++ if ip_address is not None: ++ for ip in ip_address: ++ if is_ipv4_addr(ip): ++ _args.setdefault("arecord", []).append(ip) ++ elif is_ipv6_addr(ip): ++ _args.setdefault("aaaarecord", []).append(ip) ++ else: ++ module.fail_json(msg="'%s' is not a valid IP address." % ip) ++ ++ return _args ++ ++ + def check_parameters( + module, state, action, + description, locality, location, platform, os, password, random, +@@ -499,8 +554,7 @@ def check_parameters( + "os", "password", "random", "mac_address", "sshpubkey", + "userclass", "auth_ind", "requires_pre_auth", + "ok_as_delegate", "ok_to_auth_as_delegate", "force", +- "reverse", "ip_address", "update_dns", +- "update_password"] ++ "reverse", "update_dns", "update_password"] + for x in invalid: + if vars()[x] is not None: + module.fail_json( +@@ -512,7 +566,7 @@ def check_parameters( + "password", "random", "mac_address", "sshpubkey", + "userclass", "auth_ind", "requires_pre_auth", + "ok_as_delegate", "ok_to_auth_as_delegate", "force", +- "reverse", "ip_address", "update_password"] ++ "reverse", "update_password"] + for x in invalid: + if vars()[x] is not None: + module.fail_json( +@@ -549,9 +603,6 @@ def main(): + default=None, no_log=True), + random=dict(type="bool", aliases=["random_password"], + default=None), +- +- +- + certificate=dict(type="list", aliases=["usercertificate"], + default=None), + managedby_host=dict(type="list", +@@ -608,7 +659,7 @@ def main(): + default=None), + force=dict(type='bool', default=None), + reverse=dict(type='bool', default=None), +- ip_address=dict(type="str", aliases=["ipaddress"], ++ ip_address=dict(type="list", aliases=["ipaddress"], + default=None), + update_dns=dict(type="bool", aliases=["updatedns"], + default=None), +@@ -820,6 +871,7 @@ def main(): + + # Make sure host exists + res_find = find_host(ansible_module, name) ++ res_find_dnsrecord = find_dnsrecord(ansible_module, name) + + # Create command + if state == "present": +@@ -829,6 +881,8 @@ def main(): + random, mac_address, sshpubkey, userclass, auth_ind, + requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate, + force, reverse, ip_address, update_dns) ++ dnsrecord_args = gen_dnsrecord_args( ++ ansible_module, ip_address, reverse) + + if action == "host": + # Found the host +@@ -938,39 +992,20 @@ def main(): + res_find.get( + "ipaallowedtoperform_read_keys_hostgroup")) + +- else: +- certificate_add = certificate or [] +- certificate_del = [] +- managedby_host_add = managedby_host or [] +- managedby_host_del = [] +- principal_add = principal or [] +- principal_del = [] +- allow_create_keytab_user_add = \ +- allow_create_keytab_user or [] +- allow_create_keytab_user_del = [] +- allow_create_keytab_group_add = \ +- allow_create_keytab_group or [] +- allow_create_keytab_group_del = [] +- allow_create_keytab_host_add = \ +- allow_create_keytab_host or [] +- allow_create_keytab_host_del = [] +- allow_create_keytab_hostgroup_add = \ +- allow_create_keytab_hostgroup or [] +- allow_create_keytab_hostgroup_del = [] +- allow_retrieve_keytab_user_add = \ +- allow_retrieve_keytab_user or [] +- allow_retrieve_keytab_user_del = [] +- allow_retrieve_keytab_group_add = \ +- allow_retrieve_keytab_group or [] +- allow_retrieve_keytab_group_del = [] +- allow_retrieve_keytab_host_add = \ +- allow_retrieve_keytab_host or [] +- allow_retrieve_keytab_host_del = [] +- allow_retrieve_keytab_hostgroup_add = \ +- allow_retrieve_keytab_hostgroup or [] +- allow_retrieve_keytab_hostgroup_del = [] ++ # IP addresses are not really a member of hosts, but ++ # we will simply treat it as this to enable the ++ # addition and removal of IPv4 and IPv6 addresses in ++ # a simple way. ++ _dnsrec = res_find_dnsrecord or {} ++ dnsrecord_a_add, dnsrecord_a_del = gen_add_del_lists( ++ dnsrecord_args.get("arecord"), ++ _dnsrec.get("arecord")) ++ dnsrecord_aaaa_add, dnsrecord_aaaa_del = \ ++ gen_add_del_lists( ++ dnsrecord_args.get("aaaarecord"), ++ _dnsrec.get("aaaarecord")) + +- else: ++ if action != "host" or (action == "host" and res_find is None): + certificate_add = certificate or [] + certificate_del = [] + managedby_host_add = managedby_host or [] +@@ -1001,6 +1036,10 @@ def main(): + allow_retrieve_keytab_hostgroup_add = \ + allow_retrieve_keytab_hostgroup or [] + allow_retrieve_keytab_hostgroup_del = [] ++ dnsrecord_a_add = dnsrecord_args.get("arecord") or [] ++ dnsrecord_a_del = [] ++ dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or [] ++ dnsrecord_aaaa_del = [] + + # Remove canonical principal from principal_del + canonical_principal = "host/" + name + "@" + server_realm +@@ -1135,6 +1174,36 @@ def main(): + "hostgroup": allow_retrieve_keytab_hostgroup_del, + }]) + ++ if len(dnsrecord_a_add) > 0 or len(dnsrecord_aaaa_add) > 0: ++ domain_name = name[name.find(".")+1:] ++ host_name = name[:name.find(".")] ++ ++ commands.append([domain_name, ++ "dnsrecord_add", ++ { ++ "idnsname": host_name, ++ "arecord": dnsrecord_a_add, ++ "a_extra_create_reverse": reverse, ++ "aaaarecord": dnsrecord_aaaa_add, ++ "aaaa_extra_create_reverse": reverse ++ }]) ++ ++ if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0: ++ domain_name = name[name.find(".")+1:] ++ host_name = name[:name.find(".")] ++ ++ # There seems to be an issue with dnsrecord_del (not ++ # for dnsrecord_add) if aaaarecord is an empty list. ++ # Therefore this is done differently here: ++ _args = {"idnsname": host_name} ++ if len(dnsrecord_a_del) > 0: ++ _args["arecord"] = dnsrecord_a_del ++ if len(dnsrecord_aaaa_del) > 0: ++ _args["aaaarecord"] = dnsrecord_aaaa_del ++ ++ commands.append([domain_name, ++ "dnsrecord_del", _args]) ++ + elif state == "absent": + if action == "host": + +@@ -1215,6 +1284,17 @@ def main(): + "hostgroup": allow_retrieve_keytab_hostgroup, + }]) + ++ dnsrecord_args = gen_dnsrecord_args(ansible_module, ++ ip_address, reverse) ++ if "arecord" in dnsrecord_args or \ ++ "aaaarecord" in dnsrecord_args: ++ domain_name = name[name.find(".")+1:] ++ host_name = name[:name.find(".")] ++ dnsrecord_args["idnsname"] = host_name ++ ++ commands.append([domain_name, "dnsrecord_del", ++ dnsrecord_args]) ++ + elif state == "disabled": + if res_find is not None: + commands.append([name, "host_disable", {}]) +@@ -1259,6 +1339,11 @@ def main(): + # Host is already disabled, ignore error + if "This entry is already disabled" in msg: + continue ++ ++ # Ignore no modification error. ++ if "no modifications to be performed" in msg: ++ continue ++ + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, + msg)) + +diff --git a/tests/host/test_host_ipaddresses.yml b/tests/host/test_host_ipaddresses.yml +new file mode 100644 +index 0000000..0a97dd5 +--- /dev/null ++++ b/tests/host/test_host_ipaddresses.yml +@@ -0,0 +1,312 @@ ++--- ++- name: Test host IP addresses ++ hosts: ipaserver ++ become: true ++ ++ tasks: ++ - name: Get Domain from server name ++ set_fact: ++ ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}" ++ when: ipaserver_domain is not defined ++ ++ - name: Set host1_fqdn .. host6_fqdn ++ set_fact: ++ host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" ++ host2_fqdn: "{{ 'host2.' + ipaserver_domain }}" ++ host3_fqdn: "{{ 'host3.' + ipaserver_domain }}" ++ ++ - name: Get IPv4 address prefix from server node ++ set_fact: ++ ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] | ++ join('.') }}" ++ ++ - name: Host absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ host1_fqdn }}" ++ - "{{ host2_fqdn }}" ++ - "{{ host3_fqdn }}" ++ update_dns: yes ++ state: absent ++ ++ - name: Host "{{ host1_fqdn }}" present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.201' }}" ++ - fe80::20c:29ff:fe02:a1b2 ++ update_dns: yes ++ reverse: no ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.201' }}" ++ - fe80::20c:29ff:fe02:a1b2 ++ update_dns: yes ++ reverse: no ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present again with new IP address ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ update_dns: yes ++ reverse: no ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" present again with new IP address again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ update_dns: yes ++ reverse: no ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv4 address present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: "{{ ipv4_prefix + '.201' }}" ++ action: member ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv4 address present again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: "{{ ipv4_prefix + '.201' }}" ++ action: member ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv4 address absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: "{{ ipv4_prefix + '.201' }}" ++ action: member ++ state: absent ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv4 address absent again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: "{{ ipv4_prefix + '.201' }}" ++ action: member ++ state: absent ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv6 address present ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: fe80::20c:29ff:fe02:a1b2 ++ action: member ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv6 address present again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: fe80::20c:29ff:fe02:a1b2 ++ action: member ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv6 address absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: fe80::20c:29ff:fe02:a1b2 ++ action: member ++ state: absent ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" member IPv6 address absent again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: fe80::20c:29ff:fe02:a1b2 ++ action: member ++ state: absent ++ register: result ++ ++ - name: Host "{{ host1_fqdn }}" member all ip-addresses absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member ++ state: absent ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host1_fqdn }}" all member ip-addresses absent again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ action: member ++ state: absent ++ register: result ++ failed_when: result.changed ++ ++ - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ - name: "{{ host2_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host1_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ - name: "{{ host2_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: result.changed ++ ++ - name: Hosts "{{ host3_fqdn }}" present with same IP addresses ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Hosts "{{ host3_fqdn }}" present with same IP addresses again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host3_fqdn }}" present with differnt IP addresses ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.111' }}" ++ - fe80::20c:29ff:fe02:a1b1 ++ - "{{ ipv4_prefix + '.121' }}" ++ - fe80::20c:29ff:fe02:a1b2 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host3_fqdn }}" present with different IP addresses again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.111' }}" ++ - fe80::20c:29ff:fe02:a1b1 ++ - "{{ ipv4_prefix + '.121' }}" ++ - fe80::20c:29ff:fe02:a1b2 ++ register: result ++ failed_when: result.changed ++ ++ - name: Host "{{ host3_fqdn }}" present with old IP addresses ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: not result.changed ++ ++ - name: Host "{{ host3_fqdn }}" present with old IP addresses again ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ hosts: ++ - name: "{{ host3_fqdn }}" ++ ip_address: ++ - "{{ ipv4_prefix + '.211' }}" ++ - fe80::20c:29ff:fe02:a1b3 ++ - "{{ ipv4_prefix + '.221' }}" ++ - fe80::20c:29ff:fe02:a1b4 ++ register: result ++ failed_when: result.changed ++ ++ - name: Host absent ++ ipahost: ++ ipaadmin_password: MyPassword123 ++ name: ++ - "{{ host1_fqdn }}" ++ - "{{ host2_fqdn }}" ++ - "{{ host3_fqdn }}" ++ update_dns: yes ++ state: absent +From 8f32cb04c1e161e1e3217f10413685a2cc9bf492 Mon Sep 17 00:00:00 2001 +From: Thomas Woerner +Date: Thu, 13 Feb 2020 14:10:38 +0100 +Subject: [PATCH] tests/host/test_host: Fix use of wrong host in the host5 test + +host1 was used instead of host5 in the repeated host5 test. This lead to an +error with the new IP address handling in ipahost. It was correctly +reporting a change for host1 which resulted in a failed test. +--- + tests/host/test_host.yml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/host/test_host.yml b/tests/host/test_host.yml +index 1a555a1..f3ec11d 100644 +--- a/tests/host/test_host.yml ++++ b/tests/host/test_host.yml +@@ -129,7 +129,7 @@ + - name: Host "{{ host5_fqdn }}" present again + ipahost: + ipaadmin_password: MyPassword123 +- name: "{{ host1_fqdn }}" ++ name: "{{ host5_fqdn }}" + ip_address: "{{ ipv4_prefix + '.205' }}" + update_dns: yes + reverse: no diff --git a/SPECS/ansible-freeipa.spec b/SPECS/ansible-freeipa.spec new file mode 100644 index 0000000..0b748d4 --- /dev/null +++ b/SPECS/ansible-freeipa.spec @@ -0,0 +1,326 @@ +# Turn off automatic python byte compilation because these are Ansible +# roles and the files are transferred to the node and compiled there with +# the python verison used in the node +%define __brp_python_bytecompile %{nil} + +Summary: Roles and playbooks to deploy FreeIPA servers, replicas and clients +Name: ansible-freeipa +Version: 0.1.8 +Release: 3%{?dist} +URL: https://github.com/freeipa/ansible-freeipa +License: GPLv3+ +Source: https://github.com/freeipa/ansible-freeipa/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz +Patch1: ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch +Patch2: ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch +Patch3: ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch +Patch4: ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch +Patch5: ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch +Patch6: ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch +Patch7: ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch +Patch8: ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch +Patch9: ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch +Patch10: ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch +BuildArch: noarch + +#Requires: ansible + +%description +ansible-freeipa provides Ansible roles and playbooks to install and uninstall +FreeIPA servers, replicas and clients also modules for management. + +Note: The ansible playbooks and roles require a configured ansible environment +where the ansible nodes are reachable and are properly set up to have an IP +address and a working package manager. + +Features + +- Server, replica and client deployment +- Cluster deployments: Server, replicas and clients in one playbook +- One-time-password (OTP) support for client installation +- Repair mode for clients +- Modules for group management +- Modules for hbacrule management +- Modules for hbacsvc management +- Modules for hbacsvcgroup management +- Modules for host management +- Modules for hostgroup management +- Modules for pwpolicy management +- Modules for sudocmd management +- Modules for sudocmdgroup management +- Modules for sudorule management +- Modules for topology management +- Modules for user management + +Supported FreeIPA Versions + +FreeIPA versions 4.6 and up are supported by all roles. + +The client role supports versions 4.4 and up, the server role is working with +versions 4.5 and up, the replica role is currently only working with versions +4.6 and up. + +Supported Distributions + +- RHEL/CentOS 7.4+ +- Fedora 26+ +- Ubuntu +- Debian 10+ (ipaclient only, no server or replica!) + +Requirements + + Controller + - Ansible version: 2.8+ (ansible-freeipa is an Ansible Collection) + - /usr/bin/kinit is required on the controller if a one time password (OTP) + is used + - python3-gssapi is required on the controller if a one time password (OTP) + is used with keytab to install the client. + + Node + - Supported FreeIPA version (see above) + - Supported distribution (needed for package installation only, see above) + +Limitations + +External CA support is not supported or working. The currently needed two step +process is an issue for the processing in the role. The configuration of the +server is partly done already and needs to be continued after the CSR has been +handled. This is for example breaking the deployment of a server with replicas +or clients in one playbook. + +%prep +%setup -q +# Do not create backup files with patches +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 +# Fix python modules and module utils: +# - Remove shebang +# - Remove execute flag +for i in roles/ipa*/library/*.py roles/ipa*/module_utils/*.py plugins/*/*.py; do + sed -i '/\/usr\/bin\/python*/d' $i + chmod a-x $i +done +# Add execute flag to py3test.py scripts +chmod a+x roles/ipa*/files/py3test.py + +%build + +%install +install -m 755 -d %{buildroot}%{_datadir}/ansible/roles/ +cp -rp roles/ipaserver %{buildroot}%{_datadir}/ansible/roles/ +cp -rp roles/ipaserver/README.md README-server.md +cp -rp roles/ipareplica %{buildroot}%{_datadir}/ansible/roles/ +cp -rp roles/ipareplica/README.md README-replica.md +cp -rp roles/ipaclient %{buildroot}%{_datadir}/ansible/roles/ +cp -rp roles/ipaclient/README.md README-client.md +install -m 755 -d %{buildroot}%{_datadir}/ansible/plugins/ +cp -rp plugins/* %{buildroot}%{_datadir}/ansible/plugins/ + +%files +%license COPYING +%{_datadir}/ansible/roles/ipaserver +%{_datadir}/ansible/roles/ipareplica +%{_datadir}/ansible/roles/ipaclient +%{_datadir}/ansible/plugins/module_utils +%{_datadir}/ansible/plugins/modules +%doc README.md +%doc README-*.md +%doc playbooks + +%changelog +* Thu Feb 20 2020 Thomas Woerner - 0.1.8-3 +- ipahost: Do not fail on missing DNS or zone when no IP address given + Resolves: RHBZ#1804838 + +* Fri Feb 14 2020 Thomas Woerner - 0.1.8-2 +- Updated RPM description for ansible-freeipa 0.1.8 + Related: RHBZ#1748986 +- ipahost: Fix choices of auth_ind parameter, allow to reset parameter + Resolves: RHBZ#1783992 +- ipauser: Allow reset of userauthtype, do not depend on first,last for mod + Resolves: RHBZ#1784474 +- ipahost: Enhanced failure msg for member params used without member action + Resolves: RHBZ#1783948 +- Add missing attributes to ipasudorule + Resolves: RHBZ#1788168 + Resolves: RHBZ#1788035 + Resolves: RHBZ#1788024 +- ipapwpolicy: Use global_policy if name is not set + Resolves: RHBZ#1797532 +- ipahbacrule: Fix handing of members with action hbacrule + Resolves: RHBZ#1787996 +- ansible_freeipa_module: Fix comparison of bool parameters in compare_args_isa + Resolves: RHBZ#1784514 +- ipahost: Add support for several IP addresses and also to change them + Resolves: RHBZ#1783979 + Resolves: RHBZ#1783976 +- ipahost: Fail on action member for new hosts, fix dnsrecord_add reverse flag + Resolves: RHBZ#1803026 + +* Sat Dec 14 2019 Thomas Woerner - 0.1.8-1 +- Update to version 0.1.8 (bug fix release) + - roles/ipaclient/README.md: Add information about ipaclient_otp + - Install and enable firewalld if it is configured for ipaserver and + ipareplica roles + - ipaserver_test: Do not use zone_overlap_check for domain name validation + - Allow execution of API commands that do not require a name + - Update README-host: Drop options from allow_*keytab parameters docs + - ipauser: Extend email addresses with default email domain if no domain is + given + Resolves: RHBZ#1747413 + Related: RHBZ#1748986 + +* Mon Dec 2 2019 Thomas Woerner - 0.1.7-1 +- Update to version 0.1.7 + - Add debian support for ipaclient + - Added support for predefining client OTP using ipaclient_otp + - ipatopologysegment: Store suffix for commands in command list + - ipatopologysegment: Fail for missing entry with reinitialized + - Utils scripts: ansible-ipa-[server,replica,client]-install + - ipaserver_test,ipareplica_prepare: Do not return _pkcs12_file settings + - ansible_freeipa_module: Add support for GSSAPI + - ansible_ipa_client: Drop import of configure_nsswitch_database + - New host management module + - New hostgroup management module + - ipagroup: Remove unused member_[present,absent] states + - external-ca tests: Fix typo in inventory files + - tests/external-signed-ca tests: Fix external-ca.sh to use proper serials + - ipagroup: Rework to use same mechanisms as ipahostgroup module + - ansible_freeipa_module: api_command should not have extra try clause + - ansible_freeipa_module: compare_args_ipa needs to compare lists orderless + - ansible_freeipa_module: New function api_check_param + - ansible_freeipa_module: New functions module_params_get and _afm_convert + - ansible_freeipa_module: Add missing to_text import for _afm_convert + - ansible_freeipa_module: Convert tuple to list in compare_args_ipa + - ansible_freeipa_module: New function api_get_realm + - ipauser: User module extension + - New sudocmd management module + - New sudocmdgroup management module + - ansible_freeipa_module: Convert int to string in compare_args_ipa + - New pwpolicy management module + - New hbacsvc (HBAC Service) management module + - New hbacsvcgroup (HBAC Service Group) management module + - ipagroup: Properly support IPA versions 4.6 and RHEL-7 + - ipagroup: Fix changed flag, new test cases + - ipauser: Add info about version limitation of passwordexpiration + - New hbacrule (HBAC Rule) management module + - ipahostgroup: Fix changed flag, support IPA 4.6 on RHEL-7, new test cases + - New sudorule (Sudo Rule) management module + - ipauser: Support 'sn' alias of 'last' for surname + - Update galaxy.yml: Update description, drop empty dependencies + - Update ipauser.py: Fix typo in users.name description + - ipaclient: Fix misspelled sssd options + - ipauser: Return generated random password + - ipahost: Return generated random password + - Added context configuration to api_connect + - ansible_freeipa_module: Better support for KRB5CCNAME environment variable + - ipa[server,replica,client]: Add support for CentOS-8 + - ipahost: Extension to be able handle several hosts and all settings + - Flake8 fixes + - Documentation updates + - Cleanup + Resolves: RHBZ#1748986 + +* Fri Sep 6 2019 Thomas Woerner - 0.1.6-4 +- ansible_ipa_client: Drop import of configure_nsswitch_database + (RHBZ#1748905) + +* Wed Jul 31 2019 Thomas Woerner - 0.1.6-3 +- ipatopologysegment: Store suffix for commands in command list (RHBZ#1733547) +- ipatopologysegment: Fail for missing entry with reinitialized (RHBZ#1733559) + +* Tue Jul 23 2019 Thomas Woerner - 0.1.6-2 +- Drop dirserv_cert_files key from utils/gen_module_docs.py for covscan + +* Tue Jul 23 2019 Thomas Woerner - 0.1.6-1 +- update to version 0.1.6 + - Lots of documentation updates in READMEs and modules + - library/ipaclient_get_otp: Enable force mode for host_add call (fixes #74) + - Flake8 and pylint reated fixes + - Fixed wrong path to CheckedIPAddress class in ipareplica_test + - Remove unused ipaserver/library/ipaserver.py + - No not use wildcard imports for modules + - ipareplica: Add support for pki_config_override + - ipareplica: Initialize dns.ip_addresses and dns.reverse_zones for dns setup + - ipareplica_prepare: Properly initialize pin and cert_name variables + - ipareplica: Fail with proper error messages + - ipaserver: Properly set settings related to pkcs12 files + - ipaclient: RawConfigParser is not always provided by six.moves.configparser + - ipaclient_setup_nss: paths.GETENT is not available before + freeipa-4.6.90.pre1 + - ipaserver_test: Initialize value from options.zonemgr + - ipareplica_setup_custodia: create_replica only available in newer releases + - ipaclient: Fix typo in dnsok assignment for ipaclient_setup_nss + - ipa[server,replica]: Set _packages_adtrust for Ubuntu + - New build script for galaxy release + - New utils script to update module docs +- Changes from ansible-freeipa-0.1.5 + - Support for IPA 4.8.0 + - New user management module + - New group management module + - ipaserver: Support external signed CA + - RHEL-8 specific vars files to be able to install needed modules + automatically + - ipareplica: Fixes for certmonger and kra setup + - New tests folder + - OTP related updates to README files + +* Thu Jul 4 2019 Thomas Woerner - 0.1.4-2 +- ansible_ipa_client: Always set options.unattended (RHBZ#1726645) +- ipaserver_prepare: Properly report error, do show trace back (RHBZ#1726668) +- ipa[server,replica,client]: RHEL-8 specific vars files (RHBZ#1727095) +- ipatopology modules: Use ipaadmin_ prefix for principal and password + (RHBZ#1727101) + +* Mon Jun 17 2019 Thomas Woerner - 0.1.4-1 +- update to version 0.1.4 + - ipatopologysegment: Use commands, not command + +* Mon Jun 17 2019 Thomas Woerner - 0.1.3-1 +- update to version 0.1.3 + - ipaclient_test: Fix Python2 decode use with Python3 + - Fixed: #86 (AttributeError: 'str' object has no attribute 'decode') + - ipaclient_get_otp: Remove ansible_python_interpreter handling + - ipaclient: Use omit (None) for password, keytab, no string length checks + - ipaclient_join: Support to use ipaadmin_keytab without ipaclient_use_otp + - ipaclient: Report error message if ipaclient_get_otp failed + - Fixes #17 Improve how tasks manage package installation + - ipareplica: The dm password is not needed for ipareplica_master_password + - ipareplica: Use ipareplica_server if set + - ipatopologysegment: Allow domain+ca suffix, new state: checked + - Documentation updates + - Cleanups + +* Tue Jun 11 2019 Thomas Woerner - 0.1.2-3 +- bump release for functional test + +* Tue Jun 11 2019 Thomas Woerner - 0.1.2-2 +- bump release for functional test + +* Fri Jun 7 2019 Thomas Woerner - 0.1.2-1 +- update to version 0.1.2 + - Now a new Ansible Collection + - Fix gssapi requirement for OTP: It is only needed if keytab is used with + OTP now. + - Fix wrong ansible argument types + - Do not fail on textwrap for replica deployments with CA + - Ansible lint and galaxy fixes + - Disable automatic removal of replication agreements in uninstall + - Enable freeipa-trust service if adtrust is enabled + - Add support for hidden replica + - New topology managament modules + - Add support for pki_config_override + - Fix host name setup in server deployment + - Fix errors when ipaservers variable is not set + - Fix ipaclient install role length typo + - Cleanups + +* Mon May 6 2019 Thomas Woerner - 0.1.1-1 +- Initial package