Files
ansible-freeipa/plugins/modules/ipasudorule.py
Rafael Guterres Jeffman 4fa0621156 ipasudorule: Add support for batch mode and multiple sudorules
Currently, ipasudorule must add or modify a single sudorule at a time,
incurring in more load in the server if there are many rules to be
processed.

This patch adds suport for adding multiple sudorules in one playbook
task by using the parameter 'sudorules' and defining a list of sudorules
configurations to be ensured.

As multiple sudorules will be processed, the patch also enables batch
mode processing of sudorules, trying to reduce the load on the server.

Test 'tests/sudorule/test_sudorule_client_context.yml' was modified to
include tasks with 'sudorules' to be executed both on the server or on
the client context.

New tests were added to the sudorule test suite:

    tests/sudorule/test_sudorules.yml
    tests/sudorule/test_sudorules_member_case_insensitive.yml
2024-11-18 11:59:21 -03:00

1082 lines
41 KiB
Python

# -*- coding: utf-8 -*-
# Authors:
# Rafael Guterres Jeffman <rjeffman@redhat.com>
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2019-2022 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
module: ipasudorule
short_description: Manage FreeIPA sudo rules
description: Manage FreeIPA sudo rules
extends_documentation_fragment:
- ipamodule_base_docs
options:
name:
description: The sudorule name
type: list
elements: str
required: false
aliases: ["cn"]
sudorules:
description: The list of sudorule dicts.
type: list
elements: dict
suboptions:
name:
description: The sudorule name
type: list
elements: str
required: true
aliases: ["cn"]
description:
description: The sudorule description
type: str
required: false
user:
description: List of users assigned to the sudo rule.
type: list
elements: str
required: false
usercategory:
description: User category the sudo rule applies to
type: str
required: false
choices: ["all", ""]
aliases: ["usercat"]
group:
description: List of user groups assigned to the sudo rule.
type: list
elements: str
required: false
runasgroupcategory:
description: RunAs Group category applied to the sudo rule.
type: str
required: false
choices: ["all", ""]
aliases: ["runasgroupcat"]
runasusercategory:
description: RunAs User category applied to the sudorule.
type: str
required: false
choices: ["all", ""]
aliases: ["runasusercat"]
nomembers:
description: Suppress processing of membership attributes
required: false
type: bool
host:
description: List of host names assigned to this sudorule.
required: false
type: list
elements: str
hostgroup:
description: List of host groups assigned to this sudorule.
required: false
type: list
elements: str
hostcategory:
description: Host category the sudo rule applies to.
type: str
required: false
choices: ["all", ""]
aliases: ["hostcat"]
allow_sudocmd:
description: List of allowed sudocmds assigned to this sudorule.
required: false
type: list
elements: str
allow_sudocmdgroup:
description: List of allowed sudocmd groups assigned to this sudorule.
required: false
type: list
elements: str
deny_sudocmd:
description: List of denied sudocmds assigned to this sudorule.
required: false
type: list
elements: str
deny_sudocmdgroup:
description: List of denied sudocmd groups assigned to this sudorule.
required: false
type: list
elements: str
cmdcategory:
description: Command category the sudo rule applies to
type: str
required: false
choices: ["all", ""]
aliases: ["cmdcat"]
order:
description: Order to apply this rule.
required: false
type: int
aliases: ["sudoorder"]
sudooption:
description: List of sudo options.
required: false
type: list
elements: str
aliases: ["options"]
runasuser:
description: List of users for Sudo to execute as.
required: false
type: list
elements: str
runasuser_group:
description: List of groups for Sudo to execute as.
required: false
type: list
elements: str
runasgroup:
description: List of groups for Sudo to execute as.
required: false
type: list
elements: str
hostmask:
description: Host masks of allowed hosts.
required: false
type: list
elements: str
description:
description: The sudorule description
type: str
required: false
user:
description: List of users assigned to the sudo rule.
type: list
elements: str
required: false
usercategory:
description: User category the sudo rule applies to
type: str
required: false
choices: ["all", ""]
aliases: ["usercat"]
group:
description: List of user groups assigned to the sudo rule.
type: list
elements: str
required: false
runasgroupcategory:
description: RunAs Group category applied to the sudo rule.
type: str
required: false
choices: ["all", ""]
aliases: ["runasgroupcat"]
runasusercategory:
description: RunAs User category applied to the sudorule.
type: str
required: false
choices: ["all", ""]
aliases: ["runasusercat"]
nomembers:
description: Suppress processing of membership attributes
required: false
type: bool
host:
description: List of host names assigned to this sudorule.
required: false
type: list
elements: str
hostgroup:
description: List of host groups assigned to this sudorule.
required: false
type: list
elements: str
hostcategory:
description: Host category the sudo rule applies to.
type: str
required: false
choices: ["all", ""]
aliases: ["hostcat"]
allow_sudocmd:
description: List of allowed sudocmds assigned to this sudorule.
required: false
type: list
elements: str
allow_sudocmdgroup:
description: List of allowed sudocmd groups assigned to this sudorule.
required: false
type: list
elements: str
deny_sudocmd:
description: List of denied sudocmds assigned to this sudorule.
required: false
type: list
elements: str
deny_sudocmdgroup:
description: List of denied sudocmd groups assigned to this sudorule.
required: false
type: list
elements: str
cmdcategory:
description: Command category the sudo rule applies to
type: str
required: false
choices: ["all", ""]
aliases: ["cmdcat"]
order:
description: Order to apply this rule.
required: false
type: int
aliases: ["sudoorder"]
sudooption:
description: List of sudo options.
required: false
type: list
elements: str
aliases: ["options"]
runasuser:
description: List of users for Sudo to execute as.
required: false
type: list
elements: str
runasuser_group:
description: List of groups for Sudo to execute as.
required: false
type: list
elements: str
runasgroup:
description: List of groups for Sudo to execute as.
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
default: sudorule
choices: ["member", "sudorule"]
state:
description: State to ensure
type: str
default: present
choices: ["present", "absent", "enabled", "disabled"]
author:
- Rafael Guterres Jeffman (@rjeffman)
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure Sudo Rule tesrule1 is present
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
# Ensure sudocmd is present in Sudo Rule
- ipasudorule:
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:
ipaadmin_password: SomeADMINpassword
name: testrule1
host: server
action: member
# Ensure hostgroup cluster is present in Sudo Rule
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
hostgroup: cluster
action: member
# Ensure sudo rule for usercategory "all" is enabled
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: allusers
usercategory: all
state: enabled
# Ensure sudo rule for hostcategory "all" is enabled
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: allhosts
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
# Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
# Ensure Sudo Rule tesrule1 is absent
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
state: absent
# Ensure multiple Sudo Rules are present using batch mode.
- ipasudorule:
ipaadmin_password: SomeADMINpassword
sudorules:
- name: testrule1
hostmask:
- 192.168.122.1/24
- name: testrule2
hostcategory: all
"""
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, netaddr, to_text, \
ipalib_errors, convert_param_value_to_lowercase, EntryFactory
def find_sudorule(module, name):
_args = {
"all": True,
}
try:
_result = module.ipa_command("sudorule_show", name, _args)
except ipalib_errors.NotFound:
return None
return _result["result"]
def gen_args(entry):
"""Generate args for sudorule."""
_args = {}
if entry.description is not None:
_args['description'] = entry.description
if entry.usercategory is not None:
_args['usercategory'] = entry.usercategory
if entry.hostcategory is not None:
_args['hostcategory'] = entry.hostcategory
if entry.cmdcategory is not None:
_args['cmdcategory'] = entry.cmdcategory
if entry.runasusercategory is not None:
_args['ipasudorunasusercategory'] = entry.runasusercategory
if entry.runasgroupcategory is not None:
_args['ipasudorunasgroupcategory'] = entry.runasgroupcategory
if entry.order is not None:
_args['sudoorder'] = entry.order
if entry.nomembers is not None:
_args['nomembers'] = entry.nomembers
return _args
def init_ansible_module():
"""Initialize IPAAnsibleModule object for sudorule."""
sudorule_spec = dict(
description=dict(required=False, type="str", default=None),
usercategory=dict(required=False, type="str", default=None,
choices=["all", ""], aliases=['usercat']),
hostcategory=dict(required=False, type="str", default=None,
choices=["all", ""], aliases=['hostcat']),
nomembers=dict(required=False, type='bool', default=None),
host=dict(required=False, type='list', elements="str",
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",
default=None),
allow_sudocmd=dict(required=False, type="list", elements="str",
default=None),
deny_sudocmd=dict(required=False, type="list", elements="str",
default=None),
allow_sudocmdgroup=dict(required=False, type="list",
elements="str", default=None),
deny_sudocmdgroup=dict(required=False, type="list", elements="str",
default=None),
cmdcategory=dict(required=False, type="str", default=None,
choices=["all", ""], aliases=['cmdcat']),
runasusercategory=dict(required=False, type="str", default=None,
choices=["all", ""],
aliases=['runasusercat']),
runasgroupcategory=dict(required=False, type="str", default=None,
choices=["all", ""],
aliases=['runasgroupcat']),
runasuser=dict(required=False, type="list", elements="str",
default=None),
runasgroup=dict(required=False, type="list", elements="str",
default=None),
runasuser_group=dict(required=False, type="list", elements="str",
default=None),
order=dict(type="int", required=False, aliases=['sudoorder']),
sudooption=dict(required=False, type='list', elements="str",
default=None, aliases=["options"]),
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", aliases=["cn"],
required=False),
sudorules=dict(
type="list",
defalut=None,
options=dict(
# name of the sudorule
name=dict(type="str", required=True, aliases=["cn"]),
# sudorule specific parameters
**sudorule_spec
),
elements='dict',
required=False,
),
# action
action=dict(type="str", default="sudorule",
choices=["member", "sudorule"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent",
"enabled", "disabled"]),
# Specific parameters for simple use case
**sudorule_spec
),
mutually_exclusive=[["name", "sudorules"]],
required_one_of=[["name", "sudorules"]],
supports_check_mode=True,
)
ansible_module._ansible_debug = True
return ansible_module
def convert_list_of_hostmask(hostmasks):
"""Ensure all hostmasks is hostmask_list is a CIDR value."""
return [
to_text(netaddr.IPNetwork(mask).cidr)
for mask in (
hostmasks if isinstance(hostmasks, (list, tuple))
else [hostmasks]
)
]
def convert_list_of_hostnames(hostnames):
"""Ensure all hostnames in hostnames are lowercase FQDN."""
return list(
set(
ensure_fqdn(value.lower(), api_get_domain())
for value in (
hostnames if isinstance(hostnames, (list, tuple))
else [hostnames]
)
)
)
def validate_entry(module, entry, state, action):
"""Ensure entry object is valid."""
if state == "present" and action == "sudorule":
# Ensure the entry is valid for state:present, action:sudorule.
if entry.hostcategory == 'all' and any([entry.host, entry.hostgroup]):
module.fail_json(
msg="Hosts cannot be added when host category='all'"
)
if entry.usercategory == 'all' and any([entry.user, entry.group]):
module.fail_json(
msg="Users cannot be added when user category='all'"
)
if entry.cmdcategory == 'all' \
and any([entry.allow_sudocmd, entry.allow_sudocmdgroup]):
module.fail_json(
msg="Commands cannot be added when command category='all'"
)
return entry
def main():
ansible_module = init_ansible_module()
# Get parameters
# general
names = ansible_module.params_get("name")
# sudorules = ansible_module.params_get("sudorules")
# action
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if names is not None and len(names) != 1:
ansible_module.fail_json(
msg="Only one sudorule can be added at a time using 'name'.")
if action == "member":
invalid = ["description", "usercategory", "hostcategory",
"cmdcategory", "runasusercategory",
"runasgroupcategory", "order", "nomembers"]
elif state == "absent":
invalid = ["description", "usercategory", "hostcategory",
"cmdcategory", "runasusercategory",
"runasgroupcategory", "nomembers", "order"]
if action == "sudorule":
invalid.extend(["host", "hostgroup", "hostmask", "user", "group",
"runasuser", "runasgroup", "allow_sudocmd",
"allow_sudocmdgroup", "deny_sudocmd",
"deny_sudocmdgroup", "sudooption",
"runasuser_group"])
elif state in ["enabled", "disabled"]:
if action == "member":
ansible_module.fail_json(
msg="Action member can not be used with states enabled and "
"disabled")
invalid = ["description", "usercategory", "hostcategory",
"cmdcategory", "runasusercategory", "runasgroupcategory",
"nomembers", "nomembers", "host", "hostgroup", "hostmask",
"user", "group", "allow_sudocmd", "allow_sudocmdgroup",
"deny_sudocmd", "deny_sudocmdgroup", "runasuser",
"runasgroup", "order", "sudooption", "runasuser_group"]
else:
ansible_module.fail_json(msg="Invalid state '%s'" % state)
# Init
changed = False
exit_args = {}
# Factory parameters
params = {
"name": {},
"description": {},
"cmdcategory": {},
"usercategory": {},
"hostcategory": {},
"runasusercategory": {},
"runasgroupcategory": {},
"host": {"convert": [convert_list_of_hostnames]},
"hostgroup": {"convert": [convert_param_value_to_lowercase]},
"hostmask": {"convert": [convert_list_of_hostmask]},
"user": {"convert": [convert_param_value_to_lowercase]},
"group": {"convert": [convert_param_value_to_lowercase]},
"allow_sudocmd": {},
"allow_sudocmdgroup": {"convert": [convert_param_value_to_lowercase]},
"deny_sudocmd": {},
"deny_sudocmdgroup": {"convert": [convert_param_value_to_lowercase]},
"sudooption": {},
"order": {},
"runasuser": {"convert": [convert_param_value_to_lowercase]},
"runasuser_group": {"convert": [convert_param_value_to_lowercase]},
"runasgroup": {"convert": [convert_param_value_to_lowercase]},
"nomembers": {},
}
# Connect to IPA API
with ansible_module.ipa_connect():
commands = []
# Creating factory after connect as host conversion
# requires 'api_get_domain()' to be available
entry_factory = EntryFactory(
ansible_module,
invalid,
"sudorules",
params,
validate_entry=validate_entry,
state=state,
action=action,
)
for entry in entry_factory:
host_add, host_del = [], []
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 = [], []
deny_cmdgroup_add, deny_cmdgroup_del = [], []
sudooption_add, sudooption_del = [], []
runasuser_add, runasuser_del = [], []
runasuser_group_add, runasuser_group_del = [], []
runasgroup_add, runasgroup_del = [], []
# Try to retrieve sudorule
res_find = find_sudorule(ansible_module, entry.name)
# Fail if sudorule must exist but is not found
if (
(state in ["enabled", "disabled"] or action == "member")
and res_find is None
):
ansible_module.fail_json(msg="No sudorule '%s'" % entry.name)
# Create command
if state == "present":
# Generate args
args = gen_args(entry)
if action == "sudorule":
# Found the sudorule
if res_find is not None:
# Remove empty usercategory, hostcategory,
# cmdcaterory, runasusercategory and hostcategory
# from args if "" and if the category is not in the
# sudorule. The empty string is used to reset the
# category.
if (
"usercategory" in args
and args["usercategory"] == ""
and "usercategory" not in res_find
):
del args["usercategory"]
if (
"hostcategory" in args
and args["hostcategory"] == ""
and "hostcategory" not in res_find
):
del args["hostcategory"]
if (
"cmdcategory" in args
and args["cmdcategory"] == ""
and "cmdcategory" not in res_find
):
del args["cmdcategory"]
if (
"ipasudorunasusercategory" in args
and args["ipasudorunasusercategory"] == ""
and "ipasudorunasusercategory" not in res_find
):
del args["ipasudorunasusercategory"]
if (
"ipasudorunasgroupcategory" in args
and args["ipasudorunasgroupcategory"] == ""
and "ipasudorunasgroupcategory" not in res_find
):
del args["ipasudorunasgroupcategory"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([entry.name, "sudorule_mod", args])
else:
commands.append([entry.name, "sudorule_add", args])
# Set res_find to empty dict for next step
res_find = {}
# Generate addition and removal lists
host_add, host_del = gen_add_del_lists(
entry.host, res_find.get('memberhost_host', []))
hostgroup_add, hostgroup_del = gen_add_del_lists(
entry.hostgroup,
res_find.get('memberhost_hostgroup', [])
)
hostmask_add, hostmask_del = gen_add_del_lists(
entry.hostmask, res_find.get('hostmask', []))
user_add, user_del = gen_add_del_lists(
entry.user, res_find.get('memberuser_user', []))
group_add, group_del = gen_add_del_lists(
entry.group, res_find.get('memberuser_group', []))
allow_cmd_add, allow_cmd_del = gen_add_del_lists(
entry.allow_sudocmd,
res_find.get('memberallowcmd_sudocmd', []))
allow_cmdgroup_add, allow_cmdgroup_del = gen_add_del_lists(
entry.allow_sudocmdgroup,
res_find.get('memberallowcmd_sudocmdgroup', []))
deny_cmd_add, deny_cmd_del = gen_add_del_lists(
entry.deny_sudocmd,
res_find.get('memberdenycmd_sudocmd', []))
deny_cmdgroup_add, deny_cmdgroup_del = gen_add_del_lists(
entry.deny_sudocmdgroup,
res_find.get('memberdenycmd_sudocmdgroup', []))
sudooption_add, sudooption_del = gen_add_del_lists(
entry.sudooption, res_find.get('ipasudoopt', []))
# runasuser attribute can be used with both IPA and
# non-IPA (external) users. IPA will handle the correct
# attribute to properly store data, so we need to compare
# the provided list against both users and external
# users list.
runasuser_add, runasuser_del = gen_add_del_lists(
entry.runasuser,
(
list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', []))
)
)
runasuser_group_add, runasuser_group_del = (
gen_add_del_lists(
entry.runasuser_group,
res_find.get('ipasudorunas_group', [])
)
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups. IPA will handle the correct
# attribute to properly store data, so we need to compare
# the provided list against both groups and external
# groups list.
runasgroup_add, runasgroup_del = gen_add_del_lists(
entry.runasgroup,
(
list(res_find.get('ipasudorunasgroup_group', []))
+ list(res_find.get('ipasudorunasextgroup', []))
)
)
elif action == "member":
# Generate add lists for host, hostgroup, user, group,
# allow_sudocmd, allow_sudocmdgroup, deny_sudocmd,
# deny_sudocmdgroup, sudooption, runasuser, runasgroup
# and res_find to only try to add the items that not in
# the sudorule already
if entry.host is not None:
host_add = gen_add_list(
entry.host, res_find.get("memberhost_host"))
if entry.hostgroup is not None:
hostgroup_add = gen_add_list(
entry.hostgroup,
res_find.get("memberhost_hostgroup")
)
if entry.hostmask is not None:
hostmask_add = gen_add_list(
entry.hostmask, res_find.get("hostmask"))
if entry.user is not None:
user_add = gen_add_list(
entry.user, res_find.get("memberuser_user"))
if entry.group is not None:
group_add = gen_add_list(
entry.group, res_find.get("memberuser_group"))
if entry.allow_sudocmd is not None:
allow_cmd_add = gen_add_list(
entry.allow_sudocmd,
res_find.get("memberallowcmd_sudocmd")
)
if entry.allow_sudocmdgroup is not None:
allow_cmdgroup_add = gen_add_list(
entry.allow_sudocmdgroup,
res_find.get("memberallowcmd_sudocmdgroup")
)
if entry.deny_sudocmd is not None:
deny_cmd_add = gen_add_list(
entry.deny_sudocmd,
res_find.get("memberdenycmd_sudocmd")
)
if entry.deny_sudocmdgroup is not None:
deny_cmdgroup_add = gen_add_list(
entry.deny_sudocmdgroup,
res_find.get("memberdenycmd_sudocmdgroup")
)
if entry.sudooption is not None:
sudooption_add = gen_add_list(
entry.sudooption, res_find.get("ipasudoopt"))
# runasuser attribute can be used with both IPA and
# non-IPA (external) users, so we need to compare
# the provided list against both users and external
# users list.
if entry.runasuser is not None:
runasuser_add = gen_add_list(
entry.runasuser,
(list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', [])))
)
if entry.runasuser_group is not None:
runasuser_group_add = gen_add_list(
entry.runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
# the provided list against both users and external
# groups list.
if entry.runasgroup is not None:
runasgroup_add = gen_add_list(
entry.runasgroup,
(list(res_find.get("ipasudorunasgroup_group", []))
+ list(res_find.get("ipasudorunasextgroup", [])))
)
elif state == "absent":
if action == "sudorule":
if res_find is not None:
commands.append([entry.name, "sudorule_del", {}])
elif action == "member":
# Generate intersection lists for host, hostgroup, user,
# group, allow_sudocmd, allow_sudocmdgroup, deny_sudocmd
# deny_sudocmdgroup, sudooption, runasuser, runasgroup
# and res_find to only try to remove the items that are
# in sudorule
if entry.host is not None:
host_del = gen_intersection_list(
entry.host, res_find.get("memberhost_host"))
if entry.hostgroup is not None:
hostgroup_del = gen_intersection_list(
entry.hostgroup,
res_find.get("memberhost_hostgroup")
)
if entry.hostmask is not None:
hostmask_del = gen_intersection_list(
entry.hostmask, res_find.get("hostmask"))
if entry.user is not None:
user_del = gen_intersection_list(
entry.user, res_find.get("memberuser_user"))
if entry.group is not None:
group_del = gen_intersection_list(
entry.group, res_find.get("memberuser_group"))
if entry.allow_sudocmd is not None:
allow_cmd_del = gen_intersection_list(
entry.allow_sudocmd,
res_find.get("memberallowcmd_sudocmd")
)
if entry.allow_sudocmdgroup is not None:
allow_cmdgroup_del = gen_intersection_list(
entry.allow_sudocmdgroup,
res_find.get("memberallowcmd_sudocmdgroup")
)
if entry.deny_sudocmd is not None:
deny_cmd_del = gen_intersection_list(
entry.deny_sudocmd,
res_find.get("memberdenycmd_sudocmd")
)
if entry.deny_sudocmdgroup is not None:
deny_cmdgroup_del = gen_intersection_list(
entry.deny_sudocmdgroup,
res_find.get("memberdenycmd_sudocmdgroup")
)
if entry.sudooption is not None:
sudooption_del = gen_intersection_list(
entry.sudooption, res_find.get("ipasudoopt"))
# runasuser attribute can be used with both IPA and
# non-IPA (external) users, so we need to compare
# the provided list against both users and external
# users list.
if entry.runasuser is not None:
runasuser_del = gen_intersection_list(
entry.runasuser,
(
list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', []))
)
)
if entry.runasuser_group is not None:
runasuser_group_del = gen_intersection_list(
entry.runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
# the provided list against both groups and external
# groups list.
if entry.runasgroup is not None:
runasgroup_del = gen_intersection_list(
entry.runasgroup,
(
list(res_find.get(
"ipasudorunasgroup_group", []))
+ list(res_find.get(
"ipasudorunasextgroup", []))
)
)
elif state == "enabled":
# sudorule_enable is not failing on an enabled sudorule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "TRUE":
commands.append([entry.name, "sudorule_enable", {}])
elif state == "disabled":
# sudorule_disable is not failing on an disabled sudorule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "FALSE":
commands.append([entry.name, "sudorule_disable", {}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage members.
# Manage hosts and hostgroups
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([entry.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([entry.name, "sudorule_remove_host", params])
# Manage users and groups
if user_add or group_add:
commands.append([
entry.name, "sudorule_add_user",
{"user": user_add, "group": group_add}
])
if user_del or group_del:
commands.append([
entry.name, "sudorule_remove_user",
{"user": user_del, "group": group_del}
])
# Manage commands allowed
if allow_cmd_add or allow_cmdgroup_add:
commands.append([
entry.name, "sudorule_add_allow_command",
{
"sudocmd": allow_cmd_add,
"sudocmdgroup": allow_cmdgroup_add,
}
])
if allow_cmd_del or allow_cmdgroup_del:
commands.append([
entry.name, "sudorule_remove_allow_command",
{
"sudocmd": allow_cmd_del,
"sudocmdgroup": allow_cmdgroup_del
}
])
# Manage commands denied
if deny_cmd_add or deny_cmdgroup_add:
commands.append([
entry.name, "sudorule_add_deny_command",
{
"sudocmd": deny_cmd_add,
"sudocmdgroup": deny_cmdgroup_add,
}
])
if deny_cmd_del or deny_cmdgroup_del:
commands.append([
entry.name, "sudorule_remove_deny_command",
{
"sudocmd": deny_cmd_del,
"sudocmdgroup": deny_cmdgroup_del
}
])
# Manage RunAS users
if runasuser_add or runasuser_group_add:
# Can't use empty lists with command "sudorule_add_runasuser".
_args = {}
if runasuser_add:
_args["user"] = runasuser_add
if runasuser_group_add:
_args["group"] = runasuser_group_add
commands.append([entry.name, "sudorule_add_runasuser", _args])
if runasuser_del or runasuser_group_del:
commands.append([
entry.name,
"sudorule_remove_runasuser",
{"user": runasuser_del, "group": runasuser_group_del}
])
# Manage RunAS Groups
if runasgroup_add:
commands.append([
entry.name, "sudorule_add_runasgroup",
{"group": runasgroup_add}
])
if runasgroup_del:
commands.append([
entry.name, "sudorule_remove_runasgroup",
{"group": runasgroup_del}
])
# Manage sudo options
if sudooption_add:
for option in sudooption_add:
commands.append([
entry.name, "sudorule_add_option",
{"ipasudoopt": option}
])
if sudooption_del:
for option in sudooption_del:
commands.append([
entry.name, "sudorule_remove_option",
{"ipasudoopt": option}
])
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, batch=True, fail_on_member_errors=True)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()