Blame SOURCES/ansible-freeipa-1.9.2-ipaclient-Defer-creating-the-final-krb5.conf-on-clients_RHBZ#2189238.patch

e80d55
From 6b5acd9b0c8de965d9b815f8033a2bace9dd737d Mon Sep 17 00:00:00 2001
e80d55
From: Thomas Woerner <twoerner@redhat.com>
e80d55
Date: Wed, 22 Feb 2023 13:35:18 +0100
e80d55
Subject: [PATCH] ipaclient: Defer creating the final krb5.conf on clients
e80d55
e80d55
A temporary krb5 configuration was used to join the domain in
e80d55
ipaclient_join. After that the final krkb5 configuration was created
e80d55
with enabled DNS discovery and used for the remainaing tasks, where also
e80d55
a connection to the IPA API was done.
e80d55
e80d55
With several servers the DNS discovery could have picked up a different
e80d55
server. If the client deployment was faster than the replication this
e80d55
could have lead to an unknown host error.
e80d55
e80d55
The issue was seen in performance testing where many simultaneous client
e80d55
enrollments have been done..
e80d55
e80d55
The goal is to keep server affinity as long as possible within the
e80d55
deployment process:
e80d55
e80d55
The temporary krb5.conf that was used before in ipaclient_join was
e80d55
pulled out into an own module. The generated temporary krb5.conf is now
e80d55
used in ipaclient_join and also ipaclient_api.
e80d55
e80d55
The generation of the final krb5.conf is moved to the end of the
e80d55
deployment process.
e80d55
e80d55
Same as: https://pagure.io/freeipa/issue/9228
e80d55
e80d55
The setup of certmonger has been pulled out of ipaclient_setup_nss and moved
e80d55
to the end of the process after generating the final krb5.conf as it will
e80d55
use t will only use /etc/krb5.conf.
e80d55
e80d55
Certificate issuance may fail during deployment due to using the final
e80d55
krb5.conf, but certmonger will re-try the request in this case.
e80d55
e80d55
Same as: https://pagure.io/freeipa/issue/9246
e80d55
---
e80d55
 roles/ipaclient/library/ipaclient_api.py      |   8 +
e80d55
 roles/ipaclient/library/ipaclient_join.py     |  55 ++----
e80d55
 .../library/ipaclient_setup_certmonger.py     | 123 +++++++++++++
e80d55
 .../ipaclient/library/ipaclient_setup_nss.py  |   4 +-
e80d55
 .../ipaclient/library/ipaclient_temp_krb5.py  | 163 ++++++++++++++++++
e80d55
 .../library/ipaclient_test_keytab.py          |   6 +
e80d55
 roles/ipaclient/tasks/install.yml             |  69 ++++++--
e80d55
 7 files changed, 365 insertions(+), 63 deletions(-)
e80d55
 create mode 100644 roles/ipaclient/library/ipaclient_setup_certmonger.py
e80d55
 create mode 100644 roles/ipaclient/library/ipaclient_temp_krb5.py
e80d55
e80d55
diff --git a/roles/ipaclient/library/ipaclient_api.py b/roles/ipaclient/library/ipaclient_api.py
e80d55
index 7d4b829..9193f60 100644
e80d55
--- a/roles/ipaclient/library/ipaclient_api.py
e80d55
+++ b/roles/ipaclient/library/ipaclient_api.py
e80d55
@@ -55,6 +55,10 @@ options:
e80d55
     type: bool
e80d55
     required: no
e80d55
     default: no
e80d55
+  krb_name:
e80d55
+    description: The krb5 config file name
e80d55
+    type: str
e80d55
+    required: yes
e80d55
 author:
e80d55
     - Thomas Woerner (@t-woerner)
e80d55
 '''
e80d55
@@ -65,6 +69,7 @@ EXAMPLES = '''
e80d55
     servers: ["server1.example.com","server2.example.com"]
e80d55
     domain: example.com
e80d55
     hostname: client1.example.com
e80d55
+    krb_name: /tmp/tmpkrb5.conf
e80d55
   register: result_ipaclient_api
