diff --git a/library/ipatest.py b/library/ipatest.py new file mode 100644 index 0000000000000000000000000000000000000000..5f51c13336b622a0ed5c667d55470a6b6de09916 --- /dev/null +++ b/library/ipatest.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# Based on ipa-client-install code +# +# Copyright (C) 2017 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/>. + +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'supported_by': 'community', + 'status': ['preview'], +} + +DOCUMENTATION = ''' +--- +module: ipatest +short description: Test if the krb5.keytab on the machine is valid and can be used. +description: + Test if the krb5.keytab on the machine is valid and can be used. + A temporary krb5.conf file will be generated to not fail on an invalid one. +options: + servers: + description: The FQDN of the IPA servers to connect to. + required: true + domain: + description: The primary DNS domain of an existing IPA deployment. + required: true + realm: + description: The Kerberos realm of an existing IPA deployment. + required: true + hostname: + description: The hostname of the machine to join (FQDN). + required: true + kdc: + description: The name or address of the host running the KDC. + required: true + principal: + description: The authorized kerberos principal used to join the IPA realm. + required: false + kinit_attempts: + description: Repeat the request for host Kerberos ticket X times. + required: false + default: 5 +author: + - Thomas Woerner +''' + +EXAMPLES = ''' +# Join IPA to get the keytab +- name: Test IPA in force mode with maximum 5 kinit attempts + ipatest: + servers: ["server1.example.com","server2.example.com"] + domain: example.com + realm: EXAMPLE.COM + kdc: server1.example.com + hostname: client1.example.com + principal: admin + kinit_attempts: 5 + +# Join IPA to get the keytab using ipadiscovery return values +- name: Join IPA + ipajoin: + servers: "{{ ipadiscovery.servers }}" + domain: "{{ ipadiscovery.domain }}" + realm: "{{ ipadiscovery.realm }}" + kdc: "{{ ipadiscovery.kdc }}" + hostname: "{{ ipadiscovery.hostname }}" + principal: admin +''' + +RETURN = ''' +krb5_keytab_ok: + description: The flag describes if krb5.keytab on the host is usable. + returned: always + type: bool +''' + +class Object(object): + pass +options = Object() + +import os +import sys +import gssapi +import tempfile +import inspect + +from ansible.module_utils.basic import AnsibleModule +from ipapython.version import NUM_VERSION, VERSION +if NUM_VERSION < 40400: + raise Exception, "freeipa version '%s' is too old" % VERSION +from ipaplatform.paths import paths +try: + from ipalib.install.kinit import kinit_keytab +except ImportError: + from ipapython.ipautil import kinit_keytab +try: + from ipaclient.install.client import configure_krb5_conf +except ImportError: + # Create temporary copy of ipa-client-install script (as + # ipa_client_install.py) to be able to import the script easily and also + # to remove the global finally clause in which the generated ccache file + # gets removed. The ccache file will be needed in the next step. + # This is done in a temporary directory that gets removed right after + # ipa_client_install has been imported. + import shutil + temp_dir = tempfile.mkdtemp(dir="/tmp") + sys.path.append(temp_dir) + temp_file = "%s/ipa_client_install.py" % temp_dir + + with open("/usr/sbin/ipa-client-install", "r") as f_in: + with open(temp_file, "w") as f_out: + for line in f_in: + if line.startswith("finally:"): + break + f_out.write(line) + import ipa_client_install + + shutil.rmtree(temp_dir, ignore_errors=True) + sys.path.remove(temp_dir) + + argspec = inspect.getargspec(ipa_client_install.configure_krb5_conf) + if argspec.keywords is None: + def configure_krb5_conf( + cli_realm, cli_domain, cli_server, cli_kdc, dnsok, + filename, client_domain, client_hostname, force, + configure_sssd): + global options + options.force = force + options.sssd = configure_sssd + return ipa_client_install.configure_krb5_conf( + cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options, + filename, client_domain, client_hostname) + else: + configure_krb5_conf = ipa_client_install.configure_krb5_conf +from ipapython.ipautil import realm_to_suffix, run + + +import logging +logger = logging.getLogger("ipa-client-install") + +def main(): + module = AnsibleModule( + argument_spec = dict( + servers=dict(required=True, type='list'), + domain=dict(required=True), + realm=dict(required=True), + hostname=dict(required=True), + kdc=dict(required=True), + principal=dict(required=False), + kinit_attempts=dict(required=False, type='int', default=5), + ), + supports_check_mode = True, + ) + + module._ansible_debug = True + servers = module.params.get('servers') + domain = module.params.get('domain') + realm = module.params.get('realm') + hostname = module.params.get('hostname') + kdc = module.params.get('kdc') + principal = module.params.get('principal') + kinit_attempts = module.params.get('kinit_attempts') + + client_domain = hostname[hostname.find(".")+1:] + host_principal = 'host/%s@%s' % (hostname, realm) + sssd = True + + krb5_keytab_ok = True + try: + (krb_fd, krb_name) = tempfile.mkstemp() + os.close(krb_fd) + configure_krb5_conf( + cli_realm=realm, + cli_domain=domain, + cli_server=servers, + cli_kdc=kdc, + dnsok=False, + filename=krb_name, + client_domain=client_domain, + client_hostname=hostname, + configure_sssd=sssd, + force=False) + + # Obtain the TGT. We do it with the temporary krb5.conf, so that + # only the KDC we're installing under is contacted. + # Other KDCs might not have replicated the principal yet. + # Once we have the TGT, it's usable on any server. + try: + kinit_keytab(host_principal, paths.KRB5_KEYTAB, + paths.IPA_DNS_CCACHE, + config=krb_name, + attempts=kinit_attempts) + except gssapi.exceptions.GSSError as e: + # failure to get ticket makes it impossible to login and bind + # from sssd to LDAP, abort installation and rollback changes + krb5_keytab_ok = False + + finally: + try: + os.remove(krb_name) + except OSError: + module.fail_json(msg="Could not remove %s" % krb_name) + + module.exit_json(changed=False, krb5_keytab_ok=krb5_keytab_ok) + +if __name__ == '__main__': + main()