diff --git a/README-sudorule.md b/README-sudorule.md index 089adab21eddfd73f8e26fdc23a5d22ac3400844..bd23c178bd6c453c55f03c82beee0db895b52751 100644 --- a/README-sudorule.md +++ b/README-sudorule.md @@ -129,6 +129,7 @@ Variable | Description | Required `nomembers` | Suppress processing of membership attributes. (bool) | no `host` | List of host name strings assigned to this sudorule. | no `hostgroup` | List of host group name strings assigned to this sudorule. | no +`hostmask` | List of host masks of allowed hosts | no `user` | List of user name strings assigned to this sudorule. | no `group` | List of user group name strings assigned to this sudorule. | no `allow_sudocmd` | List of sudocmd name strings assigned to the allow group of this sudorule. | no diff --git a/playbooks/sudorule/ensure-sudorule-hostmask-member-is-absent.yml b/playbooks/sudorule/ensure-sudorule-hostmask-member-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..32f8bc36c9897b29d83c465bbb61e1a0ebb646db --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-hostmask-member-is-absent.yml @@ -0,0 +1,14 @@ +--- +- name: Playbook to manage sudorule + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure hostmask network is absent in sudorule + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule1 + hostmask: 192.168.122.37/24 + action: member + state: absent diff --git a/playbooks/sudorule/ensure-sudorule-hostmask-member-is-present.yml b/playbooks/sudorule/ensure-sudorule-hostmask-member-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..51cb968d101b999d8bd8c2f409dfb0777feda3d3 --- /dev/null +++ b/playbooks/sudorule/ensure-sudorule-hostmask-member-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Playbook to manage sudorule + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure hostmask network is present in sudorule + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule1 + hostmask: 192.168.122.37/24 + action: member diff --git a/plugins/modules/ipasudorule.py b/plugins/modules/ipasudorule.py index bd00ae1de273394f64c14a8385420399dec2ba15..2be49c224450eb0c0bac8b090cb2c156e2ad1913 100644 --- a/plugins/modules/ipasudorule.py +++ b/plugins/modules/ipasudorule.py @@ -143,6 +143,11 @@ options: required: false type: list elements: str + hostmask: + description: Host masks of allowed hosts. + required: false + type: list + elements: str action: description: Work on sudorule or member level type: str @@ -202,6 +207,15 @@ EXAMPLES = """ hostcategory: all state: enabled +# Ensure sudo rule applies for hosts with hostmasks +- ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule1 + hostmask: + - 192.168.122.1/24 + - 192.168.120.1/24 + action: member + # Ensure Sudo Rule tesrule1 is absent - ipasudorule: ipaadmin_password: SomeADMINpassword @@ -214,7 +228,7 @@ RETURN = """ from ansible.module_utils.ansible_freeipa_module import \ IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \ - gen_intersection_list, api_get_domain, ensure_fqdn + gen_intersection_list, api_get_domain, ensure_fqdn, netaddr, to_text def find_sudorule(module, name): @@ -275,6 +289,8 @@ def main(): default=None), hostgroup=dict(required=False, type='list', elements="str", default=None), + hostmask=dict(required=False, type='list', elements="str", + default=None), user=dict(required=False, type='list', elements="str", default=None), group=dict(required=False, type='list', elements="str", @@ -334,6 +350,7 @@ def main(): nomembers = ansible_module.params_get("nomembers") # noqa host = ansible_module.params_get("host") hostgroup = ansible_module.params_get_lowercase("hostgroup") + hostmask = ansible_module.params_get("hostmask") user = ansible_module.params_get_lowercase("user") group = ansible_module.params_get_lowercase("group") allow_sudocmd = ansible_module.params_get('allow_sudocmd') @@ -351,6 +368,10 @@ def main(): # state state = ansible_module.params_get("state") + # ensure hostmasks are network cidr + if hostmask is not None: + hostmask = [to_text(netaddr.IPNetwork(x).cidr) for x in hostmask] + # Check parameters invalid = [] @@ -382,7 +403,7 @@ def main(): "cmdcategory", "runasusercategory", "runasgroupcategory", "nomembers", "order"] if action == "sudorule": - invalid.extend(["host", "hostgroup", "user", "group", + invalid.extend(["host", "hostgroup", "hostmask", "user", "group", "runasuser", "runasgroup", "allow_sudocmd", "allow_sudocmdgroup", "deny_sudocmd", "deny_sudocmdgroup", "sudooption"]) @@ -396,7 +417,7 @@ def main(): "disabled") invalid = ["description", "usercategory", "hostcategory", "cmdcategory", "runasusercategory", "runasgroupcategory", - "nomembers", "nomembers", "host", "hostgroup", + "nomembers", "nomembers", "host", "hostgroup", "hostmask", "user", "group", "allow_sudocmd", "allow_sudocmdgroup", "deny_sudocmd", "deny_sudocmdgroup", "runasuser", "runasgroup", "order", "sudooption"] @@ -425,6 +446,7 @@ def main(): user_add, user_del = [], [] group_add, group_del = [], [] hostgroup_add, hostgroup_del = [], [] + hostmask_add, hostmask_del = [], [] allow_cmd_add, allow_cmd_del = [], [] allow_cmdgroup_add, allow_cmdgroup_del = [], [] deny_cmd_add, deny_cmd_del = [], [] @@ -490,6 +512,9 @@ def main(): hostgroup_add, hostgroup_del = gen_add_del_lists( hostgroup, res_find.get('memberhost_hostgroup', [])) + hostmask_add, hostmask_del = gen_add_del_lists( + hostmask, res_find.get('hostmask', [])) + user_add, user_del = gen_add_del_lists( user, res_find.get('memberuser_user', [])) @@ -556,6 +581,9 @@ def main(): if hostgroup is not None: hostgroup_add = gen_add_list( hostgroup, res_find.get("memberhost_hostgroup")) + if hostmask is not None: + hostmask_add = gen_add_list( + hostmask, res_find.get("hostmask")) if user is not None: user_add = gen_add_list( user, res_find.get("memberuser_user")) @@ -628,6 +656,10 @@ def main(): hostgroup_del = gen_intersection_list( hostgroup, res_find.get("memberhost_hostgroup")) + if hostmask is not None: + hostmask_del = gen_intersection_list( + hostmask, res_find.get("hostmask")) + if user is not None: user_del = gen_intersection_list( user, res_find.get("memberuser_user")) @@ -719,18 +751,19 @@ def main(): # Manage members. # Manage hosts and hostgroups - if host_add or hostgroup_add: - commands.append([name, "sudorule_add_host", - { - "host": host_add, - "hostgroup": hostgroup_add, - }]) - if host_del or hostgroup_del: - commands.append([name, "sudorule_remove_host", - { - "host": host_del, - "hostgroup": hostgroup_del, - }]) + if any([host_add, hostgroup_add, hostmask_add]): + params = {"host": host_add, "hostgroup": hostgroup_add} + # An empty Hostmask cannot be used, or IPA API will fail. + if hostmask_add: + params["hostmask"] = hostmask_add + commands.append([name, "sudorule_add_host", params]) + + if any([host_del, hostgroup_del, hostmask_del]): + params = {"host": host_del, "hostgroup": hostgroup_del} + # An empty Hostmask cannot be used, or IPA API will fail. + if hostmask_del: + params["hostmask"] = hostmask_del + commands.append([name, "sudorule_remove_host", params]) # Manage users and groups if user_add or group_add: diff --git a/tests/sudorule/test_sudorule.yml b/tests/sudorule/test_sudorule.yml index 0ba8d8fe3b9ab2d3ad92bb624d8d7a0f9146f925..622438cd3899ee7f860f1a768f3e04ee9e3461d9 100644 --- a/tests/sudorule/test_sudorule.yml +++ b/tests/sudorule/test_sudorule.yml @@ -83,6 +83,7 @@ ipaapi_context: "{{ ipa_context | default(omit) }}" name: - test_upstream_issue_664 + - testrule_hostmask - testrule1 - allusers - allhosts @@ -1005,6 +1006,116 @@ register: result failed_when: not result.changed or result.failed + - name: Ensure sudorule is present with hostmask + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule_hostmask + hostmask: + - 192.168.122.1/24 + - 192.168.120.1/24 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure sudorule is present with hostmask, again + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule_hostmask + hostmask: + - 192.168.122.1/24 + - 192.168.120.1/24 + register: result + failed_when: result.changed or result.failed + + - name: Ensure sudorule hostmask member is absent + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule_hostmask + hostmask: 192.168.122.0/24 + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure sudorule hostmask member is absent, again + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule_hostmask + hostmask: 192.168.122.0/24 + action: member + state: absent + register: result + failed_when: result.changed or result.failed + + - name: Ensure sudorule is present with another hostmask + ipasudorule: + ipaadmin_password: SomeADMINpassword + name: testrule_hostmask + hostmask: 192.168.122.0/24 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure sudorule is present with another hostmask, again + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.122.0/24 + register: result + failed_when: result.changed + + - name: Check sudorule with hostmask is absent + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.120.0/24 + action: member + register: result + check_mode: yes + failed_when: not result.changed or result.failed + + - name: Ensure sudorule hostmask member is present + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.120.0/24 + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure sudorule hostmask member is present, again + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.120.0/24 + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure sudorule hostmask member is absent + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.120.0/24 + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure sudorule hostmask member is absent, again + ipasudorule: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: testrule_hostmask + hostmask: 192.168.120.0/24 + action: member + state: absent + register: result + failed_when: result.changed or result.failed + # cleanup - name: Ensure sudocmdgroup is absent ipasudocmdgroup: @@ -1013,6 +1124,7 @@ name: - test_sudorule - test_sudorule2 + - testrule_hostmask state: absent - name: Ensure sudocmds are absent