e80d55
 '''
e80d55
 
e80d55
@@ -99,6 +104,7 @@ def main():
e80d55
             realm=dict(required=True, type='str'),
e80d55
             hostname=dict(required=True, type='str'),
e80d55
             debug=dict(required=False, type='bool', default="false"),
e80d55
+            krb_name=dict(required=True, type='str'),
e80d55
         ),
e80d55
         supports_check_mode=False,
e80d55
     )
e80d55
@@ -110,9 +116,11 @@ def main():
e80d55
     realm = module.params.get('realm')
e80d55
     hostname = module.params.get('hostname')
e80d55
     debug = module.params.get('debug')
e80d55
+    krb_name = module.params.get('krb_name')
e80d55
 
e80d55
     host_principal = 'host/%s@%s' % (hostname, realm)
e80d55
     os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
e80d55
+    os.environ['KRB5_CONFIG'] = krb_name
e80d55
 
e80d55
     ca_certs = x509.load_certificate_list_from_file(paths.IPA_CA_CRT)
e80d55
     if 40500 <= NUM_VERSION < 40590:
e80d55
diff --git a/roles/ipaclient/library/ipaclient_join.py b/roles/ipaclient/library/ipaclient_join.py
e80d55
index 5d41a54..68379ea 100644
e80d55
--- a/roles/ipaclient/library/ipaclient_join.py
e80d55
+++ b/roles/ipaclient/library/ipaclient_join.py
e80d55
@@ -46,10 +46,6 @@ options:
e80d55
     type: list
e80d55
     elements: str
e80d55
     required: yes
e80d55
-  domain:
e80d55
-    description: Primary DNS domain of the IPA deployment
e80d55
-    type: str
e80d55
-    required: yes
e80d55
   realm:
e80d55
     description: Kerberos realm name of the IPA deployment
e80d55
     type: str
e80d55
@@ -58,10 +54,6 @@ options:
e80d55
     description: Fully qualified name of this host
e80d55
     type: str
e80d55
     required: yes
e80d55
-  kdc:
e80d55
-    description: The name or address of the host running the KDC
e80d55
-    type: str
e80d55
-    required: yes
e80d55
   basedn:
e80d55
     description: The basedn of the IPA server (of the form dc=example,dc=com)
e80d55
     type: str
e80d55
@@ -102,6 +94,10 @@ options:
e80d55
     description: Turn on extra debugging
e80d55
     type: bool
e80d55
     required: no
e80d55
+  krb_name:
e80d55
+    description: The krb5 config file name
e80d55
+    type: str
e80d55
+    required: yes
e80d55
 author:
e80d55
     - Thomas Woerner (@t-woerner)
e80d55
 '''
e80d55
@@ -111,27 +107,25 @@ EXAMPLES = '''
e80d55
 - name: Join IPA in force mode with maximum 5 kinit attempts
e80d55
   ipaclient_join:
e80d55
     servers: ["server1.example.com","server2.example.com"]
e80d55
-    domain: example.com
e80d55
     realm: EXAMPLE.COM
e80d55
-    kdc: server1.example.com
e80d55
     basedn: dc=example,dc=com
e80d55
     hostname: client1.example.com
e80d55
     principal: admin
e80d55
     password: MySecretPassword
e80d55
     force_join: yes
e80d55
     kinit_attempts: 5
e80d55
+    krb_name: /tmp/tmpkrb5.conf
e80d55
 
e80d55
 # Join IPA to get the keytab using ipadiscovery return values
e80d55
 - name: Join IPA
e80d55
   ipaclient_join:
e80d55
     servers: "{{ ipadiscovery.servers }}"
e80d55
-    domain: "{{ ipadiscovery.domain }}"
e80d55
     realm: "{{ ipadiscovery.realm }}"
e80d55
-    kdc: "{{ ipadiscovery.kdc }}"
e80d55
     basedn: "{{ ipadiscovery.basedn }}"
e80d55
     hostname: "{{ ipadiscovery.hostname }}"
e80d55
     principal: admin
e80d55
     password: MySecretPassword
e80d55
+    krb_name: /tmp/tmpkrb5.conf
e80d55
 '''
e80d55
 
