# -*- coding: utf-8 -*- # Authors: # Thomas Woerner <twoerner@redhat.com> # # Copyright (C) 2024 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 = """ --- name: freeipa version_added: "1.13.0" short_description: Compiles a dynamic inventory from IPA domain description: | Compiles a dynamic inventory from IPA domain, filters servers by role(s). options: plugin: description: Marks this as an instance of the "freeipa" plugin. required: True choices: ["freeipa"] ipaadmin_principal: description: The admin principal. default: admin type: str ipaadmin_password: description: The admin password. required: true type: str server: description: FQDN of server to start the scan. type: str required: true verify: description: | The server TLS certificate file for verification (/etc/ipa/ca.crt). Turned off if not set. type: str required: false role: description: | The role(s) of the server. If several roles are given, only servers that have all the roles are returned. type: list elements: str choices: ["IPA master", "CA server", "KRA server", "DNS server", "AD trust controller", "AD trust agent"] required: false inventory_group: description: | The inventory group to create. The default group name is "ipaservers". type: str default: ipaservers author: - Thomas Woerner (@t-woerner) """ EXAMPLES = """ # inventory.config file in YAML format plugin: freeipa server: ipaserver-01.ipa.local ipaadmin_password: SomeADMINpassword # inventory.config file in YAML format with server TLS certificate verification plugin: freeipa server: ipaserver-01.ipa.local ipaadmin_password: SomeADMINpassword verify: ca.crt """ import os try: import requests except ImportError: requests = None try: import urllib3 except ImportError: urllib3 = None from ansible import constants from ansible.errors import AnsibleParserError from ansible.module_utils.common.text.converters import to_native from ansible.plugins.inventory import BaseInventoryPlugin from ansible.module_utils.six.moves.urllib.parse import quote class InventoryModule(BaseInventoryPlugin): NAME = 'freeipa' def verify_file(self, path): # pylint: disable=super-with-arguments if super(InventoryModule, self).verify_file(path): _name, ext = os.path.splitext(path) if ext in constants.YAML_FILENAME_EXTENSIONS: return True return False def parse(self, inventory, loader, path, cache=False): # pylint: disable=super-with-arguments super(InventoryModule, self).parse(inventory, loader, path, cache=cache) self._read_config_data(path) # This also loads the cache self.get_option("plugin") if requests is None: raise AnsibleParserError("The required Python library " "'requests' could not be imported.") ipaadmin_principal = self.get_option("ipaadmin_principal") ipaadmin_password = self.get_option("ipaadmin_password") server = self.get_option("server") verify = self.get_option("verify") role = self.get_option("role") inventory_group = self.get_option("inventory_group") if verify is not None: if not os.path.exists(verify): raise AnsibleParserError("ERROR: Could not load %s" % verify) else: verify = False # Disable certificate verification warning without certificate # as long as urllib3 could have been loaded. if urllib3 is not None: urllib3.disable_warnings( urllib3.exceptions.InsecureRequestWarning) self.inventory.add_group(inventory_group) ipa_url = "https://%s/ipa" % server s = requests.Session() s.headers.update({"referer": ipa_url}) s.headers.update({"Content-Type": "application/x-www-form-urlencoded"}) s.headers.update({"Accept": "text/plain"}) data = 'user=%s&password=%s' % (quote(ipaadmin_principal, safe=''), quote(ipaadmin_password, safe='')) response = s.post("%s/session/login_password" % ipa_url, data=data, verify=verify) # Now use json API s.headers.update({"Content-Type": "application/json"}) kw_args = {} if role is not None: kw_args["servrole"] = role json_data = { "method" : "server_find", "params": [[], kw_args], "id": 0 } response = s.post("%s/session/json" % ipa_url, json=json_data, verify=verify) json_res = response.json() error = json_res.get("error") if error is not None: raise AnsibleParserError("ERROR: %s" % to_native(error)) if "result" in json_res and "result" in json_res["result"]: res = json_res["result"].get("result") if isinstance(res, list): for server in res: self.inventory.add_host(server["cn"][0], group=inventory_group)