# -*- coding: utf-8 -*- # Authors: # Rafael Guterres Jeffman <rjeffman@redhat.com> # Thomas Woerner <twoerner@redhat.com> # # Copyright (C) 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: ipaidrange short_description: Manage FreeIPA idrange description: Manage FreeIPA idrange extends_documentation_fragment: - ipamodule_base_docs - ipamodule_base_docs.delete_continue options: name: description: The list of idrange name strings. type: list elements: str required: true aliases: ["cn"] base_id: description: First Posix ID of the range. type: int required: false aliases: ["ipabaseid"] range_size: description: Number of IDs in the range. type: int required: false aliases: ["ipaidrangesize"] rid_base: description: First RID of the corresponding RID range. type: int required: false aliases: ["ipabaserid"] secondary_rid_base: description: First RID of the secondary RID range. type: int required: false aliases: ["ipasecondarybaserid"] idrange_type: description: ID range type. type: str required: false choices: ["ipa-ad-trust", "ipa-ad-trust-posix", "ipa-local"] aliases: ["iparangetype"] dom_sid: description: Domain SID of the trusted domain. type: str required: false aliases: ["ipanttrusteddomainsid"] dom_name: description: | Domain name of the trusted domain. Can only be used when `ipaapi_context: server`. type: str required: false aliases: ["ipanttrusteddomainname"] auto_private_groups: description: Auto creation of private groups. type: str required: false choices: ["true", "false", "hybrid"] aliases: ["ipaautoprivategroups"] state: description: The state to ensure. type: str choices: ["present", "absent"] default: present required: false author: - Rafael Guterres Jeffman (@rjeffman) - Thomas Woerner (@t-woerner) """ EXAMPLES = """ # Ensure local domain idrange is present - ipaidrange: ipaadmin_password: SomeADMINpassword name: id_range base_id: 150000000 range_size: 200000 rid_base: 1000000 secondary_rid_base: 200000000 # Ensure local domain idrange is absent - ipaidrange: ipaadmin_password: SomeADMINpassword name: id_range state: absent # Ensure AD-trust idrange is present - ipaidrange: name: id_range base_id: 150000000 range_size: 200000 rid_base: 1000000 idrange_type: ipa-ad-trust dom_sid: S-1-5-21-2870384104-3340008087-3140804251 auto_private_groups: "false" # Ensure AD-trust idrange is present, with range type ad-trust-posix, # and using domain name - ipaidrange: name: id_range base_id: 150000000 range_size: 200000 rid_base: 1000000 idrange_type: ipa-ad-trust-posix dom_name: ad.ipa.test auto_private_groups: "hybrid" """ RETURN = """ """ from ansible.module_utils.ansible_freeipa_module import \ IPAAnsibleModule, compare_args_ipa, get_trusted_domain_sid_from_name from ansible.module_utils import six if six.PY3: unicode = str def find_idrange(module, name): """Find if a idrange with the given name already exist.""" try: _result = module.ipa_command("idrange_show", name, {"all": True}) except Exception: # pylint: disable=broad-except # An exception is raised if idrange name is not found. return None else: return _result["result"] def gen_args( base_id, range_size, rid_base, secondary_rid_base, idrange_type, dom_sid, dom_name, auto_private_groups ): _args = {} # Integer parameters are stored as strings. # Converting them here allows the proper use of compare_args_ipa. if base_id is not None: _args["ipabaseid"] = base_id if range_size is not None: _args["ipaidrangesize"] = range_size if rid_base is not None: _args["ipabaserid"] = rid_base if secondary_rid_base is not None: _args["ipasecondarybaserid"] = secondary_rid_base if idrange_type is not None: _args["iparangetype"] = idrange_type if dom_name is not None: dom_sid = get_trusted_domain_sid_from_name(dom_name) if dom_sid is not None: _args["ipanttrusteddomainsid"] = dom_sid if auto_private_groups is not None: _args["ipaautoprivategroups"] = auto_private_groups return _args def main(): ansible_module = IPAAnsibleModule( argument_spec=dict( # general name=dict(type="list", elements="str", aliases=["cn"], required=True), # present base_id=dict(required=False, type='int', aliases=["ipabaseid"], default=None), range_size=dict(required=False, type='int', aliases=["ipaidrangesize"], default=None), rid_base=dict(required=False, type='int', aliases=["ipabaserid"], default=None), secondary_rid_base=dict(required=False, type='int', default=None, aliases=["ipasecondarybaserid"]), idrange_type=dict(required=False, aliases=["iparangetype"], type="str", default=None, choices=["ipa-ad-trust", "ipa-ad-trust-posix", "ipa-local"]), dom_sid=dict(required=False, type='str', default=None, aliases=["ipanttrusteddomainsid"]), dom_name=dict(required=False, type='str', default=None, aliases=["ipanttrusteddomainname"]), auto_private_groups=dict(required=False, type='str', default=None, aliases=["ipaautoprivategroups"], choices=['true', 'false', 'hybrid']), # state state=dict(type="str", default="present", choices=["present", "absent"]), ), mutually_exclusive=[ ["dom_sid", "secondary_rid_base"], ["dom_name", "secondary_rid_base"], ["dom_sid", "dom_name"], ], supports_check_mode=True, ipa_module_options=["delete_continue"], ) ansible_module._ansible_debug = True # Get parameters # general names = ansible_module.params_get("name") delete_continue = ansible_module.params_get("delete_continue") # present base_id = ansible_module.params_get("base_id") range_size = ansible_module.params_get("range_size") rid_base = ansible_module.params_get("rid_base") secondary_rid_base = ansible_module.params_get("secondary_rid_base") idrange_type = ansible_module.params_get("idrange_type") dom_sid = ansible_module.params_get("dom_sid") dom_name = ansible_module.params_get("dom_name") auto_private_groups = \ ansible_module.params_get_lowercase("auto_private_groups") # state state = ansible_module.params_get("state") # Check parameters invalid = [] if state == "present": if len(names) != 1: ansible_module.fail_json( msg="Only one idrange can be added at a time.") if state == "absent": if len(names) < 1: ansible_module.fail_json(msg="No name given.") invalid = [ "base_id", "range_size", "idrange_type", "dom_sid", "dom_name", "rid_base", "secondary_rid_base", "auto_private_groups" ] ansible_module.params_fail_used_invalid(invalid, state) # Init changed = False exit_args = {} range_types = { "Active Directory domain range": "ipa-ad-trust", "Active Directory trust range with POSIX attributes": "ipa-ad-trust-posix", "local domain range": "ipa-local", } # Connect to IPA API with ansible_module.ipa_connect(): commands = [] for name in names: # Make sure idrange exists res_find = find_idrange(ansible_module, name) # Create command if state == "present": # Generate args args = gen_args( base_id, range_size, rid_base, secondary_rid_base, idrange_type, dom_sid, dom_name, auto_private_groups ) # Found the idrange if res_find is not None: # 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, ignore=["iparangetype"] ): res_type = range_types.get( res_find.get("iparangetype")[0] ) if res_type == "local_id_range": ansible_module.fail_json( "Cannot modify local IPA domain idrange." ) arg_type = args.get("iparangetype") if arg_type: if arg_type != res_type: ansible_module.fail_json( "Cannot modify idrange type." ) del args["iparangetype"] commands.append([name, "idrange_mod", args]) else: commands.append([name, "idrange_add", args]) elif state == "absent": if res_find is not None: commands.append([ name, "idrange_del", {"continue": delete_continue or False} ]) else: ansible_module.fail_json(msg="Unkown state '%s'" % state) # Execute commands changed = ansible_module.execute_ipa_commands(commands) # Done ansible_module.exit_json(changed=changed, **exit_args) if __name__ == "__main__": main()