e80d55
 RETURN = '''
e80d55
@@ -147,9 +141,9 @@ import tempfile
e80d55
 from ansible.module_utils.basic import AnsibleModule
e80d55
 from ansible.module_utils.ansible_ipa_client import (
e80d55
     setup_logging, check_imports,
e80d55
-    SECURE_PATH, sysrestore, paths, options, configure_krb5_conf,
e80d55
-    realm_to_suffix, kinit_keytab, GSSError, kinit_password, NUM_VERSION,
e80d55
-    get_ca_cert, get_ca_certs, errors, run
e80d55
+    SECURE_PATH, sysrestore, paths, options, realm_to_suffix, kinit_keytab,
e80d55
+    GSSError, kinit_password, NUM_VERSION, get_ca_cert, get_ca_certs, errors,
e80d55
+    run
e80d55
 )
e80d55
 
e80d55
 
e80d55
@@ -157,10 +151,8 @@ def main():
e80d55
     module = AnsibleModule(
e80d55
         argument_spec=dict(
e80d55
             servers=dict(required=True, type='list', elements='str'),
e80d55
-            domain=dict(required=True, type='str'),
e80d55
             realm=dict(required=True, type='str'),
e80d55
             hostname=dict(required=True, type='str'),
e80d55
-            kdc=dict(required=True, type='str'),
e80d55
             basedn=dict(required=True, type='str'),
e80d55
             principal=dict(required=False, type='str'),
e80d55
             password=dict(required=False, type='str', no_log=True),
e80d55
@@ -170,6 +162,7 @@ def main():
e80d55
             force_join=dict(required=False, type='bool'),
e80d55
             kinit_attempts=dict(required=False, type='int', default=5),
e80d55
             debug=dict(required=False, type='bool'),
e80d55
+            krb_name=dict(required=True, type='str'),
e80d55
         ),
e80d55
         supports_check_mode=False,
e80d55
     )
e80d55
@@ -179,11 +172,9 @@ def main():
e80d55
     setup_logging()
e80d55
 
e80d55
     servers = module.params.get('servers')
e80d55
-    domain = module.params.get('domain')
e80d55
     realm = module.params.get('realm')
e80d55
     hostname = module.params.get('hostname')
e80d55
     basedn = module.params.get('basedn')
e80d55
-    kdc = module.params.get('kdc')
e80d55
     force_join = module.params.get('force_join')
e80d55
     principal = module.params.get('principal')
e80d55
     password = module.params.get('password')
e80d55
@@ -192,6 +183,7 @@ def main():
e80d55
     ca_cert_file = module.params.get('ca_cert_file')
e80d55
     kinit_attempts = module.params.get('kinit_attempts')
e80d55
     debug = module.params.get('debug')
e80d55
+    krb_name = module.params.get('krb_name')
e80d55
 
e80d55
     if password is not None and keytab is not None:
e80d55
         module.fail_json(msg="Password and keytab cannot be used together")
e80d55
@@ -199,12 +191,10 @@ def main():
e80d55
     if password is None and admin_keytab is None:
e80d55
         module.fail_json(msg="Password or admin_keytab is needed")
e80d55
 
e80d55
-    client_domain = hostname[hostname.find(".") + 1:]
e80d55
     nolog = tuple()
e80d55
     env = {'PATH': SECURE_PATH}
e80d55
     fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
e80d55
     host_principal = 'host/%s@%s' % (hostname, realm)
e80d55
-    sssd = True
e80d55
 
e80d55
     options.ca_cert_file = ca_cert_file
e80d55
     options.principal = principal
e80d55
@@ -215,19 +205,6 @@ def main():
e80d55
     changed = False
e80d55
     already_joined = False
e80d55
     try:
e80d55
-        (krb_fd, krb_name) = tempfile.mkstemp()
e80d55
-        os.close(krb_fd)
e80d55
-        configure_krb5_conf(
e80d55
-            cli_realm=realm,
e80d55
-            cli_domain=domain,
e80d55
-            cli_server=servers,
e80d55
-            cli_kdc=kdc,
e80d55
-            dnsok=False,
e80d55
-            filename=krb_name,
e80d55
-            client_domain=client_domain,
e80d55
-            client_hostname=hostname,
e80d55
-            configure_sssd=sssd,
e80d55
-            force=False)
e80d55
         env['KRB5_CONFIG'] = krb_name
e80d55
         ccache_dir = tempfile.mkdtemp(prefix='krbcc')
e80d55
         ccache_name = os.path.join(ccache_dir, 'ccache')
e80d55
@@ -336,27 +313,17 @@ def main():
e80d55
                          paths.IPA_DNS_CCACHE,
e80d55
                          config=krb_name,
e80d55
                          attempts=kinit_attempts)
e80d55
-            env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
e80d55
         except GSSError as e:
e80d55
             # failure to get ticket makes it impossible to login and
e80d55
             # bind from sssd to LDAP, abort installation
e80d55
             module.fail_json(msg="Failed to obtain host TGT: %s" % e)
e80d55
 
e80d55
     finally:
e80d55
-        try:
e80d55
-            os.remove(krb_name)
e80d55
-        except OSError:
e80d55
-            module.fail_json(msg="Could not remove %s" % krb_name)
e80d55
         if ccache_dir is not None:
e80d55
             try:
e80d55
                 os.rmdir(ccache_dir)
e80d55
             except OSError:
e80d55
                 pass
e80d55
-        if os.path.exists(krb_name + ".ipabkp"):
e80d55
-            try:
e80d55
-                os.remove(krb_name + ".ipabkp")
e80d55
-            except OSError:
e80d55
-                module.fail_json(msg="Could not remove %s.ipabkp" % krb_name)
e80d55
 
e80d55
     module.exit_json(changed=changed,
e80d55
                      already_joined=already_joined)
e80d55
diff --git a/roles/ipaclient/library/ipaclient_setup_certmonger.py b/roles/ipaclient/library/ipaclient_setup_certmonger.py
e80d55
new file mode 100644
e80d55
index 0000000..5c81b40
e80d55
--- /dev/null
e80d55
+++ b/roles/ipaclient/library/ipaclient_setup_certmonger.py
e80d55
@@ -0,0 +1,123 @@
e80d55
+# -*- coding: utf-8 -*-
e80d55
+
e80d55
+# Authors:
e80d55
+#   Thomas Woerner <twoerner@redhat.com>
e80d55
+#
e80d55
+# Based on ipa-client-install code
e80d55
+#
e80d55
+# Copyright (C) 2017-2022  Red Hat
e80d55
+# see file 'COPYING' for use and warranty information
e80d55
+#
e80d55
+# This program is free software; you can redistribute it and/or modify
e80d55
+# it under the terms of the GNU General Public License as published by
e80d55
+# the Free Software Foundation, either version 3 of the License, or
e80d55
+# (at your option) any later version.
e80d55
+#
e80d55
+# This program is distributed in the hope that it will be useful,
e80d55
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
e80d55
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e80d55
+# GNU General Public License for more details.
e80d55
+#
e80d55
+# You should have received a copy of the GNU General Public License
e80d55
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
e80d55
+
e80d55
+from __future__ import (absolute_import, division, print_function)
e80d55
+
e80d55
+__metaclass__ = type
e80d55
+
e80d55
+ANSIBLE_METADATA = {
e80d55
+    'metadata_version': '1.0',
e80d55
+    'supported_by': 'community',
e80d55
+    'status': ['preview'],
e80d55
+}
e80d55
+
e80d55
+DOCUMENTATION = '''
e80d55
+---
e80d55
+module: ipaclient_setup_certmonger
e80d55
+short_description: Setup certmonger for IPA client
e80d55
+description: Setup certmonger for IPA client
e80d55
+options:
e80d55
+  realm:
e80d55
+    description: Kerberos realm name of the IPA deployment
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  hostname:
e80d55
+    description: Fully qualified name of this host
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  subject_base:
e80d55
+    description: |
e80d55
+      The certificate subject base (default O=<realm-name>).
e80d55
+      RDNs are in LDAP order (most specific RDN first).
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  ca_enabled:
e80d55
+    description: Whether the Certificate Authority is enabled or not
e80d55
+    type: bool
e80d55
+    required: yes
e80d55
+  request_cert:
e80d55
+    description: Request certificate for the machine
e80d55
+    type: bool
e80d55
+    required: yes
e80d55
+author:
e80d55
+    - Thomas Woerner (@t-woerner)
e80d55
+'''
e80d55
+
e80d55
+EXAMPLES = '''
e80d55
+- name: Setup certmonger for IPA client
e80d55
+  ipaclient_setup_certmonger:
e80d55
+    realm: EXAMPLE.COM
e80d55
+    hostname: client1.example.com
e80d55
+    subject_base: O=EXAMPLE.COM
e80d55
+    ca_enabled: true
e80d55
+    request_cert: false
e80d55
+'''
e80d55
+
e80d55
+RETURN = '''
e80d55
+'''
e80d55
+
e80d55
+from ansible.module_utils.basic import AnsibleModule
e80d55
+from ansible.module_utils.ansible_ipa_client import (
e80d55
+    setup_logging, check_imports,
e80d55
+    options, sysrestore, paths, ScriptError, configure_certmonger
e80d55
+)
e80d55
+
e80d55
+
e80d55
+def main():
e80d55
+    module = AnsibleModule(
e80d55
+        argument_spec=dict(
e80d55
+            realm=dict(required=True, type='str'),
e80d55
+            hostname=dict(required=True, type='str'),
e80d55
+            subject_base=dict(required=True, type='str'),
e80d55
+            ca_enabled=dict(required=True, type='bool'),
e80d55
+            request_cert=dict(required=True, type='bool'),
e80d55
+        ),
e80d55
+        supports_check_mode=False,
e80d55
+    )
e80d55
+
e80d55
+    module._ansible_debug = True
e80d55
+    check_imports(module)
e80d55
+    setup_logging()
e80d55
+
e80d55
+    cli_realm = module.params.get('realm')
e80d55
+    hostname = module.params.get('hostname')
e80d55
+    subject_base = module.params.get('subject_base')
e80d55
+    ca_enabled = module.params.get('ca_enabled')
e80d55
+
e80d55
+    fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
e80d55
+
e80d55
+    options.request_cert = module.params.get('request_cert')
e80d55
+    options.hostname = hostname
e80d55
+
e80d55
+    try:
e80d55
+        configure_certmonger(fstore, subject_base, cli_realm, hostname,
e80d55
+                             options, ca_enabled)
e80d55
+
e80d55
+    except ScriptError as e:
e80d55
+        module.fail_json(msg=str(e))
e80d55
+
e80d55
+    module.exit_json(changed=True)
e80d55
+
e80d55
+
e80d55
+if __name__ == '__main__':
e80d55
+    main()
e80d55
diff --git a/roles/ipaclient/library/ipaclient_setup_nss.py b/roles/ipaclient/library/ipaclient_setup_nss.py
e80d55
index 3dc0dcc..240bc76 100644
e80d55
--- a/roles/ipaclient/library/ipaclient_setup_nss.py
e80d55
+++ b/roles/ipaclient/library/ipaclient_setup_nss.py
e80d55
@@ -177,7 +177,7 @@ from ansible.module_utils.ansible_ipa_client import (
e80d55
     options, sysrestore, paths, ansible_module_get_parsed_ip_addresses,
e80d55
     api, errors, create_ipa_nssdb, ipautil, ScriptError, CLIENT_INSTALL_ERROR,
e80d55
     get_certs_from_ldap, DN, certstore, x509, logger, certdb,
e80d55
-    CalledProcessError, tasks, client_dns, configure_certmonger, services,
e80d55
+    CalledProcessError, tasks, client_dns, services,
e80d55
     update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
e80d55
     configure_openldap_conf, hardcode_ldap_server, getargspec, NUM_VERSION,
e80d55
     serialization
e80d55
@@ -350,8 +350,6 @@ def main():
e80d55
 
e80d55
         if not options.on_master:
e80d55
             client_dns(cli_server[0], hostname, options)
e80d55
-            configure_certmonger(fstore, subject_base, cli_realm, hostname,
e80d55
-                                 options, ca_enabled)
e80d55
 
e80d55
         if hasattr(paths, "SSH_CONFIG_DIR"):
e80d55
             ssh_config_dir = paths.SSH_CONFIG_DIR
e80d55
diff --git a/roles/ipaclient/library/ipaclient_temp_krb5.py b/roles/ipaclient/library/ipaclient_temp_krb5.py
e80d55
new file mode 100644
e80d55
index 0000000..cbe652c
e80d55
--- /dev/null
e80d55
+++ b/roles/ipaclient/library/ipaclient_temp_krb5.py
e80d55
@@ -0,0 +1,163 @@
e80d55
+# -*- coding: utf-8 -*-
e80d55
+
e80d55
+# Authors:
e80d55
+#   Thomas Woerner <twoerner@redhat.com>
e80d55
+#
e80d55
+# Based on ipa-client-install code
e80d55
+#
e80d55
+# Copyright (C) 2017-2022  Red Hat
e80d55
+# see file 'COPYING' for use and warranty information
e80d55
+#
e80d55
+# This program is free software; you can redistribute it and/or modify
e80d55
+# it under the terms of the GNU General Public License as published by
e80d55
+# the Free Software Foundation, either version 3 of the License, or
e80d55
+# (at your option) any later version.
e80d55
+#
e80d55
+# This program is distributed in the hope that it will be useful,
e80d55
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
e80d55
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e80d55
+# GNU General Public License for more details.
e80d55
+#
e80d55
+# You should have received a copy of the GNU General Public License
e80d55
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
e80d55
+
e80d55
+from __future__ import (absolute_import, division, print_function)
e80d55
+
e80d55
+__metaclass__ = type
e80d55
+
e80d55
+ANSIBLE_METADATA = {
e80d55
+    'metadata_version': '1.0',
e80d55
+    'supported_by': 'community',
e80d55
+    'status': ['preview'],
e80d55
+}
e80d55
+
e80d55
+DOCUMENTATION = '''
e80d55
+---
e80d55
+module: ipaclient_temp_krb5
e80d55
+short_description:
e80d55
+  Create temporary krb5 configuration.
e80d55
+description:
e80d55
+  Create temporary krb5 configuration for deferring the creation of the final
e80d55
+  krb5.conf on clients
e80d55
+options:
e80d55
+  servers:
e80d55
+    description: Fully qualified name of IPA servers to enroll to
e80d55
+    type: list
e80d55
+    elements: str
e80d55
+    required: yes
e80d55
+  domain:
e80d55
+    description: Primary DNS domain of the IPA deployment
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  realm:
e80d55
+    description: Kerberos realm name of the IPA deployment
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  hostname:
e80d55
+    description: Fully qualified name of this host
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  kdc:
e80d55
+    description: The name or address of the host running the KDC
e80d55
+    type: str
e80d55
+    required: yes
e80d55
+  on_master:
e80d55
+    description: Whether the configuration is done on the master or not
e80d55
+    type: bool
e80d55
+    required: no
e80d55
+    default: no
e80d55
+author:
e80d55
+    - Thomas Woerner (@t-woerner)
e80d55
+'''
e80d55
+
e80d55
+EXAMPLES = '''
e80d55
+# Test IPA with local keytab
e80d55
+- name: Test IPA in force mode with maximum 5 kinit attempts
e80d55
+  ipaclient_test_keytab:
e80d55
+    servers: ["server1.example.com","server2.example.com"]
e80d55
+    domain: example.com
e80d55
+    realm: EXAMPLE.COM
e80d55
+    kdc: server1.example.com
e80d55
+    hostname: client1.example.com
e80d55
+
e80d55
+# Test IPA with ipadiscovery return values
e80d55
+- name: Join IPA
e80d55
+  ipaclient_test_keytab:
e80d55
+    servers: "{{ ipadiscovery.servers }}"
e80d55
+    domain: "{{ ipadiscovery.domain }}"
e80d55
+    realm: "{{ ipadiscovery.realm }}"
e80d55
+    kdc: "{{ ipadiscovery.kdc }}"
e80d55
+    hostname: "{{ ipadiscovery.hostname }}"
e80d55
+'''
e80d55
+
e80d55
+RETURN = '''
e80d55
+krb_name:
e80d55
+  description: The krb5 config file name
e80d55
+  returned: always
e80d55
+  type: str
e80d55
+'''
e80d55
+
e80d55
+import os
e80d55
+import tempfile
e80d55
+
e80d55
+from ansible.module_utils.basic import AnsibleModule
e80d55
+from ansible.module_utils.ansible_ipa_client import (
e80d55
+    setup_logging, check_imports, configure_krb5_conf
e80d55
+)
e80d55
+
e80d55
+
e80d55
+def main():
e80d55
+    module = AnsibleModule(
e80d55
+        argument_spec=dict(
e80d55
+            servers=dict(required=True, type='list', elements='str'),
e80d55
+            domain=dict(required=True, type='str'),
e80d55
+            realm=dict(required=True, type='str'),
e80d55
+            hostname=dict(required=True, type='str'),
e80d55
+            kdc=dict(required=True, type='str'),
e80d55
+            on_master=dict(required=False, type='bool', default=False),
e80d55
+        ),
e80d55
+        supports_check_mode=False,
e80d55
+    )
e80d55
+
e80d55
+    module._ansible_debug = True
e80d55
+    check_imports(module)
e80d55
+    setup_logging()
e80d55
+
e80d55
+    servers = module.params.get('servers')
e80d55
+    domain = module.params.get('domain')
e80d55
+    realm = module.params.get('realm')
e80d55
+    hostname = module.params.get('hostname')
e80d55
+    kdc = module.params.get('kdc')
e80d55
+    client_domain = hostname[hostname.find(".") + 1:]
e80d55
+
e80d55
+    krb_name = None
e80d55
+    # Create temporary krb5 configuration
e80d55
+    try:
e80d55
+        (krb_fd, krb_name) = tempfile.mkstemp()
e80d55
+        os.close(krb_fd)
e80d55
+        configure_krb5_conf(
e80d55
+            cli_realm=realm,
e80d55
+            cli_domain=domain,
e80d55
+            cli_server=servers,
e80d55
+            cli_kdc=kdc,
e80d55
+            dnsok=False,
e80d55
+            filename=krb_name,
e80d55
+            client_domain=client_domain,
e80d55
+            client_hostname=hostname,
e80d55
+            configure_sssd=True,
e80d55
+            force=False)
e80d55
+    except Exception as ex:
e80d55
+        if krb_name:
e80d55
+            try:
e80d55
+                os.remove(krb_name)
e80d55
+            except OSError:
e80d55
+                module.fail_json(msg="Could not remove %s" % krb_name)
e80d55
+        module.fail_json(
e80d55
+            msg="Failed to create temporary krb5 configuration: %s" % str(ex))
e80d55
+
e80d55
+    module.exit_json(changed=False,
e80d55
+                     krb_name=krb_name)
e80d55
+
e80d55
+
e80d55
+if __name__ == '__main__':
e80d55
+    main()
e80d55
diff --git a/roles/ipaclient/library/ipaclient_test_keytab.py b/roles/ipaclient/library/ipaclient_test_keytab.py
e80d55
index 3f1c69d..3bebeea 100644
e80d55
--- a/roles/ipaclient/library/ipaclient_test_keytab.py
e80d55
+++ b/roles/ipaclient/library/ipaclient_test_keytab.py
e80d55
@@ -244,6 +244,12 @@ def main():
e80d55
                 os.remove(krb_name)
e80d55
             except OSError:
e80d55
                 module.fail_json(msg="Could not remove %s" % krb_name)
e80d55
+            if os.path.exists(krb_name + ".ipabkp"):
e80d55
+                try:
e80d55
+                    os.remove(krb_name + ".ipabkp")
e80d55
+                except OSError:
e80d55
+                    module.fail_json(
e80d55
+                        msg="Could not remove %s.ipabkp" % krb_name)
e80d55
 
e80d55
     module.exit_json(changed=False,
e80d55
                      krb5_keytab_ok=krb5_keytab_ok,
e80d55
diff --git a/roles/ipaclient/tasks/install.yml b/roles/ipaclient/tasks/install.yml
e80d55
index fa33f89..1b889d0 100644
e80d55
--- a/roles/ipaclient/tasks/install.yml
e80d55
+++ b/roles/ipaclient/tasks/install.yml
e80d55
@@ -239,12 +239,19 @@
e80d55
       hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
     when: not ipaclient_on_master | bool
e80d55
 
e80d55
-  - name: Install - Join IPA
e80d55
-    ipaclient_join:
e80d55
+  - name: Install - Create temporary krb5 configuration
e80d55
+    ipaclient_temp_krb5:
e80d55
       servers: "{{ result_ipaclient_test.servers }}"
e80d55
       domain: "{{ result_ipaclient_test.domain }}"
e80d55
       realm: "{{ result_ipaclient_test.realm }}"
e80d55
+      hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
       kdc: "{{ result_ipaclient_test.kdc }}"
e80d55
+    register: result_ipaclient_temp_krb5
e80d55
+
e80d55
+  - name: Install - Join IPA
e80d55
+    ipaclient_join:
e80d55
+      servers: "{{ result_ipaclient_test.servers }}"
e80d55
+      realm: "{{ result_ipaclient_test.realm }}"
e80d55
       basedn: "{{ result_ipaclient_test.basedn }}"
e80d55
       hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
       force_join: "{{ ipaclient_force_join | default(omit) }}"
e80d55
@@ -255,6 +262,7 @@
e80d55
       admin_keytab: "{{ ipaadmin_keytab if ipaadmin_keytab is defined and not ipaclient_use_otp | bool else omit }}"
e80d55
       # ca_cert_file: "{{ ipaclient_ca_cert_file | default(omit) }}"
e80d55
       kinit_attempts: "{{ ipaclient_kinit_attempts | default(omit) }}"
e80d55
+      krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
e80d55
     register: result_ipaclient_join
e80d55
     when: not ipaclient_on_master | bool and
e80d55
           (not result_ipaclient_test_keytab.krb5_keytab_ok or
e80d55
@@ -323,26 +331,13 @@
e80d55
           "{{ ipassd_no_krb5_offline_passwords
e80d55
               | default(ipasssd_no_krb5_offline_passwords) }}"
e80d55
 
e80d55
-    - name: Install - Configure krb5 for IPA realm
e80d55
-      ipaclient_setup_krb5:
e80d55
-        realm: "{{ result_ipaclient_test.realm }}"
e80d55
-        domain: "{{ result_ipaclient_test.domain }}"
e80d55
-        servers: "{{ result_ipaclient_test.servers }}"
e80d55
-        kdc: "{{ result_ipaclient_test.kdc }}"
e80d55
-        dnsok: "{{ result_ipaclient_test.dnsok }}"
e80d55
-        client_domain: "{{ result_ipaclient_test.client_domain }}"
e80d55
-        hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
-        sssd: "{{ result_ipaclient_test.sssd }}"
e80d55
-        force: "{{ ipaclient_force }}"
e80d55
-        # on_master: "{{ ipaclient_on_master }}"
e80d55
-      when: not ipaclient_on_master | bool
e80d55
-
e80d55
     - name: Install - IPA API calls for remaining enrollment parts
e80d55
       ipaclient_api:
e80d55
         servers: "{{ result_ipaclient_test.servers }}"
e80d55
         realm: "{{ result_ipaclient_test.realm }}"
e80d55
         hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
         # debug: yes
e80d55
+        krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
e80d55
       register: result_ipaclient_api
e80d55
 
e80d55
     - name: Install - Fix IPA ca
e80d55
@@ -412,6 +407,36 @@
e80d55
         domain: "{{ result_ipaclient_test.domain }}"
e80d55
         nisdomain: "{{ ipaclient_nisdomain | default(omit) }}"
e80d55
       when: not ipaclient_no_nisdomain | bool
e80d55
+
e80d55
+    - name: Remove temporary krb5.conf
e80d55
+      ansible.builtin.file:
e80d55
+        path: "{{ result_ipaclient_temp_krb5.krb_name }}"
e80d55
+        state: absent
e80d55
+      when: result_ipaclient_temp_krb5.krb_name is defined
e80d55
+
e80d55
+    - name: Install - Configure krb5 for IPA realm
e80d55
+      ipaclient_setup_krb5:
e80d55
+        realm: "{{ result_ipaclient_test.realm }}"
e80d55
+        domain: "{{ result_ipaclient_test.domain }}"
e80d55
+        servers: "{{ result_ipaclient_test.servers }}"
e80d55
+        kdc: "{{ result_ipaclient_test.kdc }}"
e80d55
+        dnsok: "{{ result_ipaclient_test.dnsok }}"
e80d55
+        client_domain: "{{ result_ipaclient_test.client_domain }}"
e80d55
+        hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
+        sssd: "{{ result_ipaclient_test.sssd }}"
e80d55
+        force: "{{ ipaclient_force }}"
e80d55
+        # on_master: "{{ ipaclient_on_master }}"
e80d55
+      when: not ipaclient_on_master | bool
e80d55
+
e80d55
+    - name: Install - Configure certmonger
e80d55
+      ipaclient_setup_certmonger:
e80d55
+        realm: "{{ result_ipaclient_test.realm }}"
e80d55
+        hostname: "{{ result_ipaclient_test.hostname }}"
e80d55
+        subject_base: "{{ result_ipaclient_api.subject_base }}"
e80d55
+        ca_enabled: "{{ result_ipaclient_api.ca_enabled }}"
e80d55
+        request_cert: "{{ ipaclient_request_cert }}"
e80d55
+      when: not ipaclient_on_master | bool
e80d55
+
e80d55
   always:
e80d55
   - name: Install - Restore original admin password if overwritten by OTP
e80d55
     no_log: yes
e80d55
@@ -423,3 +448,15 @@
e80d55
     ansible.builtin.file:
e80d55
       path: "/etc/ipa/.dns_ccache"
e80d55
       state: absent
e80d55
+
e80d55
+  - name: Remove temporary krb5.conf
e80d55
+    ansible.builtin.file:
e80d55
+      path: "{{ result_ipaclient_temp_krb5.krb_name }}"
e80d55
+      state: absent
e80d55
+    when: result_ipaclient_temp_krb5.krb_name is defined
e80d55
+
e80d55
+  - name: Remove temporary krb5.conf backup
e80d55
+    ansible.builtin.file:
e80d55
+      path: "{{ result_ipaclient_temp_krb5.krb_name }}.ipabkp"
e80d55
+      state: absent
e80d55
+    when: result_ipaclient_temp_krb5.krb_name is defined
e80d55
-- 
e80d55
2.39.2
e80d55