Skip to content
Snippets Groups Projects
Commit d859ddc7 authored by Rafael Guterres Jeffman's avatar Rafael Guterres Jeffman
Browse files

sudorule: Add support for 'hostmask' parameter

The hostmask parameter allows matching a sudorule against a network
address, and was missing from ipasudorule module.

Documentation and tests were updated to reflect changes.

Two new example playbooks are available:

    playbooks/sudorule/ensure-sudorule-hostmask-member-is-absent.yml
    playbooks/sudorule/ensure-sudorule-hostmask-member-is-present.yml
parent 9423eb81
Branches
Tags
No related merge requests found
...@@ -129,6 +129,7 @@ Variable | Description | Required ...@@ -129,6 +129,7 @@ Variable | Description | Required
`nomembers` | Suppress processing of membership attributes. (bool) | no `nomembers` | Suppress processing of membership attributes. (bool) | no
`host` | List of host name strings assigned to this sudorule. | no `host` | List of host name strings assigned to this sudorule. | no
`hostgroup` | List of host group 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 `user` | List of user name strings assigned to this sudorule. | no
`group` | List of user group 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 `allow_sudocmd` | List of sudocmd name strings assigned to the allow group of this sudorule. | no
......
---
- 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
---
- 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
...@@ -143,6 +143,11 @@ options: ...@@ -143,6 +143,11 @@ options:
required: false required: false
type: list type: list
elements: str elements: str
hostmask:
description: Host masks of allowed hosts.
required: false
type: list
elements: str
action: action:
description: Work on sudorule or member level description: Work on sudorule or member level
type: str type: str
...@@ -202,6 +207,15 @@ EXAMPLES = """ ...@@ -202,6 +207,15 @@ EXAMPLES = """
hostcategory: all hostcategory: all
state: enabled 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 # Ensure Sudo Rule tesrule1 is absent
- ipasudorule: - ipasudorule:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
...@@ -214,7 +228,7 @@ RETURN = """ ...@@ -214,7 +228,7 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import \ from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \ 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): def find_sudorule(module, name):
...@@ -275,6 +289,8 @@ def main(): ...@@ -275,6 +289,8 @@ def main():
default=None), default=None),
hostgroup=dict(required=False, type='list', elements="str", hostgroup=dict(required=False, type='list', elements="str",
default=None), default=None),
hostmask=dict(required=False, type='list', elements="str",
default=None),
user=dict(required=False, type='list', elements="str", user=dict(required=False, type='list', elements="str",
default=None), default=None),
group=dict(required=False, type='list', elements="str", group=dict(required=False, type='list', elements="str",
...@@ -334,6 +350,7 @@ def main(): ...@@ -334,6 +350,7 @@ def main():
nomembers = ansible_module.params_get("nomembers") # noqa nomembers = ansible_module.params_get("nomembers") # noqa
host = ansible_module.params_get("host") host = ansible_module.params_get("host")
hostgroup = ansible_module.params_get_lowercase("hostgroup") hostgroup = ansible_module.params_get_lowercase("hostgroup")
hostmask = ansible_module.params_get("hostmask")
user = ansible_module.params_get_lowercase("user") user = ansible_module.params_get_lowercase("user")
group = ansible_module.params_get_lowercase("group") group = ansible_module.params_get_lowercase("group")
allow_sudocmd = ansible_module.params_get('allow_sudocmd') allow_sudocmd = ansible_module.params_get('allow_sudocmd')
...@@ -351,6 +368,10 @@ def main(): ...@@ -351,6 +368,10 @@ def main():
# state # state
state = ansible_module.params_get("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 # Check parameters
invalid = [] invalid = []
...@@ -382,7 +403,7 @@ def main(): ...@@ -382,7 +403,7 @@ def main():
"cmdcategory", "runasusercategory", "cmdcategory", "runasusercategory",
"runasgroupcategory", "nomembers", "order"] "runasgroupcategory", "nomembers", "order"]
if action == "sudorule": if action == "sudorule":
invalid.extend(["host", "hostgroup", "user", "group", invalid.extend(["host", "hostgroup", "hostmask", "user", "group",
"runasuser", "runasgroup", "allow_sudocmd", "runasuser", "runasgroup", "allow_sudocmd",
"allow_sudocmdgroup", "deny_sudocmd", "allow_sudocmdgroup", "deny_sudocmd",
"deny_sudocmdgroup", "sudooption"]) "deny_sudocmdgroup", "sudooption"])
...@@ -396,7 +417,7 @@ def main(): ...@@ -396,7 +417,7 @@ def main():
"disabled") "disabled")
invalid = ["description", "usercategory", "hostcategory", invalid = ["description", "usercategory", "hostcategory",
"cmdcategory", "runasusercategory", "runasgroupcategory", "cmdcategory", "runasusercategory", "runasgroupcategory",
"nomembers", "nomembers", "host", "hostgroup", "nomembers", "nomembers", "host", "hostgroup", "hostmask",
"user", "group", "allow_sudocmd", "allow_sudocmdgroup", "user", "group", "allow_sudocmd", "allow_sudocmdgroup",
"deny_sudocmd", "deny_sudocmdgroup", "runasuser", "deny_sudocmd", "deny_sudocmdgroup", "runasuser",
"runasgroup", "order", "sudooption"] "runasgroup", "order", "sudooption"]
...@@ -425,6 +446,7 @@ def main(): ...@@ -425,6 +446,7 @@ def main():
user_add, user_del = [], [] user_add, user_del = [], []
group_add, group_del = [], [] group_add, group_del = [], []
hostgroup_add, hostgroup_del = [], [] hostgroup_add, hostgroup_del = [], []
hostmask_add, hostmask_del = [], []
allow_cmd_add, allow_cmd_del = [], [] allow_cmd_add, allow_cmd_del = [], []
allow_cmdgroup_add, allow_cmdgroup_del = [], [] allow_cmdgroup_add, allow_cmdgroup_del = [], []
deny_cmd_add, deny_cmd_del = [], [] deny_cmd_add, deny_cmd_del = [], []
...@@ -490,6 +512,9 @@ def main(): ...@@ -490,6 +512,9 @@ def main():
hostgroup_add, hostgroup_del = gen_add_del_lists( hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get('memberhost_hostgroup', [])) 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_add, user_del = gen_add_del_lists(
user, res_find.get('memberuser_user', [])) user, res_find.get('memberuser_user', []))
...@@ -556,6 +581,9 @@ def main(): ...@@ -556,6 +581,9 @@ def main():
if hostgroup is not None: if hostgroup is not None:
hostgroup_add = gen_add_list( hostgroup_add = gen_add_list(
hostgroup, res_find.get("memberhost_hostgroup")) 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: if user is not None:
user_add = gen_add_list( user_add = gen_add_list(
user, res_find.get("memberuser_user")) user, res_find.get("memberuser_user"))
...@@ -628,6 +656,10 @@ def main(): ...@@ -628,6 +656,10 @@ def main():
hostgroup_del = gen_intersection_list( hostgroup_del = gen_intersection_list(
hostgroup, res_find.get("memberhost_hostgroup")) 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: if user is not None:
user_del = gen_intersection_list( user_del = gen_intersection_list(
user, res_find.get("memberuser_user")) user, res_find.get("memberuser_user"))
...@@ -719,18 +751,19 @@ def main(): ...@@ -719,18 +751,19 @@ def main():
# Manage members. # Manage members.
# Manage hosts and hostgroups # Manage hosts and hostgroups
if host_add or hostgroup_add: if any([host_add, hostgroup_add, hostmask_add]):
commands.append([name, "sudorule_add_host", params = {"host": host_add, "hostgroup": hostgroup_add}
{ # An empty Hostmask cannot be used, or IPA API will fail.
"host": host_add, if hostmask_add:
"hostgroup": hostgroup_add, params["hostmask"] = hostmask_add
}]) commands.append([name, "sudorule_add_host", params])
if host_del or hostgroup_del:
commands.append([name, "sudorule_remove_host", if any([host_del, hostgroup_del, hostmask_del]):
{ params = {"host": host_del, "hostgroup": hostgroup_del}
"host": host_del, # An empty Hostmask cannot be used, or IPA API will fail.
"hostgroup": hostgroup_del, if hostmask_del:
}]) params["hostmask"] = hostmask_del
commands.append([name, "sudorule_remove_host", params])
# Manage users and groups # Manage users and groups
if user_add or group_add: if user_add or group_add:
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
ipaapi_context: "{{ ipa_context | default(omit) }}" ipaapi_context: "{{ ipa_context | default(omit) }}"
name: name:
- test_upstream_issue_664 - test_upstream_issue_664
- testrule_hostmask
- testrule1 - testrule1
- allusers - allusers
- allhosts - allhosts
...@@ -1005,6 +1006,116 @@ ...@@ -1005,6 +1006,116 @@
register: result register: result
failed_when: not result.changed or result.failed 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 # cleanup
- name: Ensure sudocmdgroup is absent - name: Ensure sudocmdgroup is absent
ipasudocmdgroup: ipasudocmdgroup:
...@@ -1013,6 +1124,7 @@ ...@@ -1013,6 +1124,7 @@
name: name:
- test_sudorule - test_sudorule
- test_sudorule2 - test_sudorule2
- testrule_hostmask
state: absent state: absent
- name: Ensure sudocmds are absent - name: Ensure sudocmds are absent
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment