public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/freeipa] rawhide: clean up unused patches
@ 2026-09-19  9:22 Alexander Bokovoy
  0 siblings, 0 replies; only message in thread
From: Alexander Bokovoy @ 2026-09-19  9:22 UTC (permalink / raw)
  To: git-commits

            A new commit has been pushed.

            Repo   : rpms/freeipa
            Branch : rawhide
            Commit : 6b2fbcd10f3f784c5a80f82e5b35854d27916832
            Author : Alexander Bokovoy <abokovoy@redhat.com>
            Date   : 2026-09-19T11:17:34+03:00
            Stats  : +0/-26115 in 13 file(s)
            URL    : https://src.fedoraproject.org/rpms/freeipa/c/6b2fbcd10f3f784c5a80f82e5b35854d27916832?branch=rawhide

            Log:
            clean up unused patches

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>

---
diff --git a/0001-SELinux-expand-policy-coverage-for-Kerberos-usage.patch b/0001-SELinux-expand-policy-coverage-for-Kerberos-usage.patch
deleted file mode 100644
index a2b53e7..0000000
--- a/0001-SELinux-expand-policy-coverage-for-Kerberos-usage.patch
+++ /dev/null
@@ -1,343 +0,0 @@
-From 1ba343fbdfec2bbd5809363c8d1a7b2e2c09010b Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 12 Jan 2026 10:54:35 +0200
-Subject: [PATCH 1/2] SELinux: expand policy coverage for Kerberos usage
-
-- Separate SSSD passwordless auth helpers to sssd_mfa_t context and
-  move rules defining network and resources access to it
-- Extract /var/lib/ipa/pki/* to ipa_public_pki_t file context
-- Allow ipa_otpd_t context to start sssd_mfa_t context executables
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- selinux/ipa.fc |   7 +--
- selinux/ipa.if |  35 +++++++++++++--
- selinux/ipa.te | 114 +++++++++++++++++++++++++++++++++++++++++++++++--
- 3 files changed, 147 insertions(+), 9 deletions(-)
-
-diff --git a/selinux/ipa.fc b/selinux/ipa.fc
-index ffab59933..29aee2c28 100644
---- a/selinux/ipa.fc
-+++ b/selinux/ipa.fc
-@@ -8,8 +8,8 @@
- 
- /usr/libexec/ipa-otpd		--	gen_context(system_u:object_r:ipa_otpd_exec_t,s0)
- /usr/libexec/ipa/ipa-otpd		--	gen_context(system_u:object_r:ipa_otpd_exec_t,s0)
--/usr/libexec/sssd/oidc_child 	-- gen_context(system_u:object_r:ipa_otpd_exec_t,s0)
--/usr/libexec/sssd/passkey_child 	-- gen_context(system_u:object_r:ipa_otpd_exec_t,s0)
-+/usr/libexec/sssd/oidc_child 	-- gen_context(system_u:object_r:sssd_mfa_exec_t,s0)
-+/usr/libexec/sssd/passkey_child 	-- gen_context(system_u:object_r:sssd_mfa_exec_t,s0)
- 
- /usr/libexec/ipa/ipa-ods-exporter	--	gen_context(system_u:object_r:ipa_ods_exporter_exec_t,s0)
- 
-@@ -20,7 +20,8 @@
- /usr/libexec/ipa/oddjob/org\.freeipa.*  --  gen_context(system_u:object_r:ipa_helper_exec_t,s0)
- 
- 
--/var/lib/ipa(/.*)?              gen_context(system_u:object_r:ipa_var_lib_t,s0)
-+/var/lib/ipa/pki(/.*)?        gen_context(system_u:object_r:ipa_public_pki_t,s0)
-+/var/lib/ipa(/.*)?            gen_context(system_u:object_r:ipa_var_lib_t,s0)
- 
- /var/lib/ipa/gssproxy/http.keytab -- gen_context(system_u:object_r:ipa_http_keytab_t,s0)
- 
-diff --git a/selinux/ipa.if b/selinux/ipa.if
-index 8f3147e10..0d263e1e0 100644
---- a/selinux/ipa.if
-+++ b/selinux/ipa.if
-@@ -12,11 +12,12 @@
- #
- interface(`ipa_domtrans_otpd',`
- 	gen_require(`
--		type ipa_otpd_t, ipa_otpd_exec_t;
-+		type ipa_otpd_t, ipa_otpd_exec_t, sssd_mfa_exec_t;
- 	')
- 
- 	corecmd_search_bin($1)
- 	domtrans_pattern($1, ipa_otpd_exec_t, ipa_otpd_t)
-+	domtrans_pattern($1, sssd_mfa_exec_t, ipa_otpd_t)
- ')
- 
- ########################################
-@@ -108,10 +109,11 @@ interface(`ipa_run_helper',`
- #
- interface(`ipa_search_lib',`
- 	gen_require(`
--		type ipa_var_lib_t;
-+		type ipa_var_lib_t, ipa_public_pki_t;
- 	')
- 
-     search_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+    search_dirs_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
- ')
- 
- ########################################
-@@ -126,11 +128,13 @@ interface(`ipa_search_lib',`
- #
- interface(`ipa_manage_lib',`
- 	gen_require(`
--		type ipa_var_lib_t;
-+		type ipa_var_lib_t, ipa_public_pki_t;
- 	')
- 
-     manage_files_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-     manage_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+    manage_files_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
-+    manage_dirs_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
- ')
- 
- ########################################
-@@ -167,11 +171,14 @@ interface(`ipa_read_lib',`
- 	gen_require(`
- 		type ipa_http_keytab_t;
- 		type ipa_var_lib_t;
-+		type ipa_public_pki_t;
- 	')
- 
-     read_files_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+    read_files_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
-     read_files_pattern($1, ipa_http_keytab_t, ipa_http_keytab_t)
-     list_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+    list_dirs_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
- ')
- 
- ########################################
-@@ -195,6 +202,28 @@ interface(`ipa_read_http_keytab',`
-     list_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
- ')
- 
-+########################################
-+## <summary>
-+##      Allow domain to read public PKI files
-+##      Also give access to realmd_var_lib_t for upgrade
-+## </summary>
-+## <param name="domain">
-+##	<summary>
-+##	Domain allowed access.
-+##	</summary>
-+## </param>
-+#
-+interface(`ipa_read_public_pki',`
-+	gen_require(`
-+		type ipa_public_pki_t, realmd_var_lib_t;
-+	')
-+
-+    read_files_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
-+    list_dirs_pattern($1, ipa_public_pki_t, ipa_public_pki_t)
-+    read_files_pattern($1, realmd_var_lib_t, realmd_var_lib_t)
-+    list_dirs_pattern($1, realmd_var_lib_t, realmd_var_lib_t)
-+')
-+
- ########################################
- ## <summary>
- ##	Allow domain to manage ipa run files/dirs.
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index b53540518..7ea771a11 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -14,12 +14,15 @@ type ipa_otpd_t, ipa_domain;
- type ipa_otpd_exec_t;
- init_daemon_domain(ipa_otpd_t, ipa_otpd_exec_t)
- 
--# for oidc_child communication with IdPs
--corenet_tcp_connect_http_port(ipa_otpd_t)
-+type sssd_mfa_t, ipa_domain;
-+type sssd_mfa_exec_t;
-+can_exec(sssd_mfa_t,sssd_mfa_exec_t)
-+
- kernel_dgram_send(ipa_otpd_t)
- allow ipa_otpd_t self:unix_dgram_socket { create getopt setopt };
- allow ipa_otpd_t ipa_otpd_exec_t:file execute_no_trans;
- 
-+
- type ipa_dnskey_t, ipa_domain;
- type ipa_dnskey_exec_t;
- init_daemon_domain(ipa_dnskey_t, ipa_dnskey_exec_t)
-@@ -43,6 +46,9 @@ logging_log_file(ipa_log_t)
- type ipa_var_lib_t;
- files_type(ipa_var_lib_t)
- 
-+type ipa_public_pki_t;
-+files_type(ipa_public_pki_t)
-+
- type ipa_http_keytab_t;
- files_type(ipa_http_keytab_t)
- 
-@@ -88,6 +94,28 @@ type ipa_pki_retrieve_key_t;
- domain_type(ipa_pki_retrieve_key_t)
- init_script_file(ipa_pki_retrieve_key_exec_t)
- 
-+# Allow dirsrv to operate with Anonymous PKINIT
-+optional_policy(`
-+  gen_require(`
-+     type dirsrv_t;
-+  ')
-+   
-+	ipa_read_lib(dirsrv_t)
-+	ipa_search_lib(dirsrv_t)
-+        ipa_read_public_pki(dirsrv_t)
-+')
-+
-+# Allow certmonger to operate with Anonymous PKINIT
-+optional_policy(`
-+  gen_require(`
-+     type certmonger_t;
-+  ')
-+   
-+	ipa_read_lib(certmonger_t)
-+	ipa_search_lib(certmonger_t)
-+        ipa_read_public_pki(certmonger_t)
-+')
-+
- ########################################
- #
- # ipa_otpd local policy
-@@ -99,6 +127,7 @@ allow ipa_otpd_t self:fifo_file rw_fifo_file_perms;
- allow ipa_otpd_t self:unix_stream_socket create_stream_socket_perms;
- 
- read_files_pattern(ipa_otpd_t, ipa_cert_t, ipa_cert_t)
-+read_files_pattern(ipa_otpd_t, ipa_public_pki_t, ipa_public_pki_t)
- read_lnk_files_pattern(ipa_otpd_t, ipa_cert_t, ipa_cert_t)
- 
- manage_dirs_pattern(ipa_otpd_t, ipa_var_run_t, ipa_var_run_t)
-@@ -110,7 +139,6 @@ corenet_tcp_connect_radius_port(ipa_otpd_t)
- dev_read_urand(ipa_otpd_t)
- dev_read_rand(ipa_otpd_t)
- dev_read_sysfs(ipa_otpd_t)
--dev_rw_generic_usb_dev(ipa_otpd_t)
- 
- sysnet_dns_name_resolve(ipa_otpd_t)
- 
-@@ -127,6 +155,75 @@ optional_policy(`
- ')
- 
- logging_send_syslog_msg(ipa_otpd_t)
-+
-+########################################
-+#
-+# sssd_mfa local policy
-+# applies to sssd's oidc_child and passkey_child
-+#
-+
-+# for oidc_child communication with IdPs
-+corenet_tcp_connect_http_port(sssd_mfa_t)
-+kernel_dgram_send(sssd_mfa_t)
-+allow sssd_mfa_t self:unix_dgram_socket { create getopt setopt };
-+allow sssd_mfa_t sssd_mfa_exec_t:file execute_no_trans;
-+allow sssd_mfa_exec_t fs_t:filesystem associate;
-+#===========================
-+# allow relabel of the older files during upgrade
-+optional_policy(`
-+  gen_require(`
-+     type setfiles_t;
-+     type rpm_t;
-+     type unconfined_t;
-+  ')
-+    allow setfiles_t sssd_mfa_exec_t:file { getattr open read relabelto rename setattr unlink };
-+    allow rpm_t sssd_mfa_exec_t:file { getattr open read relabelto rename setattr unlink };
-+    allow unconfined_t sssd_mfa_exec_t:file { getattr open read relabelto rename setattr unlink };
-+')
-+
-+dev_read_urand(sssd_mfa_t)
-+dev_read_rand(sssd_mfa_t)
-+dev_read_sysfs(sssd_mfa_t)
-+dev_rw_generic_usb_dev(sssd_mfa_t)
-+
-+sysnet_dns_name_resolve(sssd_mfa_t)
-+
-+optional_policy(`
-+    dirsrv_stream_connect(sssd_mfa_t)
-+')
-+
-+optional_policy(`
-+    kerberos_use(sssd_mfa_t)
-+')
-+
-+optional_policy(`
-+    sssd_stream_connect(sssd_mfa_t)
-+')
-+
-+logging_send_syslog_msg(sssd_mfa_t)
-+
-+optional_policy(`
-+	ipa_read_lib(sssd_mfa_t)
-+	ipa_search_lib(sssd_mfa_t)
-+        ipa_read_public_pki(sssd_mfa_t)
-+')
-+
-+
-+########################################
-+#
-+# samba local policy extensions
-+#
-+
-+optional_policy(`
-+    gen_require(`
-+        type smbd_t, winbind_t, samba_net_t;
-+    ')
-+        ipa_read_public_pki(smbd_t)
-+        ipa_read_public_pki(winbind_t)
-+        ipa_read_public_pki(samba_net_t)
-+')
-+
-+
- ########################################
- #
- # password policy local policy
-@@ -196,6 +289,10 @@ ipa_read_lib(ipa_helper_t)
- 
- logging_send_syslog_msg(ipa_helper_t)
- 
-+optional_policy(`
-+    ipa_read_public_pki(ipa_helper_t)
-+')
-+
- optional_policy(`
-     dirsrv_stream_connect(ipa_helper_t)
- ')
-@@ -298,6 +395,10 @@ miscfiles_read_generic_certs(ipa_dnskey_t)
- 
- sysnet_read_config(ipa_dnskey_t)
- 
-+optional_policy(`
-+    ipa_read_public_pki(ipa_dnskey_t)
-+')
-+
- optional_policy(`
-     apache_search_config(ipa_dnskey_t)
- ')
-@@ -359,6 +460,10 @@ miscfiles_read_generic_certs(ipa_ods_exporter_t)
- 
- sysnet_read_config(ipa_ods_exporter_t)
- 
-+optional_policy(`
-+    ipa_read_public_pki(ipa_ods_exporter_t)
-+')
-+
- optional_policy(`
-     bind_search_cache(ipa_ods_exporter_t)
- ')
-@@ -451,6 +556,7 @@ optional_policy(`
- optional_policy(`
- 	ipa_read_lib(ipa_custodia_t)
- 	ipa_search_lib(ipa_custodia_t)
-+        ipa_read_public_pki(ipa_custodia_t)
- ')
- 
- optional_policy(`
-@@ -547,6 +653,7 @@ optional_policy(`
-         type gssproxy_t;
-     ')
-     ipa_read_http_keytab(gssproxy_t)
-+    ipa_read_public_pki(gssproxy_t)
- ')
- 
- # Allow Cockpit to use HTTP keytab on IPA servers for GSSAPI authentication
-@@ -555,4 +662,5 @@ optional_policy(`
-         type cockpit_session_t;
-     ')
-     ipa_read_http_keytab(cockpit_session_t)
-+    ipa_read_public_pki(cockpit_session_t)
- ')
--- 
-2.52.0
-

diff --git a/0002-ipa-sam-use-internal-Samba-method-to-populate-in-mem.patch b/0002-ipa-sam-use-internal-Samba-method-to-populate-in-mem.patch
deleted file mode 100644
index 558f545..0000000
--- a/0002-ipa-sam-use-internal-Samba-method-to-populate-in-mem.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-From 1c3f1cddb997d819774e2f65b5349c3dbe474ea4 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 15 Jan 2026 13:05:05 +0200
-Subject: [PATCH 2/2] ipa-sam: use internal Samba method to populate in-memory
- keytab
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- daemons/ipa-sam/Makefile.am | 1 +
- daemons/ipa-sam/ipa_sam.c   | 5 +++--
- 2 files changed, 4 insertions(+), 2 deletions(-)
-
-diff --git a/daemons/ipa-sam/Makefile.am b/daemons/ipa-sam/Makefile.am
-index f80455339..cb9026154 100644
---- a/daemons/ipa-sam/Makefile.am
-+++ b/daemons/ipa-sam/Makefile.am
-@@ -4,6 +4,7 @@ SAMBA40EXTRA_LIBS = $(SAMBA40EXTRA_LIBPATH)	\
- 			-lsmbldap		\
- 			-l$(LIBPDB_NAME)			\
- 			-lsmbconf		\
-+			-lgse-private-samba	\
- 			$(NULL)
- 
- AM_CPPFLAGS =						\
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index ea25934d5..261a4b62e 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -4895,7 +4895,7 @@ static krb5_error_code bind_callback_obtain_creds(struct ipasam_sasl_interact_pr
- 	return rc;
- }
- 
--extern const char * lp_dedicated_keytab_file(void);
-+extern krb5_error_code gse_krb5_get_server_keytab(krb5_context krbctx, krb5_keytab *keytab);
- static int bind_callback(LDAP *ldap_struct, struct smbldap_state *ldap_state, void* ipasam_priv) {
- 	krb5_error_code rc;
- 	krb5_creds *out_creds = NULL;
-@@ -4937,7 +4937,8 @@ static int bind_callback(LDAP *ldap_struct, struct smbldap_state *ldap_state, vo
- 		return LDAP_LOCAL_ERROR;
- 	}
- 
--	rc = krb5_kt_resolve(data.context, lp_dedicated_keytab_file(), &data.keytab);
-+
-+	rc = gse_krb5_get_server_keytab(data.context, &data.keytab);
- 	if (rc) {
- 		bind_callback_cleanup(&data, rc);
- 		return LDAP_LOCAL_ERROR;
--- 
-2.52.0
-

diff --git a/0005-Add-path-to-identify-the-tomcat-version-for-Tomcat-10.patch b/0005-Add-path-to-identify-the-tomcat-version-for-Tomcat-10.patch
deleted file mode 100644
index 6ec1b7f..0000000
--- a/0005-Add-path-to-identify-the-tomcat-version-for-Tomcat-10.patch
+++ /dev/null
@@ -1,65 +0,0 @@
-From ed8091860be1c4d5805559535d5f48261c69c29e Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Wed, 23 Jul 2025 13:20:50 -0400
-Subject: [PATCH] Add path to identify the tomcat version for Tomcat 10
-
-Tomcat in Fedora previously used some different locations
-for scripts than upstream tomcat. In Fedora 45 these
-customizations are being dropped.
-
-Instead of being able to call /usr/sbin/tomcat directly the
-scripts are split between /usr/libexec and /usr/share/tomcat/bin.
-
-We used to call /usr/sbin/tomcat to get the version number for
-backwards compatibility. We can use the script version.sh
-instead of calling tomcat/catalina.sh directly.
-
-Fixes: https://pagure.io/freeipa/issue/9832
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
----
- ipaplatform/fedora/paths.py | 3 +++
- ipaplatform/rhel/paths.py   | 3 +++
- 2 files changed, 6 insertions(+)
-
-diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py
-index 4e993c063e2..2ea43f7324d 100644
---- a/ipaplatform/fedora/paths.py
-+++ b/ipaplatform/fedora/paths.py
-@@ -27,6 +27,7 @@
- 
- from ipaplatform.redhat.paths import RedHatPathNamespace
- from ipaplatform.fedora.constants import HAS_NFS_CONF
-+from ipaplatform.osinfo import osinfo
- 
- 
- class FedoraPathNamespace(RedHatPathNamespace):
-@@ -36,6 +37,8 @@ class FedoraPathNamespace(RedHatPathNamespace):
-     NAMED_CRYPTO_POLICY_FILE = "/etc/crypto-policies/back-ends/bind.config"
-     if HAS_NFS_CONF:
-         SYSCONFIG_NFS = '/etc/nfs.conf'
-+    if osinfo.version_number >= (45,):
-+        BIN_TOMCAT = "/usr/share/tomcat/bin/version.sh"
- 
- 
- paths = FedoraPathNamespace()
-diff --git a/ipaplatform/rhel/paths.py b/ipaplatform/rhel/paths.py
-index 3631550eba5..f348509e1b7 100644
---- a/ipaplatform/rhel/paths.py
-+++ b/ipaplatform/rhel/paths.py
-@@ -27,12 +27,15 @@
- 
- from ipaplatform.redhat.paths import RedHatPathNamespace
- from ipaplatform.rhel.constants import HAS_NFS_CONF
-+from ipaplatform.osinfo import osinfo
- 
- 
- class RHELPathNamespace(RedHatPathNamespace):
-     NAMED_CRYPTO_POLICY_FILE = "/etc/crypto-policies/back-ends/bind.config"
-     if HAS_NFS_CONF:
-         SYSCONFIG_NFS = '/etc/nfs.conf'
-+    if osinfo.version_number >= (11,0):
-+        BIN_TOMCAT = "/usr/share/tomcat/bin/version.sh"
- 
- 
- paths = RHELPathNamespace()

diff --git a/freeipa-4-12-2-post-updates-1.patch b/freeipa-4-12-2-post-updates-1.patch
deleted file mode 100644
index b587e70..0000000
--- a/freeipa-4-12-2-post-updates-1.patch
+++ /dev/null
@@ -1,14539 +0,0 @@
-From ad4b7f6cedaed54acf279033b650010c65face10 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Tue, 20 Aug 2024 14:52:03 +0530
-Subject: [PATCH 003/116] ipatests: Check Default PAC type is added to config
-
-This patch checks that the default PAC type
-is added to configuration i.e ipaKrbAuthzData: MS-PAC
-during ipa-server-installation
-
-The patch also checks that if 'ipaKrbAuthzData: MS-PAC'
-attribute is deleted and then when we run 'ipa-server-upgrade'
-command the attribute is added back.
-
-Related: https://pagure.io/freeipa/issue/9632
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_installation.py     | 15 +++++++++++
- ipatests/test_integration/test_upgrade.py     | 26 ++++++++++++++++++-
- 2 files changed, 40 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index ada43e33f..c5565c452 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -1190,6 +1190,21 @@ class TestInstallMaster(IntegrationTest):
-                 expected_stdout=f'href="https://{self.master.hostname}/'
-             )
- 
-+    def test_pac_configuration_enabled(self):
-+        """
-+        This testcase checks that the default PAC type
-+        is added to configuration.
-+        """
-+        base_dn = str(self.master.domain.basedn)
-+        dn = DN(
-+            ("cn", "ipaConfig"),
-+            ("cn", "etc"),
-+            base_dn
-+        )
-+        result = tasks.ldapsearch_dm(self.master, str(dn),
-+                                     ["ipaKrbAuthzData"])
-+        assert 'ipaKrbAuthzData: MS-PAC' in result.stdout_text
-+
-     def test_hostname_parameter(self, server_cleanup):
-         """
-         Test that --hostname parameter is respected in interactive mode.
-diff --git a/ipatests/test_integration/test_upgrade.py b/ipatests/test_integration/test_upgrade.py
-index 011de939e..a0f393780 100644
---- a/ipatests/test_integration/test_upgrade.py
-+++ b/ipatests/test_integration/test_upgrade.py
-@@ -165,7 +165,6 @@ class TestUpgrade(IntegrationTest):
-                 ldap.update_entry(location_krb_rec)
- 
-         yield _setup_locations
--
-         ldap = self.master.ldap_connect()
- 
-         modified = False
-@@ -491,3 +490,28 @@ class TestUpgrade(IntegrationTest):
-         tasks.reinstall_packages(self.master, ['*ipa-client'])
-         assert not self.master.transport.file_exists(
-             paths.SSH_CONFIG + ".orig")
-+
-+    def test_mspac_attribute_set(self):
-+        """
-+        This testcase deletes the already existing attribute
-+        'ipaKrbAuthzData: MS-PAC'.
-+        The test then runs ipa-server-upgrade and checks that
-+        the attribute 'ipaKrbAuthzData: MS-PAC' is added again.
-+        """
-+        base_dn = str(self.master.domain.basedn)
-+        dn = DN(
-+            ("cn", "ipaConfig"),
-+            ("cn", "etc"),
-+            base_dn
-+        )
-+        ldif = textwrap.dedent("""
-+             dn: cn=ipaConfig,cn=etc,{}
-+             changetype: modify
-+             delete: ipaKrbAuthzData
-+        """).format(base_dn)
-+        tasks.ldapmodify_dm(self.master, ldif)
-+        tasks.kinit_admin(self.master)
-+        self.master.run_command(['ipa-server-upgrade'])
-+        result = tasks.ldapsearch_dm(self.master, str(dn),
-+                                     ["ipaKrbAuthzData"])
-+        assert 'ipaKrbAuthzData: MS-PAC' in result.stdout_text
--- 
-2.49.0
-
-
-From 42eb97ee6bd8011b590aef321d4386ea9352933d Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 28 Aug 2024 10:02:19 +0300
-Subject: [PATCH 004/116] selinux: add all IPA log files to ipa_log_t file
- context
-
-We have multiple log files that produced by IPA components. Some of them
-are written by the tools that run as root and inherit their file context
-from /var/log -> var_log_t. However, increasingly we get tools that were
-run through oddjob helpers. These supposed to be run within ipa_helper_t
-SELinux context which has write permissions for ipa_log_t file context.
-
-Add all known log files from the base platform. The following script was
-used to generate them:
-$ git grep '_LOG = .*ipa.*\.log' ipaplatform/base/paths.py | cut -d= -f2 | \
-      xargs -I% echo -e "%\t--\tgen_context(system_u:object_r:ipa_log_t,s0)"
-
-/var/log/ipabackup.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaclient-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaclient-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaclientsamba-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaclientsamba-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipareplica-ca-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipareplica-conncheck.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipareplica-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/iparestore.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-enable-sid.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-adtrust-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-dns-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-kra-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaserver-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaupgrade.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipatrust-enable-agent.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipaepn.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipa-custodia.audit.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-/var/log/ipa-migrate.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-
-ipa-custodia.audit.log was already in the present list.
-
-Additionally, ipa-migrate-conflict.ldif is used by the ipa-migrate tool
-but is not provided through the ipaplatform mechanism. It is added
-explicitly.
-
-Fixes: https://pagure.io/freeipa/issue/9654
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- selinux/ipa.fc | 21 ++++++++++++++++++++-
- 1 file changed, 20 insertions(+), 1 deletion(-)
-
-diff --git a/selinux/ipa.fc b/selinux/ipa.fc
-index 700e3a14a..47bd19ba7 100644
---- a/selinux/ipa.fc
-+++ b/selinux/ipa.fc
-@@ -24,7 +24,26 @@
- 
- /var/log/ipa(/.*)?              gen_context(system_u:object_r:ipa_log_t,s0)
- 
--/var/log/ipareplica-conncheck.log.*	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipabackup.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaclient-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaclient-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaclientsamba-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaclientsamba-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipareplica-ca-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipareplica-conncheck.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipareplica-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/iparestore.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-enable-sid.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-adtrust-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-dns-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-kra-install.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaserver-uninstall.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaupgrade.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipatrust-enable-agent.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipaepn.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipa-migrate.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-+/var/log/ipa-migrate-conflict.ldif	--	gen_context(system_u:object_r:ipa_log_t,s0)
- 
- /var/run/ipa(/.*)?              gen_context(system_u:object_r:ipa_var_run_t,s0)
- 
--- 
-2.49.0
-
-
-From aa4651526e6697e15ce4960bf1d15d1389889c7f Mon Sep 17 00:00:00 2001
-From: "asharov@redhat.com" <asharov@redhat.com>
-Date: Mon, 24 Jun 2024 15:33:34 +0200
-Subject: [PATCH 005/116] Add ipa-idrange-fix
-
-ipa-idrange-fix is a tool for analysis of existing IPA ranges, users
-and groups outside of those ranges, and functionality to propose
-and apply remediations to make sure as much users and groups as
-possible end up in the IPA-managed ranges.
-
-Fixes: https://pagure.io/freeipa/issue/9612
-
-Signed-off-by: Aleksandr Sharov <asharov@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in                               |    2 +
- install/tools/Makefile.am                     |    2 +
- install/tools/ipa-idrange-fix.in              |    8 +
- install/tools/man/Makefile.am                 |    1 +
- install/tools/man/ipa-idrange-fix.1           |  111 ++
- ipaserver/install/ipa_idrange_fix.py          | 1085 +++++++++++++++++
- .../test_integration/test_ipa_idrange_fix.py  |  189 +++
- 7 files changed, 1398 insertions(+)
- create mode 100644 install/tools/ipa-idrange-fix.in
- create mode 100644 install/tools/man/ipa-idrange-fix.1
- create mode 100644 ipaserver/install/ipa_idrange_fix.py
- create mode 100644 ipatests/test_integration/test_ipa_idrange_fix.py
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index e370290bc..171b6ad27 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1517,6 +1517,7 @@ fi
- %{_sbindir}/ipa-pkinit-manage
- %{_sbindir}/ipa-crlgen-manage
- %{_sbindir}/ipa-cert-fix
-+%{_sbindir}/ipa-idrange-fix
- %{_sbindir}/ipa-acme-manage
- %{_sbindir}/ipa-migrate
- %if 0%{?fedora} >= 38
-@@ -1596,6 +1597,7 @@ fi
- %{_mandir}/man1/ipa-pkinit-manage.1*
- %{_mandir}/man1/ipa-crlgen-manage.1*
- %{_mandir}/man1/ipa-cert-fix.1*
-+%{_mandir}/man1/ipa-idrange-fix.1*
- %{_mandir}/man1/ipa-acme-manage.1*
- %{_mandir}/man1/ipa-migrate.1*
- 
-diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
-index c454fad97..ca484ec37 100644
---- a/install/tools/Makefile.am
-+++ b/install/tools/Makefile.am
-@@ -31,6 +31,7 @@ dist_noinst_DATA =		\
- 	ipa-pkinit-manage.in	\
- 	ipa-crlgen-manage.in	\
- 	ipa-cert-fix.in		\
-+	ipa-idrange-fix.in  \
- 	ipa-custodia.in		\
- 	ipa-custodia-check.in	\
- 	ipa-httpd-kdcproxy.in	\
-@@ -68,6 +69,7 @@ nodist_sbin_SCRIPTS =		\
- 	ipa-pkinit-manage	\
- 	ipa-crlgen-manage	\
- 	ipa-cert-fix		\
-+	ipa-idrange-fix		\
- 	ipa-acme-manage		\
- 	ipa-migrate	\
- 	$(NULL)
-diff --git a/install/tools/ipa-idrange-fix.in b/install/tools/ipa-idrange-fix.in
-new file mode 100644
-index 000000000..5994bd28b
---- /dev/null
-+++ b/install/tools/ipa-idrange-fix.in
-@@ -0,0 +1,8 @@
-+#!/usr/bin/python3
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+
-+from ipaserver.install.ipa_idrange_fix import IPAIDRangeFix
-+
-+IPAIDRangeFix.run_cli()
-diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
-index 34f359863..e9542a77b 100644
---- a/install/tools/man/Makefile.am
-+++ b/install/tools/man/Makefile.am
-@@ -29,6 +29,7 @@ dist_man1_MANS = 			\
- 	ipa-pkinit-manage.1		\
- 	ipa-crlgen-manage.1		\
- 	ipa-cert-fix.1			\
-+	ipa-idrange-fix.1		\
- 	ipa-acme-manage.1		\
- 	ipa-migrate.1			\
- 	$(NULL)
-diff --git a/install/tools/man/ipa-idrange-fix.1 b/install/tools/man/ipa-idrange-fix.1
-new file mode 100644
-index 000000000..178d2e887
---- /dev/null
-+++ b/install/tools/man/ipa-idrange-fix.1
-@@ -0,0 +1,111 @@
-+.\"
-+.\" Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+.\"
-+.TH "ipa-idrange-fix" "1" "May 26 2024" "IPA" "IPA Manual Pages"
-+.SH "NAME"
-+ipa\-idrange\-fix \- Analyse and fix IPA ID ranges
-+.SH "SYNOPSIS"
-+ipa\-idrange\-fix [options]
-+.SH "DESCRIPTION"
-+
-+\fIipa-idrange-fix\fR is a tool for analysis of existing IPA ranges, users and 
-+groups outside of those ranges, and functionality to propose and apply 
-+remediations to make sure as many users and groups as possible end up in the 
-+IPA-managed ranges. Before any changes are applied, a full backup of the system
-+is \fBSTRONGLY RECOMMENDED\fR.
-+
-+Do not use this program in unattended mode unless you are absolutely sure
-+you are consenting to the tool's proposals.
-+
-+You can apply the proposals manually via \fIipa idrange(1)\fR commands.
-+
-+This tool requires it to be run as \fBroot\fR and does not require a kerberos
-+ticket. The directory server needs to be running.
-+
-+\fIipa-idrange-fix\fR will read current ranges from LDAP, then check their
-+basic constraints, RID bases, etc. If it finds critical issues with ranges,
-+manual adjustment will be required.
-+
-+After analyzing existing ranges, the tool will search for users and groups that
-+are outside of ipa-local ranges. Then it will attempt to propose new ipa-local
-+ranges in order to cover users and groups found.
-+
-+Finally, the tool will summarize the analysis, and, if there are proposed
-+changes, will ask if the user wants to apply those. Please read the
-+proposals carefully before proceeding with changes!
-+
-+Important note: By default, \fIipa-idrange-fix\fR will not cover the users and 
-+groups that have IDs under 1000 as these IDs are reserved for system and
-+service users and groups. We \fBdon't recommend\fR using IDs under 1000 for
-+IPA users and groups as they can possibly overlap with local ones. Please
-+consider moving those users out of the range 1..1000, unless they are
-+absolutely needed.
-+
-+.SH "OPTIONS"
-+.TP
-+\fB\-\-version\fR
-+Show the program's version and exit.
-+.TP
-+\fB\-h\fR, \fB\-\-help\fR
-+Show the help for this program.
-+.TP
-+\fB\-\-ridoffset \fIINT\fR
-+An offset for newly proposed base RIDs for ranges. We introduce offset in order
-+to have an ability to increase ranges in the future, increase to more than
-+offset will result in RID bases overlapping, and will be denied. If set to 0,
-+there will be no offset, proposed RID ranges will start directly one after
-+another.
-+
-+Default - \fI100000\fR, allowed values - from \fI0\fR to \fI2^31\fR.
-+.TP
-+\fB\-\-rangegap \fIINT\fR
-+A number of IDs between out of ranges IDs to be considered too big to be inside 
-+a proposed range. If the gap is bigger than this attribute, a new range will be 
-+started. If set to 0, every entity will get its own range, if allowed by 
-+\fI--minrange\fR.
-+
-+Default - \fI200000\fR, allowed values - from \fI0\fR to \fI2^31\fR.
-+.TP
-+\fB\-\-minrange \fIINT\fR
-+A minimal amount of IDs the tool considers to be a valid range. All IDs that
-+would form a range with less than this number will be considered outliers, not 
-+worth creating an IDrange for, and will be listed explicitly to be moved 
-+manually. If set to 1, a range will be proposed for every entity, even if the 
-+entity is single in the middle of an empty space.
-+
-+Default - \fI10\fR, allowed values - from \fI1\fR to \fI2^31\fR.
-+.TP
-+\fB\-\-allowunder1000\fR
-+A flag to allow proposing ranges that start with IDs lower than \fI1000\fR. 
-+Remember, this is not recommended - IDs under 1000 are reserved for system and 
-+service users and groups. IDranges with these low IDs may result with 
-+overlapping of IPA and system local users and groups, which can be a serious 
-+security issue and generally produce a lot of issues around these entities' 
-+resolution.
-+.TP
-+\fB\-\-norounding\fR
-+A flag to turn off idrange starting id and size rounding - e.g. if we find 
-+ID 1234, and the size 567, it will stay that way, the proposed range will 
-+start at ID 1234, and have a 567 size. If not specified, basic rounding to 
-+outer margins will be applied. Rounding will be 10^size of the proposed range.
-+.TP
-+\fB\-\-unattended\fR
-+Run the tool in unattended mode, if any changes would be proposed, they will
-+be applied automatically.
-+.TP
-+\fB\-v\fR, \fB\-\-verbose\fR
-+Print debugging information.
-+.TP
-+\fB\-q\fR, \fB\-\-quiet\fR
-+Output only errors (output from child processes may still be shown).
-+.TP
-+\fB\-\-log\-file\fR=\fIFILE\fR
-+Log to the given file.
-+.SH "EXIT STATUS"
-+0 if the command was successful
-+
-+1 if an error occurred
-+
-+.SH "SEE ALSO"
-+.BR ipa\ idrange-mod(1)
-+.BR ipa\ idrange-add(1)
-diff --git a/ipaserver/install/ipa_idrange_fix.py b/ipaserver/install/ipa_idrange_fix.py
-new file mode 100644
-index 000000000..c6c67ae93
---- /dev/null
-+++ b/ipaserver/install/ipa_idrange_fix.py
-@@ -0,0 +1,1085 @@
-+"""Tool to analyze and fix IPA ID ranges"""
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+
-+import logging
-+import ldap
-+
-+from ipalib import api, errors
-+from ipapython.admintool import AdminTool
-+from ipapython.dn import DN
-+from ipapython import ipautil
-+from typing import List, Tuple
-+
-+logger = logging.getLogger(__name__)
-+
-+
-+class IDRange:
-+    """Class for ID range entity"""
-+
-+    def __init__(self):
-+        self.last_id: int = None
-+        self.last_base_rid: int = None
-+        self.last_secondary_rid: int = None
-+        self.name: str = None
-+        self.size: int = None
-+        self.first_id: int = None
-+        self.base_rid: int = None
-+        self.secondary_base_rid: int = None
-+        self.type: str = None
-+        self.suffix: str = None
-+        self.dn: str = None
-+
-+    def _count(self) -> None:
-+        """Function to calculate last IDs for the range"""
-+        self.last_id = self.first_id + self.size - 1
-+        if self.type == "ipa-local":
-+            self.last_base_rid = (
-+                self.base_rid + self.size
-+                if self.base_rid is not None
-+                else None
-+            )
-+            self.last_secondary_rid = (
-+                self.secondary_base_rid + self.size
-+                if self.secondary_base_rid is not None
-+                else None
-+            )
-+
-+    def __repr__(self):
-+        return (
-+            f"IDRange(name='{self.name}', "
-+            f"type={self.type}, "
-+            f"size={self.size}, "
-+            f"first_id={self.first_id}, "
-+            f"base_rid={self.base_rid}, "
-+            f"secondary_base_rid={self.secondary_base_rid})"
-+        )
-+
-+    def __eq__(self, other):
-+        return self.first_id == other.first_id
-+
-+
-+class IDentity:
-+    """A generic class for ID entity - users or groups"""
-+
-+    def __init__(self, **kwargs):
-+        self.dn: str = kwargs.get('dn')
-+        self.name: str = kwargs.get('name')
-+        self.user: str = kwargs.get('user')
-+        self.number: int = kwargs.get('number')
-+
-+    def __str__(self):
-+        if self.user:
-+            return (f"user '{self.name}', uid={self.number}")
-+        return (f"group '{self.name}', gid={self.number}")
-+
-+    def debug(self):
-+        if self.user:
-+            return (
-+                f"user(username='{self.name}', "
-+                f"uid={self.number}, "
-+                f"{self.dn})"
-+            )
-+        return (
-+            f"group(groupname='{self.name}', "
-+            f"gid={self.number}, "
-+            f"{self.dn})"
-+        )
-+
-+    def __eq__(self, other):
-+        return self.number == other.number and self.user == other.user
-+
-+
-+class IPAIDRangeFix(AdminTool):
-+    """Tool to analyze and fix IPA ID ranges"""
-+
-+    command_name = "ipa-idrange-fix"
-+    log_file_name = "/var/log/ipa-idrange-fix.log"
-+    usage = "%prog"
-+    description = "Analyze and fix IPA ID ranges"
-+
-+    @classmethod
-+    def add_options(cls, parser, debug_option=False):
-+        super(IPAIDRangeFix, cls).add_options(parser)
-+        parser.add_option(
-+            "--ridoffset",
-+            dest="ridoffset",
-+            type=int,
-+            default=100000,
-+            metavar=100000,
-+            help="Offset for a next base RID from previous RID range. \
-+Needed for future range size expansions. Has to be > 0",
-+        )
-+        parser.add_option(
-+            "--rangegap",
-+            dest="rangegap",
-+            type=int,
-+            default=200000,
-+            metavar=200000,
-+            help="Threshold for a gap between out-of-range IDs to be \
-+considered a different range. Has to be > 0",
-+        )
-+        parser.add_option(
-+            "--minrange",
-+            dest="minrange",
-+            type=int,
-+            default=10,
-+            metavar=10,
-+            help="Minimal considered range size for out-of-range IDs.\
-+All ranges with amount of IDs lower than this number will be discarded and \
-+IDs will be listed to be moved manually. Has to be > 1",
-+        )
-+        parser.add_option(
-+            "--allowunder1000",
-+            dest="allowunder1000",
-+            action="store_true",
-+            default=False,
-+            help="Allow idranges to start below 1000. Be careful to not \
-+overlap IPA users/groups with existing system-local ones!",
-+        )
-+        parser.add_option(
-+            "--norounding",
-+            dest="norounding",
-+            action="store_true",
-+            default=False,
-+            help="Disable IDrange rounding attempt in order to get ranges \
-+exactly covering just IDs provided",
-+        )
-+        parser.add_option(
-+            "--unattended",
-+            dest="unattended",
-+            action="store_true",
-+            default=False,
-+            help="Automatically fix all range issues found without asking \
-+for confirmation",
-+        )
-+
-+    def __init__(self, *args, **kwargs):
-+        super().__init__(*args, **kwargs)
-+        self.realm: str = None
-+        self.suffix: DN = None
-+        self.proposals_rid: List[IDRange] = []
-+        self.proposals_new: List[IDRange] = []
-+        self.outliers: List[IDentity] = []
-+        self.under1000: List[IDentity] = []
-+        self.id_ranges: List[IDRange] = []
-+
-+    def validate_options(self, needs_root=True):
-+        super().validate_options(needs_root)
-+
-+    def run(self):
-+        api.bootstrap(in_server=True)
-+        api.finalize()
-+
-+        self.realm = api.env.realm
-+        self.suffix = ipautil.realm_to_suffix(self.realm)
-+        try:
-+            api.Backend.ldap2.connect()
-+
-+            # Reading range data
-+            self.id_ranges = read_ranges(self.suffix)
-+
-+            # Evaluating existing ranges, if something is off, exit
-+            if self.evaluate_ranges() != 0:
-+                return 1
-+
-+            # reading out of range IDs
-+            ids_out_of_range = read_outofrange_identities(
-+                self.suffix, self.id_ranges
-+            )
-+
-+            # Evaluating out of range IDs
-+            self.evaluate_identities(ids_out_of_range)
-+
-+            # Print the proposals
-+            self.print_intentions()
-+
-+            # If there are no proposals, we have nothing to do, exiting
-+            if (len(self.proposals_rid) == 0
-+                    and len(self.proposals_new) == 0):
-+                logger.info("\nNo changes proposed, nothing to do.")
-+                return 0
-+
-+            logger.info("\nID ranges table after proposed changes:")
-+            draw_ascii_table(self.id_ranges)
-+
-+            if self.options.unattended:
-+                logger.info(
-+                    "Unattended mode, proceeding with applying changes!"
-+                )
-+            else:
-+                response = ipautil.user_input('Enter "yes" to proceed')
-+                if response.lower() != "yes":
-+                    logger.info("Not proceeding.")
-+                    return 0
-+                logger.info("Proceeding.")
-+
-+            # Applying changes
-+            for id_range in self.proposals_rid:
-+                apply_ridbases(id_range)
-+
-+            for id_range in self.proposals_new:
-+                create_range(id_range)
-+
-+            logger.info("All changes applied successfully!")
-+
-+        finally:
-+            if api.Backend.ldap2.isconnected():
-+                api.Backend.ldap2.disconnect()
-+
-+        return 0
-+
-+    def evaluate_ranges(self) -> int:
-+        """Function to evaluate existing ID ranges"""
-+        if len(self.id_ranges) == 0:
-+            logger.error("No ID ranges found!")
-+            return 1
-+
-+        draw_ascii_table(self.id_ranges)
-+
-+        if not ranges_overlap_check(self.id_ranges):
-+            logger.error(
-+                "Ranges overlap detected, cannot proceed! Please adjust \
-+existing ranges manually."
-+            )
-+            return 1
-+
-+        # Checking RID bases for existing ranges
-+        id_ranges_nobase = get_ranges_no_base(self.id_ranges)
-+
-+        if len(id_ranges_nobase) > 0:
-+            logger.info(
-+                "Found %s ranges without base RIDs", len(id_ranges_nobase)
-+            )
-+            for id_range in id_ranges_nobase:
-+                logger.debug(
-+                    "Range '%s' has RID base %s and secondary RID base %s",
-+                    id_range.name,
-+                    id_range.base_rid,
-+                    id_range.secondary_base_rid,
-+                )
-+            propose_rid_ranges(
-+                self.id_ranges,
-+                self.options.ridoffset,
-+                self.proposals_rid
-+            )
-+        else:
-+            logger.info(
-+                "All ID ranges have base RIDs set, RID adjustments are \
-+not needed."
-+            )
-+        return 0
-+
-+    def evaluate_identities(self, ids_out_of_range: List[IDentity]) -> None:
-+        """Function to evaluate out of range IDs"""
-+        if len(ids_out_of_range) == 0:
-+            logger.info("No out of range IDs found!")
-+        else:
-+            logger.info(
-+                "Found overall %s IDs out of existing ID ranges.\n",
-+                len(ids_out_of_range),
-+            )
-+            # ruling out IDs under 1000 if flag is not set
-+            if not self.options.allowunder1000:
-+                self.under1000, ids_out_of_range = separate_under1000(
-+                    ids_out_of_range
-+                )
-+                if len(self.under1000) > 0:
-+                    logger.info(
-+                        "Found IDs under 1000, which is not recommeneded \
-+(if you definitely need ranges proposed for those, use --allowunder1000):"
-+                    )
-+                    for identity in self.under1000:
-+                        logger.info("%s", identity)
-+
-+            # Get initial divide of IDs into groups
-+            groups = group_identities_by_threshold(
-+                ids_out_of_range, self.options.rangegap
-+            )
-+
-+            # Get outliers from too small groups and clean groups for
-+            # further processing
-+            self.outliers, cleangroups = separate_ranges_and_outliers(
-+                groups, self.options.minrange
-+            )
-+
-+            # Print the outliers, they have to be moved manually
-+            if len(self.outliers) > 0:
-+                logger.info(
-+                    "\nIdentities that don't fit the criteria to get a new "
-+                    "range found! Current attributes:\n"
-+                    "Minimal range size: %s\n"
-+                    "Maximum gap between IDs: %s\n"
-+                    "Try adjusting --minrange, --rangegap or move the "
-+                    "following identities into already existing ranges:",
-+                    self.options.minrange,
-+                    self.options.rangegap
-+                )
-+                for identity in self.outliers:
-+                    logger.info("%s", identity)
-+
-+            if len(cleangroups) > 0:
-+                # Get IDrange name base
-+                basename = get_rangename_base(self.id_ranges)
-+
-+                # Create proposals for new ranges from groups
-+                for group in cleangroups:
-+                    newrange = propose_range(
-+                        group,
-+                        self.id_ranges,
-+                        self.options.ridoffset,
-+                        basename,
-+                        self.options.norounding,
-+                        self.options.allowunder1000
-+                    )
-+                    if newrange is not None:
-+                        self.proposals_new.append(newrange)
-+                        self.id_ranges.append(newrange)
-+                        self.id_ranges.sort(key=lambda x: x.first_id)
-+            else:
-+                logger.info(
-+                    "\nNo IDs fit the criteria for a new ID range to propose!"
-+                )
-+
-+    def print_intentions(self) -> None:
-+        """Function to print out the summary of the proposed changes"""
-+        logger.info("\nSummary:")
-+
-+        if len(self.outliers) > 0:
-+            logger.info("Outlier IDs that are too far away to get a range:")
-+            for identity in self.outliers:
-+                logger.info("%s", identity)
-+
-+        if len(self.under1000) > 0:
-+            if self.options.allowunder1000:
-+                logger.info("IDs under 1000 were treated like normal IDs.")
-+            else:
-+                logger.info("IDs under 1000:")
-+                for identity in self.under1000:
-+                    logger.info("%s", identity)
-+        else:
-+            logger.info("No IDs under 1000 found.")
-+
-+        if len(self.proposals_rid) > 0:
-+            logger.info("Proposed changes to existing ranges:")
-+            for id_range in self.proposals_rid:
-+                logger.info(
-+                    "Range '%s' - base RID: %s, secondary base RID: %s",
-+                    id_range.name,
-+                    id_range.base_rid,
-+                    id_range.secondary_base_rid,
-+                )
-+        else:
-+            logger.info("No changes proposed for existing ranges.")
-+
-+        if len(self.proposals_new) > 0:
-+            logger.info("Proposed new ranges:")
-+            for id_range in self.proposals_new:
-+                logger.info("%s", id_range)
-+        else:
-+            logger.info("No new ranges proposed.")
-+
-+# Working with output
-+# region
-+
-+
-+def draw_ascii_table(id_ranges: List[IDRange], stdout: bool = False) -> None:
-+    """Function to draw a table with ID ranges in ASCII"""
-+    table: str = "\n"
-+    # Calculate the maximum width required for each column using column names
-+    max_widths = {
-+        column: max(
-+            len(str(column)),
-+            max(
-+                (
-+                    len(str(getattr(id_range, column)))
-+                    if getattr(id_range, column) is not None
-+                    else 0
-+                )
-+                for id_range in id_ranges
-+            ),
-+        )
-+        for column in [
-+            "name",
-+            "type",
-+            "size",
-+            "first_id",
-+            "last_id",
-+            "base_rid",
-+            "last_base_rid",
-+            "secondary_base_rid",
-+            "last_secondary_rid",
-+        ]
-+    }
-+
-+    # Draw the table header
-+    header = "| "
-+    for column, width in max_widths.items():
-+        header += f"{column.ljust(width)} | "
-+    horizontal_line = "-" * (len(header) - 1)
-+    table += horizontal_line + "\n"
-+    table += header + "\n"
-+    table += horizontal_line + "\n"
-+
-+    # Draw the table rows
-+    for id_range in id_ranges:
-+        row = "| "
-+        for column, width in max_widths.items():
-+            value = getattr(id_range, column)
-+            if value is not None:
-+                row += f"{str(value).rjust(width)} | "
-+            else:
-+                # Adding the separator
-+                row += " " * (width + 1) + "| "
-+        table += row + "\n"
-+    table += horizontal_line + "\n"
-+    if stdout:
-+        print(table)
-+    else:
-+        logger.info(table)
-+# endregion
-+# Reading from LDAP
-+# region
-+
-+
-+def read_ranges(suffix) -> List[IDRange]:
-+    """Function to read ID ranges from LDAP"""
-+    id_ranges: IDRange = []
-+    try:
-+        ranges = api.Backend.ldap2.get_entries(
-+            DN(api.env.container_ranges, suffix),
-+            ldap.SCOPE_ONELEVEL,
-+            "(objectclass=ipaIDRange)",
-+        )
-+    except errors.NotFound:
-+        logger.error("LDAPError: No ranges found!")
-+    except errors.ExecutionError as e:
-+        logger.error("Exception while reading users: %s", e)
-+    else:
-+        for entry in ranges:
-+            sv = entry.single_value
-+            id_range = IDRange()
-+            id_range.name = sv.get("cn")
-+            id_range.size = int(sv.get("ipaidrangesize"))
-+            id_range.first_id = int(sv.get("ipabaseid"))
-+            id_range.base_rid = (
-+                int(sv.get("ipabaserid")) if sv.get("ipabaserid") else None
-+            )
-+            id_range.secondary_base_rid = (
-+                int(sv.get("ipasecondarybaserid"))
-+                if sv.get("ipasecondarybaserid")
-+                else None
-+            )
-+            id_range.suffix = suffix
-+            id_range.type = sv.get("iparangetype")
-+            id_range.dn = entry.dn
-+
-+            id_range._count()
-+            logger.debug("ID range found: %s", id_range)
-+
-+            id_ranges.append(id_range)
-+
-+        id_ranges.sort(key=lambda x: x.first_id)
-+    return id_ranges
-+
-+
-+def read_outofrange_identities(suffix, id_ranges) -> List[IDentity]:
-+    """Function to read out of range users and groups from LDAP"""
-+    users_outofrange = read_ldap_ids(
-+        DN(api.env.container_user, suffix),
-+        True,
-+        id_ranges
-+    )
-+    logger.info("Users out of range found: %s", len(users_outofrange))
-+    del_outofrange = read_ldap_ids(
-+        DN(api.env.container_deleteuser, suffix),
-+        True,
-+        id_ranges
-+    )
-+    logger.info("Preserved users out of range found: %s", len(del_outofrange))
-+    groups_outofrange = read_ldap_ids(
-+        DN(api.env.container_group, suffix),
-+        False,
-+        id_ranges
-+    )
-+    logger.info("Groups out of range found: %s", len(groups_outofrange))
-+    outofrange = users_outofrange + del_outofrange + groups_outofrange
-+    outofrange.sort(key=lambda x: x.number)
-+    return outofrange
-+
-+
-+def read_ldap_ids(container_dn, user: bool, id_ranges) -> List[IDentity]:
-+    """Function to read IDs from containter in LDAP"""
-+    id_entities = []
-+    if user:
-+        id_name = "user"
-+        ldap_filter = get_outofrange_filter(
-+            id_ranges,
-+            "posixaccount",
-+            "uidNumber"
-+        )
-+    else:
-+        id_name = "group"
-+        ldap_filter = get_outofrange_filter(
-+            id_ranges,
-+            "posixgroup",
-+            "gidNumber"
-+        )
-+
-+    logger.debug("Searching %ss in %s with filter: %s", id_name, container_dn,
-+                 ldap_filter)
-+    try:
-+        identities = api.Backend.ldap2.get_entries(
-+            container_dn,
-+            ldap.SCOPE_ONELEVEL,
-+            ldap_filter,
-+        )
-+        for entry in identities:
-+            id_entities.append(read_identity(entry, user))
-+    except errors.NotFound:
-+        logger.debug("No out of range %ss found in %s!", id_name, container_dn)
-+    except errors.ExecutionError as e:
-+        logger.error("Exception while reading %s: %s", container_dn, e)
-+    return id_entities
-+
-+
-+def read_identity(ldapentry, user: bool = True) -> IDentity:
-+    """Function to convert LDAP entry to IDentity object"""
-+    sv = ldapentry.single_value
-+    id_entity = IDentity()
-+    id_entity.dn = ldapentry.dn
-+    id_entity.name = sv.get("cn")
-+    id_entity.number = (
-+        int(sv.get("uidNumber")) if user else int(sv.get("gidNumber"))
-+    )
-+    id_entity.user = user
-+    logger.debug("Out of range found: %s", id_entity.debug())
-+    return id_entity
-+
-+
-+def get_outofrange_filter(
-+    id_ranges_all: List[IDRange], object_class: str, posix_id: str
-+) -> str:
-+    """Function to create LDAP filter for out of range users and groups"""
-+    # we need to look only for ipa-local ranges
-+    id_ranges = get_ipa_local_ranges(id_ranges_all)
-+
-+    ldap_filter = f"(&(objectClass={object_class})(|"
-+
-+    # adding gaps in ranges to the filter
-+    for i in range(len(id_ranges) + 1):
-+        if i == 0:
-+            start_condition = f"({posix_id}>=1)"
-+        else:
-+            start_condition = f"({posix_id}>={id_ranges[i - 1].last_id + 1})"
-+
-+        if i < len(id_ranges):
-+            end_condition = f"({posix_id}<={id_ranges[i].first_id - 1})"
-+        else:
-+            end_condition = f"({posix_id}<=2147483647)"
-+
-+        ldap_filter += f"(&{start_condition}{end_condition})"
-+
-+    ldap_filter += "))"
-+
-+    return ldap_filter
-+# endregion
-+# Writing to LDAP
-+# region
-+
-+
-+def apply_ridbases(id_range: IDRange) -> None:
-+    """Funtion to apply RID bases to the range in LDAP"""
-+    try:
-+        api.Backend.ldap2.modify_s(
-+            id_range.dn,
-+            [
-+                (ldap.MOD_ADD, "ipaBaseRID", str(id_range.base_rid)),
-+                (
-+                    ldap.MOD_ADD,
-+                    "ipaSecondaryBaseRID",
-+                    str(id_range.secondary_base_rid),
-+                ),
-+            ],
-+        )
-+        logger.info("RID bases updated for range '%s'", id_range.name)
-+
-+    except ldap.CONSTRAINT_VIOLATION as e:
-+        logger.error(
-+            "Failed to add RID bases to the range '%s': %s",
-+            id_range.name,
-+            e
-+        )
-+        raise RuntimeError("Constraint violation.\n") from e
-+
-+    except Exception as e:
-+        logger.error(
-+            "Exception while updating RID bases for range '%s': %s",
-+            id_range.name,
-+            e,
-+        )
-+        raise RuntimeError("Failed to update RID bases.\n") from e
-+
-+
-+def create_range(id_range: IDRange) -> None:
-+    """Function to create a new range in LDAP"""
-+    try:
-+        logger.info("Creating range '%s'...", id_range.name)
-+
-+        entry = api.Backend.ldap2.make_entry(
-+            DN(id_range.dn),
-+            objectclass=["ipaIDRange", "ipaDomainIDRange"],
-+            ipaidrangesize=[str(id_range.size)],
-+            ipabaseid=[str(id_range.first_id)],
-+            ipabaserid=[str(id_range.base_rid)],
-+            ipasecondarybaserid=[str(id_range.secondary_base_rid)],
-+            iparangetype=[id_range.type],
-+        )
-+
-+        api.Backend.ldap2.add_entry(entry)
-+        logger.info("Range '%s' created successfully", id_range.name)
-+    except Exception as e:
-+        logger.error(
-+            "Exception while creating range '%s': %s",
-+            id_range.name,
-+            e
-+        )
-+        raise RuntimeError("Failed to create range.\n") from e
-+# endregion
-+# Working with ranges
-+# region
-+
-+
-+def get_ipa_local_ranges(id_ranges: List[IDRange]) -> List[IDRange]:
-+    """Function to get only ipa-local ranges from the list of ranges"""
-+    ipa_local_ranges = []
-+
-+    for id_range in id_ranges:
-+        if id_range.type == "ipa-local":
-+            ipa_local_ranges.append(id_range)
-+
-+    return ipa_local_ranges
-+
-+
-+def range_overlap_check(
-+    range1_start: int, range1_end: int, range2_start: int, range2_end: int
-+) -> bool:
-+    """Function to check if two ranges overlap"""
-+    # False when overlapping
-+    return not (range1_start <= range2_end and range2_start <= range1_end)
-+
-+
-+def range_overlap_check_idrange(range1: IDRange, range2: IDRange) -> bool:
-+    """Function to check if two ranges overlap"""
-+    # False when overlapping
-+    return range_overlap_check(
-+        range1.first_id, range1.last_id, range2.first_id, range2.last_id)
-+
-+
-+def newrange_overlap_check(
-+    id_ranges: List[IDRange], newrange: IDRange
-+) -> bool:
-+    """Function to check if proposed range overlaps with existing ones"""
-+    for id_range in id_ranges:
-+        if not range_overlap_check_idrange(id_range, newrange):
-+            return False
-+    return True
-+
-+
-+def ranges_overlap_check(id_ranges: List[IDRange]) -> bool:
-+    """Function to check if any of the existing ranges overlap"""
-+    if len(id_ranges) < 2:
-+        return True
-+    for i in range(len(id_ranges) - 1):
-+        for j in range(i + 1, len(id_ranges)):
-+            if not range_overlap_check_idrange(id_ranges[i], id_ranges[j]):
-+                logger.error(
-+                    "Ranges '%s' and '%s' overlap!",
-+                    id_ranges[i].name,
-+                    id_ranges[j].name,
-+                )
-+                return False
-+    return True
-+# endregion
-+# Working with RID bases
-+# region
-+
-+
-+def propose_rid_ranges(
-+    id_ranges: List[IDRange], delta: int, proposals: List[IDRange]
-+) -> None:
-+    """
-+    Function to propose RID bases for ranges that don't have them set.
-+
-+    - delta represents how far we start new base off existing range,
-+    used in order to allow for future expansion of existing ranges up
-+    to [delta] IDs.
-+    """
-+    ipa_local_ranges = get_ipa_local_ranges(id_ranges)
-+
-+    for id_range in ipa_local_ranges:
-+        proposed_base_rid = 0
-+        proposed_secondary_base_rid = 0
-+
-+        # Calculate proposed base RID and secondary base RID
-+        if id_range.base_rid is None:
-+            result, proposed_base_rid = propose_rid_base(
-+                id_range, ipa_local_ranges, delta, True
-+            )
-+            if result:
-+                id_range.base_rid = proposed_base_rid
-+                id_range.last_base_rid = proposed_base_rid + id_range.size
-+            else:
-+                # if this fails too, we print the warning and abandon the idea
-+                logger.warning(
-+                    "Warning: Proposed base RIDs %s for '%s' both failed, \
-+please adjust manually",
-+                    proposed_base_rid,
-+                    id_range.name,
-+                )
-+                continue
-+
-+        if id_range.secondary_base_rid is None:
-+            result, proposed_secondary_base_rid = propose_rid_base(
-+                id_range, ipa_local_ranges, delta, False, proposed_base_rid
-+            )
-+            if result:
-+                id_range.secondary_base_rid = proposed_secondary_base_rid
-+                id_range.last_secondary_rid = (
-+                    proposed_secondary_base_rid + id_range.size
-+                )
-+            else:
-+                # if this fails too, we print the warning and abandon the idea
-+                logger.warning(
-+                    "Warning: Proposed secondary base RIDs %s for '%s' \
-+both failed, please adjust manually",
-+                    proposed_secondary_base_rid,
-+                    id_range.name,
-+                )
-+                continue
-+
-+        # Add range to the proposals if we changed something successfully
-+        if proposed_base_rid > 0 or proposed_secondary_base_rid > 0:
-+            logger.debug(
-+                "Proposed RIDs for range '%s': pri %s, sec %s",
-+                id_range.name,
-+                proposed_base_rid,
-+                proposed_secondary_base_rid,
-+            )
-+            proposals.append(id_range)
-+
-+
-+def propose_rid_base(
-+    idrange: IDRange,
-+    ipa_local_ranges: List[IDRange],
-+    delta: int,
-+    primary: bool = True,
-+    previous_base_rid: int = -1
-+) -> Tuple[bool, str]:
-+    """
-+    Function to propose a base RID for a range, primary or secondary.
-+    We are getting the biggest base RID + size + delta and try
-+    if it's a viable option, check same kind first, then the other.
-+    """
-+    proposed_base_rid = max_rid(ipa_local_ranges, primary) + delta
-+    if proposed_base_rid == previous_base_rid:
-+        proposed_base_rid += idrange.size + delta
-+    if check_rid_base(ipa_local_ranges, proposed_base_rid, idrange.size):
-+        return True, proposed_base_rid
-+
-+    # if we fail, we try the same with biggest of a different kind
-+    proposed_base_rid_orig = proposed_base_rid
-+    proposed_base_rid = max_rid(ipa_local_ranges, not primary) + delta
-+    if proposed_base_rid == previous_base_rid:
-+        proposed_base_rid += idrange.size + delta
-+    if check_rid_base(ipa_local_ranges, proposed_base_rid, idrange.size):
-+        return True, proposed_base_rid
-+
-+    # if it fails, we return both RID proposals for the range
-+    return False, f"{proposed_base_rid_orig} and {proposed_base_rid}"
-+
-+
-+def max_rid(id_ranges: List[IDRange], primary: bool = True) -> int:
-+    """Function to get maximum RID of primary or secondary RIDs"""
-+    maximum_rid = 0
-+    for id_range in id_ranges:
-+
-+        # looking only for primary RIDs
-+        if primary:
-+            if id_range.last_base_rid is not None:
-+                maximum_rid = max(maximum_rid, id_range.last_base_rid)
-+        # looking only for secondary RIDs
-+        else:
-+            if id_range.last_secondary_rid is not None:
-+                maximum_rid = max(maximum_rid, id_range.last_secondary_rid)
-+
-+    return maximum_rid
-+
-+
-+def check_rid_base(id_ranges: List[IDRange], base: int, size: int) -> bool:
-+    """Function to check if proposed RID base is viable"""
-+    end = base + size + 1
-+
-+    # Checking sanity of RID range
-+    if base + size > 2147483647:
-+        return False
-+    if base < 1000:
-+        return False
-+
-+    # Checking RID range overlaps
-+    for id_range in id_ranges:
-+        # we are interested only in ipa-local ranges
-+        if id_range.type != "ipa-local":
-+            continue
-+
-+        # if there is no base rid set, there is no secondary base rid set,
-+        # so nothing to overlap with
-+        if id_range.base_rid is None:
-+            continue
-+
-+        # checking for an overlap
-+        if not range_overlap_check(
-+            base, end, id_range.base_rid, id_range.last_base_rid
-+        ):
-+            logger.debug(
-+                "RID check failure: proposed Primary %s + %s, \
-+intersects with %s-%s from range '%s'",
-+                base,
-+                size,
-+                id_range.base_rid,
-+                id_range.last_base_rid,
-+                id_range.name,
-+            )
-+            return False
-+
-+        # if there is no secondary base rid set, nothing to overlap with
-+        if id_range.secondary_base_rid is None:
-+            continue
-+
-+        # if either start of end of the range fails inside existing range,
-+        # or existing range is inside proposed one, we have an overlap
-+        if not range_overlap_check(
-+            base, end, id_range.secondary_base_rid, id_range.last_secondary_rid
-+        ):
-+            logger.debug(
-+                "RID check failure: proposed Secondary %s + %s, \
-+intersects with %s-%s from range '%s'",
-+                base,
-+                size,
-+                id_range.secondary_base_rid,
-+                id_range.last_secondary_rid,
-+                id_range.name,
-+            )
-+            return False
-+
-+    return True
-+
-+
-+def get_ranges_no_base(id_ranges: List[IDRange]) -> List[IDRange]:
-+    """Function to get ranges without either of base RIDs set"""
-+    ipa_local_ranges = get_ipa_local_ranges(id_ranges)
-+    ranges_no_base = []
-+    for id_range in ipa_local_ranges:
-+        if id_range.base_rid is None or id_range.secondary_base_rid is None:
-+            ranges_no_base.append(id_range)
-+
-+    return ranges_no_base
-+# endregion
-+# Working with IDentities out of range
-+# region
-+
-+
-+def group_identities_by_threshold(
-+    identities: List[IDentity], threshold: int
-+) -> List[List[IDentity]]:
-+    """Function to group out of range IDs by threshold"""
-+    groups: List[List[IDentity]] = []
-+    currentgroup: List[IDentity] = []
-+    if len(identities) == 0:
-+        return groups
-+
-+    for i in range(len(identities) - 1):
-+        # add id to current group
-+        currentgroup.append(identities[i])
-+
-+        # If the difference with the next one is greater than the threshold,
-+        # start a new group
-+        if identities[i + 1].number - identities[i].number > threshold:
-+            groups.append(currentgroup)
-+            currentgroup = []
-+
-+    # Add the last ID number to the last group
-+    currentgroup.append(identities[-1])
-+    groups.append(currentgroup)
-+
-+    return groups
-+
-+
-+def separate_under1000(
-+    identities: List[IDentity],
-+) -> Tuple[List[IDentity], List[IDentity]]:
-+    """Function to separate IDs under 1000, expects sorted list"""
-+    for i, identity in enumerate(identities):
-+        if identity.number >= 1000:
-+            return identities[:i], identities[i:]
-+    return identities, []
-+
-+
-+def separate_ranges_and_outliers(
-+    groups: List[List[IDentity]], minrangesize=int
-+) -> Tuple[List[List[IDentity]], List[List[IDentity]]]:
-+    """Function to separate IDs into outliers and IDs that can get ranges"""
-+    outliers = []
-+    cleangroups = []
-+    for group in groups:
-+        # if group is smaller than minrangesize, add it's memebers to ourliers
-+        if group[-1].number - group[0].number + 1 < minrangesize:
-+            for identity in group:
-+                outliers.append(identity)
-+        # if the group is OK, add it to cleaned groups
-+        else:
-+            cleangroups.append(group)
-+
-+    return outliers, cleangroups
-+
-+
-+def round_idrange(start: int, end: int, under1000: bool) -> Tuple[int, int]:
-+    """Function to round up range margins to look pretty"""
-+    # calculating power of the size
-+    sizepower = len(str(end - start + 1))
-+    # multiplier for the nearest rounded number
-+    multiplier = 10 ** (sizepower - 1)
-+    # getting rounded range margins
-+    rounded_start = (start // multiplier) * multiplier
-+    if not under1000:
-+        rounded_start = max(rounded_start, 1000)
-+    else:
-+        rounded_start = max(rounded_start, 1)
-+    rounded_end = ((end + multiplier) // multiplier) * multiplier - 1
-+
-+    return rounded_start, rounded_end
-+
-+
-+def get_rangename_base(id_ranges: List[IDRange]) -> str:
-+    """Function to get a base name for new range proposals"""
-+    base_name = ""
-+    # we want to use default range name as a base for new ranges
-+    for id_range in id_ranges:
-+        if id_range.base_rid == 1000:
-+            base_name = id_range.name
-+
-+    # if we didn't find it, propose generic name
-+    if base_name == "":
-+        base_name = "Auto_added_range"
-+
-+    return base_name
-+
-+
-+def get_rangename(id_ranges: List[IDRange], basename: str) -> str:
-+    """
-+    Function to get a new range name, we add the counter as 3-digit number
-+    extension and make sure it's unique
-+    """
-+    counter = 1
-+    full_name = f"{basename}_{counter:03}"
-+    while any(id_range.name == full_name for id_range in id_ranges):
-+        counter += 1
-+        full_name = f"{basename}_{counter:03}"
-+    return full_name
-+
-+
-+def propose_range(
-+    group: List[IDentity],
-+    id_ranges: List[IDRange],
-+    delta: int,
-+    basename: str,
-+    norounding: bool,
-+    allowunder1000: bool
-+) -> IDRange:
-+    """Function to propose a new range for group of IDs out of ranges"""
-+    startid = group[0].number
-+    endid = group[-1].number
-+
-+    logger.debug(
-+        "Proposing a range for existing IDs out of ranges with start id %s \
-+and end id %s...",
-+        startid,
-+        endid,
-+    )
-+
-+    # creating new range
-+    newrange = IDRange()
-+    newrange.type = "ipa-local"
-+    newrange.name = get_rangename(id_ranges, basename)
-+    newrange.suffix = id_ranges[0].suffix
-+    newrange.dn = f"cn={newrange.name},cn=ranges,cn=etc,{newrange.suffix}"
-+
-+    if norounding:
-+        newrange.first_id = startid
-+        newrange.last_id = endid
-+        newrange.size = newrange.last_id - newrange.first_id + 1
-+    else:
-+        # first trying to round up ranges to look pretty
-+        newrange.first_id, newrange.last_id = round_idrange(
-+            startid,
-+            endid,
-+            allowunder1000
-+        )
-+        newrange.size = newrange.last_id - newrange.first_id + 1
-+
-+    # if this creates an overlap, try without rounding
-+    if not newrange_overlap_check(id_ranges, newrange):
-+        newrange.first_id = startid
-+        newrange.last_id = endid
-+        newrange.size = newrange.last_id - newrange.first_id + 1
-+        # if we still failed, abandon idea
-+        if not newrange_overlap_check(id_ranges, newrange):
-+            logger.error(
-+                "ERROR! Failed to create idrange for existing IDs out of \
-+ranges with start id %s and end id %s, it overlaps with existing range!",
-+                startid,
-+                endid,
-+            )
-+            return None
-+
-+    # creating RID bases
-+    ipa_local_ranges = get_ipa_local_ranges(id_ranges)
-+
-+    result, proposed_base_rid = propose_rid_base(
-+        newrange, ipa_local_ranges, delta, True
-+    )
-+    if result:
-+        newrange.base_rid = proposed_base_rid
-+        newrange.last_base_rid = proposed_base_rid + newrange.size
-+    else:
-+        # if this fails we print the warning
-+        logger.warning(
-+            "Warning! Proposed base RIDs %s for new range start id %s and \
-+end id %s both failed, please adjust manually",
-+            proposed_base_rid,
-+            newrange.first_id,
-+            newrange.last_id,
-+        )
-+
-+    result, proposed_secondary_base_rid = propose_rid_base(
-+        newrange, ipa_local_ranges, delta, False, proposed_base_rid
-+    )
-+    if result:
-+        newrange.secondary_base_rid = proposed_secondary_base_rid
-+        newrange.last_secondary_rid = (
-+            proposed_secondary_base_rid + newrange.size
-+        )
-+    else:
-+        # if this fails we print the warning
-+        logger.warning(
-+            "Warning! Proposed secondary base RIDs %s for new range start id \
-+%s and end id %s both failed, please adjust manually",
-+            proposed_secondary_base_rid,
-+            newrange.first_id,
-+            newrange.last_id,
-+        )
-+
-+    logger.debug("Proposed range: %s", newrange)
-+    return newrange
-+# endregion
-diff --git a/ipatests/test_integration/test_ipa_idrange_fix.py b/ipatests/test_integration/test_ipa_idrange_fix.py
-new file mode 100644
-index 000000000..de3da9bfd
---- /dev/null
-+++ b/ipatests/test_integration/test_ipa_idrange_fix.py
-@@ -0,0 +1,189 @@
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+
-+"""
-+Module provides tests for ipa-idrange-fix CLI.
-+"""
-+
-+import logging
-+import re
-+
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+
-+
-+logger = logging.getLogger(__name__)
-+
-+
-+class TestIpaIdrangeFix(IntegrationTest):
-+    @classmethod
-+    def install(cls, mh):
-+        super(TestIpaIdrangeFix, cls).install(mh)
-+        tasks.kinit_admin(cls.master)
-+
-+    def test_no_issues(self):
-+        """Test ipa-idrange-fix command with no issues."""
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+
-+        expected_under1000 = "No IDs under 1000 found"
-+        expected_nochanges = "No changes proposed for existing ranges"
-+        expected_newrange = "No new ranges proposed"
-+        expected_noissues = "No changes proposed, nothing to do."
-+        assert expected_under1000 in result.stderr_text
-+        assert expected_nochanges in result.stderr_text
-+        assert expected_newrange in result.stderr_text
-+        assert expected_noissues in result.stderr_text
-+
-+    def test_idrange_no_rid_bases(self):
-+        """Test ipa-idrange-fix command with IDrange with no RID bases."""
-+        self.master.run_command([
-+            "ipa",
-+            "idrange-add",
-+            "idrange_no_rid_bases",
-+            "--base-id", '10000',
-+            "--range-size", '20000',
-+        ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = "RID bases updated for range 'idrange_no_rid_bases'"
-+
-+        # Remove IDrange with no rid bases
-+        self.master.run_command(["ipa", "idrange-del", "idrange_no_rid_bases"])
-+
-+        assert expected_text in result.stderr_text
-+
-+    def test_idrange_no_rid_bases_reversed(self):
-+        """
-+        Test ipa-idrange-fix command with IDrange with no RID bases, but we
-+        previously had a range with RID bases reversed - secondary lower than
-+        primary. It is a valid configuration, so we should fix no-RID range.
-+        """
-+        self.master.run_command([
-+            "ipa",
-+            "idrange-add",
-+            "idrange_no_rid_bases",
-+            "--base-id", '10000',
-+            "--range-size", '20000',
-+        ])
-+        self.master.run_command([
-+            "ipa",
-+            "idrange-add",
-+            "idrange_reversed",
-+            "--base-id", '50000',
-+            "--range-size", '20000',
-+            "--rid-base", '100300000'
-+            "--secondary-rid-base", '301000'
-+        ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = "RID bases updated for range 'idrange_no_rid_bases'"
-+
-+        # Remove test IDranges
-+        self.master.run_command(["ipa", "idrange-del", "idrange_no_rid_bases"])
-+        self.master.run_command(["ipa", "idrange-del", "idrange_reversed"])
-+
-+        assert expected_text in result.stderr_text
-+
-+    def test_users_outofrange(self):
-+        """Test ipa-idrange-fix command with users out of range."""
-+        for i in range(1, 20):
-+            self.master.run_command([
-+                "ipa",
-+                "user-add",
-+                "testuser{}".format(i),
-+                "--first", "Test",
-+                "--last", "User {}".format(i),
-+                "--uid", str(100000 + i * 10),
-+            ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = r"Range '[\w\.]+_id_range_\d{3}' created successfully"
-+        match = re.search(expected_text, result.stderr_text)
-+
-+        # Remove users out of range and created IDrange
-+        for i in range(1, 20):
-+            self.master.run_command([
-+                "ipa",
-+                "user-del",
-+                "testuser{}".format(i)
-+            ])
-+        if match is not None:
-+            self.master.run_command([
-+                "ipa",
-+                "idrange-del",
-+                match.group(0).split(" ")[1].replace("'", "")
-+            ])
-+
-+        assert match is not None
-+
-+    def test_user_outlier(self):
-+        """Test ipa-idrange-fix command with outlier user."""
-+        self.master.run_command([
-+            "ipa",
-+            "user-add",
-+            "testuser_outlier",
-+            "--first", "Outlier",
-+            "--last", "User",
-+            "--uid", '500000',
-+        ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = "Identities that don't fit the criteria to get a new \
-+range found!"
-+        expected_user = "user 'Outlier User', uid=500000"
-+
-+        # Remove outlier user
-+        self.master.run_command(["ipa", "user-del", "testuser_outlier"])
-+
-+        assert expected_text in result.stderr_text
-+        assert expected_user in result.stderr_text
-+
-+    def test_user_under1000(self):
-+        """Test ipa-idrange-fix command with user under 1000."""
-+        self.master.run_command([
-+            "ipa",
-+            "user-add",
-+            "testuser_under1000",
-+            "--first", "Under",
-+            "--last", "1000",
-+            "--uid", '999',
-+        ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = "IDs under 1000:"
-+        expected_user = "user 'Under 1000', uid=999"
-+
-+        # Remove user under 1000
-+        self.master.run_command(["ipa", "user-del", "testuser_under1000"])
-+
-+        assert expected_text in result.stderr_text
-+        assert expected_user in result.stderr_text
-+
-+    def test_user_preserved(self):
-+        """Test ipa-idrange-fix command with preserved user."""
-+        self.master.run_command([
-+            "ipa",
-+            "user-add",
-+            "testuser_preserved",
-+            "--first", "Preserved",
-+            "--last", "User",
-+            "--uid", '9999',
-+        ])
-+        self.master.run_command([
-+            "ipa",
-+            "user-del",
-+            "testuser_preserved",
-+            "--preserve"
-+        ])
-+
-+        result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-+        expected_text = "Identities that don't fit the criteria to get a new \
-+range found!"
-+        expected_user = "user 'Preserved User', uid=9999"
-+
-+        # Remove preserved user
-+        self.master.run_command(["ipa", "user-del", "testuser_preserved"])
-+
-+        assert expected_text in result.stderr_text
-+        assert expected_user in result.stderr_text
--- 
-2.49.0
-
-
-From a5b0a4136bfd591701e3bd9219690b7b242fc267 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Sep 2024 11:39:33 +0200
-Subject: [PATCH 006/116] Add PR-CI definitions for test_ipa_idrange_fix
-
-Related: https://pagure.io/freeipa/issue/9613
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 7c2118668..015dcdf49 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -1794,6 +1794,18 @@ jobs:
-         timeout: 10800
-         topology: *master_1repl
- 
-+  fedora-latest-ipa-4-12/test_idrange_fix:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_ipa_idrange_fix.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *ipaserver
-+
-   fedora-latest-ipa-4-12/test_subids:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 312b3e5fb..40563fedd 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -1936,6 +1936,19 @@ jobs:
-         timeout: 10800
-         topology: *master_1repl
- 
-+  fedora-latest-ipa-4-12/test_idrange_fix:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_ipa_idrange_fix.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *ipaserver
-+
-   fedora-latest-ipa-4-12/test_subids:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
--- 
-2.49.0
-
-
-From 4fef80aeaaf017b286bd12ebfc30529f6a65a80e Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Sep 2024 18:28:27 +0200
-Subject: [PATCH 007/116] ipatests: Add missing comma in
- test_idrange_no_rid_bases_reversed
-
-The test is calling ipa idrange-add but is missing a comma in
-the arguments list.
-The resulting call is using "--rid-base 100300000--secondary-rid-base".
-Add the missing comma to build the command with
-"--rid-base 100300000 --secondary-rid-base"
-
-Fixes: https://pagure.io/freeipa/issue/9656
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/test_integration/test_ipa_idrange_fix.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_ipa_idrange_fix.py b/ipatests/test_integration/test_ipa_idrange_fix.py
-index de3da9bfd..ff8fbdac9 100644
---- a/ipatests/test_integration/test_ipa_idrange_fix.py
-+++ b/ipatests/test_integration/test_ipa_idrange_fix.py
-@@ -72,7 +72,7 @@ class TestIpaIdrangeFix(IntegrationTest):
-             "idrange_reversed",
-             "--base-id", '50000',
-             "--range-size", '20000',
--            "--rid-base", '100300000'
-+            "--rid-base", '100300000',
-             "--secondary-rid-base", '301000'
-         ])
- 
--- 
-2.49.0
-
-
-From a18eb8358675b3697ccf8f8d8dc230cc62df6a4d Mon Sep 17 00:00:00 2001
-From: Erik Belko <ebelko@redhat.com>
-Date: Thu, 29 Aug 2024 16:47:21 +0200
-Subject: [PATCH 008/116] ipatests: Update ipa-adtrust-install test
-
-update test_user_connects_smb_share_if_locked_specific_group with wait
-for SSSD to be online after ipa-adtrust-install command
-
-Related: https://pagure.io/freeipa/issue/9655
-
-Signed-off-by: Erik Belko <ebelko@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/test_integration/test_adtrust_install.py | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
-index 72e8d874f..de252db17 100644
---- a/ipatests/test_integration/test_adtrust_install.py
-+++ b/ipatests/test_integration/test_adtrust_install.py
-@@ -853,6 +853,8 @@ class TestIpaAdTrustInstall(IntegrationTest):
-              self.master.config.admin_password,
-              "-U"]
-         )
-+        # Wait for SSSD to become online before doing any other check
-+        tasks.wait_for_sssd_domain_status_online(self.master)
-         self.master.run_command(["mkdir", "/freeipa4234"])
-         self.master.run_command(
-             ["chcon", "-t", "samba_share_t",
--- 
-2.49.0
-
-
-From 373d41f211c1a04dc432a068bc7d2ba825ff554c Mon Sep 17 00:00:00 2001
-From: Francisco Trivino <ftrivino@redhat.com>
-Date: Tue, 13 Aug 2024 12:44:21 +0200
-Subject: [PATCH 009/116] Installer: activate ssh service in sssd.conf
-
-This commit enables SSSD's ssh service in ipa-client-install to ensure
-sss_ssh_knownhosts and sss_ssh_knownhostsproxy functions properly.
-
-Fixes: https://pagure.io/freeipa/issue/9649
-Related: https://pagure.io/freeipa/issue/9536
-
-Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaclient/install/client.py | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 802db9614..47a371f62 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -974,6 +974,8 @@ def configure_sssd_conf(
- 
-     sssd_enable_service(sssdconfig, 'nss')
-     sssd_enable_service(sssdconfig, 'pam')
-+    if options.conf_ssh:
-+        sssd_enable_service(sssdconfig, 'ssh')
- 
-     domain.set_option('ipa_domain', cli_domain)
-     domain.set_option('ipa_hostname', client_hostname)
--- 
-2.49.0
-
-
-From 8d242ba741ec22b258d5e70a530cefd0940783c7 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 23 Jul 2024 17:07:06 -0400
-Subject: [PATCH 010/116] ipa-migrate - fix migration issues with entries using
- ipaUniqueId in the RDN
-
-We need to handle these entries differently and specify what attribute
-and search base to use to find the entry on the local server.  Most
-entries can use the "cn" attribute but for selinux usermaps we need to
-search using the ipaOwner attribute which is a DN, and in turn requires
-additional handling/converting in order to properly check if the usermap
-exists or not.
-
-Also fixed an issue where an attribute should be removed from the local
-entry if it does not exist on the remote entry.
-
-And fixed the handling od "sudoOrder" which is defined as multi-valued
-in the schema, but we really need to treat it as single-valued
-
-Fixes: https://pagure.io/freeipa/issue/9640
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py           | 119 +++++++++++++++++++--
- ipaserver/install/ipa_migrate_constants.py |  84 +++++++++++++--
- 2 files changed, 188 insertions(+), 15 deletions(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index e21937401..78c530f24 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -32,7 +32,7 @@ from ipaserver.install.ipa_migrate_constants import (
-     DS_CONFIG, DB_OBJECTS, DS_INDEXES, BIND_DN, LOG_FILE_NAME,
-     STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS,
-     DNA_REGEN_VAL, DNA_REGEN_ATTRS, NIS_PLUGIN, IGNORE_ATTRS,
--    DB_EXCLUDE_TREES
-+    DB_EXCLUDE_TREES, POLICY_OP_ATTRS
- )
- 
- """
-@@ -529,6 +529,14 @@ class IPAMigrate():
-     #
-     # Helper functions
-     #
-+    def attr_is_operational(self, attr):
-+        schema = self.local_conn.schema
-+        attr_obj = schema.get_obj(ldap.schema.AttributeType, attr)
-+        if attr_obj is not None:
-+            if attr_obj.usage == 1:
-+                return True
-+        return False
-+
-     def replace_suffix(self, entry_dn):
-         """
-         Replace the base DN in an entry DN
-@@ -1122,6 +1130,18 @@ class IPAMigrate():
-             stats['reset_range'] += 1
-         return entry
- 
-+    def attr_is_required(self, attr, entry):
-+        """
-+        Check if an attribute is required in this entry
-+        """
-+        entry_oc = entry['objectClass']
-+        for oc in entry_oc:
-+            required_attrs = self.local_conn.get_allowed_attributes(
-+                [oc], raise_on_unknown=False, attributes="must")
-+            if attr.lower() in required_attrs:
-+                return True
-+        return False
-+
-     def clean_entry(self, entry_dn, entry_type, entry_attrs):
-         """
-         Clean up the entry from the remote server
-@@ -1311,7 +1331,17 @@ class IPAMigrate():
-                                                    f"'{old_value}' "
-                                                    "new value "
-                                                    f"'{local_entry[attr][0]}'")
--
-+                            elif 'single' == sp_attr[1]:
-+                                # The attribute is defined as multivalued, but
-+                                # we really need to treat it as single valued
-+                                self.log_debug("Entry is different and will "
-+                                               f"be updated: '{local_dn}' "
-+                                               f"attribute '{attr}' replaced "
-+                                               "with val "
-+                                               f"'{remote_attrs[attr][0]}' "
-+                                               "old value: "
-+                                               f"{local_entry[attr][0]}")
-+                                local_entry[attr][0] = remote_attrs[attr][0]
-                             goto_next_attr = True
-                             break
- 
-@@ -1358,6 +1388,31 @@ class IPAMigrate():
-                 local_entry[attr] = remote_attrs[attr]
-                 entry_updated = True
- 
-+        # Remove attributes in the local entry that do not exist in the
-+        # remote entry
-+        remove_attrs = []
-+        for attr in local_entry:
-+            if (self.attr_is_operational(attr)
-+                and attr.lower() not in POLICY_OP_ATTRS) or \
-+               attr.lower() in IGNORE_ATTRS or \
-+               attr.lower() in STRIP_ATTRS or \
-+               attr.lower() == "usercertificate":
-+                # This is an attribute that we do not want to remove
-+                continue
-+
-+            if attr not in remote_attrs and \
-+               not self.attr_is_required(attr, local_entry):
-+                # Mark this attribute for deletion
-+                remove_attrs.append(attr)
-+                entry_updated = True
-+
-+        # Remove attributes
-+        for remove_attr in remove_attrs:
-+            self.log_debug("Entry is different and will be updated: "
-+                           f"'{local_dn}' attribute '{remove_attr}' "
-+                           "is being removed")
-+            del local_entry[remove_attr]
-+
-         if range_reset:
-             stats['reset_range'] += 1
- 
-@@ -1371,6 +1426,9 @@ class IPAMigrate():
-         """
-         Process chunks of remote entries from a paged results search
- 
-+        entry_dn = the remote entry DN
-+        entry_attrs = the remote entry's attributes stored in a dict
-+
-         Identify entry type
-         Process entry (removing/change attr/val/schema)
-         Compare processed remote entry with local entry, merge/overwrite?
-@@ -1426,6 +1484,47 @@ class IPAMigrate():
-         # Based on the entry type do additional work
-         #
- 
-+        # For entries with alternate identifying needs we need to rebuild the
-+        # local dn. Typically this is for entries that use ipaUniqueId as the
-+        # RDN attr
-+        if entry_type != "custom" and 'alt_id' in DB_OBJECTS[entry_type]:
-+            attr = DB_OBJECTS[entry_type]['alt_id']['attr']
-+            base = DB_OBJECTS[entry_type]['alt_id']['base']
-+            srch_filter = f'{attr}={entry_attrs[attr][0]}'
-+            if DB_OBJECTS[entry_type]['alt_id']['isDN'] is True:
-+                # Convert the filter to match the local suffix
-+                srch_filter = self.replace_suffix(srch_filter)
-+            srch_base = base + str(self.local_suffix)
-+
-+            try:
-+                entries = self.local_conn.get_entries(DN(srch_base),
-+                                                      filter=srch_filter)
-+                if len(entries) == 1:
-+                    local_dn = entries[0].dn
-+                elif len(entries) == 0:
-+                    # Not found, no problem just proceed and we will add it
-+                    pass
-+                else:
-+                    # Found too many entries - should not happen
-+                    self.log_error('Found too many local matching entries '
-+                                   f'for "{local_dn}"')
-+                    if self.args.force:
-+                        stats['ignored_errors'] += 1
-+                        return
-+                    else:
-+                        sys.exit(1)
-+            except errors.EmptyResult:
-+                # Not found, no problem just proceed and we will add it later
-+                pass
-+            except (errors.NetworkError, errors.DatabaseError) as e:
-+                self.log_error('Failed to find a local matching entry for '
-+                               f'"{local_dn}" error: {str(e)}')
-+                if self.args.force:
-+                    stats['ignored_errors'] += 1
-+                    return
-+                else:
-+                    sys.exit(1)
-+
-         # See if the entry exists on the local server
-         try:
-             local_entry = self.local_conn.get_entry(DN(local_dn),
-@@ -1441,14 +1540,20 @@ class IPAMigrate():
- 
-             if self.dryrun:
-                 self.write_update_to_ldif(local_entry)
--                DB_OBJECTS[entry_type]['count'] += 1
-+                if entry_type == "custom":
-+                    stats['custom'] += 1
-+                else:
-+                    DB_OBJECTS[entry_type]['count'] += 1
-                 stats['total_db_migrated'] += 1
-                 return
- 
-             # Update the local entry
-             try:
-                 self.local_conn.update_entry(local_entry)
--                DB_OBJECTS[entry_type]['count'] += 1
-+                if entry_type == "custom":
-+                    stats['custom'] += 1
-+                else:
-+                    DB_OBJECTS[entry_type]['count'] += 1
-             except errors.ExecutionError as e:
-                 self.log_error(f'Failed to update "{local_dn}" error: '
-                                f'{str(e)}')
-@@ -1567,7 +1672,7 @@ class IPAMigrate():
-         """
-         Used paged search for online method to avoid large memory footprint
-         """
--        self.log_info("Migrating database ... (this make take a while)")
-+        self.log_info("Migrating database ... (this may take a while)")
-         if self.args.db_ldif is not None:
-             self.processDBOffline()
-         else:
-@@ -1608,7 +1713,7 @@ class IPAMigrate():
-                        f"{len(objectclasses)} objectClasses")
- 
-         # Loop over attributes and objectclasses and count them
--        schema = self.local_conn._get_schema()
-+        schema = self.local_conn.schema
-         local_schema = schema.ldap_entry()
-         for schema_type in [(attributes, "attributeTypes"),
-                             (objectclasses, "objectClasses")]:
-@@ -1967,7 +2072,7 @@ class IPAMigrate():
- 
-         # Run ipa-server-upgrade
-         self.log_info("Running ipa-server-upgrade ... "
--                      "(this make take a while)")
-+                      "(this may take a while)")
-         if self.dryrun:
-             self.log_info("Skipping ipa-server-upgrade in dryrun mode.")
-         else:
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index 0e26c7549..250f1b5b0 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -19,6 +19,28 @@ STRIP_OP_ATTRS = [
-     'nsuniqueid',
-     'dsentrydn',
-     'entryuuid',
-+    'entrydn',
-+    'entryid',
-+    'entryusn',
-+    'numsubordinates',
-+    'parentid',
-+    'tombstonenumsubordinates'
-+]
-+
-+# Operational attributes that we would want to remove from the local entry if
-+# they don't exist in the remote entry
-+POLICY_OP_ATTRS = [
-+    'nsaccountlock',
-+    'passwordexpiratontime',
-+    'passwordgraceusertime',
-+    'pwdpolicysubentry',
-+    'passwordexpwarned',
-+    'passwordretrycount',
-+    'retrycountresettime',
-+    'accountunlocktime',
-+    'passwordhistory',
-+    'passwordallowchangetime',
-+    'pwdreset'
- ]
- 
- # Atributes to strip from users/groups
-@@ -110,7 +132,7 @@ STRIP_OC = [
- #
- # The DS_CONFIG mapping breaks each config entry (or type of entry) into its
- # own catagory. Each catagory, or type, as DN list "dn", the attributes# we
--# are intrested in.  These attributes are broken into singel valued "attrs",
-+# are intrested in.  These attributes are broken into single valued "attrs",
- # or multi-valued attributes "multivalued".  If the attributes is single
- # valued then the value is replaced, if it's multivalued then it is "appended"
- #
-@@ -503,7 +525,7 @@ DS_CONFIG = {
- }
- 
- #
--# Slpai NIS is an optional plugin.  It requires special handling
-+# Slapi NIS is an optional plugin.  It requires special handling
- #
- NIS_PLUGIN = {
-     'dn': 'cn=NIS Server,cn=plugins,cn=config',
-@@ -565,6 +587,12 @@ DS_INDEXES = {
- # identify the entry.
- # The "label" and "count" attributes are used for the Summary Report
- #
-+# Some entries use ipaUniqueId as the RDN attribute, this makes comparing
-+# entries between the remote and local servers problematic. So we need special
-+# identifying information to find the local entry. In this case we use the
-+# "alt_id" key which is a dict of an attribute 'attr' and partial base DN
-+# 'base' - which is expected to end in a comma.
-+#
- DB_OBJECTS = {
-     # Plugins
-     'automember_def': {
-@@ -640,8 +668,8 @@ DB_OBJECTS = {
-         'oc': ['ipaconfigobject', 'ipaguiconfig'],
-         'subtree': 'cn=ipaconfig,cn=etc,$SUFFIX',
-         'special_attrs': [
--            # needs special handling, but
--            # ipa-server-upgrade rewrites this attribute anyway!
-+            # needs special handling, but ipa-server-upgrade rewrites this
-+            # attribute anyway!
-             ('ipausersearchfields', 'list'),
-         ],
-         'label': 'IPA Config',
-@@ -772,11 +800,16 @@ DB_OBJECTS = {
-         'mode': 'all',
-         'count': 0,
-     },
--    'subids': {  # unknown what these entries look like TODO
-+    'subids': {
-         'oc': [],
-         'subtree': ',cn=subids,cn=accounts,$SUFFIX',
-         'label': 'Sub IDs',
--        'mode': 'all',  # TODO Maybe production only?
-+        'mode': 'production',
-+        'alt_id': {
-+            'attr': 'ipaOwner',
-+            'isDN': True,
-+            'base': 'cn=subids,cn=accounts,',
-+        },
-         'count': 0,
-     },
- 
-@@ -884,6 +917,11 @@ DB_OBJECTS = {
-         'oc': ['ipahbacrule'],
-         'subtree': ',cn=hbac,$SUFFIX',
-         'label': 'HBAC Rules',
-+        'alt_id': {
-+            'attr': 'cn',
-+            'base': 'cn=hbac,',
-+            'isDN': False,
-+        },
-         'mode': 'all',
-         'count': 0,
-     },
-@@ -892,6 +930,11 @@ DB_OBJECTS = {
-     'selinux_usermap': {  # Not sure if this is needed, entry is empty  TODO
-         'oc': [],
-         'subtree': ',cn=usermap,cn=selinux,$SUFFIX',
-+        'alt_id': {
-+            'attr': 'cn',
-+            'base': 'cn=usermap,cn=selinux,',
-+            'isDN': False,
-+        },
-         'label': 'Selinux Usermaps',
-         'mode': 'all',
-         'count': 0,
-@@ -902,12 +945,27 @@ DB_OBJECTS = {
-         'oc': ['ipasudorule'],
-         'subtree': ',cn=sudorules,cn=sudo,$SUFFIX',
-         'label': 'Sudo Rules',
-+        'alt_id': {
-+            'attr': 'cn',
-+            'base': 'cn=sudorules,cn=sudo,',
-+            'isDN': False,
-+        },
-+        'special_attrs': [
-+            # schema defines sudoOrder as mutlivalued, but we need to treat
-+            # it as single valued
-+            ('sudoorder', 'single'),
-+        ],
-         'mode': 'all',
-         'count': 0,
-     },
-     'sudo_cmds': {
-         'oc': ['ipasudocmd'],
-         'subtree': ',cn=sudocmds,cn=sudo,$SUFFIX',
-+        'alt_id': {
-+            'attr': 'sudoCmd',
-+            'base': 'cn=sudocmds,cn=sudo,',
-+            'isDN': False,
-+        },
-         'label': 'Sudo Commands',
-         'mode': 'all',
-         'count': 0,
-@@ -991,6 +1049,11 @@ DB_OBJECTS = {
-         'oc': ['ipanisnetgroup'],
-         'not_oc': ['mepmanagedentry'],
-         'subtree': ',cn=ng,cn=alt,$SUFFIX',
-+        'alt_id': {
-+            'attr': 'cn',
-+            'base': 'cn=ng,cn=alt,',
-+            'isDN': False,
-+        },
-         'label': 'Network Groups',
-         'mode': 'all',
-         'count': 0,
-@@ -1006,9 +1069,14 @@ DB_OBJECTS = {
-         'count': 0,
-     },
-     'caacls': {
--        'oc': ['top'],
-+        'oc': ['ipacaacl'],
-         'subtree': ',cn=caacls,cn=ca,$SUFFIX',
--        'label': 'CA Certificates',
-+        'alt_id': {
-+            'attr': 'cn',
-+            'base': 'cn=caacls,cn=ca,',
-+            'isDN': False,
-+        },
-+        'label': 'CA Certificate ACLs',
-         'mode': 'all',
-         'count': 0,
-     },
--- 
-2.49.0
-
-
-From 3b5a980f5b65b03b9fd7ad0cfbb6c87874d3ff24 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 3 Sep 2024 13:42:05 -0400
-Subject: [PATCH 011/116] ipa-migrate - fix alternate entry search filter
-
-Processing a filter like a DN can cause normalization issues that result
-in an invalid filter. Make sure the filter is encapsulated with
-parenthesis and we call replace_suffix_value() instead of
-replace_suffix()
-
-Fixes: https://pagure.io/freeipa/issue/9658
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-
-Fix typo in test
-
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/ipa_migrate.py                    | 4 ++--
- ipatests/test_integration/test_ipa_ipa_migration.py | 6 +++---
- 2 files changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index 78c530f24..38356aa23 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -1490,10 +1490,10 @@ class IPAMigrate():
-         if entry_type != "custom" and 'alt_id' in DB_OBJECTS[entry_type]:
-             attr = DB_OBJECTS[entry_type]['alt_id']['attr']
-             base = DB_OBJECTS[entry_type]['alt_id']['base']
--            srch_filter = f'{attr}={entry_attrs[attr][0]}'
-+            srch_filter = f'({attr}={entry_attrs[attr][0]})'
-             if DB_OBJECTS[entry_type]['alt_id']['isDN'] is True:
-                 # Convert the filter to match the local suffix
--                srch_filter = self.replace_suffix(srch_filter)
-+                srch_filter = self.replace_suffix_value(srch_filter)
-             srch_base = base + str(self.local_suffix)
- 
-             try:
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index f697bbfbf..288165e8a 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -610,7 +610,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
-         MIGRATION_CONFIG_LOG_MSG = "Migrating configuration ...\n"
-         IPA_UPGRADE_LOG_MSG = (
--            "Running ipa-server-upgrade ... (this make take a while)\n"
-+            "Running ipa-server-upgrade ... (this may take a while)\n"
-         )
-         SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
-         MIGRATION_COMPLETE_LOG_MSG = "Migration complete!\n"
-@@ -641,10 +641,10 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         tasks.kinit_admin(self.replicas[0])
-         MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
-         MIGRATION_DATABASE_LOG_MSG = (
--            "Migrating database ... (this make take a while)\n"
-+            "Migrating database ... (this may take a while)\n"
-         )
-         IPA_UPGRADE_LOG_MSG = (
--            "Running ipa-server-upgrade ... (this make take a while)\n"
-+            "Running ipa-server-upgrade ... (this may take a while)\n"
-         )
-         SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
-         result = run_migrate(
--- 
-2.49.0
-
-
-From c96d172d7d2e87513d9bd51a98591858e1f88def Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 5 Sep 2024 14:52:26 +0200
-Subject: [PATCH 012/116] Custodia: in fips mode add -nomac or -nomacver to
- openssl pkcs12
-
-In FIPS mode the command openssl pkcs12 fails unless the
-export is called with -nomac and import with -nomacver
-
-The command is used by custodia to export private keys from the
-master and import them in the replica.
-
-Fixes: https://pagure.io/freeipa/issue/9577
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/secrets/handlers/pemfile.py | 20 ++++++++++++++++++--
- 1 file changed, 18 insertions(+), 2 deletions(-)
-
-diff --git a/ipaserver/secrets/handlers/pemfile.py b/ipaserver/secrets/handlers/pemfile.py
-index ad36bd020..006d35169 100644
---- a/ipaserver/secrets/handlers/pemfile.py
-+++ b/ipaserver/secrets/handlers/pemfile.py
-@@ -8,6 +8,7 @@ import json
- import os
- 
- from ipaplatform.paths import paths
-+from ipaplatform.tasks import tasks
- from ipapython import ipautil
- from . import common
- 
-@@ -25,7 +26,7 @@ def export_key(args, tmpdir):
-         f.write(password)
- 
-     # OpenSSL does not support pkcs12 export of a cert without key
--    ipautil.run([
-+    cmd = [
-         paths.OPENSSL, 'pkcs12', '-export',
-         '-in', args.certfile,
-         '-out', pk12file,
-@@ -34,7 +35,13 @@ def export_key(args, tmpdir):
-         '-keypbe', 'AES-256-CBC',
-         '-certpbe', 'AES-256-CBC',
-         '-macalg', 'sha384',
--    ])
-+    ]
-+
-+    fips_enabled = tasks.is_fips_enabled()
-+    if fips_enabled:
-+        cmd.append('-nomac')
-+
-+    ipautil.run(cmd)
- 
-     with open(pk12file, 'rb') as f:
-         p12data = f.read()
-@@ -69,6 +76,11 @@ def import_key(args, tmpdir):
-         '-out', args.certfile,
-         '-password', 'file:{pk12pwfile}'.format(pk12pwfile=pk12pwfile),
-     ]
-+
-+    fips_enabled = tasks.is_fips_enabled()
-+    if fips_enabled:
-+        cmd.append('-nomacver')
-+
-     ipautil.run(cmd, umask=0o027)
- 
-     # get the private key from the file
-@@ -79,6 +91,10 @@ def import_key(args, tmpdir):
-         '-out', args.keyfile,
-         '-password', 'file:{pk12pwfile}'.format(pk12pwfile=pk12pwfile),
-     ]
-+
-+    if fips_enabled:
-+        cmd.append('-nomacver')
-+
-     ipautil.run(cmd, umask=0o027)
- 
- 
--- 
-2.49.0
-
-
-From 32a46d1acef1360c6737a702675ea170fe50d5ba Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 13 Sep 2024 08:56:21 +0200
-Subject: [PATCH 013/116] azure tests: move to fedora 40
-
-Fedora 39 is approaching its EOL (2024-11-12),
-move azure tests to fedora 40
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/azure/Dockerfiles/Dockerfile.build.fedora | 2 +-
- ipatests/azure/templates/variables-fedora.yml      | 2 +-
- 2 files changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-index aef670eb8..3d2926140 100644
---- a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-+++ b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-@@ -1,4 +1,4 @@
--FROM registry.fedoraproject.org/fedora-toolbox:39
-+FROM registry.fedoraproject.org/fedora-toolbox:40
- MAINTAINER [FreeIPA Developers freeipa-devel@lists.fedorahosted.org]
- ENV container=docker LANG=en_US.utf8 LANGUAGE=en_US.utf8 LC_ALL=en_US.utf8
- 
-diff --git a/ipatests/azure/templates/variables-fedora.yml b/ipatests/azure/templates/variables-fedora.yml
-index f2c2f5241..fafa9a073 100644
---- a/ipatests/azure/templates/variables-fedora.yml
-+++ b/ipatests/azure/templates/variables-fedora.yml
-@@ -1,7 +1,7 @@
- variables:
-   IPA_PLATFORM: fedora
-   # the Docker public image to build IPA packages (rpms)
--  DOCKER_BUILD_IMAGE: 'registry.fedoraproject.org/fedora-toolbox:39'
-+  DOCKER_BUILD_IMAGE: 'registry.fedoraproject.org/fedora-toolbox:40'
- 
-   # the Dockerfile to build Docker image for running IPA tests
-   DOCKER_DOCKERFILE: ${{ format('Dockerfile.build.{0}', variables.IPA_PLATFORM) }}
--- 
-2.49.0
-
-
-From a9e653ca36a0829ae59cd204e7388d7a6c91e082 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 13 Sep 2024 09:58:36 +0200
-Subject: [PATCH 014/116] UnsafeIPAddress: pass flag=0 to IPNetwork
-
-When parsing a string, the constructor tries to parse the value
-as an IP Address first, or falls back to an IPNetwork with the
-flags INET_PTON.
-
-Use the flag 0 instead for an IPNetwork.
-
-Fixes: https://pagure.io/freeipa/issue/9645
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipapython/ipautil.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
-index 3e98bfd6a..c237d59fb 100644
---- a/ipapython/ipautil.py
-+++ b/ipapython/ipautil.py
-@@ -119,7 +119,7 @@ class UnsafeIPAddress(netaddr.IPAddress):
-                 if addr.version != 6:
-                     raise
-         except ValueError:
--            self._net = netaddr.IPNetwork(addr, flags=self.netaddr_ip_flags)
-+            self._net = netaddr.IPNetwork(addr, flags=0)
-             addr = self._net.ip
-         super(UnsafeIPAddress, self).__init__(addr,
-                                               flags=self.netaddr_ip_flags)
--- 
-2.49.0
-
-
-From 8a8fb11447a76b835c7ec5e51e01ccb26b0906ba Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 13 Sep 2024 10:31:12 +0200
-Subject: [PATCH 015/116] azure pipeline: use latest version of
- DownloadPipelineArtifact task
-
-update the task from v0 to v2
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/azure/templates/setup-test-environment.yml | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/azure/templates/setup-test-environment.yml b/ipatests/azure/templates/setup-test-environment.yml
-index f6d33fab6..584abb990 100644
---- a/ipatests/azure/templates/setup-test-environment.yml
-+++ b/ipatests/azure/templates/setup-test-environment.yml
-@@ -17,13 +17,13 @@ steps:
-     sudo modprobe {nfs,nfsd}
-   displayName: Configure NFS to allow NFS server/client within containers
- 
--- task: DownloadPipelineArtifact@0
-+- task: DownloadPipelineArtifact@2
-   displayName: Download prebuilt packages '$(IPA_PACKAGES_ARTIFACT)'
-   inputs:
-     artifactName: $(IPA_PACKAGES_ARTIFACT)
-     targetPath: $(Build.Repository.LocalPath)/dist
- 
--- task: DownloadPipelineArtifact@0
-+- task: DownloadPipelineArtifact@2
-   displayName: Download pre-built container '$(IPA_IMAGE_ARTIFACT)'
-   inputs:
-     artifactName: $(IPA_IMAGE_ARTIFACT)
--- 
-2.49.0
-
-
-From d102773ce24481c6797f71557b75e77921164285 Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Thu, 12 Sep 2024 12:38:52 +0300
-Subject: [PATCH 016/116] ipatests: make TestDuplicates teardowns order
- agnostic
-
-Fixtures 'user4' and 'user5' track the same actual user 'tuser'.
-If used together their teardowns can fail depending on the
-order of execution.
-
-With this change fixtures of TestDuplicates are simplified and
-method-scoped.
-
-Related: https://pagure.io/freeipa/issue/9571
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_xmlrpc/test_stageuser_plugin.py | 76 +++++++------------
- 1 file changed, 28 insertions(+), 48 deletions(-)
-
-diff --git a/ipatests/test_xmlrpc/test_stageuser_plugin.py b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-index 9ae5561df..6ed593fbf 100644
---- a/ipatests/test_xmlrpc/test_stageuser_plugin.py
-+++ b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-@@ -120,12 +120,6 @@ def stageduser3(request, xmlrpc_setup):
-     return tracker.make_fixture_activate(request)
- 
- 
--@pytest.fixture(scope='class')
--def stageduser4(request, xmlrpc_setup):
--    tracker = StageUserTracker(u'tuser', u'test', u'user')
--    return tracker.make_fixture(request)
--
--
- @pytest.fixture(scope='class')
- def stageduser_notposix(request, xmlrpc_setup):
-     tracker = StageUserTracker(u'notposix', u'notposix', u'notposix')
-@@ -161,18 +155,6 @@ def user3(request, xmlrpc_setup):
-     return tracker.make_fixture(request)
- 
- 
--@pytest.fixture(scope='class')
--def user4(request, xmlrpc_setup):
--    tracker = UserTracker(u'tuser', u'test', u'user')
--    return tracker.make_fixture(request)
--
--
--@pytest.fixture(scope='class')
--def user5(request, xmlrpc_setup):
--    tracker = UserTracker(u'tuser', u'test', u'user')
--    return tracker.make_fixture(request)
--
--
- @pytest.fixture(scope='class')
- def user6(request, xmlrpc_setup):
-     tracker = UserTracker(u'suser2', u'staged', u'user')
-@@ -724,52 +706,50 @@ class TestManagers(XMLRPC_test):
- 
- @pytest.mark.tier1
- class TestDuplicates(XMLRPC_test):
--    def test_active_same_as_preserved(self, user4, user5):
--        user4.ensure_missing()
--        user5.make_preserved_user()
--        command = user4.make_create_command()
-+    @pytest.fixture
-+    def user(self, request, xmlrpc_setup):
-+        tracker = UserTracker("tuser", "test", "user")
-+        return tracker.make_fixture(request)
-+
-+    @pytest.fixture
-+    def stageduser(self, request, xmlrpc_setup):
-+        tracker = StageUserTracker("tuser", "test", "user")
-+        return tracker.make_fixture(request)
-+
-+    def test_active_same_as_preserved(self, user):
-+        user.make_preserved_user()
-+        command = user.make_create_command()
-         with raises_exact(errors.DuplicateEntry(
--                message=u'user with name "%s" already exists' % user4.uid)):
-+                message=u'user with name "%s" already exists' % user.uid)):
-             command()
--        user5.delete()
- 
--    def test_staged_same_as_active(self, user4, stageduser4):
--        user4.ensure_exists()
--        stageduser4.create()  # can be created
-+    def test_staged_same_as_active(self, user, stageduser):
-+        user.create()
-+        stageduser.create()  # can be created
- 
--        command = stageduser4.make_activate_command()
-+        command = stageduser.make_activate_command()
-         with raises_exact(errors.DuplicateEntry(
-                 message=u'active user with name "%s" already exists' %
--                user4.uid)):
-+                user.uid)):
-             command()  # cannot be activated
- 
--        user4.delete()
--        stageduser4.delete()
--
--    def test_staged_same_as_preserved(self, user5, stageduser4):
--        user5.make_preserved_user()
--        stageduser4.create()  # can be created
-+    def test_staged_same_as_preserved(self, user, stageduser):
-+        user.make_preserved_user()
-+        stageduser.create()  # can be created
- 
--        command = stageduser4.make_activate_command()
-+        command = stageduser.make_activate_command()
-         with raises_exact(errors.DuplicateEntry(
-                 message=u'This entry already exists')):
-             command()  # cannot be activated
- 
--        user5.delete()
--        stageduser4.delete()
-+    def test_active_same_as_staged(self, user, stageduser):
-+        stageduser.create()
-+        user.create()  # can be created
- 
--    def test_active_same_as_staged(self, user4, stageduser4):
--        user4.ensure_missing()
--        stageduser4.ensure_exists()
--        command = user4.make_create_command()
--        result = command()
--        user4.track_create()
--        user4.check_create(result)  # can be created
--
--        command = stageduser4.make_activate_command()
-+        command = stageduser.make_activate_command()
-         with raises_exact(errors.DuplicateEntry(
-                 message=u'active user with name "%s" already exists' %
--                user4.uid)):
-+                user.uid)):
-             command()  # cannot be activated
- 
- 
--- 
-2.49.0
-
-
-From 993f792f2e26f8a14a5e3691c06a6b15d57072e7 Mon Sep 17 00:00:00 2001
-From: Sam Morris <sam@robots.org.uk>
-Date: Tue, 17 Sep 2024 12:43:08 +0100
-Subject: [PATCH 017/116] Fix a couple of instances of the "no-break control
- character" being used inadvertently
-
-Fixes https://pagure.io/freeipa/issue/9665
-
-Signed-off-by: Sam Morris <sam@robots.org.uk>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- client/man/ipa-client-install.1 | 4 ++--
- client/man/ipa.1                | 6 +++---
- 2 files changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/client/man/ipa-client-install.1 b/client/man/ipa-client-install.1
-index 4a755ac33..725b11422 100644
---- a/client/man/ipa-client-install.1
-+++ b/client/man/ipa-client-install.1
-@@ -240,8 +240,8 @@ for more information. The option is mutually exclusive with
- \fB\-\-pkinit\-anchor\fR=\fIFILEDIR\fR
- Trust anchors (root and intermediate CA certs) for PKINIT. \fIFILEDIR\fR is
- either the absolute path to a PEM bundle (for example
--'FILE:/etc/pki/tls/cert.pem') or to an OpenSSL hash directory (for example
--'DIR:/etc/ssl/certs/'). The option can be used multiple times. PKINIT
-+\fIFILE:/etc/pki/tls/cert.pem\fR) or to an OpenSSL hash directory (for example
-+\fIDIR:/etc/ssl/certs/\fR). The option can be used multiple times. PKINIT
- requires the full trust chain of the Kerberos KDC server as well as the full
- trust chain of the identity certificate.
- 
-diff --git a/client/man/ipa.1 b/client/man/ipa.1
-index 172c50d8a..c404c5be3 100644
---- a/client/man/ipa.1
-+++ b/client/man/ipa.1
-@@ -176,11 +176,11 @@ journal as journald records execution context. See systemd.journal\-fields(7)
- for details.
- 
- The details of the individual logged messages can be explained with the help of
--'\fBjournalctl -x\fR' command, while full set of logged properties can be
--retrieved with '\fBjournalctl -o json-pretty\fR'. See journalctl(1) for details
-+\fBjournalctl -x\fR command, while full set of logged properties can be
-+retrieved with \fBjournalctl -o json-pretty\fR. See journalctl(1) for details
- on the systemd journal viewer.
- 
--For the sample message above, an explanation could be requested with '\fBjournalctl -x -g ldap2_140328582446688\fR' where LDAP backend connection instance identifier can be used to uniquely fetch that individual message.
-+For the sample message above, an explanation could be requested with \fBjournalctl -x -g ldap2_140328582446688\fR where LDAP backend connection instance identifier can be used to uniquely fetch that individual message.
- 
- .SH "EXAMPLES"
- .TP
--- 
-2.49.0
-
-
-From a343c149838a3058794f33c75c58b75bc1748f7f Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 17 Sep 2024 17:00:49 +0200
-Subject: [PATCH 018/116] ipatests: provide a ccache to rpcclient
- deletetrustdom
-
-With samba update to samba-4.20.4, rpcclient now needs a
-ccache otherwise it prompts for a password.
-
-Fixes: https://pagure.io/freeipa/issue/9667
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/pytest_ipa/integration/tasks.py | 23 ++++++++++++++++++++---
- 1 file changed, 20 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index 9d6b5f67a..677fb7534 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -795,15 +795,22 @@ def remove_trust_info_from_ad(master, ad_domain, ad_hostname):
-     kinit_as_user(master,
-                   'Administrator@{}'.format(ad_domain.upper()),
-                   master.config.ad_admin_password)
-+    # Find cache for the user
-+    cache_args = []
-+    cache = get_credential_cache(master)
-+    if cache:
-+        cache_args = ["--use-krb5-ccache", cache]
-+
-     # Detect whether rpcclient supports -k or --use-kerberos option
-     res = master.run_command(['rpcclient', '-h'], raiseonerr=False)
-     if "--use-kerberos" in res.stderr_text:
-         rpcclient_krb5_knob = "--use-kerberos=desired"
-     else:
-         rpcclient_krb5_knob = "-k"
--    master.run_command(['rpcclient', rpcclient_krb5_knob, ad_hostname,
--                        '-c', 'deletetrustdom {}'.format(master.domain.name)],
--                       raiseonerr=False)
-+    cmd_args = ['rpcclient', rpcclient_krb5_knob, ad_hostname]
-+    cmd_args.extend(cache_args)
-+    cmd_args.extend(['-c', 'deletetrustdom {}'.format(master.domain.name)])
-+    master.run_command(cmd_args, raiseonerr=False)
- 
- 
- def configure_auth_to_local_rule(master, ad):
-@@ -1086,6 +1093,16 @@ def kinit_admin(host, raiseonerr=True):
-                       raiseonerr=raiseonerr)
- 
- 
-+def get_credential_cache(host):
-+    # Return the credential cache currently in use on host or None
-+    result = host.run_command(["klist"]).stdout_text
-+    pattern = re.compile(r'Ticket cache: (?P<cache>.*)\n')
-+    res = pattern.search(result)
-+    if res:
-+        return res['cache']
-+    return None
-+
-+
- def uninstall_master(host, ignore_topology_disconnect=True,
-                      ignore_last_of_role=True, clean=True, verbose=False):
-     uninstall_cmd = ['ipa-server-install', '--uninstall', '-U']
--- 
-2.49.0
-
-
-From 743c7b46e463bef666dc84e9f513eb7dee7f59f6 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 17 Sep 2024 14:48:58 +0200
-Subject: [PATCH 019/116] test_adtrust_install: add --use-krb5-ccache to
- smbclient command
-
-With samba 4.20.4 the smbclient commands needs a ccache otherwise it
-prompts for a password.
-
-Fixes: https://pagure.io/freeipa/issue/9666
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- .../test_integration/test_adtrust_install.py  | 28 ++++++++++++-------
- 1 file changed, 18 insertions(+), 10 deletions(-)
-
-diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
-index de252db17..79a91dfaa 100644
---- a/ipatests/test_integration/test_adtrust_install.py
-+++ b/ipatests/test_integration/test_adtrust_install.py
-@@ -873,17 +873,25 @@ class TestIpaAdTrustInstall(IntegrationTest):
-              "path", "/freeipa4234"])
-         self.master.run_command(["touch", "before"])
-         self.master.run_command(["touch", "after"])
--        self.master.run_command(
--            ["smbclient", "--use-kerberos=desired",
--             "-c=put before", "//{}/share".format(
--                 self.master.hostname)]
--        )
-+        # Find cache for the admin user
-+        cache_args = []
-+        cache = tasks.get_credential_cache(self.master)
-+        if cache:
-+            cache_args = ["--use-krb5-ccache", cache]
-+
-+        cmd_args = ["smbclient", "--use-kerberos=desired"]
-+        cmd_args.extend(cache_args)
-+        cmd_args.extend([
-+            "-c=put before", "//{}/share".format(self.master.hostname)
-+        ])
-+        self.master.run_command(cmd_args)
-         self.master.run_command(
-             ["net", "conf", "setparm", "share",
-              "valid users", "@admins"])
--        result = self.master.run_command(
--            ["smbclient", "--use-kerberos=desired",
--             "-c=put after", "//{}/share".format(
--                 self.master.hostname)]
--        )
-+        cmd_args = ["smbclient", "--use-kerberos=desired"]
-+        cmd_args.extend(cache_args)
-+        cmd_args.extend([
-+            "-c=put after", "//{}/share".format(self.master.hostname)
-+        ])
-+        result = self.master.run_command(cmd_args)
-         assert msg not in result.stdout_text
--- 
-2.49.0
-
-
-From a785d0c561b8e22bd9d56739481095e07e0a7eb7 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 30 Sep 2024 13:30:46 -0400
-Subject: [PATCH 020/116] Don't rely on removing the CA to uninstall the ACME
- depoyment
-
-There has always been a pki-server commnd acme-remove. We were
-not aware that it should be called prior to removing a CA. In
-11.5.0 this is strongly encouraged by the PKI team. In 11.6.0
-ACME is treated as a full subsystem so will be removed in the
-future using pkidestroy -s ACME
-
-The new class acmeinstance.ACMEInstance is introduced so its
-uninstallation can be handled in a similar way as the other
-PKI services via DogtagInstance. It is, right now, a pretty
-thin wrapper.
-
-We can discuss moving the ACME installation routines here at
-some point. It would be ok as long as we don't have to introduce
-another PKI restart as part of it.
-
-In PKI 11.6.0 pkidestroy has new options to ensure a clean
-uninstall: --remove-conf --remove-logs. Pass those options
-into pkidestroy calls for 11.6.0+.
-
-Clean up an additional IPA-generated file that needs to be
-cleaned up during uninstall: /root/kracert.p12. 11.6.0 is
-more sensitive to leftover files than previous versions.
-
-Fixes: https://pagure.io/freeipa/issue/9673
-Fixes: https://pagure.io/freeipa/issue/9674
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/install/acmeinstance.py             | 31 +++++++++++++
- ipaserver/install/ca.py                       |  5 ++-
- ipaserver/install/cainstance.py               |  1 +
- ipaserver/install/dogtaginstance.py           | 44 +++++++++++++------
- .../test_integration/test_uninstallation.py   | 21 +++++++++
- 5 files changed, 87 insertions(+), 15 deletions(-)
- create mode 100644 ipaserver/install/acmeinstance.py
-
-diff --git a/ipaserver/install/acmeinstance.py b/ipaserver/install/acmeinstance.py
-new file mode 100644
-index 000000000..0027c3145
---- /dev/null
-+++ b/ipaserver/install/acmeinstance.py
-@@ -0,0 +1,31 @@
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+
-+import logging
-+
-+from ipaserver.install.dogtaginstance import DogtagInstance
-+
-+logger = logging.getLogger(__name__)
-+
-+
-+class ACMEInstance(DogtagInstance):
-+    """
-+    ACME is deployed automatically with a CA subsystem but it is the
-+    responsibility of IPA to uninstall the service.
-+
-+    This is mostly a placeholder for the uninstaller. We can
-+    eventually move the ACME installation routines into this class
-+    if we want but it might result in an extra PKI restart which
-+    would be slow.
-+    """
-+    def __init__(self, realm=None, host_name=None):
-+        super(ACMEInstance, self).__init__(
-+            realm=realm,
-+            subsystem="ACME",
-+            service_desc="ACME server",
-+            host_name=host_name
-+        )
-+
-+    def uninstall(self):
-+        DogtagInstance.uninstall(self)
-diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
-index ffcb52683..520e3fc5d 100644
---- a/ipaserver/install/ca.py
-+++ b/ipaserver/install/ca.py
-@@ -22,7 +22,7 @@ from ipaplatform.constants import constants
- from ipaserver.install import sysupgrade
- from ipapython.install import typing
- from ipapython.install.core import group, knob, extend_knob
--from ipaserver.install import cainstance, bindinstance, dsinstance
-+from ipaserver.install import acmeinstance, cainstance, bindinstance, dsinstance
- from ipapython import ipautil, certdb
- from ipapython import ipaldap
- from ipapython.admintool import ScriptError
-@@ -715,6 +715,9 @@ def install_step_1(standalone, replica_config, options, custodia):
- 
- 
- def uninstall():
-+    acme = acmeinstance.ACMEInstance(api.env.realm)
-+    acme.uninstall()
-+
-     ca_instance = cainstance.CAInstance(api.env.realm)
-     ca_instance.stop_tracking_certificates()
-     ipautil.remove_file(paths.RA_AGENT_PEM)
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index 5dac2c044..5c2c9f8b9 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -1118,6 +1118,7 @@ class CAInstance(DogtagInstance):
- 
-         ipautil.remove_file(paths.DOGTAG_ADMIN_P12)
-         ipautil.remove_file(paths.CACERT_P12)
-+        ipautil.remove_file(paths.ADMIN_CERT_PATH)
- 
-     def unconfigure_certmonger_renewal_guard(self):
-         if not self.is_configured():
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index e89492312..4b0f4d274 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -304,21 +304,37 @@ class DogtagInstance(service.Service):
-         if self.is_installed():
-             self.print_msg("Unconfiguring %s" % self.subsystem)
- 
--        args = [paths.PKIDESTROY,
--                "-i", "pki-tomcat", "--force",
--                "-s", self.subsystem]
--
--        # specify --log-file <path> on PKI 11.0.0 or later
--
-+        args = []
-         pki_version = pki.util.Version(pki.specification_version())
--        if pki_version >= pki.util.Version("11.0.0"):
--            timestamp = time.strftime(
--                "%Y%m%d%H%M%S",
--                time.localtime(time.time()))
--            log_file = os.path.join(
--                paths.VAR_LOG_PKI_DIR,
--                "pki-%s-destroy.%s.log" % (self.subsystem.lower(), timestamp))
--            args.extend(["--log-file", log_file])
-+        if self.subsystem == "ACME":
-+            if pki_version < pki.util.Version("11.0.0"):
-+                return
-+            elif (
-+                pki.util.Version("11.0.0") <= pki_version
-+                <= pki.util.Version("11.5.0")
-+            ):
-+                args = ['pki-server', 'acme-remove']
-+            else:
-+                # fall through for PKI >= 11.6.0
-+                pass
-+        if not args:
-+            args = [paths.PKIDESTROY,
-+                    "-i", "pki-tomcat", "--force",
-+                    "-s", self.subsystem]
-+
-+            # specify --log-file <path> on PKI 11.0.0 or later
-+
-+            if pki_version >= pki.util.Version("11.0.0"):
-+                timestamp = time.strftime(
-+                    "%Y%m%d%H%M%S",
-+                    time.localtime(time.time()))
-+                log_file = os.path.join(
-+                    paths.VAR_LOG_PKI_DIR,
-+                    "pki-%s-destroy.%s.log" %
-+                    (self.subsystem.lower(), timestamp))
-+                args.extend(["--log-file", log_file])
-+            if pki_version >= pki.util.Version("11.6.0"):
-+                args.extend(["--remove-conf", "--remove-logs"])
- 
-         try:
-             ipautil.run(args)
-diff --git a/ipatests/test_integration/test_uninstallation.py b/ipatests/test_integration/test_uninstallation.py
-index 4f8f17ce3..049c50db5 100644
---- a/ipatests/test_integration/test_uninstallation.py
-+++ b/ipatests/test_integration/test_uninstallation.py
-@@ -197,6 +197,7 @@ class TestUninstallCleanup(IntegrationTest):
-             '/var/lib/sss/pubconf/krb5.include.d/localauth_plugin',
-             '/var/named/dynamic/managed-keys.bind',
-             '/var/named/dynamic/managed-keys.bind.jnl',
-+            '/var/lib/systemd/coredump/',
-         ]
- 
-         leftovers = []
-@@ -217,3 +218,23 @@ class TestUninstallCleanup(IntegrationTest):
-                 leftovers.append(line)
- 
-         assert len(leftovers) == 0
-+
-+
-+class TestUninstallReinstall(IntegrationTest):
-+    """Test install, uninstall, re-install.
-+
-+       Reinstall with PKI 11.6.0 was failing
-+       https://pagure.io/freeipa/issue/9673
-+    """
-+
-+    num_replicas = 0
-+
-+    @classmethod
-+    def install(cls, mh):
-+        tasks.install_master(cls.master, setup_dns=False)
-+
-+    def test_uninstall_server(self):
-+        tasks.uninstall_master(self.master)
-+
-+    def test_reinstall_server(self):
-+        tasks.install_master(self.master, setup_dns=False)
--- 
-2.49.0
-
-
-From ae4c2ad6cd966d48c063814f494dcc16cf0ccd4c Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Tue, 24 Sep 2024 13:46:48 +0530
-Subject: [PATCH 021/116] ipatests: Fixes for ipa-idrange-fix testsuite
-
-This patch adds the line tasks.install_master(cls.master).
-The kinit admin command fails with the below error as the
-IPA is not configured on the test system
-
-'ipa: ERROR: stderr: kinit: Configuration file does not specify default
-realm when parsing name admin'
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipa_idrange_fix.py | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipa_idrange_fix.py b/ipatests/test_integration/test_ipa_idrange_fix.py
-index ff8fbdac9..0c915bd09 100644
---- a/ipatests/test_integration/test_ipa_idrange_fix.py
-+++ b/ipatests/test_integration/test_ipa_idrange_fix.py
-@@ -17,6 +17,9 @@ logger = logging.getLogger(__name__)
- 
- 
- class TestIpaIdrangeFix(IntegrationTest):
-+
-+    topology = 'line'
-+
-     @classmethod
-     def install(cls, mh):
-         super(TestIpaIdrangeFix, cls).install(mh)
--- 
-2.49.0
-
-
-From 4b34bfb9819fa9a9b7b8131dddc772c59c14501f Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 9 Oct 2024 11:35:59 +0200
-Subject: [PATCH 022/116] Nightly test definition: use master_1repl topology
- for idrange_fix
-
-The test test_ipa_idrange_fix is installing IPA server as it sets
-topology=line. Its test definition should not use a template
-that pre-installs the IPA server (ipaserver preinstalls IPA server
-but master_1repl does not).
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml         | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml | 2 +-
- 2 files changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 015dcdf49..f6dbf6f4c 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -1804,7 +1804,7 @@ jobs:
-         test_suite: test_integration/test_ipa_idrange_fix.py
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
--        topology: *ipaserver
-+        topology: *master_1repl
- 
-   fedora-latest-ipa-4-12/test_subids:
-     requires: [fedora-latest-ipa-4-12/build]
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 40563fedd..d3b5705da 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -1947,7 +1947,7 @@ jobs:
-         test_suite: test_integration/test_ipa_idrange_fix.py
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
--        topology: *ipaserver
-+        topology: *master_1repl
- 
-   fedora-latest-ipa-4-12/test_subids:
-     requires: [fedora-latest-ipa-4-12/build]
--- 
-2.49.0
-
-
-From 642af014d9e7de8e53934af4ca3970977957cba7 Mon Sep 17 00:00:00 2001
-From: Yaakov Selkowitz <yselkowi@redhat.com>
-Date: Tue, 8 Oct 2024 02:13:00 -0400
-Subject: [PATCH 023/116] spec: Use nodejs22 on RHEL 10 and ELN
-
-nodejs22 is now the default nodejs version in RHEL 10 as well as ELN.
-
-Signed-off-by: Yaakov Selkowitz <yselkowi@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- freeipa.spec.in | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 171b6ad27..9b3d916f3 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -308,10 +308,10 @@ BuildRequires:  libpwquality-devel
- BuildRequires:  libsss_idmap-devel
- BuildRequires:  libsss_certmap-devel
- BuildRequires:  libsss_nss_idmap-devel >= %{sssd_version}
--%if 0%{?fedora} >= 41
-+%if 0%{?fedora} >= 41 || 0%{?rhel} >= 10
- # Do not use nodejs22 on fedora < 41, https://pagure.io/freeipa/issue/9643
- BuildRequires: nodejs(abi)
--%elif 0%{?fedora} >= 39 || 0%{?rhel} >= 10
-+%elif 0%{?fedora} >= 39
- # Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374
- BuildRequires:  nodejs(abi) < 127
- %else
--- 
-2.49.0
-
-
-From 18303b94bea4e08a0c889fc357df6ba2f308fa0d Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Wed, 2 Oct 2024 21:26:34 -0400
-Subject: [PATCH 024/116] Do not let user with an expired OTP token to log in
- if only OTP is allowed
-
-If only OTP authentication is allowed, and a user tries to login with an
-expired token, do not let them log in with their password. Forcing the
-admin to intervene. If the user does not have an OTP token then allow
-them to log in with a password until an OTP token is configured
-
-Fixes: https://pagure.io/freeipa/issue/9387
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb_principals.c          | 63 +++++++++++--
- .../ipa-slapi-plugins/ipa-pwd-extop/prepost.c |  3 +-
- ipatests/test_integration/test_otp.py         | 94 ++++++++++++++++++-
- 3 files changed, 151 insertions(+), 9 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
-index 14603e528..114957b88 100644
---- a/daemons/ipa-kdb/ipa_kdb_principals.c
-+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
-@@ -107,7 +107,6 @@ static char *std_principal_obj_classes[] = {
-     "krbprincipal",
-     "krbprincipalaux",
-     "krbTicketPolicyAux",
--
-     NULL
- };
- 
-@@ -338,14 +337,16 @@ static void ipadb_validate_otp(struct ipadb_context *ipactx,
-     if (dn == NULL)
-         return;
-     count = asprintf(&filter, ftmpl, dn, datetime, datetime);
--    ldap_memfree(dn);
--    if (count < 0)
-+    if (count < 0) {
-+        ldap_memfree(dn);
-         return;
-+    }
- 
-     /* Fetch the active token list. */
-     kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
-                                filter, (char**) attrs, &res);
-     free(filter);
-+    filter = NULL;
-     if (kerr != 0 || res == NULL)
-         return;
- 
-@@ -353,10 +354,60 @@ static void ipadb_validate_otp(struct ipadb_context *ipactx,
-     count = ldap_count_entries(ipactx->lcontext, res);
-     ldap_msgfree(res);
- 
--    /* If the user is configured for OTP, but has no active tokens, remove
--     * OTP from the list since the user obviously can't log in this way. */
--    if (count == 0)
-+    /*
-+     * If there are no valid tokens then we need to remove the OTP flag,
-+     * unless OTP is the only auth type allowed...
-+     */
-+    if (count == 0) {
-+        /* Remove the OTP flag for now */
-         *ua &= ~IPADB_USER_AUTH_OTP;
-+
-+        if (*ua == 0) {
-+            /*
-+             * Ok, we "only" allow OTP, so if there is an expired/disabled
-+             * token then add back the OTP flag as the server will double
-+             * check the validity and reject the entire bind. Otherwise, this
-+             * is the first time the user is authenticating and the user
-+             * should be allowed to bind using its password
-+             */
-+            static const char *expired_ftmpl = "(&"
-+                "(objectClass=ipaToken)(ipatokenOwner=%s)"
-+                "(|(ipatokenNotAfter<=%s)(!(ipatokenNotAfter=*))"
-+                "(ipatokenDisabled=True))"
-+            ")";
-+            if (asprintf(&filter, expired_ftmpl, dn, datetime) < 0) {
-+                ldap_memfree(dn);
-+                return;
-+            }
-+
-+            krb5_klog_syslog(LOG_INFO,
-+                "Entry (%s) does not have a valid token and only OTP "
-+                "authentication is supported, checking for expired tokens...",
-+                dn);
-+
-+            kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
-+                                       filter, (char**) attrs, &res);
-+            free(filter);
-+            if (kerr != 0 || res == NULL) {
-+                ldap_memfree(dn);
-+                return;
-+            }
-+
-+            if (ldap_count_entries(ipactx->lcontext, res) > 0) {
-+                /*
-+                 * Ok we only allow OTP, and there are expired/disabled tokens
-+                 * so add the OTP flag back, and the server will reject the
-+                 * bind
-+                 */
-+                krb5_klog_syslog(LOG_INFO,
-+                    "Entry (%s) does have an expired/disabled token so this "
-+                    "user can not fall through to password auth", dn);
-+                *ua |= IPADB_USER_AUTH_OTP;
-+            }
-+            ldap_msgfree(res);
-+        }
-+    }
-+    ldap_memfree(dn);
- }
- 
- static void ipadb_validate_radius(struct ipadb_context *ipactx,
-diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-index c967e2cff..1c1340e31 100644
---- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-@@ -1528,7 +1528,8 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
-     if (!syncreq && (otpreq == OTP_IS_NOT_REQUIRED)) {
-         ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_ONLY_CONFIG);
-         if (ret != 0) {
--            LOG_FATAL("ipapwd_gen_checks failed!?\n");
-+            LOG_FATAL("ipapwd_gen_checks failed for '%s': %s\n",
-+                      slapi_sdn_get_dn(sdn), errMesg);
-             slapi_entry_free(entry);
-             slapi_sdn_free(&sdn);
-             return 0;
-diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
-index 350371bfe..878b4fb56 100644
---- a/ipatests/test_integration/test_otp.py
-+++ b/ipatests/test_integration/test_otp.py
-@@ -10,6 +10,7 @@ import re
- import time
- import textwrap
- from urllib.parse import urlparse, parse_qs
-+from paramiko import AuthenticationException
- 
- from cryptography.hazmat.backends import default_backend
- from cryptography.hazmat.primitives import hashes
-@@ -83,7 +84,7 @@ def kinit_otp(host, user, *, password, otp, success=True):
-     )
- 
- 
--def ssh_2f(hostname, username, answers_dict, port=22):
-+def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""):
-     """
-     :param hostname: hostname
-     :param username: username
-@@ -103,6 +104,10 @@ def ssh_2f(hostname, username, answers_dict, port=22):
-             logger.info("Prompt is: '%s'", prmpt_str)
-             logger.info(
-                 "Answer to ssh prompt is: '%s'", answers_dict[prmpt_str])
-+            if unwanted_prompt and prmpt_str == unwanted_prompt:
-+                # We should not see this prompt
-+                raise ValueError("We got an unwanted prompt: "
-+                                 + answers_dict[prmpt_str])
-         return resp
- 
-     import paramiko
-@@ -193,7 +198,8 @@ class TestOTPToken(IntegrationTest):
- 
-         # skipping too many OTP fails
-         otp1 = hotp.generate(10).decode("ascii")
--        kinit_otp(self.master, USER, password=PASSWORD, otp=otp1, success=False)
-+        kinit_otp(self.master, USER, password=PASSWORD, otp=otp1,
-+                  success=False)
-         # Now the token is desynchronized
-         yield (otpuid, hotp)
- 
-@@ -536,3 +542,87 @@ class TestOTPToken(IntegrationTest):
-         finally:
-             master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '1'])
-             master.run_command(['ipa', 'user-del', USER1])
-+
-+    def test_totp_expired_ldap(self):
-+        master = self.master
-+        basedn = master.domain.basedn
-+        USER1 = 'user-expired-otp'
-+        TMP_PASSWORD = 'Secret1234509'
-+        binddn = DN(f"uid={USER1},cn=users,cn=accounts,{basedn}")
-+        controls = [
-+            BooleanControl(
-+                controlType="2.16.840.1.113730.3.8.10.7",
-+                booleanValue=True)
-+        ]
-+
-+        tasks.kinit_admin(master)
-+        master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '0'])
-+        tasks.user_add(master, USER1, password=TMP_PASSWORD)
-+        # Enforce use of OTP token for this user
-+        master.run_command(['ipa', 'user-mod', USER1,
-+                            '--user-auth-type=otp'])
-+        try:
-+            # Change initial password through the IPA endpoint
-+            url = f'https://{master.hostname}/ipa/session/change_password'
-+            master.run_command(['curl', '-d', f'user={USER1}',
-+                                '-d', f'old_password={TMP_PASSWORD}',
-+                                '-d', f'new_password={PASSWORD}',
-+                                '--referer', f'https://{master.hostname}/ipa',
-+                                url])
-+            conn = master.ldap_connect()
-+            # First, attempt authenticating with a password but without LDAP
-+            # control to enforce OTP presence and without server-side
-+            # enforcement of the OTP presence check.
-+            conn.simple_bind(binddn, f"{PASSWORD}")
-+
-+            # Add an OTP token and then modify it to be expired
-+            otpuid, totp = add_otptoken(master, USER1, otptype="totp")
-+
-+            # Make sure OTP auth is working
-+            otpvalue = totp.generate(int(time.time())).decode("ascii")
-+            conn = master.ldap_connect()
-+            conn.simple_bind(binddn, f"{PASSWORD}{otpvalue}",
-+                             client_controls=controls)
-+            conn.unbind()
-+
-+            # Modfy token so that is now expired
-+            args = [
-+                "ipa",
-+                "otptoken-mod",
-+                otpuid,
-+                "--not-after",
-+                "20241001010000Z",
-+            ]
-+            master.run_command(args)
-+
-+            # Next, authenticate with Password+OTP again and with the LDAP
-+            # control this operation should now fail
-+            time.sleep(45)
-+            otpvalue = totp.generate(int(time.time())).decode("ascii")
-+
-+            conn = master.ldap_connect()
-+            with pytest.raises(errors.ACIError):
-+                conn.simple_bind(binddn, f"{PASSWORD}{otpvalue}",
-+                                 client_controls=controls)
-+
-+            # Sleep to make sure we are going to use a different token value
-+            time.sleep(45)
-+
-+            # Use OTP token again but authenticate over ssh and make sure it
-+            # doesn't fallthrough to asking for a password
-+            otpvalue = totp.generate(int(time.time())).decode("ascii")
-+            answers = {
-+                'Enter first factor:': PASSWORD,
-+                'Enter second factor:': otpvalue
-+            }
-+            with pytest.raises(AuthenticationException):
-+                # ssh should fail and NOT ask for a password
-+                ssh_2f(master.hostname, USER1, answers,
-+                       unwanted_prompt="Password:")
-+
-+            # Remove token
-+            del_otptoken(self.master, otpuid)
-+
-+        finally:
-+            master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '1'])
-+            master.run_command(['ipa', 'user-del', USER1])
--- 
-2.49.0
-
-
-From d8d4c5507bd3784d323e6836ff8456ba9608f115 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 20 Sep 2024 09:36:41 +0200
-Subject: [PATCH 025/116] test_ipahealthcheck: skip connectivity_and_data check
-
-PKI removed the clones.check connectivity_and_data check in
-11.5 and master branches. Skip the test depending on PKI version.
-The most recent version on 11.5 is 11.5.4 and still contains the check,
-hence skipping if version >= 11.5.5.
-
-Fixes: https://pagure.io/freeipa/issue/9668
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index a3e10b044..cc51a5a6a 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -1219,6 +1219,9 @@ class TestIpaHealthCheck(IntegrationTest):
-         This testcase checks that when ClonesConnectivyAndDataCheck
-         is run it doesn't display source not found error
-         """
-+        if (tasks.get_pki_version(
-+                self.master) >= tasks.parse_version('11.5.5')):
-+            raise pytest.skip("PKI dropped ClonesConnectivyAndDataCheck")
-         error_msg = (
-             "Source 'pki.server.healthcheck.clones.connectivity_and_data' "
-             "not found"
--- 
-2.49.0
-
-
-From c71e12e902b3912c31245d46ad6f2c2ddee01126 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 1 Oct 2024 11:28:28 +0300
-Subject: [PATCH 026/116] selinux: allow Cockpit to use HTTP keytab on IPA
- servers
-
-Cockpit can use GSSAPI authentication and has pretty good definition of
-how to enable it: https://cockpit-project.org/guide/latest/sso.html.
-These instructions work on IPA clients but they cannot be used on IPA
-servers because IPA framework already owns HTTP/.. Kerberos service and
-its keytab.
-
-Luckily, there are two changes that need to be done to enable Cockpit
-single sign-on with GSSAPI on IPA servers:
-
-  - create a symlink /etc/cockpit/krb5.keytab to
-    /var/lib/ipa/gssproxy/http.keytab
-
-  - add SELinux policy to allow cockpit_session_t to operate on
-    /var/lib/ipa/gssproxy/http.keytab file
-
-For existing installation an upgrade process would restore SELinux
-context of the http.keytab file to the new value.
-
-Note that Cockpit documentation above also talks about Kerberos service
-modifications to enable delegation.  These modifications should not be
-done for IPA servers' HTTP services, as these services are already
-enabled to handle delegation.
-
-Fixes: https://pagure.io/freeipa/issue/9675
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/server/upgrade.py |  1 +
- selinux/ipa.fc                      |  2 ++
- selinux/ipa.if                      | 24 ++++++++++++++++++++++++
- selinux/ipa.te                      | 19 +++++++++++++++++++
- 4 files changed, 46 insertions(+)
-
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index 31d4f8398..d5c466ee2 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -1124,6 +1124,7 @@ def update_http_keytab(http):
-                 paths.OLD_IPA_KEYTAB, e
-             )
-     http.keytab_user.chown(http.keytab)
-+    tasks.restore_context(http.keytab)
- 
- 
- def ds_enable_sidgen_extdom_plugins(ds):
-diff --git a/selinux/ipa.fc b/selinux/ipa.fc
-index 47bd19ba7..15e8e41aa 100644
---- a/selinux/ipa.fc
-+++ b/selinux/ipa.fc
-@@ -22,6 +22,8 @@
- 
- /var/lib/ipa(/.*)?              gen_context(system_u:object_r:ipa_var_lib_t,s0)
- 
-+/var/lib/ipa/gssproxy/http.keytab -- gen_context(system_u:object_r:ipa_http_keytab_t,s0)
-+
- /var/log/ipa(/.*)?              gen_context(system_u:object_r:ipa_log_t,s0)
- 
- /var/log/ipabackup.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-diff --git a/selinux/ipa.if b/selinux/ipa.if
-index 8c47e7963..8f3147e10 100644
---- a/selinux/ipa.if
-+++ b/selinux/ipa.if
-@@ -155,6 +155,7 @@ interface(`ipa_manage_log',`
- ########################################
- ## <summary>
- ##	Allow domain to manage ipa lib files/dirs.
-+##      This includes reading ipa_http_keytab_t files.
- ## </summary>
- ## <param name="domain">
- ##	<summary>
-@@ -164,10 +165,33 @@ interface(`ipa_manage_log',`
- #
- interface(`ipa_read_lib',`
- 	gen_require(`
-+		type ipa_http_keytab_t;
- 		type ipa_var_lib_t;
- 	')
- 
-     read_files_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+    read_files_pattern($1, ipa_http_keytab_t, ipa_http_keytab_t)
-+    list_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
-+')
-+
-+########################################
-+## <summary>
-+##	Allow domain to manage ipa HTTP keytab file.
-+##      This includes reading ipa_var_lib_t directories.
-+## </summary>
-+## <param name="domain">
-+##	<summary>
-+##	Domain allowed access.
-+##	</summary>
-+## </param>
-+#
-+interface(`ipa_read_http_keytab',`
-+	gen_require(`
-+		type ipa_http_keytab_t;
-+		type ipa_var_lib_t;
-+	')
-+
-+    read_files_pattern($1, ipa_http_keytab_t, ipa_http_keytab_t)
-     list_dirs_pattern($1, ipa_var_lib_t, ipa_var_lib_t)
- ')
- 
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index 2546a9bd9..e4ce66687 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -43,6 +43,9 @@ logging_log_file(ipa_log_t)
- type ipa_var_lib_t;
- files_type(ipa_var_lib_t)
- 
-+type ipa_http_keytab_t;
-+files_type(ipa_http_keytab_t)
-+
- type ipa_var_run_t;
- files_pid_file(ipa_var_run_t)
- 
-@@ -516,3 +519,19 @@ optional_policy(`
-     ')
-     allow certmonger_t pki_tomcat_etc_rw_t:file { getattr ioctl open read };
- ')
-+
-+# gssproxy needs to read http keytab
-+optional_policy(`
-+    gen_require(`
-+        type gssproxy_t;
-+    ')
-+    ipa_read_http_keytab(gssproxy_t)
-+')
-+
-+# Allow Cockpit to use HTTP keytab on IPA servers for GSSAPI authentication
-+optional_policy(`
-+    gen_require(`
-+        type cockpit_session_t;
-+    ')
-+    ipa_read_http_keytab(cockpit_session_t)
-+')
--- 
-2.49.0
-
-
-From 0dadcbb4ac9f6142b5130f025f64d918d6f208a9 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 8 Oct 2024 10:25:08 +0300
-Subject: [PATCH 027/116] Minimal test for Cockpit integration on IPA master
-
-Add a test to share HTTP service keytab on IPA master between IPA and
-Cockpit. The test configures Cockpit with IPA CA-issued certificate and
-allows Cockpit to access IPA HTTP service keytab for authentication.
-
-The test then attempts to authenticate with GSSAPI as admin user. A
-successful result is when we receive CSRF token from the Cockpit as
-the result of this authentication. This means we have logged in
-successfully with Kerberos.
-
-Fixes: https://pagure.io/freeipa/issue/9675
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_cockpit.py | 61 +++++++++++++++++++++++
- 1 file changed, 61 insertions(+)
- create mode 100644 ipatests/test_integration/test_cockpit.py
-
-diff --git a/ipatests/test_integration/test_cockpit.py b/ipatests/test_integration/test_cockpit.py
-new file mode 100644
-index 000000000..cdc96170a
---- /dev/null
-+++ b/ipatests/test_integration/test_cockpit.py
-@@ -0,0 +1,61 @@
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+
-+from __future__ import absolute_import
-+
-+import time
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+from ipaplatform.paths import paths
-+
-+
-+class TestCockpitIntegration(IntegrationTest):
-+    topology = "line"
-+    reqcert = '/etc/cockpit/ws-certs.d/99-cockpit.cert'
-+    reqkey = '/etc/cockpit/ws-certs.d/99-cockpit.key'
-+    symlink = '/etc/cockpit/krb5.keytab'
-+
-+    @classmethod
-+    def uninstall(cls, mh):
-+        cls.master.run_command(['ipa-getcert', 'stop-tracking', '-f',
-+                                cls.reqcert], raiseonerr=False)
-+        cls.master.run_command(['rm', '-f', cls.symlink], raiseonerr=False)
-+        cls.master.run_command(['systemctl', 'disable', '--now',
-+                                'cockpit.socket'])
-+        super(TestCockpitIntegration, cls).uninstall(mh)
-+
-+    @classmethod
-+    def install(cls, mh):
-+        master = cls.master
-+
-+        # Install Cockpit and configure it to use IPA certificate and keytab
-+        master.run_command(['dnf', 'install', '-y', 'cockpit', 'curl'],
-+                           raiseonerr=False)
-+
-+        super(TestCockpitIntegration, cls).install(mh)
-+
-+        master.run_command(['ipa-getcert', 'request', '-f', cls.reqcert, '-k',
-+                            cls.reqkey, '-D', cls.master.hostname, '-K',
-+                            'host/' + cls.master.hostname, '-m', '0640', '-o',
-+                            'root:cockpit-ws', '-O', 'root:root', '-M',
-+                            '0644'], raiseonerr=False)
-+
-+        master.run_command(['ln', '-s', paths.HTTP_KEYTAB, cls.symlink],
-+                           raiseonerr=False)
-+
-+        time.sleep(5)
-+        master.run_command(['systemctl', 'enable', '--now', 'cockpit.socket'])
-+
-+    def test_login_with_kerberos(self):
-+        """
-+        Login to Cockpit using GSSAPI authentication
-+        """
-+        master = self.master
-+        tasks.kinit_admin(master)
-+
-+        cockpit_login = f'https://{master.hostname}:9090/cockpit/login'
-+        result = master.run_command([paths.BIN_CURL, '-u:', '--negotiate',
-+                                     '--cacert', paths.IPA_CA_CRT,
-+                                     cockpit_login])
-+        assert ("csrf-token" in result.stdout_text)
--- 
-2.49.0
-
-
-From 4994728fc646e67ec5ae8ce2a53d579f1496272e Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 10 Oct 2024 10:39:06 +0200
-Subject: [PATCH 028/116] Extend nightly tests with Cockpit test
-
-Fixes: https://pagure.io/freeipa/issue/9675
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index f6dbf6f4c..6d18e708f 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -2117,3 +2117,15 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
-         topology: *master_1repl_1client
-+
-+  fedora-latest-ipa-4-12/test_cockpit:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_cockpit.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *master_1repl
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index d3b5705da..52686df97 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2286,3 +2286,16 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
-         topology: *master_1repl_1client
-+
-+  fedora-latest-ipa-4-12/test_cockpit:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_cockpit.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *master_1repl
--- 
-2.49.0
-
-
-From 761647f842567713032709753b6d63467d9871a6 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Mon, 23 Sep 2024 14:05:43 +0530
-Subject: [PATCH 029/116] ipatests: Activate ssh in sssd.conf
-
-This testcase checks that services: ssh
-is included in the sssd.conf file when
-ipa-client-install is successful.
-
-Ref: https://pagure.io/freeipa/issue/9649
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_installation_client.py | 10 ++++++++++
- 1 file changed, 10 insertions(+)
-
-diff --git a/ipatests/test_integration/test_installation_client.py b/ipatests/test_integration/test_installation_client.py
-index f8567b39e..884bff2f2 100644
---- a/ipatests/test_integration/test_installation_client.py
-+++ b/ipatests/test_integration/test_installation_client.py
-@@ -94,6 +94,16 @@ class TestInstallClient(IntegrationTest):
-         ).encode() not in krb5_cfg
-         tasks.uninstall_client(self.clients[0])
- 
-+    def test_check_ssh_service_is_activated(self):
-+        """
-+        This test checks all default services are activated
-+        in sssd.conf including ssh
-+        """
-+        tasks.install_client(self.master, self.clients[0])
-+        sssd_cfg = self.clients[0].get_file_contents(paths.SSSD_CONF)
-+        assert 'services = nss, pam, ssh, sudo' in sssd_cfg.decode()
-+        tasks.uninstall_client(self.clients[0])
-+
-     def test_install_with_automount(self):
-         """Test that installation with automount is successful"""
-         tasks.install_client(self.master, self.clients[0],
--- 
-2.49.0
-
-
-From 6735327c18b99420816d31dd64ba402ff93031be Mon Sep 17 00:00:00 2001
-From: Ian Brown <ian.brown@templeandwebster.com.au>
-Date: Fri, 4 Oct 2024 13:25:32 +1000
-Subject: [PATCH 030/116] Replace instances of del os.environ with
- os.environ.pop
-
-Fixes: https://pagure.io/freeipa/issue/9450
-
-Signed-off-by: Ian Brown <ian.brown@templeandwebster.com.au>
-Signed-off-by: Ian Brown <ianbrown78@gmail.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- install/certmonger/dogtag-ipa-ca-renew-agent-submit.in | 2 +-
- ipaclient/install/ipa_certupdate.py                    | 2 +-
- ipaserver/install/server/upgrade.py                    | 2 +-
- ipatests/test_ipalib/test_x509.py                      | 2 +-
- ipatests/test_ipaserver/test_ipap11helper.py           | 2 +-
- 5 files changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in b/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
-index 7979ad86d..da81d2a30 100644
---- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
-+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit.in
-@@ -475,7 +475,7 @@ def renew_ca_cert(reuse_existing, force_self_signed, **kwargs):
-         if profile is not None:
-             os.environ['CERTMONGER_CA_PROFILE'] = profile
-         else:
--            del os.environ['CERTMONGER_CA_PROFILE']
-+            os.environ.pop('CERTMONGER_CA_PROFILE', None)
- 
-     if result[0] == WAIT:
-         return (result[0], '%s:%s' % (state, result[1]))
-diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py
-index a2497c703..bc70254e2 100644
---- a/ipaclient/install/ipa_certupdate.py
-+++ b/ipaclient/install/ipa_certupdate.py
-@@ -70,7 +70,7 @@ class CertUpdate(admintool.AdminTool):
-             raise
-         finally:
-             if old_krb5ccname is None:
--                del os.environ['KRB5CCNAME']
-+                os.environ.pop('KRB5CCNAME', None)
-             else:
-                 os.environ['KRB5CCNAME'] = old_krb5ccname
- 
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index d5c466ee2..fb71df81a 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -2061,7 +2061,7 @@ def empty_ccache():
-         if old_path:
-             os.environ['KRB5CCNAME'] = old_path
-         else:
--            del os.environ['KRB5CCNAME']
-+            os.environ.pop('KRB5CCNAME', None)
-         shutil.rmtree(kpath_dir)
- 
- 
-diff --git a/ipatests/test_ipalib/test_x509.py b/ipatests/test_ipalib/test_x509.py
-index 8ab2ea8c3..74761296e 100644
---- a/ipatests/test_ipalib/test_x509.py
-+++ b/ipatests/test_ipalib/test_x509.py
-@@ -275,7 +275,7 @@ class test_x509:
-         if tz:
-             os.environ['TZ'] = tz
-         else:
--            del os.environ['TZ']
-+            os.environ.pop('TZ', None)
-         # ensure the timezone doesn't mess with not_before and not_after
-         assert cert.not_valid_before == not_before
-         assert cert.not_valid_after == not_after
-diff --git a/ipatests/test_ipaserver/test_ipap11helper.py b/ipatests/test_ipaserver/test_ipap11helper.py
-index 5e7053442..1ad827a03 100644
---- a/ipatests/test_ipaserver/test_ipap11helper.py
-+++ b/ipatests/test_ipaserver/test_ipap11helper.py
-@@ -84,7 +84,7 @@ def p11(request, token_path):
-                 [SOFTHSM2_UTIL, '--delete-token', '--label', 'test'],
-                 cwd=token_path
-             )
--            del os.environ['SOFTHSM2_CONF']
-+            os.environ.pop('SOFTHSM2_CONF', None)
- 
-     request.addfinalizer(fin)
- 
--- 
-2.49.0
-
-
-From f978fa05e3ed9d4ad9d20493c05c77fb9b4976a7 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 15 Oct 2024 17:04:55 +0200
-Subject: [PATCH 031/116] ipa-migrate man page: fix typos and errors
-
-ipa-migrate man page mentions non-existing option --hostname.
-Fix the SYNOPSIS and various typos.
-
-Fixes: https://pagure.io/freeipa/issue/9681
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Mark Reynolds <mreynolds@redhat.com>
----
- install/tools/man/ipa-migrate.1 | 38 +++++++++++++++------------------
- 1 file changed, 17 insertions(+), 21 deletions(-)
-
-diff --git a/install/tools/man/ipa-migrate.1 b/install/tools/man/ipa-migrate.1
-index 47ae47ea4..5106f4f0f 100644
---- a/install/tools/man/ipa-migrate.1
-+++ b/install/tools/man/ipa-migrate.1
-@@ -5,11 +5,11 @@
- .SH "NAME"
- ipa\-migrate \- Migrate an IPA server from one machine to another
- .SH "SYNOPSIS"
--ipa\-migrate
-+\fBipa\-migrate\fR [OPTIONS] \fBprod\-mode\fR|\fBstage\-mode\fR \fIhostname\fR
- .SH "DESCRIPTION"
- 
- Use the \fIipa-migrate\fR command to migrate one
--IPA server to an existing local IPA server installation.
-+IPA server \fIhostname\fR to an existing local IPA server installation.
- 
- Migrate IPA schema, configuration, and database to a local IPA server.  This
- migration can be done online, where the tool will query the remote server. Or,
-@@ -19,7 +19,6 @@ and then use an exported LDIF file for the database migration portion (this
- might be more useful for very large databases as you don't need to worry about
- network interruptions)
- 
--.SH POSITIONAL ARGUMENTS
- .TP
- \fBprod\-mode\fR
- In this mode everything will be migrated including the current user SIDs and
-@@ -28,13 +27,10 @@ DNA ranges
- \fBstage\-mode\fR
- In this mode, SIDs & DNA ranges are not migrated, and DNA attributes are reset
- 
--.SH "COMMANDS"
-+.SH "OPTIONS"
- .TP
- \fB\-v\fR, \fB\-\-verbose\fR
--Use verbose output while running the migration tool.
--.TP
--\fB\-e\fR, \fB\-\-hostname=HOSTNAME\fR
--The host name of the remote IPA server that is being migrated from.
-+Use verbose output while running the migration tool
- .TP
- \fB\-D\fR, \fB\-\-bind\-dn=BIND_DN\fR
- The Bind DN (Distinguished Name) or an LDAP entry to bind to the remote IPA server with.
-@@ -43,10 +39,10 @@ access to read the userPassword attribute.  If ommitted the default is "cn=direc
- .TP
- \fB\-w\fR, \fB\-\-bind\-pw=PASSWORD\fR
- The password for the Bind DN that is authenticating against the remote IPA server.  If
--a password is not provided then the tool with prompt for the password if needed.
-+a password is not provided then the tool with prompt for the password if needed
- .TP
--\fB\-Just\fR, \fB\-\-bind\-pw\-file=FILE_PATH\fR
--Path to a file containing the password for the Bind DN.
-+\fB\-j\fR, \fB\-\-bind\-pw\-file=FILE_PATH\fR
-+Path to a file containing the password for the Bind DN
- .TP
- \fB\-Z\fR, \fB\-\-cacertfile=FILE_PATH\fR
- Path to a file containing a CA Certificate that the remote server trusts
-@@ -55,23 +51,23 @@ Path to a file containing a CA Certificate that the remote server trusts
- Path to a file containing the migration log.  By default the tool will use \fI/var/log/ipa-migrate.log\fR
- .TP
- \fB\-x\fR, \fB\-\-dryrun\fR
--Go through the migration process but do not write and data to the new IPA server.
-+Go through the migration process but do not write any data to the new IPA server
- .TP
- \fB\-o\fR, \fB\-\-dryrun\-record=FILE_PATH\fR
- Go through the migration process but do not write any data to the new IPA server. However, write the
--migration operations to an LDIF file which can be applied later or reused for multiple migrations.
-+migration operations to an LDIF file which can be applied later or reused for multiple migrations
- .TP
- \fB\-r\fR, \fB\-\-reset\-range\fR
- Reset the ID range for migrated users/groups. In "stage-mode" this is done automatically
- .TP
- \fB\-F\fR, \fB\-\-force\fR
--Ignore any errors and continue to proceed with migration effort.
-+Ignore any errors and continue to proceed with migration effort
- .TP
- \fB\-q\fR, \fB\-\-quiet\fR
--Only log errors during the migration process.
-+Only log errors during the migration process
- .TP
- \fB\-B\fR, \fB\-\-migrate\-dns\fR
--Migrate thr DNS records
-+Migrate the DNS records
- .TP
- \fB\-S\fR, \fB\-\-skip\-schema\fR
- Do not migrate the database schema
-@@ -80,21 +76,21 @@ Do not migrate the database schema
- Do not migrate the database configuration (dse.ldif/cn=config)
- .TP
- \fB\-O\fR, \fB\-\-schema\-overwrite\fR
--Overwrite existing schema definitions.  By default duplicate schema is skipped.
-+Overwrite existing schema definitions.  By default duplicate schema is skipped
- .TP
- \fB\-s\fR, \fB\-\-subtree=DN\fR
- Specifies a custom database subtree that should be included in the migration.
- This is only needed if non-default subtrees/branches were added to the database
--outside of IPA.
-+outside of IPA
- .TP
- \fB\-f\fR, \fB\-\-db\-ldif=FILE_PATH\fR
--LDIF file containing the entire backend. If omitted the tool will query the remote IPA server.
-+LDIF file containing the entire backend. If omitted the tool will query the remote IPA server
- .TP
- \fB\-m\fR, \fB\-\-schema\-ldif=FILE_PATH\fR
--LDIF file containing the schema. If omitted the tool will query the remote IPA server.
-+LDIF file containing the schema. If omitted the tool will query the remote IPA server
- .TP
- \fB\-g\fR, \fB\-\-config\-ldif=FILE_PATH\fR
--LDIF file containing the entire "cn=config" DIT. If omitted the tool will query the remote IPA server.
-+LDIF file containing the entire "cn=config" DIT. If omitted the tool will query the remote IPA server
- .TP
- \fB\-n\fR, \fB\-\-no\-prompt\fR
- Do not prompt for confirmation before starting migration.  Use at your own risk!
--- 
-2.49.0
-
-
-From 7f4e7e1d6a2ae9d05a2dfcf620f4df07d09d9d2b Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Thu, 3 Oct 2024 18:45:31 +0530
-Subject: [PATCH 032/116] ipatests: Test for ipa hbac rule duplication
-
-This test checks that ipa-migrate is not creating duplicate default hbac rules
-for allow_all and allow_systemd-user rules.
-
-Related: https://pagure.io/freeipa/issue/9640
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- .../test_ipa_ipa_migration.py                 | 26 +++++++++++++++++++
- 1 file changed, 26 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index 288165e8a..70c268951 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -9,6 +9,7 @@ from __future__ import absolute_import
- from ipatests.test_integration.base import IntegrationTest
- from ipatests.pytest_ipa.integration import tasks
- from ipaplatform.paths import paths
-+from collections import Counter
- 
- import pytest
- import textwrap
-@@ -920,3 +921,28 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         )
-         assert result.returncode == 1
-         assert ERR_MSG in result.stderr_text
-+
-+    def test_ipa_hbac_rule_duplication(self):
-+        """
-+        This testcase checks that default hbac rules
-+        are not duplicated on the local server when
-+        ipa-migrate command is run.
-+        """
-+        run_migrate(
-+            self.replicas[0],
-+            "prod-mode",
-+            self.master.hostname,
-+            "cn=Directory Manager",
-+            self.master.config.admin_password,
-+            extra_args=['-n']
-+        )
-+        result = self.replicas[0].run_command(
-+            ['ipa', 'hbacrule-find']
-+        )
-+        lines = result.stdout_text.splitlines()
-+        line = []
-+        for i in lines:
-+            line.append(i.strip())
-+        count = Counter(line)
-+        assert count.get('Rule name: allow_all') < 2
-+        assert count.get('Rule name: allow_systemd-user') < 2
--- 
-2.49.0
-
-
-From 142f52fc981fe9f1d693b79a7b49506af2e98829 Mon Sep 17 00:00:00 2001
-From: Mohammad Rizwan <myusuf@redhat.com>
-Date: Mon, 19 Aug 2024 16:08:53 +0530
-Subject: [PATCH 033/116] ipatests: refactor password file handling in
- TestHSMInstall
-
-When token and associated certs are not being cleaned
-up properly, the subsequent installation fails. Hence
-Password file related scenarios moved out to new test class
-so that it have fresh installation.
-
-Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- .../nightly_ipa-4-12_latest.yaml              | 12 ++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml      | 13 ++++++++
- ipatests/test_integration/test_hsm.py         | 30 ++++++++++---------
- 3 files changed, 41 insertions(+), 14 deletions(-)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 6d18e708f..07e2a8399 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -1950,6 +1950,18 @@ jobs:
-         timeout: 6300
-         topology: *master_3repl_1client
- 
-+  fedora-latest-ipa-4-12/test_hsm_TestHSMInstallPasswordFile:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_hsm.py::TestHSMInstallPasswordFile
-+        template: *ci-ipa-4-12-latest
-+        timeout: 6300
-+        topology: *master_1repl
-+
-   fedora-latest-ipa-4-12/test_hsm_TestHSMInstallADTrustBase:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 52686df97..11046be13 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2105,6 +2105,19 @@ jobs:
-         timeout: 6300
-         topology: *master_3repl_1client
- 
-+  fedora-latest-ipa-4-12/test_hsm_TestHSMInstallPasswordFile:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_hsm.py::TestHSMInstallPasswordFile
-+        template: *ci-ipa-4-12-latest
-+        timeout: 6300
-+        topology: *master_1repl
-+
-   fedora-latest-ipa-4-12/test_hsm_TestHSMInstallADTrustBase:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/test_integration/test_hsm.py b/ipatests/test_integration/test_hsm.py
-index 374f5c25f..42895fcd6 100644
---- a/ipatests/test_integration/test_hsm.py
-+++ b/ipatests/test_integration/test_hsm.py
-@@ -312,24 +312,26 @@ class TestHSMInstall(BaseHSMTest):
-         assert returncode == 0
-         assert output == "No issues found."
- 
--    def test_hsm_install_server_password_file(self):
--        check_version(self.master)
--        # cleanup before fresh install with password file
--        for client in self.clients:
--            tasks.uninstall_client(client)
- 
--        for replica in self.replicas:
--            tasks.uninstall_master(replica)
-+class TestHSMInstallPasswordFile(BaseHSMTest):
- 
--        tasks.uninstall_master(self.master)
-+    num_replicas = 1
- 
--        delete_hsm_token([self.master] + self.replicas, self.token_name)
--        self.token_name, self.token_password = get_hsm_token(self.master)
--        self.master.put_file_contents(self.token_password_file,
--                                      self.token_password)
--        self.replicas[0].put_file_contents(self.token_password_file,
--                                           self.token_password)
-+    @classmethod
-+    def install(cls, mh):
-+        check_version(cls.master)
-+        # Enable pkiuser to read softhsm tokens
-+        cls.master.run_command(['usermod', 'pkiuser', '-a', '-G', 'ods'])
-+        cls.token_name, cls.token_password = get_hsm_token(cls.master)
-+        cls.master.put_file_contents(
-+            cls.token_password_file, cls.token_password
-+        )
-+        cls.replicas[0].put_file_contents(
-+            cls.token_password_file, cls.token_password
-+        )
- 
-+    def test_hsm_install_server_password_file(self):
-+        check_version(self.master)
-         tasks.install_master(
-             self.master, setup_dns=self.master_with_dns,
-             setup_kra=self.master_with_kra,
--- 
-2.49.0
-
-
-From 6ac11ae003740faf19f3c75bf542ec44f717114f Mon Sep 17 00:00:00 2001
-From: Madhuri Upadhye <mupadhye@redhat.com>
-Date: Tue, 23 Jul 2024 18:14:36 +0530
-Subject: [PATCH 034/116] ipatests: 2FA test cases
-
-Added following:
-
-Added 'ssh_2fa_with_cmd' method for authentication,
-as for '\n' with paramiko did not work. In a test case
-need to just press `Enter` for `second factor`.
-Advantage of above function is no having paramiko
-dependancy.
-We can run the any command in same session after
-authentication of user.
-
-Test cases:
-1. Authenticate the user only with password,
-just press enter at `Second factor` and check tgt after auth.
-when User authentication types: otp, password
-2. Authenticate the user with password and otpvalues and
-check tgt of user after auth when
-User authentication types: otp, password
-
-related: https://github.com/SSSD/sssd/pull/7500
-
-Signed-off-by: Madhuri Upadhye <mupadhye@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_otp.py | 192 ++++++++++++++++++++++++--
- 1 file changed, 181 insertions(+), 11 deletions(-)
-
-diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
-index 878b4fb56..0babb4589 100644
---- a/ipatests/test_integration/test_otp.py
-+++ b/ipatests/test_integration/test_otp.py
-@@ -5,26 +5,27 @@
- """
- import base64
- import logging
--import pytest
- import re
--import time
-+import tempfile
- import textwrap
--from urllib.parse import urlparse, parse_qs
--from paramiko import AuthenticationException
-+import time
-+from urllib.parse import parse_qs, urlparse
- 
-+import pytest
- from cryptography.hazmat.backends import default_backend
- from cryptography.hazmat.primitives import hashes
- from cryptography.hazmat.primitives.twofactor.hotp import HOTP
- from cryptography.hazmat.primitives.twofactor.totp import TOTP
--
--from ipatests.test_integration.base import IntegrationTest
--from ipaplatform.paths import paths
--from ipatests.pytest_ipa.integration import tasks
--from ipapython.dn import DN
--
- from ldap.controls.simple import BooleanControl
-+from paramiko import AuthenticationException
- 
- from ipalib import errors
-+from ipaplatform.osinfo import osinfo
-+from ipaplatform.paths import paths
-+from ipapython.dn import DN
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+from ipatests.util import xfail_context
- 
- PASSWORD = "DummyPassword123"
- USER = "opttestuser"
-@@ -84,6 +85,65 @@ def kinit_otp(host, user, *, password, otp, success=True):
-     )
- 
- 
-+def ssh_2fa_with_cmd(host, hostname, username, password, otpvalue,
-+                     command="exit 0"):
-+    """ ssh to user and in same session pass the command to check tgt of user
-+    :param host: host to ssh
-+    :param hostname: hostname to ssh
-+    :param str username: The name of user
-+    :param str password: password, usually the first factor
-+    :param str otpvalue: generated pin of user
-+    :param str command: command to execute in same session,
-+     by deafult set to "exit 0"
-+    :return: object class of expect command run
-+    """
-+    temp_conf = tempfile.NamedTemporaryFile(suffix='.exp', delete=False)
-+    with open(temp_conf.name, 'w') as tfile:
-+        tfile.write('proc exitmsg { msg code } {\n')
-+        tfile.write('\t# Close spawned program, if we are in the prompt\n')
-+        tfile.write('\tcatch close\n\n')
-+        tfile.write('\t# Wait for the exit code\n')
-+        tfile.write('\tlassign [wait] pid spawnid os_error_flag rc\n\n')
-+        tfile.write('\tputs ""\n')
-+        tfile.write('\tputs "expect result: $msg"\n')
-+        tfile.write('\tputs "expect exit code: $code"\n')
-+        tfile.write('\tputs "expect spawn exit code: $rc"\n')
-+        tfile.write('\texit $code\n')
-+        tfile.write('}\n')
-+        tfile.write('set timeout 60\n')
-+        tfile.write('set prompt ".*\\[#\\$>\\] $"\n')
-+        tfile.write(f'set password "{password}"\n')
-+        tfile.write(f'set otpvalue "{otpvalue}"\n')
-+        tfile.write(f'spawn ssh -o NumberOfPasswordPrompts=1 -o '
-+                    f'StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
-+                    f' -l {username} {hostname} {command}\n')
-+        tfile.write('expect {\n')
-+        tfile.write('"Enter first factor:*" {send -- "$password\r"}\n')
-+        tfile.write('timeout {exitmsg "Unexpected output" 201}\n')
-+        tfile.write('eof {exitmsg "Unexpected end of file" 202}\n')
-+        tfile.write('}\n')
-+        tfile.write('expect {\n')
-+        tfile.write('"Enter second factor:*" {send -- "$otpvalue\r"}\n')
-+        tfile.write('timeout {exitmsg "Unexpected output" 201}\n')
-+        tfile.write('eof {exitmsg "Unexpected end of file" 202}\n')
-+        tfile.write('}\n')
-+        tfile.write('expect {\n')
-+        tfile.write('"Authentication failure" '
-+                    '{exitmsg "Authentication failure" 1}\n')
-+        tfile.write('eof {exitmsg "Authentication successful" 0}\n')
-+        tfile.write('timeout {exitmsg "Unexpected output" 201}\n')
-+        tfile.write('}\n')
-+        tfile.write('expect {\n')
-+        tfile.write('exitmsg "Unexpected code path" 203\n')
-+        tfile.write('EOF\n')
-+        tfile.write('}')
-+    host.transport.put_file(temp_conf.name, '/tmp/ssh.exp')
-+    tasks.clear_sssd_cache(host)
-+    expect_cmd = 'expect -f /tmp/ssh.exp'
-+    cmd = host.run_command(expect_cmd, raiseonerr=False)
-+    return cmd
-+
-+
- def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""):
-     """
-     :param hostname: hostname
-@@ -91,6 +151,7 @@ def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""):
-     :param answers_dict: dictionary of options with prompt_message and value.
-     :param port: port for ssh
-     """
-+
-     # Handler for server questions
-     def answer_handler(title, instructions, prompt_list):
-         resp = []
-@@ -131,8 +192,9 @@ class TestOTPToken(IntegrationTest):
- 
-     @classmethod
-     def install(cls, mh):
--        super(TestOTPToken, cls).install(mh)
-         master = cls.master
-+        tasks.install_packages(master, ['expect'])
-+        super(TestOTPToken, cls).install(mh)
- 
-         tasks.kinit_admin(master)
-         # create service with OTP auth indicator
-@@ -398,6 +460,114 @@ class TestOTPToken(IntegrationTest):
-             self.master.run_command(['semanage', 'login', '-D'])
-             sssd_conf_backup.restore()
- 
-+    def test_2fa_only_with_password(self):
-+        """Test ssh with 2FA only with the password(first factor) when
-+        user-auth-type is opt and password.
-+
-+        Test for :  https://github.com/SSSD/sssd/pull/7500
-+
-+        Add the IPA user and user-auth-type set to opt and password.
-+        Authenticate the user only with password, just press enter
-+        at `Second factor`
-+        """
-+        master = self.master
-+        USER3 = 'sshuser3'
-+        sssd_conf_backup = tasks.FileBackup(master, paths.SSSD_CONF)
-+        first_prompt = 'Enter first factor:'
-+        second_prompt = 'Enter second factor:'
-+        add_contents = textwrap.dedent('''
-+            [prompting/2fa/sshd]
-+            single_prompt = False
-+            first_prompt = {0}
-+            second_prompt = {1}
-+            ''').format(first_prompt, second_prompt)
-+        set_sssd_conf(master, add_contents)
-+        tasks.create_active_user(master, USER3, PASSWORD)
-+        tasks.kinit_admin(master)
-+        master.run_command(['ipa', 'user-mod', USER3, '--user-auth-type=otp',
-+                            '--user-auth-type=password'])
-+        try:
-+            otpuid, totp = add_otptoken(master, USER3, otptype='totp')
-+            master.run_command(['ipa', 'otptoken-show', otpuid])
-+            totp.generate(int(time.time())).decode('ascii')
-+            otpvalue = "\n"
-+            tasks.clear_sssd_cache(self.master)
-+            github_ticket = "https://github.com/SSSD/sssd/pull/7500"
-+            sssd_version = tasks.get_sssd_version(master)
-+            rhel_fail = (
-+                osinfo.id == 'rhel'
-+                and sssd_version < tasks.parse_version("2.9.5")
-+            )
-+            fedora_fail = (
-+                osinfo.id == 'fedora'
-+                and sssd_version == tasks.parse_version("2.9.5")
-+            )
-+            with xfail_context(rhel_fail or fedora_fail, reason=github_ticket):
-+                result = ssh_2fa_with_cmd(master,
-+                                          self.master.external_hostname,
-+                                          USER3, PASSWORD, otpvalue=otpvalue,
-+                                          command="klist")
-+                print(result.stdout_text)
-+                assert ('Authentication successful') in result.stdout_text
-+                assert USER3 in result.stdout_text
-+                assert (f'Default principal: '
-+                        f'{USER3}@{self.master.domain.realm}' in
-+                        result.stdout_text)
-+                cmd = self.master.run_command(['semanage', 'login', '-l'])
-+                assert USER3 in cmd.stdout_text
-+        finally:
-+            master.run_command(['ipa', 'user-del', USER3])
-+            self.master.run_command(['semanage', 'login', '-D'])
-+            sssd_conf_backup.restore()
-+
-+    def test_2fa_with_otp_password(self):
-+        """Test ssh with 2FA only with password and otpvalue when
-+        user-auth-type is opt and password.
-+
-+        Test for :  https://github.com/SSSD/sssd/pull/7500
-+
-+        Add the IPA user and user-auth-type set to opt and password.
-+        Authenticate the user only with password and otpvalue.
-+        """
-+        master = self.master
-+        USER4 = 'sshuser4'
-+        sssd_conf_backup = tasks.FileBackup(master, paths.SSSD_CONF)
-+        first_prompt = 'Enter first factor:'
-+        second_prompt = 'Enter second factor:'
-+        add_contents = textwrap.dedent('''
-+            [prompting/2fa/sshd]
-+            single_prompt = False
-+            first_prompt = {0}
-+            second_prompt = {1}
-+            ''').format(first_prompt, second_prompt)
-+        set_sssd_conf(master, add_contents)
-+        tasks.create_active_user(master, USER4, PASSWORD)
-+        tasks.kinit_admin(master)
-+
-+        master.run_command(['ipa', 'user-mod', USER4, '--user-auth-type=otp',
-+                            '--user-auth-type=password'])
-+        try:
-+            otpuid, totp = add_otptoken(master, USER4, otptype='totp')
-+            master.run_command(['ipa', 'otptoken-show', otpuid])
-+            otpvalue = totp.generate(int(time.time())).decode('ascii')
-+            tasks.clear_sssd_cache(self.master)
-+            result = ssh_2fa_with_cmd(master,
-+                                      self.master.external_hostname,
-+                                      USER4, PASSWORD, otpvalue=otpvalue,
-+                                      command="klist")
-+            print(result.stdout_text)
-+            cmd = self.master.run_command(['semanage', 'login', '-l'])
-+            # check the output
-+            assert ('Authentication successful') in result.stdout_text
-+            assert USER4 in result.stdout_text
-+            assert (f'Default principal: {USER4}@'
-+                    f'{self.master.domain.realm}' in result.stdout_text)
-+            assert USER4 in cmd.stdout_text
-+        finally:
-+            master.run_command(['ipa', 'user-del', USER4])
-+            self.master.run_command(['semanage', 'login', '-D'])
-+            sssd_conf_backup.restore()
-+
-     @pytest.fixture
-     def setup_otp_nsslapd(self):
-         check_services = self.master.run_command(
--- 
-2.49.0
-
-
-From 3d0962014adda39b754c4274ccb5ca5d70963c33 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Mon, 21 Oct 2024 13:51:13 -0400
-Subject: [PATCH 035/116] ipa-migrate - dryrun write updates crashes when
- removing values
-
-When removing values the mod value list is None and that leads to a
-crash when trying to iterate it. Instead check that the vals are not
-None before looping.
-
-Fixes: https://pagure.io/freeipa/issue/9682
-
-Signed-off-by: MArk Reynolds <mreynolds@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index 38356aa23..f35629378 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -622,7 +622,7 @@ class IPAMigrate():
-                 else:
-                     action = "replace"
-                 ldif_entry += f"{action}: {attr}\n"
--                for val in vals:
-+                for val in list(vals or []):
-                     ldif_entry += get_ldif_attr_val(attr, val)
-                 ldif_entry += "-\n"
-             ldif_entry += "\n"
--- 
-2.49.0
-
-
-From 533ec8737599816ac9729d67cedfde76297022fa Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 22 Oct 2024 10:04:25 +0300
-Subject: [PATCH 036/116] web ui: Add explicit white border for QR code widget
-
-There was discussion in the ticket 9202 how to do that and the simplest
-way is to force a border via CSS. We have a CSS specifically for QR code
-widget, so use it.
-
-A default view is white, so the border will not be visible (white on
-white) but for dark backgrounds the border will be there clearly
-visible.
-
-Fixes: https://pagure.io/freeipa/issue/9202
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/ui/less/plugins/otp.less | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/install/ui/less/plugins/otp.less b/install/ui/less/plugins/otp.less
-index bd9d7ab91..d1973caea 100644
---- a/install/ui/less/plugins/otp.less
-+++ b/install/ui/less/plugins/otp.less
-@@ -6,4 +6,5 @@
- .qrcode-widget img {
- 	margin: 0 auto;
- 	max-width: 100%;
--}
-\ No newline at end of file
-+	border: 20px solid rgb(255, 255, 255) !important;
-+}
--- 
-2.49.0
-
-
-From 6bdb8603054fc60e9479f6aaf8b6315dfe508891 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 22 Oct 2024 13:00:03 -0400
-Subject: [PATCH 037/116] ipa-migrate should migrate dns forward zones
-
-Fixes: https://pagure.io/freeipa/issue/9686
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate_constants.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index 250f1b5b0..c140414ea 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -993,7 +993,7 @@ DB_OBJECTS = {
-         'count': 0,
-     },
-     'dns_records': {
--        'oc': ['idnsrecord', 'idnszone'],
-+        'oc': ['idnsrecord', 'idnszone', 'idnsforwardzone'],
-         'subtree': ',cn=dns,$SUFFIX',
-         'label': 'DNS Records',
-         'mode': 'all',
--- 
-2.49.0
-
-
-From 9a2de23eb5e00efa72189c4a86d9db1fab52c2ca Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 24 Oct 2024 11:49:17 -0400
-Subject: [PATCH 038/116] Small fixup to determine which ACME uninstaller to
- use
-
-The conditional was <= 11.5.0 which it should have been
-< 11.6.0 to allow for small updates to the 11.5.0 branch.
-
-Fixes: https://pagure.io/freeipa/issue/9673
-Fixes: https://pagure.io/freeipa/issue/9674
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/dogtaginstance.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index 4b0f4d274..58421a1d8 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -311,7 +311,7 @@ class DogtagInstance(service.Service):
-                 return
-             elif (
-                 pki.util.Version("11.0.0") <= pki_version
--                <= pki.util.Version("11.5.0")
-+                < pki.util.Version("11.6.0")
-             ):
-                 args = ['pki-server', 'acme-remove']
-             else:
--- 
-2.49.0
-
-
-From dad2f06ed6854abbd81b747c26de6c13dfea327b Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 30 Oct 2024 10:48:50 +0200
-Subject: [PATCH 039/116] vault: handle pyca InternalError exception for PKCS#1
- v1.5 padding
-
-In FIPS mode one cannot use PKCS#1 v1.5 padding. OpenSSL did remove it
-from the FIPS provider and will report an error that PyCA cannot
-process, so it will raise its own InternalException.
-
-Handle it the same way as ValueError.
-
-Fixes: https://pagure.io/freeipa/issue/9689
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipaclient/plugins/vault.py | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
-index 96edf09a2..75415c03a 100644
---- a/ipaclient/plugins/vault.py
-+++ b/ipaclient/plugins/vault.py
-@@ -37,6 +37,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.primitives.padding import PKCS7
- from cryptography.hazmat.primitives.serialization import (
-     load_pem_public_key, load_pem_private_key)
-+from cryptography.exceptions import InternalError as CryptographyInternalError
- 
- from ipaclient.frontend import MethodOverride
- from ipalib import x509
-@@ -717,7 +718,7 @@ class ModVaultData(Local):
-                     algo.key,
-                     padding.PKCS1v15()
-                 )
--            except ValueError:
-+            except (ValueError, CryptographyInternalError):
-                 wrapped_session_key = public_key.encrypt(
-                     algo.key,
-                     padding.OAEP(
--- 
-2.49.0
-
-
-From 9da927c8eec7db6d1c75c296eef45beb93797e58 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Thu, 1 Aug 2024 16:30:16 +0530
-Subject: [PATCH 040/116] ipatests: Tests for ipa-migrate tool
-
-This patch includes test to covers below scenarios
-
-1. hbac and sudo rules are migrated to local server
-2. uid for user migrated varies in stage/prod mode.
-3. subids are migrated to local server
-4. idranges are migrated to local server
-5. vaults are not migrated to local server.
-6. Ensure trust related data is also migrated to local server
-7. Added paths.IPA_MIGRATE_LOG in ipatests/pytest_ipa/integration/__init__.py
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/pytest_ipa/integration/__init__.py   |   2 +
- .../test_ipa_ipa_migration.py                 | 596 ++++++++++++++----
- 2 files changed, 460 insertions(+), 138 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/__init__.py b/ipatests/pytest_ipa/integration/__init__.py
-index 34b6ef0fb..eb032cd72 100644
---- a/ipatests/pytest_ipa/integration/__init__.py
-+++ b/ipatests/pytest_ipa/integration/__init__.py
-@@ -88,6 +88,8 @@ CLASS_LOGFILES = [
-     paths.VAR_LOG_AUDIT,
-     # sssd
-     paths.VAR_LOG_SSSD_DIR,
-+    # ipa-ipa-migration logs
-+    paths.IPA_MIGRATE_LOG,
-     # system
-     paths.RESOLV_CONF,
-     paths.HOSTS,
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index 70c268951..d852ca63a 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -12,6 +12,7 @@ from ipaplatform.paths import paths
- from collections import Counter
- 
- import pytest
-+import re
- import textwrap
- 
- 
-@@ -65,29 +66,7 @@ def prepare_ipa_server(master):
-             "--secondary-rid-base=400000",
-         ]
-     )
--
--    # Add Automount locations and maps
--    master.run_command(["ipa", "automountlocation-add", "baltimore"])
--    master.run_command(["ipa", "automountmap-add", "baltimore", "auto.share"])
--    master.run_command(
--        [
--            "ipa",
--            "automountmap-add-indirect",
--            "baltimore",
--            "--parentmap=auto.share",
--            "--mount=sub auto.man",
--        ]
--    )
--    master.run_command(
--        [
--            "ipa",
--            "automountkey-add",
--            "baltimore",
--            "auto.master",
--            "--key=/share",
--            "--info=auto.share",
--        ]
--    )
-+    master.run_command(["ipactl", "restart"])
- 
-     # Run ipa-adtrust-install
-     master.run_command(["dnf", "install", "-y", "ipa-server-trust-ad"])
-@@ -235,6 +214,17 @@ def prepare_ipa_server(master):
-         ["ipa", "hbacrule-add-service", "--hbacsvcs=sshd", "testuser_sshd"]
-     )
- 
-+    # Add DNSForwardzone
-+    master.run_command(
-+        [
-+            "ipa",
-+            "dnsforwardzone-add",
-+            "forwardzone.test",
-+            "--forwarder",
-+            "10.11.12.13",
-+        ]
-+    )
-+
-     # Vault addition
-     master.run_command(
-         [
-@@ -244,6 +234,7 @@ def prepare_ipa_server(master):
-             "vault1234",
-             "--type",
-             "symmetric",
-+            "testvault",
-         ]
-     )
- 
-@@ -260,7 +251,46 @@ def prepare_ipa_server(master):
- 
-     # Modify passkeyconfig
-     master.run_command(
--        ["ipa", "passkeyconfig-mod", "--require-user-verification=FALSE"]
-+        [
-+            "ipa", "passkeyconfig-mod",
-+            "--require-user-verification=FALSE"
-+        ]
-+    )
-+
-+    # Adding automountlocation, maps, keys
-+    master.run_command(
-+        [
-+            "ipa", "automountlocation-add",
-+            "baltimore"
-+        ]
-+    )
-+
-+    master.run_command(
-+        [
-+            "ipa", "automountmap-add",
-+            "baltimore",
-+            "auto.share"
-+        ]
-+    )
-+
-+    master.run_command(
-+        [
-+            "ipa", "automountmap-add-indirect",
-+            "baltimore",
-+            "--parentmap=auto.share",
-+            "--mount=sub",
-+            "auto.man",
-+        ]
-+    )
-+
-+    master.run_command(
-+        [
-+            "ipa", "automountkey-add",
-+            "baltimore",
-+            "auto.master",
-+            "--key=/share",
-+            "--info=auto.share",
-+        ]
-     )
- 
- 
-@@ -288,12 +318,24 @@ def run_migrate(
-     return result
- 
- 
--class TestIPAMigrateScenario1(IntegrationTest):
-+@pytest.fixture()
-+def empty_log_file(request):
-     """
--    Tier-1 tests for ipa-migrate tool with DNS enabled on
--    local and remote server
-+    This fixture empties the log file before ipa-migrate tool
-+    is run since the log is appended everytime the tool is run.
-     """
-+    request.cls.replicas[0].run_command(
-+        ["truncate", "-s", "0", paths.IPA_MIGRATE_LOG]
-+    )
-+    yield
- 
-+
-+class MigrationTest(IntegrationTest):
-+    """
-+    This class will help setup remote IPA server(cls.master)
-+    and local IPA server(cls.replicas[0]) and it will
-+    also prepare the remote IPA before migration actually begins.
-+    """
-     num_replicas = 1
-     num_clients = 1
-     topology = "line"
-@@ -303,14 +345,14 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
-         prepare_ipa_server(cls.master)
-         tasks.install_client(cls.master, cls.clients[0], nameservers=None)
-+        tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True)
- 
--    def test_remote_server(self):
--        """
--        This test installs IPA server instead of replica on
--        system under test with the same realm and domain name.
--        """
--        tasks.install_master(self.replicas[0], setup_dns=True, setup_kra=True)
- 
-+class TestIPAMigrateCLIOptions(MigrationTest):
-+    """
-+    Tests to check CLI options for ipa-migrate tool with
-+    DNS enabled on local and remote server.
-+    """
-     def test_ipa_migrate_without_kinit_as_admin(self):
-         """
-         This test checks that ipa-migrate tool displays
-@@ -417,7 +459,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         """
-         ldif_file = "/tmp/test.ldif"
-         param = ['-x', '-o', ldif_file]
--        run_migrate(
-+        result = run_migrate(
-             self.replicas[0],
-             "stage-mode",
-             self.master.hostname,
-@@ -426,45 +468,21 @@ class TestIPAMigrateScenario1(IntegrationTest):
-             extra_args=param,
-         )
-         assert self.replicas[0].transport.file_exists("/tmp/test.ldif")
-+        assert result.returncode == 0
- 
--    @pytest.fixture()
--    def empty_log_file(self):
--        """
--        This fixture empties the log file before ipa-migrate tool
--        is run since the log is appended everytime the tool is run.
--        """
--        self.replicas[0].run_command(
--            ["truncate", "-s", "0", paths.IPA_MIGRATE_LOG]
--        )
--        yield
--
--    def test_ipa_sigden_plugin_fail_error(self, empty_log_file):
--        """
--        This testcase checks that sidgen plugin fail error is
--        not seen during migrate prod-mode
--        """
--        SIDGEN_ERR_MSG = "SIDGEN task failed: \n"
--        run_migrate(
--            self.replicas[0],
--            "stage-mode",
--            self.master.hostname,
--            "cn=Directory Manager",
--            self.master.config.admin_password,
--            extra_args=['-x'],
--        )
--        error_msg = self.replicas[0].get_file_contents(
--            paths.IPA_MIGRATE_LOG, encoding="utf-8"
--        )
--        assert SIDGEN_ERR_MSG not in error_msg
--
--    def test_ipa_migrate_stage_mode_dry_run(self, empty_log_file):
-+    def test_ipa_migrate_stage_mode_dry_run(self):
-         """
-         Test ipa-migrate stage mode with dry-run option
-+        This test also checks SIDGEN task failure is
-+        not seen in ipa migrate log.
-         """
-         tasks.kinit_admin(self.master)
-         tasks.kinit_admin(self.replicas[0])
-+        SIDGEN_ERR_MSG = "SIDGEN task failed: \n"
-         IPA_MIGRATE_STAGE_DRY_RUN_LOG = "--dryrun=True\n"
--        IPA_SERVER_UPRGADE_LOG = "Skipping ipa-server-upgrade in dryrun mode.\n"
-+        IPA_SERVER_UPRGADE_LOG = (
-+            "Skipping ipa-server-upgrade in dryrun mode.\n"
-+        )
-         IPA_SKIP_SIDGEN_LOG = "Skipping SIDGEN task in dryrun mode."
-         result = run_migrate(
-             self.replicas[0],
-@@ -481,6 +499,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         assert IPA_MIGRATE_STAGE_DRY_RUN_LOG in install_msg
-         assert IPA_SERVER_UPRGADE_LOG in install_msg
-         assert IPA_SKIP_SIDGEN_LOG in install_msg
-+        assert SIDGEN_ERR_MSG not in install_msg
- 
-     def test_ipa_migrate_prod_mode_dry_run(self, empty_log_file):
-         """
-@@ -509,7 +528,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         assert IPA_SERVER_UPRGADE_LOG in install_msg
-         assert IPA_SIDGEN_LOG in install_msg
- 
--    def test_ipa_migrate_with_skip_schema_option_dry_run(self, empty_log_file):
-+    def test_ipa_migrate_skip_schema_dry_run(self, empty_log_file):
-         """
-         This test checks that ipa-migrate tool works
-         with -S(schema) options in stage mode
-@@ -532,7 +551,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         )
-         assert SKIP_SCHEMA_MSG_LOG in install_msg
- 
--    def test_ipa_migrate_with_skip_config_option_dry_run(self, empty_log_file):
-+    def test_ipa_migrate_skip_config_dry_run(self, empty_log_file):
-         """
-         This test checks that ipa-migrate tool works
-         with -C(config) options in stage mode
-@@ -579,7 +598,7 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         )
-         assert RESET_RANGE_LOG in install_msg
- 
--    def test_ipa_migrate_stage_mode_dry_override_schema(self, empty_log_file):
-+    def test_ipa_migrate_stage_mode_override_schema(self, empty_log_file):
-         """
-         This test checks that -O option (override schema) works
-         in dry mode
-@@ -601,70 +620,6 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         )
-         assert SCHEMA_OVERRIDE_LOG in install_msg
- 
--    def test_ipa_migrate_stage_mode(self, empty_log_file):
--        """
--        This test checks that ipa-migrate is successful
--        in dry run mode
--        """
--        tasks.kinit_admin(self.master)
--        tasks.kinit_admin(self.replicas[0])
--        MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
--        MIGRATION_CONFIG_LOG_MSG = "Migrating configuration ...\n"
--        IPA_UPGRADE_LOG_MSG = (
--            "Running ipa-server-upgrade ... (this may take a while)\n"
--        )
--        SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
--        MIGRATION_COMPLETE_LOG_MSG = "Migration complete!\n"
--        result = run_migrate(
--            self.replicas[0],
--            "stage-mode",
--            self.master.hostname,
--            "cn=Directory Manager",
--            self.master.config.admin_password,
--            extra_args=['-n'],
--        )
--        install_msg = self.replicas[0].get_file_contents(
--            paths.IPA_MIGRATE_LOG, encoding="utf-8"
--        )
--        assert result.returncode == 0
--        assert MIGRATION_SCHEMA_LOG_MSG in install_msg
--        assert MIGRATION_CONFIG_LOG_MSG in install_msg
--        assert IPA_UPGRADE_LOG_MSG in install_msg
--        assert SIDGEN_TASK_LOG_MSG in install_msg
--        assert MIGRATION_COMPLETE_LOG_MSG in install_msg
--
--    def test_ipa_migrate_prod_mode(self, empty_log_file):
--        """
--        This test checks that ipa-migrate is successful
--        in prod run mode
--        """
--        tasks.kinit_admin(self.master)
--        tasks.kinit_admin(self.replicas[0])
--        MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
--        MIGRATION_DATABASE_LOG_MSG = (
--            "Migrating database ... (this may take a while)\n"
--        )
--        IPA_UPGRADE_LOG_MSG = (
--            "Running ipa-server-upgrade ... (this may take a while)\n"
--        )
--        SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
--        result = run_migrate(
--            self.replicas[0],
--            "prod-mode",
--            self.master.hostname,
--            "cn=Directory Manager",
--            self.master.config.admin_password,
--            extra_args=['-n'],
--        )
--        install_msg = self.replicas[0].get_file_contents(
--            paths.IPA_MIGRATE_LOG, encoding="utf-8"
--        )
--        assert result.returncode == 0
--        assert MIGRATION_SCHEMA_LOG_MSG in install_msg
--        assert MIGRATION_DATABASE_LOG_MSG in install_msg
--        assert IPA_UPGRADE_LOG_MSG in install_msg
--        assert SIDGEN_TASK_LOG_MSG in install_msg
--
-     def test_ipa_migrate_with_bind_pwd_file_option(self, empty_log_file):
-         """
-         This testcase checks that ipa-migrate tool
-@@ -801,6 +756,9 @@ class TestIPAMigrateScenario1(IntegrationTest):
- 
-     @pytest.fixture()
-     def modify_dns_zone(self):
-+        """
-+        This fixture adds dnszone and then removes the zone.
-+        """
-         zone_name = 'ipatest.test'
-         self.master.run_command(
-             ["ipa", "dnszone-add", zone_name, "--force"]
-@@ -844,6 +802,20 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         assert DNS_LOG2 in install_msg
-         assert DNS_LOG3 in install_msg
- 
-+    def test_ipa_migrate_dns_forwardzone(self):
-+        """
-+        This testcase checks that DNS forwardzone is
-+        also migrated in prod-mode
-+        """
-+        zone_name = "forwardzone.test"
-+        result = self.replicas[0].run_command(
-+            ["ipa", "dnsforwardzone-show", zone_name]
-+        )
-+        assert 'Zone name: {}'.format(zone_name) in result.stdout_text
-+        assert 'Active zone: True' in result.stdout_text
-+        assert 'Zone forwarders: 10.11.12.13' in result.stdout_text
-+        assert 'Forward policy: first' in result.stdout_text
-+
-     def test_ipa_migrate_version_option(self):
-         """
-         The -V option has been removed.
-@@ -922,20 +894,179 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         assert result.returncode == 1
-         assert ERR_MSG in result.stderr_text
- 
--    def test_ipa_hbac_rule_duplication(self):
-+
-+class TestIPAMigrationStageMode(MigrationTest):
-+    """
-+    Tests for ipa-migrate tool in stage mode
-+    """
-+    def test_ipa_migrate_stage_mode(self, empty_log_file):
-         """
--        This testcase checks that default hbac rules
--        are not duplicated on the local server when
--        ipa-migrate command is run.
-+        This test checks that ipa-migrate is successful
-+        in dry run mode
-         """
-+        tasks.kinit_admin(self.master)
-+        tasks.kinit_admin(self.replicas[0])
-+        MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
-+        MIGRATION_CONFIG_LOG_MSG = "Migrating configuration ...\n"
-+        IPA_UPGRADE_LOG_MSG = (
-+            "Running ipa-server-upgrade ... (this may take a while)\n"
-+        )
-+        SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
-+        MIGRATION_COMPLETE_LOG_MSG = "Migration complete!\n"
-         run_migrate(
-+            self.replicas[0],
-+            "stage-mode",
-+            self.master.hostname,
-+            "cn=Directory Manager",
-+            self.master.config.admin_password,
-+            extra_args=['-n'],
-+        )
-+        install_msg = self.replicas[0].get_file_contents(
-+            paths.IPA_MIGRATE_LOG, encoding="utf-8"
-+        )
-+        assert MIGRATION_SCHEMA_LOG_MSG in install_msg
-+        assert MIGRATION_CONFIG_LOG_MSG in install_msg
-+        assert IPA_UPGRADE_LOG_MSG in install_msg
-+        assert SIDGEN_TASK_LOG_MSG in install_msg
-+        assert MIGRATION_COMPLETE_LOG_MSG in install_msg
-+
-+    def test_ipa_migrate_stage_mode_new_user(self):
-+        """
-+        This testcase checks that when a new user is added and
-+        ipa-migrate is run in stage-mode, uid/gid of the
-+        migrated user is not preserved i.e we have different
-+        uid/gid for user on remote and local IPA server.
-+        """
-+        username = 'testuser4'
-+        base_dn = str(self.master.domain.basedn)
-+        LOG_MSG1 = (
-+            "DEBUG Resetting the DNA range for new entry: "
-+            "uid={},cn=users,cn=accounts,{}\n"
-+        ).format(username, base_dn)
-+        install_msg = self.replicas[0].get_file_contents(
-+            paths.IPA_MIGRATE_LOG, encoding="utf-8"
-+        )
-+        assert LOG_MSG1 not in install_msg
-+        tasks.clear_sssd_cache(self.master)
-+        self.master.run_command(['ipa', 'user-show', username])
-+        cmd1 = self.master.run_command(['id', username])
-+        tasks.clear_sssd_cache(self.replicas[0])
-+        self.replicas[0].run_command(['ipa', 'user-show', username])
-+        cmd2 = self.replicas[0].run_command(['id', username])
-+        assert cmd1.stdout_text != cmd2.stdout_text
-+
-+
-+class TestIPAMigrationProdMode(MigrationTest):
-+    """
-+    Tests for ipa-migrate tool in prod mode
-+    """
-+    def test_ipa_migrate_prod_mode(self, empty_log_file):
-+        """
-+        This test checks that ipa-migrate is successful
-+        in prod run mode
-+        """
-+        tasks.kinit_admin(self.master)
-+        tasks.kinit_admin(self.replicas[0])
-+        MIGRATION_SCHEMA_LOG_MSG = "Migrating schema ...\n"
-+        MIGRATION_DATABASE_LOG_MSG = (
-+            "Migrating database ... (this may take a while)\n"
-+        )
-+        IPA_UPGRADE_LOG_MSG = (
-+            "Running ipa-server-upgrade ... (this may take a while)\n"
-+        )
-+        SIDGEN_TASK_LOG_MSG = "Running SIDGEN task ...\n"
-+        result = run_migrate(
-             self.replicas[0],
-             "prod-mode",
-             self.master.hostname,
-             "cn=Directory Manager",
-             self.master.config.admin_password,
--            extra_args=['-n']
-+            extra_args=['-n'],
-         )
-+        install_msg = self.replicas[0].get_file_contents(
-+            paths.IPA_MIGRATE_LOG, encoding="utf-8"
-+        )
-+        assert result.returncode == 0
-+        assert MIGRATION_SCHEMA_LOG_MSG in install_msg
-+        assert MIGRATION_DATABASE_LOG_MSG in install_msg
-+        assert IPA_UPGRADE_LOG_MSG in install_msg
-+        assert SIDGEN_TASK_LOG_MSG in install_msg
-+
-+    def test_ipa_migrate_prod_mode_hbac_rule(self):
-+        """
-+        This testcase checks that hbac rule is migrated from
-+        remote server to local server in prod mode.
-+        """
-+        hbac_rule_name1 = 'test1'
-+        hbac_rule_name2 = 'testuser_sshd'
-+        tasks.kinit_admin(self.replicas[0])
-+        cmd1 = self.replicas[0].run_command(
-+            ["ipa", "hbacrule-find", hbac_rule_name1])
-+        cmd2 = self.replicas[0].run_command(
-+            ["ipa", "hbacrule-find", hbac_rule_name2])
-+        assert hbac_rule_name1 in cmd1.stdout_text
-+        assert hbac_rule_name2 in cmd2.stdout_text
-+
-+    def test_ipa_migrate_prod_mode_sudo_rule(self):
-+        """
-+        This testcase checks that sudo cmd and rules are
-+        migrated from remote server to local server in prod mode.
-+        """
-+        sudorule = 'readfiles'
-+        sudocmd = '/usr/bin/less'
-+        tasks.kinit_admin(self.replicas[0])
-+        cmd1 = self.replicas[0].run_command(
-+            ["ipa", "sudorule-find", sudorule])
-+        cmd2 = self.replicas[0].run_command(
-+            ["ipa", "sudocmd-find", sudocmd])
-+        assert 'Rule name: readfiles\n' in cmd1.stdout_text
-+        assert 'Sudo Command: /usr/bin/less\n' in cmd2.stdout_text
-+
-+    def test_ipa_migrate_prod_mode_new_user_sid(self):
-+        """
-+        This testcase checks that in prod-mode uid/gid of the
-+        migrated user is preserved i.e we have same
-+        uid/gid for user on remote and local IPA server.
-+        """
-+        username = 'testuser4'
-+        tasks.clear_sssd_cache(self.master)
-+        result1 = self.master.run_command(['id', username])
-+        tasks.clear_sssd_cache(self.replicas[0])
-+        result2 = self.replicas[0].run_command(['id', username])
-+        assert result1.stdout_text == result2.stdout_text
-+
-+    def test_check_vault_is_not_migrated(self):
-+        """
-+        This testcase checks that vault is
-+        not migrated
-+        """
-+        vault_name = "testvault"
-+        CMD_OUTPUT = "Number of entries returned 0"
-+        cmd = self.replicas[0].run_command(
-+            ["ipa", "vault-find", vault_name], raiseonerr=False)
-+        assert cmd.returncode != 0
-+        assert CMD_OUTPUT in cmd.stdout_text
-+
-+    def test_ipa_migrate_subids(self):
-+        """
-+        This testcase checks that subids for users are migrated
-+        to the local server from the remote server
-+        """
-+        user_name = 'admin'
-+        CMD_MSG = "1 subordinate id matched"
-+        cmd = self.replicas[0].run_command(
-+            ['ipa', 'subid-find',
-+             '--owner', user_name]
-+        )
-+        assert cmd.returncode == 0
-+        assert CMD_MSG in cmd.stdout_text
-+
-+    def test_ipa_hbac_rule_duplication(self):
-+        """
-+        This testcase checks that default hbac rules
-+        are not duplicated on the local server when
-+        ipa-migrate command is run.
-+        """
-         result = self.replicas[0].run_command(
-             ['ipa', 'hbacrule-find']
-         )
-@@ -946,3 +1077,192 @@ class TestIPAMigrateScenario1(IntegrationTest):
-         count = Counter(line)
-         assert count.get('Rule name: allow_all') < 2
-         assert count.get('Rule name: allow_systemd-user') < 2
-+
-+    def test_ipa_migrate_otptoken(self):
-+        """
-+        This testcase checks that the otptoken
-+        is migrated for the user.
-+        """
-+        owner = "testuser1"
-+        CMD_OUTPUT = "1 OTP token matched"
-+        result = self.replicas[0].run_command([
-+            "ipa", "otptoken-find"
-+        ])
-+        assert CMD_OUTPUT in result.stdout_text
-+        assert 'Type: TOTP' in result.stdout_text
-+        assert 'Owner: {}'.format(owner) in result.stdout_text
-+
-+    def test_ipa_migrate_check_passkey_config(self):
-+        """
-+        This testcase checks that passkey config
-+        is migrated
-+        """
-+        CMD_OUTPUT = "Require user verification: False"
-+        result = self.replicas[0].run_command([
-+            "ipa", "passkeyconfig-show"
-+        ])
-+        assert CMD_OUTPUT in result.stdout_text
-+
-+    def test_ipa_migrate_check_service_status(self):
-+        """
-+        This testcase checks that ipactl and sssd
-+        services are running post ipa-migrate tool
-+        successful runs completed
-+        """
-+        cmd1 = self.replicas[0].run_command([
-+            "ipactl", "status"
-+        ])
-+        assert cmd1.returncode == 0
-+        cmd2 = self.replicas[0].run_command([
-+            "systemctl", "status", "sssd"
-+        ])
-+        assert cmd2.returncode == 0
-+
-+    def test_custom_idrange_is_migrated(self):
-+        """
-+        This testcase checks that custom idrange is migrated
-+        from remote server to local server in production
-+        mode.
-+        """
-+        range_name = "testrange"
-+        CMD_OUTPUT = (
-+            "---------------\n"
-+            "1 range matched\n"
-+            "---------------\n"
-+            "  Range name: testrange\n"
-+            "  First Posix ID of the range: 10000\n"
-+            "  Number of IDs in the range: 10000\n"
-+            "  First RID of the corresponding RID range: 300000\n"
-+            "  First RID of the secondary RID range: 400000\n"
-+            "  Range type: local domain range\n"
-+            "----------------------------\n"
-+            "Number of entries returned 1\n"
-+            "----------------------------\n"
-+        )
-+        cmd = self.replicas[0].run_command(
-+            ["ipa", "idrange-find", range_name])
-+        assert CMD_OUTPUT in cmd.stdout_text
-+
-+    def test_automountlocation_is_migrated(self):
-+        """
-+        This testcase checks that automount location/maps
-+        and keys are migrated.
-+        """
-+        base_dn = str(self.master.domain.basedn)
-+        automount_cn = "automount"
-+        loc_name = "baltimore"
-+        auto_map_name = "auto.share"
-+        DEBUG_LOG = (
-+            "Added entry: cn={},cn={},{}\n"
-+        ).format(loc_name, automount_cn, base_dn)
-+        CMD1_OUTPUT = (
-+            "  Location: baltimore\n"
-+        )
-+        CMD2_OUTPUT = (
-+            "  Map: auto.share\n"
-+        )
-+        CMD3_OUTPUT = (
-+            "-----------------------\n"
-+            "1 automount key matched\n"
-+            "-----------------------\n"
-+            "  Key: sub\n"
-+            "  Mount information: -fstype=autofs ldap:auto.man\n"
-+        )
-+        cmd1 = self.replicas[0].run_command(
-+            ["ipa", "automountlocation-show", loc_name])
-+        cmd2 = self.replicas[0].run_command(
-+            ["ipa", "automountmap-find", loc_name])
-+        cmd3 = self.replicas[0].run_command(
-+            ["ipa", "automountkey-find", loc_name, auto_map_name]
-+        )
-+        install_msg = self.replicas[0].get_file_contents(
-+            paths.IPA_MIGRATE_LOG, encoding="utf-8"
-+        )
-+        assert CMD1_OUTPUT in cmd1.stdout_text
-+        assert CMD2_OUTPUT in cmd2.stdout_text
-+        assert CMD3_OUTPUT in cmd3.stdout_text
-+        assert DEBUG_LOG in install_msg
-+
-+
-+class TestIPAMigrationWithADtrust(IntegrationTest):
-+    """
-+    Test for ipa-migrate tool with IPA Master having trust setup
-+    with Windows AD.
-+    """
-+    topology = "line"
-+    num_ad_domains = 1
-+    num_replicas = 1
-+
-+    @classmethod
-+    def install(cls, mh):
-+        tasks.install_master(
-+            cls.master, setup_dns=True, extra_args=['--no-dnssec-validation']
-+        )
-+        cls.ad = cls.ads[0]
-+        cls.ad_domain = cls.ad.domain.name
-+        tasks.install_adtrust(cls.master)
-+        tasks.configure_dns_for_trust(cls.master, cls.ad)
-+        tasks.establish_trust_with_ad(cls.master, cls.ad.domain.name)
-+
-+    def test_install_local_server(self):
-+        """
-+        This test installs local IPA Server() i.e new IPA server with
-+        the same realm and domain name that will receive the migration data.
-+        """
-+        tasks.install_master(
-+            self.replicas[0], setup_dns=True,
-+            extra_args=['--no-dnssec-validation']
-+        )
-+        tasks.install_adtrust(self.replicas[0])
-+
-+    def test_check_ad_attributes_migrate_prod_mode(self):
-+        """
-+        This test checks that IPA-AD trust related attributes
-+        are migrated to local server.
-+        """
-+        result = run_migrate(
-+            self.replicas[0],
-+            "prod-mode",
-+            self.master.hostname,
-+            "cn=Directory Manager",
-+            self.master.config.admin_password,
-+            extra_args=['-n']
-+        )
-+        assert result.returncode == 0
-+        trust1 = self.master.run_command(
-+            ['ipa', 'trust-show', self.ad_domain]
-+        ).stdout_text
-+        trust2 = self.replicas[0].run_command(
-+            ['ipa', 'trust-show', self.ad_domain]).stdout_text
-+        assert trust1 == trust2
-+
-+    def test_check_domain_sid_is_migrated(self):
-+        """
-+        This testcase checks that domain sid is
-+        migrated from a remote server having trust with AD
-+        to local server and is displayed in the
-+        ipa trustconfig-show command
-+        """
-+        regexp = (r'Security Identifier: (.*)$')
-+        cmd1 = self.master.run_command(["ipa", "trustconfig-show"])
-+        sid1 = re.findall(regexp, cmd1.stdout_text, re.MULTILINE)
-+        cmd2 = self.replicas[0].run_command(
-+            ["ipa", "trustconfig-show"]
-+        )
-+        sid2 = re.findall(regexp, cmd2.stdout_text, re.MULTILINE)
-+        assert sid1 == sid2
-+
-+    def test_check_ad_idrange_is_migrated(self):
-+        """
-+        This testcase checks AD idrange is migrated
-+        from remote IPA server having trust with AD
-+        to local IPA server
-+        """
-+        ad_domain_name = self.ad.domain.name.upper()
-+        cmd1 = self.master.run_command(
-+            ["ipa", "idrange-show", ad_domain_name + "_id_range"]
-+        )
-+        cmd2 = self.replicas[0].run_command(
-+            ["ipa", "idrange-show", ad_domain_name + "_id_range"]
-+        )
-+        assert cmd1.stdout_text == cmd2.stdout_text
--- 
-2.49.0
-
-
-From e08f4fb2226fa8ba2f39127fe8e6b1a525816c63 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Mon, 4 Nov 2024 14:33:33 +0530
-Subject: [PATCH 041/116] ipatests: Include ipa-ipa migration testsuite in
- nightly test definitions
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../nightly_ipa-4-12_latest.yaml                | 16 ++++++++++++++--
- .../nightly_ipa-4-12_latest_selinux.yaml        | 17 +++++++++++++++--
- 2 files changed, 29 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 07e2a8399..5b0f89c35 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -2125,11 +2125,23 @@ jobs:
-       class: RunPytest
-       args:
-         build_url: '{fedora-latest-ipa-4-12/build_url}'
--        test_suite: test_integration/test_ipa_ipa_migration.py
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigrateCLIOptions test_integration/test_ipa_ipa_migration.py::TestIPAMigrationStageMode test_integration/test_ipa_ipa_migration.py::TestIPAMigrationProdMode
-         template: *ci-ipa-4-12-latest
--        timeout: 3600
-+        timeout: 9000
-         topology: *master_1repl_1client
- 
-+  fedora-latest-ipa-4-12/test_IPAMigrateADTrust:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunADTests
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigrationWithADtrust
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *ad_master_1repl_1client
-+
-   fedora-latest-ipa-4-12/test_cockpit:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 11046be13..277a547a4 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2295,11 +2295,24 @@ jobs:
-       args:
-         build_url: '{fedora-latest-ipa-4-12/build_url}'
-         selinux_enforcing: True
--        test_suite: test_integration/test_ipa_ipa_migration.py
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigrateCLIOptions test_integration/test_ipa_ipa_migration.py::TestIPAMigrationStageMode test_integration/test_ipa_ipa_migration.py::TestIPAMigrationProdMode
-         template: *ci-ipa-4-12-latest
--        timeout: 3600
-+        timeout: 9000
-         topology: *master_1repl_1client
- 
-+  fedora-latest-ipa-4-12/test_IPAMigrateADTrust:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunADTests
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigrationWithADtrust
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *ad_master_1repl_1client
-+
-   fedora-latest-ipa-4-12/test_cockpit:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
--- 
-2.49.0
-
-
-From 70ee983d0d669ec6beecbfc0117bca3e58b8ae66 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 31 Oct 2024 11:50:40 +0100
-Subject: [PATCH 042/116] ipatests: move tests to fedora 41
-
-Now that fedora 41 is available, move the tests on ipa-4-12 branch
-onto fedora 41 instead of fedora 40.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- ipatests/prci_definitions/gating.yaml                          | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml         | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml | 2 +-
- 3 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index e284cf2b8..416a99cc8 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -30,7 +30,7 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f40
-+          name: freeipa/ci-ipa-4-12-f41
-           version: 0.0.1
-         timeout: 1800
-         topology: *build
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 5b0f89c35..f0283615d 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -54,7 +54,7 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f40
-+          name: freeipa/ci-ipa-4-12-f41
-           version: 0.0.1
-         timeout: 1800
-         topology: *build
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 277a547a4..c2871c473 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -54,7 +54,7 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f40
-+          name: freeipa/ci-ipa-4-12-f41
-           version: 0.0.1
-         timeout: 1800
-         topology: *build
--- 
-2.49.0
-
-
-From c64c098e1d0ae492499caa83a1b73532da511f84 Mon Sep 17 00:00:00 2001
-From: Carla Martinez <carlmart@redhat.com>
-Date: Tue, 29 Oct 2024 15:23:55 +0100
-Subject: [PATCH 043/116] Fix: 'Organization' field in Okta not required
-
-Although the 'Organization' field is not required
-when using the Okta template, the WebUI requires it
-in order to create a new IDP. If this is not provided,
-an error is shown.
-
-Fixes: https://pagure.io/freeipa/issue/9687
-Signed-off-by: Carla Martinez <carlmart@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- install/ui/src/freeipa/idp.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/install/ui/src/freeipa/idp.js b/install/ui/src/freeipa/idp.js
-index ada09c075..04daad591 100644
---- a/install/ui/src/freeipa/idp.js
-+++ b/install/ui/src/freeipa/idp.js
-@@ -41,7 +41,7 @@ idp.templates = [
-       fields: ['ipaidporg']},
-     { value: 'okta',
-       label: text.get('@i18n:objects.idp.template_okta'),
--      fields: ['ipaidporg', 'ipaidpbaseurl']}
-+      fields: ['ipaidpbaseurl']}
- ];
- 
- 
--- 
-2.49.0
-
-
-From baa9fc3e3e2f6b39db5ec465c92dc597cd5399b9 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 12 Nov 2024 16:44:46 +0100
-Subject: [PATCH 044/116] ipatests: install master with allow-zone-overlap
-
-In the IPA to IPA migration tests, install the destination master
-with --setup-dns --allow-zone-overlap to allow installation
-even if the zone is already served by the source master.
-
-Fixes: https://pagure.io/freeipa/issue/9697
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Sudhir Menon <sumenon@redhat.com>
----
- ipatests/test_integration/test_ipa_ipa_migration.py | 5 +++--
- 1 file changed, 3 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index d852ca63a..0c637a014 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -345,7 +345,8 @@ class MigrationTest(IntegrationTest):
-         tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
-         prepare_ipa_server(cls.master)
-         tasks.install_client(cls.master, cls.clients[0], nameservers=None)
--        tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True)
-+        tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True,
-+                             extra_args=['--allow-zone-overlap'])
- 
- 
- class TestIPAMigrateCLIOptions(MigrationTest):
-@@ -1211,7 +1212,7 @@ class TestIPAMigrationWithADtrust(IntegrationTest):
-         """
-         tasks.install_master(
-             self.replicas[0], setup_dns=True,
--            extra_args=['--no-dnssec-validation']
-+            extra_args=['--no-dnssec-validation', '--allow-zone-overlap']
-         )
-         tasks.install_adtrust(self.replicas[0])
- 
--- 
-2.49.0
-
-
-From c306c613399cdd9a2c716b83ce0d47d320aec2a8 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 19 Nov 2024 12:57:46 +0200
-Subject: [PATCH 045/116] ipaserver/dcerpc: support Samba 4.21
-
-Samba 4.21 moved samba.trust_utils module to samba.lsa_utils.
-
-Fixes: https://pagure.io/freeipa/issue/9702
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/dcerpc.py | 8 ++++++--
- 1 file changed, 6 insertions(+), 2 deletions(-)
-
-diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
-index a28c72361..3344ea226 100644
---- a/ipaserver/dcerpc.py
-+++ b/ipaserver/dcerpc.py
-@@ -55,9 +55,13 @@ from samba import ntstatus
- import samba
- 
- try:
--    from samba.trust_utils import CreateTrustedDomainRelax
-+    from samba.lsa_utils import CreateTrustedDomainRelax
- except ImportError:
--    CreateTrustedDomainRelax = None
-+    try:
-+        from samba.trust_utils import CreateTrustedDomainRelax
-+    except ImportError:
-+        CreateTrustedDomainRelax = None
-+
- try:
-     from samba import arcfour_encrypt
- except ImportError:
--- 
-2.49.0
-
-
-From fdfe94258b2e5a26b3a2ebc4be56c15f0682b6ba Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 29 Nov 2024 14:07:28 +0100
-Subject: [PATCH 046/116] readthedocs: install crypto 43.0.0
-
-If crypto 44.0.0 is installed, readthedocs build fails
-with:
-TypeError: type 'cryptography.hazmat.bindings._rust.x509.Certificate'
-is not an acceptable base type
-
-Force installation of cryptography < 44.0.0
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- doc/requirements.txt | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/doc/requirements.txt b/doc/requirements.txt
-index 8d91b893f..b2071369a 100644
---- a/doc/requirements.txt
-+++ b/doc/requirements.txt
-@@ -21,7 +21,7 @@ requests
- 
- ## C libraries with binary wheels
- cffi
--cryptography
-+cryptography < 44.0.0
- lxml
- 
- ## C libraries without binaries wheels
--- 
-2.49.0
-
-
-From 184589fac4ff36b5583541f40dff91296c33370a Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 2 Dec 2024 10:23:29 -0500
-Subject: [PATCH 047/116] Allow looking up constants.Group by gid in addition
- to name
-
-This adds flexibility so we can look up groups by both gid and
-by name in order to have a more consistent API for management.
-
-Related: https://pagure.io/freeipa/issue/9709
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaplatform/base/constants.py               |  5 ++++-
- ipatests/test_ipaplatform/test_constants.py | 11 +++++++++++
- 2 files changed, 15 insertions(+), 1 deletion(-)
-
-diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py
-index 1689efe52..f1ef7efff 100644
---- a/ipaplatform/base/constants.py
-+++ b/ipaplatform/base/constants.py
-@@ -86,7 +86,10 @@ class Group(_Entity):
-             try:
-                 self._entity = entity = grp.getgrnam(self)
-             except KeyError:
--                raise ValueError(f"group '{self!s}' not found") from None
-+                try:
-+                    self._entity = entity = grp.getgrgid(int(self))
-+                except (TypeError, ValueError):
-+                    raise ValueError(f"group '{self!s}' not found") from None
-         return entity
- 
-     @property
-diff --git a/ipatests/test_ipaplatform/test_constants.py b/ipatests/test_ipaplatform/test_constants.py
-index b57bfa48e..9bb122836 100644
---- a/ipatests/test_ipaplatform/test_constants.py
-+++ b/ipatests/test_ipaplatform/test_constants.py
-@@ -1,6 +1,7 @@
- #
- # Copyright (C) 2020  FreeIPA Contributors see COPYING for license
- #
-+import grp
- import pytest
- 
- from ipaplatform.base.constants import User, Group
-@@ -40,6 +41,16 @@ def test_group():
-     assert Group(str(group)) is not group
- 
- 
-+def test_numeric_group():
-+    g = grp.getgrnam('apache')
-+    group = Group(g.gr_gid)
-+    assert group.gid == g.gr_gid
-+    assert type(str(group)) is str
-+    assert repr(group) == '<Group "%d">' % g.gr_gid
-+    assert group.gid == g.gr_gid
-+    assert group.entity.gr_gid == g.gr_gid
-+
-+
- def test_group_invalid():
-     invalid = Group("invalid")
-     with pytest.raises(ValueError) as e:
--- 
-2.49.0
-
-
-From 934d4a291d44a40b5ea006aa1f09afa8e4a985fc Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 2 Dec 2024 10:27:15 -0500
-Subject: [PATCH 048/116] Pass all pkiuser groups as suplementary when
- validating an HSM
-
-We were doing a "best effort" when validating the HSM token is
-visible with a valid PIN when it came to groups. A specific
-workaround was added for softhsm2 but this didn't carry over
-to other HSMs that may have group-specific read/write access.
-
-Use the new capability in ipaplatform.constants.py::Group to be
-able to use generate a valid entry from a group GID. Pair this
-with os.getgrouplist() and all groups will be passed correctly
-via ipautil.run().
-
-Fixes: https://pagure.io/freeipa/issue/9709
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaserver/install/ca.py | 12 ++++--------
- 1 file changed, 4 insertions(+), 8 deletions(-)
-
-diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
-index 520e3fc5d..2959aceed 100644
---- a/ipaserver/install/ca.py
-+++ b/ipaserver/install/ca.py
-@@ -211,11 +211,7 @@ def hsm_validator(token_name, token_library, token_password):
-         )
-     pkiuser = constants.PKI_USER
-     pkigroup = constants.PKI_GROUP
--    if 'libsofthsm' in token_library:
--        import grp
--        group = grp.getgrnam(constants.ODS_GROUP)
--        if str(constants.PKI_USER) in group.gr_mem:
--            pkigroup = constants.ODS_GROUP
-+    group_list = os.getgrouplist(pkiuser, pkigroup.gid)
-     with certdb.NSSDatabase() as tempnssdb:
-         tempnssdb.create_db(user=str(pkiuser), group=str(pkigroup))
-         # Try adding the token library to the temporary database in
-@@ -231,7 +227,7 @@ def hsm_validator(token_name, token_library, token_password):
-         # It may fail if p11-kit has already registered the library, that's
-         # ok.
-         ipautil.run(command, stdin='\n', cwd=tempnssdb.secdir,
--                    runas=pkiuser, suplementary_groups=[pkigroup],
-+                    runas=pkiuser, suplementary_groups=group_list,
-                     raiseonerr=False)
- 
-         command = [
-@@ -242,7 +238,7 @@ def hsm_validator(token_name, token_library, token_password):
-         ]
-         lines = ipautil.run(
-             command, cwd=tempnssdb.secdir, capture_output=True,
--            runas=pkiuser, suplementary_groups=[pkigroup]).output
-+            runas=pkiuser, suplementary_groups=group_list).output
-         found = False
-         token_line = f'token: {token_name}'
-         for line in lines.split('\n'):
-@@ -265,7 +261,7 @@ def hsm_validator(token_name, token_library, token_password):
-         ]
-         result = ipautil.run(args, cwd=tempnssdb.secdir,
-                              runas=pkiuser,
--                             suplementary_groups=[pkigroup],
-+                             suplementary_groups=group_list,
-                              capture_error=True, raiseonerr=False)
-         if result.returncode != 0 and len(result.error_output):
-             if 'SEC_ERROR_BAD_PASSWORD' in result.error_output:
--- 
-2.49.0
-
-
-From e3cf4d0bf971a803e75f0b5b54634f65e641b752 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 2 Dec 2024 09:34:49 +0200
-Subject: [PATCH 049/116] Revert "readthedocs: install crypto 43.0.0"
-
-This reverts commit b20c3fb60558b538ef13e0e0fe89ae361d529553.
-
-As we support PyCA 44.0.0, no need to prevent its installation anymore.
-
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- doc/requirements.txt | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/doc/requirements.txt b/doc/requirements.txt
-index b2071369a..8d91b893f 100644
---- a/doc/requirements.txt
-+++ b/doc/requirements.txt
-@@ -21,7 +21,7 @@ requests
- 
- ## C libraries with binary wheels
- cffi
--cryptography < 44.0.0
-+cryptography
- lxml
- 
- ## C libraries without binaries wheels
--- 
-2.49.0
-
-
-From d4d56a6705c870901bc73882e4804367f7c9c91a Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Sun, 1 Dec 2024 20:16:54 +0200
-Subject: [PATCH 050/116] ipalib/x509: support PyCA 44.0
-
-PyCA made x509.Certificate class concrete, it cannot be extended anymore
-by Python code. The intent is to use helper functions to instantiate
-certificate objects and never create them directly.
-
-FreeIPA wraps PyCA's x509.Certificate class and provides own shim
-on top of it. In most cases we load the certificate content via the
-helper functions and don't really need to derive from the certificate
-class.
-
-Move IPACertificate to be a normal Python object class that stores
-x509.Certificate internally. The only place where this breaks is when
-IPACertificate object needs to be passed to a code that expects
-x509.Certificate (Dogtag PKI). In such cases, expose the underlying
-certificate instance via IPACertificate.cert property.
-
-Fixes: https://pagure.io/freeipa/issue/9708
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipalib/ipajson.py           |  4 ++--
- ipalib/x509.py              | 10 +++++++++-
- ipapython/ipaldap.py        | 15 +++++++--------
- ipaserver/plugins/dogtag.py |  3 ++-
- 4 files changed, 20 insertions(+), 12 deletions(-)
-
-diff --git a/ipalib/ipajson.py b/ipalib/ipajson.py
-index 5551d12e5..fd99c8219 100644
---- a/ipalib/ipajson.py
-+++ b/ipalib/ipajson.py
-@@ -9,7 +9,7 @@ from decimal import Decimal
- import json
- import six
- from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
--from ipalib import capabilities
-+from ipalib import capabilities, x509
- from ipalib.x509 import Encoding as x509_Encoding
- from ipapython.dn import DN
- from ipapython.dnsutil import DNSName
-@@ -72,7 +72,7 @@ class _JSONPrimer(dict):
-             list: self._enc_list,
-             tuple: self._enc_list,
-             dict: self._enc_dict,
--            crypto_x509.Certificate: self._enc_certificate,
-+            x509.IPACertificate: self._enc_certificate,
-             crypto_x509.CertificateSigningRequest: self._enc_certificate,
-         })
- 
-diff --git a/ipalib/x509.py b/ipalib/x509.py
-index fd0823896..6780bead0 100644
---- a/ipalib/x509.py
-+++ b/ipalib/x509.py
-@@ -88,7 +88,7 @@ SAN_UPN = '1.3.6.1.4.1.311.20.2.3'
- SAN_KRB5PRINCIPALNAME = '1.3.6.1.5.2.2'
- 
- 
--class IPACertificate(crypto_x509.Certificate):
-+class IPACertificate:
-     """
-     A proxy class wrapping a python-cryptography certificate representation for
-     IPA purposes
-@@ -205,6 +205,10 @@ class IPACertificate(crypto_x509.Certificate):
-         """
-         return self._cert.fingerprint(algorithm)
- 
-+    @property
-+    def cert(self):
-+        return self._cert
-+
-     @property
-     def serial_number(self):
-         return self._cert.serial_number
-@@ -457,6 +461,8 @@ def load_pem_x509_certificate(data):
-     :returns: a ``IPACertificate`` object.
-     :raises: ``ValueError`` if unable to load the certificate.
-     """
-+    if isinstance(data, IPACertificate):
-+        return data
-     return IPACertificate(
-         crypto_x509.load_pem_x509_certificate(data, backend=default_backend())
-     )
-@@ -469,6 +475,8 @@ def load_der_x509_certificate(data):
-     :returns: a ``IPACertificate`` object.
-     :raises: ``ValueError`` if unable to load the certificate.
-     """
-+    if isinstance(data, IPACertificate):
-+        return data
-     return IPACertificate(
-         crypto_x509.load_der_x509_certificate(data, backend=default_backend())
-     )
-diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
-index 1888e4091..5bb81c1bc 100644
---- a/ipapython/ipaldap.py
-+++ b/ipapython/ipaldap.py
-@@ -33,7 +33,6 @@ import warnings
- 
- from collections import OrderedDict
- 
--from cryptography import x509 as crypto_x509
- from cryptography.hazmat.primitives import serialization
- 
- import ldap
-@@ -748,10 +747,10 @@ class LDAPClient:
-         'dnszoneidnsname': DNSName,
-         'krbcanonicalname': Principal,
-         'krbprincipalname': Principal,
--        'usercertificate': crypto_x509.Certificate,
--        'usercertificate;binary': crypto_x509.Certificate,
--        'cACertificate': crypto_x509.Certificate,
--        'cACertificate;binary': crypto_x509.Certificate,
-+        'usercertificate': x509.IPACertificate,
-+        'usercertificate;binary': x509.IPACertificate,
-+        'cACertificate': x509.IPACertificate,
-+        'cACertificate;binary': x509.IPACertificate,
-         'nsds5replicalastupdatestart': unicode,
-         'nsds5replicalastupdateend': unicode,
-         'nsds5replicalastinitstart': unicode,
-@@ -1000,7 +999,7 @@ class LDAPClient:
-             return dct
-         elif isinstance(val, datetime):
-             return val.strftime(LDAP_GENERALIZED_TIME_FORMAT).encode('utf-8')
--        elif isinstance(val, crypto_x509.Certificate):
-+        elif isinstance(val, x509.IPACertificate):
-             return val.public_bytes(x509.Encoding.DER)
-         elif val is None:
-             return None
-@@ -1027,7 +1026,7 @@ class LDAPClient:
-                     return DNSName.from_text(val.decode('utf-8'))
-                 elif target_type in (DN, Principal):
-                     return target_type(val.decode('utf-8'))
--                elif target_type is crypto_x509.Certificate:
-+                elif target_type is x509.IPACertificate:
-                     return x509.load_der_x509_certificate(val)
-                 else:
-                     return target_type(val)
-@@ -1381,7 +1380,7 @@ class LDAPClient:
-             ]
-             return cls.combine_filters(flts, rules)
-         elif value is not None:
--            if isinstance(value, crypto_x509.Certificate):
-+            if isinstance(value, x509.IPACertificate):
-                 value = value.public_bytes(serialization.Encoding.DER)
-             if isinstance(value, bytes):
-                 value = binascii.hexlify(value).decode('ascii')
-diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
-index 78afb2797..ee6d0e347 100644
---- a/ipaserver/plugins/dogtag.py
-+++ b/ipaserver/plugins/dogtag.py
-@@ -1581,7 +1581,8 @@ class kra(Backend):
- 
-         crypto = cryptoutil.CryptographyCryptoProvider(
-             transport_cert_nick="ra_agent",
--            transport_cert=x509.load_certificate_from_file(paths.RA_AGENT_PEM)
-+            transport_cert=x509.load_certificate_from_file(
-+                paths.RA_AGENT_PEM).cert
-         )
- 
-         # TODO: obtain KRA host & port from IPA service list or point to KRA load balancer
--- 
-2.49.0
-
-
-From 8dfec28647f7c17e47fbfc96a1720dcde1592386 Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Mon, 2 Dec 2024 15:04:30 +0300
-Subject: [PATCH 051/116] pyca: adapt import paths for TripleDES cipher
-
-https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.algorithms.TripleDES
-
-> This algorithm has been deprecated and moved to the Decrepit
-  cryptography module. If you need to continue using it then update your
-  code to use the new module path. It will be removed from this namespace
-  in 48.0.0.
-
-Fixes: https://pagure.io/freeipa/issue/9708
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaclient/plugins/vault.py               |  8 +++++++-
- ipalib/constants.py                      | 24 +++++++++++-------------
- ipaserver/install/ipa_otptoken_import.py |  8 +++++++-
- 3 files changed, 25 insertions(+), 15 deletions(-)
-
-diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
-index 75415c03a..6af729793 100644
---- a/ipaclient/plugins/vault.py
-+++ b/ipaclient/plugins/vault.py
-@@ -34,6 +34,12 @@ from cryptography.hazmat.primitives import hashes
- from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
- from cryptography.hazmat.primitives.asymmetric import padding
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-+try:
-+    # cryptography>=43.0.0
-+    from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
-+except ImportError:
-+    # will be removed from this module in cryptography 48.0.0
-+    from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
- from cryptography.hazmat.primitives.padding import PKCS7
- from cryptography.hazmat.primitives.serialization import (
-     load_pem_public_key, load_pem_private_key)
-@@ -661,7 +667,7 @@ class ModVaultData(Local):
-         if name == constants.VAULT_WRAPPING_AES128_CBC:
-             return algorithms.AES(os.urandom(128 // 8))
-         elif name == constants.VAULT_WRAPPING_3DES:
--            return algorithms.TripleDES(os.urandom(196 // 8))
-+            return TripleDES(os.urandom(196 // 8))
-         else:
-             # unreachable
-             raise ValueError(name)
-diff --git a/ipalib/constants.py b/ipalib/constants.py
-index b657e5a90..c90caa221 100644
---- a/ipalib/constants.py
-+++ b/ipalib/constants.py
-@@ -25,20 +25,19 @@ All constants centralised in one file.
- import os
- import string
- import uuid
--import warnings
--
--warnings.filterwarnings(
--    "ignore",
--    "TripleDES has been moved to "
--    "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and "
--    "will be removed from this module in 48.0.0",
--    category=UserWarning)
- 
- from ipaplatform.constants import constants as _constants
- from ipapython.dn import DN
- from ipapython.fqdn import gethostfqdn
- from ipapython.version import VERSION, API_VERSION
--from cryptography.hazmat.primitives.ciphers import algorithms, modes
-+from cryptography.hazmat.primitives.ciphers import modes
-+try:
-+    # cryptography>=43.0.0
-+    from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
-+except ImportError:
-+    # will be removed from this module in cryptography 48.0.0
-+    from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
-+
- from cryptography.hazmat.backends.openssl.backend import backend
- 
- 
-@@ -389,7 +388,6 @@ VAULT_WRAPPING_SUPPORTED_ALGOS = (
- VAULT_WRAPPING_DEFAULT_ALGO = VAULT_WRAPPING_AES128_CBC
- 
- # Add 3DES for backwards compatibility if supported
--if getattr(algorithms, 'TripleDES', None):
--    if backend.cipher_supported(algorithms.TripleDES(
--                                b"\x00" * 8), modes.CBC(b"\x00" * 8)):
--        VAULT_WRAPPING_SUPPORTED_ALGOS += (VAULT_WRAPPING_3DES,)
-+if backend.cipher_supported(TripleDES(
-+                            b"\x00" * 8), modes.CBC(b"\x00" * 8)):
-+    VAULT_WRAPPING_SUPPORTED_ALGOS += (VAULT_WRAPPING_3DES,)
-diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
-index 279a7502d..17457f6c5 100644
---- a/ipaserver/install/ipa_otptoken_import.py
-+++ b/ipaserver/install/ipa_otptoken_import.py
-@@ -37,6 +37,12 @@ from cryptography.hazmat.primitives import hashes, hmac
- from cryptography.hazmat.primitives.padding import PKCS7
- from cryptography.hazmat.primitives.kdf import pbkdf2
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-+try:
-+    # cryptography>=43.0.0
-+    from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
-+except ImportError:
-+    # will be removed from this module in cryptography 48.0.0
-+    from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
- from cryptography.hazmat.backends import default_backend
- 
- from ipaplatform.paths import paths
-@@ -169,7 +175,7 @@ def convertAlgorithm(value):
-     # in the list of the vault wrapping algorithms, we cannot use 3DES anywhere
-     if VAULT_WRAPPING_3DES in VAULT_WRAPPING_SUPPORTED_ALGOS:
-         supported_algs["http://www.w3.org/2001/04/xmlenc#tripledes-cbc"] = (
--            algorithms.TripleDES, modes.CBC, 64)
-+            TripleDES, modes.CBC, 64)
- 
-     return supported_algs.get(value.lower(), (None, None, None))
- 
--- 
-2.49.0
-
-
-From 3e7ec3dc49d0f559bdbe330e52019e59f0b57c18 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 3 Dec 2024 18:06:45 +0200
-Subject: [PATCH 052/116] ipa-pwd-extop: clarify OTP use over LDAP binds
-
-OTP use during LDAP bind can be enforced either explicitly via client
-specifying a control with OID 2.16.840.1.113730.3.8.10.7 and no payload
-or implicitly through the global IPA configuration with EnforceLDAPOTP.
-
-OTP token enforcement overrides IPA user authentication types
-requirements:
-
-If OTP enforcement is required:
-
- - if user authentication types still allow password authentication,
-   authentication with just a password is denied, regardless whether OTP
-   tokens are associated with the user or not.
-
-If OTP enforcement is not required:
-
- - if user has no OTP tokens but user authentication types require OTP
-   use, authentication with just a password is allowed until a token is
-   added.
-
- - if user has OTP tokens and user authentication types require OTP use
-   but not password, authentication with just a password is denied.
-
-Additionally, enforcement of OTP only applies to LDAP objects which
-don't use 'simpleSecurityObject' objectclass. This allows system service
-accounts to continue authenticate with a password regardless of the
-OTP enforcement.
-
-Fixes: https://pagure.io/freeipa/issue/9699
-Fixes: https://pagure.io/freeipa/issue/9711
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 38 +++++++++++++++----
- 1 file changed, 30 insertions(+), 8 deletions(-)
-
-diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-index 1c1340e31..42e880fd0 100644
---- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
-@@ -1219,12 +1219,10 @@ typedef enum {
- } otp_req_enum;
- static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
-                                 struct berval *creds, otp_req_enum otpreq,
--                                bool *notokens)
-+                                bool *notokens, uint32_t *auth_types)
- {
--    uint32_t auth_types;
--
-     /* Get the configured authentication types. */
--    auth_types = otp_config_auth_types(otp_config, entry);
-+    *auth_types = otp_config_auth_types(otp_config, entry);
-     *notokens = false;
- 
-     /*
-@@ -1237,7 +1235,8 @@ static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
-      * 2. If PWD is enabled or OTP succeeded, fall through to PWD validation.
-      */
- 
--    if (auth_types & OTP_CONFIG_AUTH_TYPE_OTP) {
-+    if ((*auth_types & OTP_CONFIG_AUTH_TYPE_OTP) ||
-+        (otpreq != OTP_IS_NOT_REQUIRED)) {
-         struct otp_token **tokens = NULL;
- 
-         LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
-@@ -1270,7 +1269,7 @@ static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
-         otp_token_free_array(tokens);
-     }
- 
--    return (auth_types & OTP_CONFIG_AUTH_TYPE_PASSWORD) &&
-+    return (*auth_types & OTP_CONFIG_AUTH_TYPE_PASSWORD) &&
-            (otpreq == OTP_IS_NOT_REQUIRED);
- }
- 
-@@ -1451,6 +1450,7 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
-     struct ipapwd_krbcfg *krbcfg = NULL;
-     struct berval *credentials = NULL;
-     Slapi_Entry *entry = NULL;
-+    Slapi_Value *objectclass = NULL;
-     Slapi_DN *target_sdn = NULL;
-     Slapi_DN *sdn = NULL;
-     const char *dn = NULL;
-@@ -1465,6 +1465,7 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
-     int rc = LDAP_INVALID_CREDENTIALS;
-     char *errMesg = NULL;
-     bool notokens = false;
-+    uint32_t auth_types = 0;
- 
-     /* get BIND parameters */
-     ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &target_sdn);
-@@ -1538,12 +1539,33 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
-             otpreq = OTP_IS_REQUIRED_IMPLICITLY;
-         }
-     }
-+    /* we only apply OTP policy to Kerberos principals */
-+    objectclass = slapi_value_new_string("krbprincipalaux");
-+    if (objectclass == NULL) {
-+        goto invalid_creds;
-+    }
-+    if (!slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS,
-+                                           objectclass)) {
-+      otpreq = OTP_IS_NOT_REQUIRED;
-+    }
-+    slapi_value_free(&objectclass);
-+
-     if (!syncreq && !ipapwd_pre_bind_otp(dn, entry,
--                                         credentials, otpreq, &notokens)) {
-+                                         credentials, otpreq,
-+                                         &notokens, &auth_types)) {
-         /* We got here because ipapwd_pre_bind_otp() returned false,
-          * it means that either token verification failed or
-          * a rule for empty tokens failed current policy. */
--        if (!(notokens || (otpreq == OTP_IS_NOT_REQUIRED)))
-+
-+	/* Check if there were any tokens associated, thus
-+         * OTP token verification has really failed */
-+	if (notokens == false)
-+            goto invalid_creds;
-+
-+	/* No tokens, check if auth type does not include OTP but OTP is
-+         * enforced by the current policy */
-+        if (!(auth_types & OTP_CONFIG_AUTH_TYPE_OTP) &&
-+            (otpreq != OTP_IS_NOT_REQUIRED))
-             goto invalid_creds;
-     }
- 
--- 
-2.49.0
-
-
-From 477dbba18bf987bf4461fdfdfba0d497159db7ce Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Wed, 4 Dec 2024 19:56:51 +0300
-Subject: [PATCH 053/116] adtrust: add missing ipaAllowedOperations objectclass
-
-Per @abbra explanation:
-> When expected Kerberos principal names for this object were flipped to
-  follow requirements for cross-realm krbtgt objects expected by Active
-  Directory, trusted object changed its canonical Kerberos principal name.
-  The keytab for this Kerberos principal name is fetched by SSSD and it
-  needs to be permitted to read the key. We added the virtual permission
-  to allow the keytab retrieval but didn't add the objectclass that
-  actually allows adding an LDAP attribute to express the permission. When
-  an attribute is added to an LDAP object, objectclasses of the object
-  must allow presence of that attribute.
-
-This is the followup to #9471 and fixes the upgrade.
-
-Thanks @abbra!
-
-Related: https://pagure.io/freeipa/issue/9471
-Fixes: https://pagure.io/freeipa/issue/9712
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/install/plugins/adtrust.py | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py
-index e6d49cb25..ab3d427ef 100644
---- a/ipaserver/install/plugins/adtrust.py
-+++ b/ipaserver/install/plugins/adtrust.py
-@@ -705,7 +705,8 @@ class update_tdo_to_new_layout(Updater):
-                 self.set_krb_principal([tgt_principal, nbt_principal],
-                                        passwd_incoming,
-                                        t_dn,
--                                       flags=self.KRB_PRINC_CREATE_DEFAULT)
-+                                       flags=self.KRB_PRINC_CREATE_DEFAULT
-+                                       | self.KRB_PRINC_CREATE_AGENT_PERMISSION)
- 
-             # 3. INBOUND: krbtgt/<OUR REALM>@<REMOTE REALM> must exist
-             trust_principal = self.tgt_principal_template.format(
--- 
-2.49.0
-
-
-From 6046aaefcbbaa69f1a28c28cb59943ca7a4f53cd Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 27 Nov 2024 11:52:00 +0100
-Subject: [PATCH 054/116] Fix copr build
-
-With fedora41 and rpm 4.20, the copr build does not work anymore.
-
-- rpm 4.20 introduces a change in %autopatch.
-The command now issues a warning if there is no patch to apply.
-This makes "dnf builddep" crash. The warning can be silenced
-with the "-q" option.
-
-- dnf builddep produces non-utf8 characters (for instance
-when installing logrotate, the output of the scriptlet is
-printed in color). rpm-coprbuild fails to decode the output
-and exits on error.
-To avoid this failure, redirect the output to a file.
-
-- dnf5 does not recognize the options --skip-broken and --spec.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- .copr/Makefile  | 5 +++--
- freeipa.spec.in | 9 ++++++++-
- 2 files changed, 11 insertions(+), 3 deletions(-)
-
-diff --git a/.copr/Makefile b/.copr/Makefile
-index a5a5f09c8..40a1792f7 100644
---- a/.copr/Makefile
-+++ b/.copr/Makefile
-@@ -1,9 +1,10 @@
- srpm:
- 	# Setup development environment
- 	echo "Installing base development environment"
--	dnf install -y dnf-plugins-core
-+	dnf install -y dnf5-plugins
- 	echo "Installing FreeIPA development dependencies"
--	dnf builddep -y --skip-broken --spec freeipa.spec.in --best --allowerasing --setopt=install_weak_deps=False
-+	cp freeipa.spec.in freeipa.spec
-+	dnf builddep -y --best --allowerasing --setopt=install_weak_deps=False freeipa.spec > ${outdir}/builddep.log 2>&1
- 
- 	# Run autoconf
- 	autoreconf -i
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 9b3d916f3..4b91aa96f 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1040,7 +1040,14 @@ for i in *.po ; do
- done
- popd
- 
--%autopatch -p1
-+%if 0%{?fedora}>=41
-+    %global autopatch_options -q -p1
-+%else
-+    %global autopatch_options -p1
-+%endif
-+%autopatch %{autopatch_options}
-+
-+
- 
- %build
- # PATH is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1005235
--- 
-2.49.0
-
-
-From 6a2310eda39b1341258211c7630ef4baf4555df5 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Mon, 9 Dec 2024 23:03:56 +0530
-Subject: [PATCH 055/116] Fix the typo in ipa_migrate_constants.
-
-ipa-migrate.log displays Privileges migrated as Privledges
-due to typo in labelling i.e 'label': 'Privledges'
-Hence changed that to 'label': 'Privileges'
-
----- LOG FILE ----
-INFO  - Privledges: 3
-------------------
-
-Fixes: https://pagure.io/freeipa/issue/9715
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/ipa_migrate_constants.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index c140414ea..e8192fb1a 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -886,7 +886,7 @@ DB_OBJECTS = {
-     'pbac_priv': {
-         'oc': ['groupofnames'],
-         'subtree': ',cn=privileges,cn=pbac,$SUFFIX',
--        'label': 'Privledges',
-+        'label': 'Privileges',
-         'mode': 'all',
-         'count': 0,
-     },
--- 
-2.49.0
-
-
-From c294b7f6dbcae9c01d5366b19b3b070ffb730fa6 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Wed, 11 Dec 2024 15:12:48 +0530
-Subject: [PATCH 056/116] ipatests: Fixes for ipa-ipa-migration tool
-
-The test test_ipa_migrate_with_invalid_host has been
-failing in downstream run due to mismatch in the expected test output,
-hence the assert statement has been modified.
-
-Related: https://pagure.io/freeipa/issue/3656
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipa_ipa_migration.py | 4 +---
- 1 file changed, 1 insertion(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index 0c637a014..95c29234f 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -437,10 +437,8 @@ class TestIPAMigrateCLIOptions(MigrationTest):
-         """
-         hostname = "server.invalid.host"
-         ERR_MSG = (
--            "IPA to IPA migration starting ...\n"
-             "Failed to bind to remote server: cannot connect to "
--            "'ldap://"
--            "{}': \n".format(hostname)
-+            "'ldap://{}':".format(hostname)
-         )
-         result = run_migrate(
-             self.replicas[0],
--- 
-2.49.0
-
-
-From ff5bb485d709ea02422cbafba51d23db384822af Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 20 Dec 2024 15:45:01 +0100
-Subject: [PATCH 057/116] Installation test: KRA on replica after cert renewal
-
-Add a new test installing the KRA on a replica after the
-KRA certs have been renewed on the master.
-
-Related: https://pagure.io/freeipa/issue/9692
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_installation.py | 10 +++++++++-
- 1 file changed, 9 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index c5565c452..1fae47267 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -1322,7 +1322,7 @@ class TestInstallMaster(IntegrationTest):
- 
- class TestInstallMasterKRA(IntegrationTest):
- 
--    num_replicas = 0
-+    num_replicas = 1
- 
-     @classmethod
-     def install(cls, mh):
-@@ -1379,6 +1379,14 @@ class TestInstallMasterKRA(IntegrationTest):
-             )
-             assert starting_serial != int(cert.serial_number)
- 
-+    def test_install_replica_after_kracert_renewal(self):
-+        """
-+        Test replica installation with CA after the KRA certs renewal
-+        """
-+        tasks.install_replica(self.master, self.replicas[0],
-+                              setup_ca=True)
-+        tasks.install_kra(self.replicas[0])
-+
- 
- class TestInstallMasterDNS(IntegrationTest):
- 
--- 
-2.49.0
-
-
-From a707083b0987e6ffabb817fcc5e5138b4c755459 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 20 Dec 2024 17:01:56 +0100
-Subject: [PATCH 058/116] KRA cert renewal: update
- ca.connector.KRA.transportCert
-
-After the KRA transport cert has been renewed, the value
-of ca.connector.KRA.transportCert must also be updated in
-/etc/pki/pki-tomcat/ca/CS.cfg.
-Otherwise replica installation with KRA fails.
-
-Fixes: https://pagure.io/freeipa/issue/9692
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/cainstance.py | 13 ++++++++-----
- 1 file changed, 8 insertions(+), 5 deletions(-)
-
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index 5c2c9f8b9..e03a8c863 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -1225,11 +1225,14 @@ class CAInstance(DogtagInstance):
-         """
- 
-         # The cert directive to update per nickname
--        directives = {'auditSigningCert cert-pki-ca': 'ca.audit_signing.cert',
--                      'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert',
--                      'caSigningCert cert-pki-ca': 'ca.signing.cert',
--                      'subsystemCert cert-pki-ca': 'ca.subsystem.cert',
--                      'Server-Cert cert-pki-ca': 'ca.sslserver.cert'}
-+        directives = {
-+            'auditSigningCert cert-pki-ca': 'ca.audit_signing.cert',
-+            'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert',
-+            'caSigningCert cert-pki-ca': 'ca.signing.cert',
-+            'subsystemCert cert-pki-ca': 'ca.subsystem.cert',
-+            'Server-Cert cert-pki-ca': 'ca.sslserver.cert',
-+            'transportCert cert-pki-kra': 'ca.connector.KRA.transportCert'
-+        }
- 
-         try:
-             self.backup_config()
--- 
-2.49.0
-
-
-From 83d47a2cd88b7ef84509117b7b8ff28cfdb7376e Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 3 Dec 2024 17:40:11 +0100
-Subject: [PATCH 059/116] Dogtag instance: add method to create temp password
- file
-
-The temp password file contains the password for the internal
-token as well as the password for HSM (if any).
-
-Related: https://pagure.io/freeipa/issue/9705
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/dogtaginstance.py        | 27 ++++++++
- ipaserver/install/server/replicainstall.py | 78 +++++++++-------------
- 2 files changed, 59 insertions(+), 46 deletions(-)
-
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index 58421a1d8..32a52dbed 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -129,6 +129,33 @@ class DogtagInstance(service.Service):
-         """Look up token name for nickname."""
-         return self.token_names.get(nickname, self.token_name)
- 
-+    def get_token_pwd_file(self, dir):
-+        """
-+        Create a temp password file in the provided directory.
-+
-+        The file contains the password for the internal token and
-+        for the HSM.
-+        The directory dir must already exist, and the caller must
-+        delete the file after use.
-+        """
-+        if tasks.is_fips_enabled():
-+            dbname = 'NSS FIPS 140-2 Certificate DB'
-+        else:
-+            dbname = 'NSS Certificate DB'
-+        pwd_file = os.path.join(dir, "pwd_file")
-+        with open(pwd_file, "w") as pwd:
-+            with open(paths.PKI_TOMCAT_PASSWORD_CONF, 'r') as fd:
-+                for line in fd:
-+                    (token, pin) = line.split('=', 1)
-+                    if token.startswith('hardware-'):
-+                        token = token.replace('hardware-', '')
-+                        pwd.write(f'{token}:{pin}')
-+                    elif token == INTERNAL_TOKEN:
-+                        pwd.write(f'{dbname}:{pin}')
-+            pwd.flush()
-+            os.fchmod(pwd.fileno(), 0o600)
-+        return pwd_file
-+
-     groups_aci = (
-         b'(targetfilter="(objectClass=groupOfUniqueNames)")'
-         b'(targetattr="cn || description || objectclass || uniquemember")'
-diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
-index 7667f4493..eeaaacb65 100644
---- a/ipaserver/install/server/replicainstall.py
-+++ b/ipaserver/install/server/replicainstall.py
-@@ -38,7 +38,6 @@ from ipalib.config import Env
- from ipalib.facts import is_ipa_configured, is_ipa_client_configured
- from ipalib.util import no_matching_interface_for_ip_address_warning
- from ipaclient.install.client import configure_krb5_conf, purge_host_keytab
--from ipaserver.install.dogtaginstance import INTERNAL_TOKEN
- from ipaserver.install import (
-     adtrust, bindinstance, ca, cainstance, dns, dsinstance, httpinstance,
-     installutils, kra, krainstance, krbinstance, otpdinstance,
-@@ -633,9 +632,11 @@ def enroll_dl0_replica(installer, fstore, remote_api, debug=False):
- 
-     try:
-         installer._enrollment_performed = True
-+        # pylint: disable=E0606
-         host_result = remote_api.Command.host_add(
-             unicode(config.host_name), force=installer.no_host_dns
-         )['result']
-+        # pylint: enable=E0606
- 
-         host_princ = unicode(host_result['krbcanonicalname'][0])
-         purge_host_keytab(config.realm_name)
-@@ -774,11 +775,6 @@ def clean_up_hsm_nicknames(api):
-     # Hardcode the token names. NSS tooling does not provide a
-     # public way to determine it other than scraping modutil
-     # output.
--    if tasks.is_fips_enabled():
--        dbname = 'NSS FIPS 140-2 Certificate DB'
--    else:
--        dbname = 'NSS Certificate DB'
--
-     api.Backend.ldap2.connect()
-     (token_name, _unused) = ca.lookup_hsm_configuration(api)
-     api.Backend.ldap2.disconnect()
-@@ -794,46 +790,36 @@ def clean_up_hsm_nicknames(api):
- 
-     try:
-         tmpdir = tempfile.mkdtemp(prefix="tmp-")
--        pwd_file = os.path.join(tmpdir, "pwd_file")
--        with open(pwd_file, "w") as pwd:
--            with open(paths.PKI_TOMCAT_PASSWORD_CONF, 'r') as fd:
--                for line in fd:
--                    (token, pin) = line.split('=', 1)
--                    if token.startswith('hardware-'):
--                        token = token.replace('hardware-', '')
--                        pwd.write(f'{token}:{pin}')
--                    elif token == INTERNAL_TOKEN:
--                        pwd.write(f'{dbname}:{pin}')
--            pwd.flush()
--            db = certs.CertDB(api.env.realm,
--                              nssdir=paths.PKI_TOMCAT_ALIAS_DIR,
--                              pwd_file=pwd_file)
--            for (nickname, _unused) in dogtag_reqs:
--                try:
--                    if nickname in (
--                        'caSigningCert cert-pki-ca',
--                        'Server-Cert cert-pki-ca'
--                    ):
--                        continue
--                    if nickname in (
--                        'auditSigningCert cert-pki-ca',
--                        'auditSigningCert cert-pki-kra',
--                    ):
--                        trust = ',,P'
--                    else:
--                        trust = ',,'
--                    db.run_certutil(['-M',
--                                     '-n', f"{token_name}:{nickname}",
--                                     '-t', trust])
--                except CalledProcessError as e:
--                    logger.debug("Modifying trust on %s failed: %s",
--                                 nickname, e)
--
--            if db.has_nickname('Directory Server CA certificate'):
--                db.run_certutil(['--rename',
--                                 '-n', 'Directory Server CA certificate',
--                                 '--new-n', 'caSigningCert cert-pki-ca'],
--                                raiseonerr=False)
-+        pwd_file = cai.get_token_pwd_file(tmpdir)
-+        db = certs.CertDB(api.env.realm,
-+                          nssdir=paths.PKI_TOMCAT_ALIAS_DIR,
-+                          pwd_file=pwd_file)
-+        for (nickname, _unused) in dogtag_reqs:
-+            try:
-+                if nickname in (
-+                    'caSigningCert cert-pki-ca',
-+                    'Server-Cert cert-pki-ca'
-+                ):
-+                    continue
-+                if nickname in (
-+                    'auditSigningCert cert-pki-ca',
-+                    'auditSigningCert cert-pki-kra',
-+                ):
-+                    trust = ',,P'
-+                else:
-+                    trust = ',,'
-+                db.run_certutil(['-M',
-+                                 '-n', f"{token_name}:{nickname}",
-+                                 '-t', trust])
-+            except CalledProcessError as e:
-+                logger.debug("Modifying trust on %s failed: %s",
-+                             nickname, e)
-+
-+        if db.has_nickname('Directory Server CA certificate'):
-+            db.run_certutil(['--rename',
-+                             '-n', 'Directory Server CA certificate',
-+                             '--new-n', 'caSigningCert cert-pki-ca'],
-+                            raiseonerr=False)
-     finally:
-         shutil.rmtree(tmpdir)
- 
--- 
-2.49.0
-
-
-From d556ac4611998ead16be4b93ba081b9f48ddb5ee Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Dec 2024 13:19:18 +0100
-Subject: [PATCH 060/116] Cert renewal: update the trust flags for audit cert
-
-When certmonger renews the auditSigningCert for the CA
-or the KRA, it has to add the P trust flag to the NSS
-database in /etc/pki/pki-tomcat/alias.
-
-If IPA is installed in FIPS mode with an HSM, the
-certutil commands must be provided a password file
-containing both the internal token password and the
-HSM password, and the cert name must be prefixed
-with the tokenname (otherwise certutil -M succeeds
-but does not update the trust flags).
-
-Fixes: https://pagure.io/freeipa/issue/9705
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/restart_scripts/renew_ca_cert.in | 33 +++++++++++++++++-------
- 1 file changed, 23 insertions(+), 10 deletions(-)
-
-diff --git a/install/restart_scripts/renew_ca_cert.in b/install/restart_scripts/renew_ca_cert.in
-index cd1489095..cbb2c89a8 100644
---- a/install/restart_scripts/renew_ca_cert.in
-+++ b/install/restart_scripts/renew_ca_cert.in
-@@ -33,6 +33,7 @@ from ipalib import api, errors
- from ipalib import x509
- from ipalib.install.kinit import kinit_keytab
- from ipaserver.install import certs, cainstance
-+from ipaserver.install.dogtaginstance import INTERNAL_TOKEN
- from ipaserver.plugins.ldap2 import ldap2
- from ipaplatform import services
- from ipaplatform.paths import paths
-@@ -68,15 +69,24 @@ def _main():
-             syslog.syslog(
-                 syslog.LOG_NOTICE, "Stopped %s" % dogtag_service.service_name)
- 
--    # Fetch the new certificate
--    db = certs.CertDB(api.env.realm, nssdir=paths.PKI_TOMCAT_ALIAS_DIR)
--    cert = db.get_cert_from_db(nickname)
--    if not cert:
--        syslog.syslog(syslog.LOG_ERR, 'No certificate %s found.' % nickname)
--        sys.exit(1)
--
-     tmpdir = tempfile.mkdtemp(prefix="tmp-")
-     try:
-+        # If we are using HSM, create a pwd file with the password for
-+        # the internal token + HSM
-+        token_name = ca.get_token_name(nickname)
-+        if token_name != INTERNAL_TOKEN:
-+            pwd_file = ca.get_token_pwd_file(tmpdir)
-+        else:
-+            # Use the default pwd file
-+            pwd_file = None
-+        # Fetch the new certificate
-+        db = certs.CertDB(api.env.realm, nssdir=paths.PKI_TOMCAT_ALIAS_DIR,
-+                          pwd_file=pwd_file)
-+        cert = db.get_cert_from_db(nickname)
-+        if not cert:
-+            syslog.syslog(syslog.LOG_ERR, 'No certificate %s found.' % nickname)
-+            sys.exit(1)
-+
-         principal = str('host/%s@%s' % (api.env.host, api.env.realm))
-         ccache_filename = os.path.join(tmpdir, 'ccache')
-         kinit_keytab(principal, paths.KRB5_KEYTAB, ccache_filename)
-@@ -95,9 +105,12 @@ def _main():
-         ):
-             # Fix trust on the audit cert
-             try:
--                db.run_certutil(['-M',
--                                 '-n', nickname,
--                                 '-t', 'u,u,Pu'])
-+                cmd_args = ['-M', '-t', 'u,u,Pu']
-+                if token_name != INTERNAL_TOKEN:
-+                    cmd_args.extend(['-n', token_name + ":" + nickname])
-+                else:
-+                    cmd_args.extend(['-n', token_name])
-+                db.run_certutil(cmd_args)
-                 syslog.syslog(
-                     syslog.LOG_NOTICE,
-                     "Updated trust on certificate %s in %s" %
--- 
-2.49.0
-
-
-From 8ecf75b29429aa6f9e0fc0abfb1d74068b5d4f48 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 6 Jan 2025 14:37:42 +0100
-Subject: [PATCH 061/116] ipatests: certbot removed the
- --manual-public-ip-logging-ok parameter
-
-The certbot CLI has deprecated the parameter --manual-public-ip-logging-ok
-and finally removed it from certbot 3.0.
-The test test_acme.py is using this parameter and fails in rawhide.
-
-Do not use this parameter any more.
-
-Fixes: https://pagure.io/freeipa/issue/9724
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_acme.py | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py
-index 4032d266a..b0d79182c 100644
---- a/ipatests/test_integration/test_acme.py
-+++ b/ipatests/test_integration/test_acme.py
-@@ -311,7 +311,6 @@ class TestACME(CALessBase):
-             '--domain', self.clients[0].hostname,
-             '--preferred-challenges', 'dns',
-             '--manual',
--            '--manual-public-ip-logging-ok',
-             '--manual-auth-hook', CERTBOT_DNS_IPA_SCRIPT,
-             '--manual-cleanup-hook', CERTBOT_DNS_IPA_SCRIPT,
-             '--key-type', 'rsa',
--- 
-2.49.0
-
-
-From 2506d5de5a9dd8ebe6efc777c2eb76461f5b57e2 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 6 Jan 2025 10:12:15 -0500
-Subject: [PATCH 062/116] Add 30-second timeout for certmonger request/start
- tracking
-
-certmonger needs to validate that the PIN/password and/or token
-are valid and available. In the case of a very slow HSM this can
-take longer than the 5-second default timeout.
-
-We saw an HSM that took 18 seconds to start tracking the CA signing
-certificate so default to 30 to be safe.
-
-Fixes: https://pagure.io/freeipa/issue/9725
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
----
- ipalib/install/certmonger.py | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
-index 7b2229515..efc1ba4f4 100644
---- a/ipalib/install/certmonger.py
-+++ b/ipalib/install/certmonger.py
-@@ -477,7 +477,7 @@ def request_cert(
-         request_parameters['cert-perms'] = perms[0]
-         request_parameters['key-perms'] = perms[1]
- 
--    result = cm.obj_if.add_request(request_parameters)
-+    result = cm.obj_if.add_request(request_parameters, timeout=30)
-     try:
-         if result[0]:
-             request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
-@@ -581,7 +581,7 @@ def start_tracking(
-     if nss_user:
-         params['nss-user'] = nss_user
- 
--    result = cm.obj_if.add_request(params)
-+    result = cm.obj_if.add_request(params, timeout=30)
-     try:
-         if result[0]:
-             request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
--- 
-2.49.0
-
-
-From d26ce5cccc211f83b3cce3fc5e548b5cb955bb81 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 13 Dec 2024 13:42:36 +0200
-Subject: [PATCH 064/116] Unify use of option parsers
-
-Do not use direct optparse references, instead import IPAOptionParser
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- install/tools/ipa-adtrust-install.in        |  4 +---
- install/tools/ipa-managed-entries.in        |  3 ++-
- ipaclient/install/ipa_client_automount.py   |  4 ++--
- ipaclient/install/ipa_client_samba.py       |  4 ++--
- ipalib/cli.py                               | 21 ++++++++++++---------
- ipalib/plugable.py                          |  8 ++++----
- ipapython/admintool.py                      |  3 +--
- ipapython/config.py                         | 18 +++++++++++-------
- ipapython/install/cli.py                    |  9 ++++-----
- ipaserver/install/ipa_acme_manage.py        |  6 ++----
- ipaserver/install/ipa_backup.py             |  5 ++---
- ipaserver/install/ipa_cacert_manage.py      |  9 ++++-----
- ipaserver/install/ipa_kra_install.py        |  5 ++---
- ipaserver/install/ipa_restore.py            |  5 ++---
- ipaserver/install/ipa_server_certinstall.py |  7 +++----
- ipatests/i18n.py                            |  8 ++++----
- makeapi.in                                  |  5 ++---
- 17 files changed, 60 insertions(+), 64 deletions(-)
-
-diff --git a/install/tools/ipa-adtrust-install.in b/install/tools/ipa-adtrust-install.in
-index cb2b78e50..e7b0e3692 100644
---- a/install/tools/ipa-adtrust-install.in
-+++ b/install/tools/ipa-adtrust-install.in
-@@ -29,8 +29,6 @@ import sys
- 
- import six
- 
--from optparse import SUPPRESS_HELP  # pylint: disable=deprecated-module
--
- from ipalib.install import sysrestore
- from ipaserver.install import adtrust, service
- from ipaserver.install.installutils import (
-@@ -41,7 +39,7 @@ from ipapython.admintool import ScriptError
- from ipapython import version
- from ipapython import ipautil
- from ipalib import api, errors, krb_utils
--from ipapython.config import IPAOptionParser
-+from ipapython.config import IPAOptionParser, SUPPRESS_HELP
- from ipaplatform.paths import paths
- from ipapython.ipa_log_manager import standard_logging_setup
- 
-diff --git a/install/tools/ipa-managed-entries.in b/install/tools/ipa-managed-entries.in
-index e9be41b7a..e3f121943 100644
---- a/install/tools/ipa-managed-entries.in
-+++ b/install/tools/ipa-managed-entries.in
-@@ -39,7 +39,8 @@ logger = logging.getLogger(os.path.basename(__file__))
- def parse_options():
-     usage = "%prog [options] <status|enable|disable>\n"
-     usage += "%prog [options]\n"
--    parser = OptionParser(usage=usage, formatter=config.IPAFormatter())
-+    parser = config.IPAOptionParser(usage=usage,
-+                                    formatter=config.IPAFormatter())
- 
-     parser.add_option("-d", "--debug", action="store_true", dest="debug",
-                       help="Display debugging information about the update(s)")
-diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py
-index 4439932bd..9f49ff9ed 100644
---- a/ipaclient/install/ipa_client_automount.py
-+++ b/ipaclient/install/ipa_client_automount.py
-@@ -34,7 +34,6 @@ import SSSDConfig
- 
- from six.moves.urllib.parse import urlsplit
- 
--from optparse import OptionParser  # pylint: disable=deprecated-module
- from ipapython import ipachangeconf
- from ipaclient import discovery
- from ipaclient.install.client import (
-@@ -52,6 +51,7 @@ from ipaplatform.tasks import tasks
- from ipaplatform import services
- from ipaplatform.paths import paths
- from ipapython.admintool import ScriptError
-+from ipapython.config import IPAOptionParser
- 
- 
- logger = logging.getLogger(os.path.basename(__file__))
-@@ -59,7 +59,7 @@ logger = logging.getLogger(os.path.basename(__file__))
- 
- def parse_options():
-     usage = "%prog [options]\n"
--    parser = OptionParser(usage=usage)
-+    parser = IPAOptionParser(usage=usage)
-     parser.add_option("--server", dest="server", help="FQDN of IPA server")
-     parser.add_option(
-         "--location",
-diff --git a/ipaclient/install/ipa_client_samba.py b/ipaclient/install/ipa_client_samba.py
-index 81d670c34..5c33abb4c 100755
---- a/ipaclient/install/ipa_client_samba.py
-+++ b/ipaclient/install/ipa_client_samba.py
-@@ -9,7 +9,6 @@ import logging
- import os
- import gssapi
- from urllib.parse import urlsplit
--from optparse import OptionParser  # pylint: disable=deprecated-module
- from contextlib import contextmanager
- 
- from ipaclient import discovery
-@@ -31,6 +30,7 @@ from ipaplatform.constants import constants
- from ipaplatform import services
- from ipapython.admintool import ScriptError
- from samba import generate_random_password
-+from ipapython.config import IPAOptionParser
- 
- logger = logging.getLogger(os.path.basename(__file__))
- logger.setLevel(logging.DEBUG)
-@@ -68,7 +68,7 @@ def use_api_as_principal(principal, keytab):
- 
- def parse_options():
-     usage = "%prog [options]\n"
--    parser = OptionParser(usage=usage)
-+    parser = IPAOptionParser(usage=usage)
-     parser.add_option(
-         "--server",
-         dest="server",
-diff --git a/ipalib/cli.py b/ipalib/cli.py
-index d9c2ac165..667b213fd 100644
---- a/ipalib/cli.py
-+++ b/ipalib/cli.py
-@@ -30,7 +30,6 @@ import textwrap
- import sys
- import getpass
- import code
--import optparse  # pylint: disable=deprecated-module
- import os
- import pprint
- import fcntl
-@@ -71,6 +70,8 @@ from ipalib.text import _
- from ipalib import api
- from ipapython.dnsutil import DNSName
- from ipapython.admintool import ScriptError
-+from ipapython.config import (IPAOptionParser, IPAFormatter,
-+                              OptionGroup, make_option)
- 
- import datetime
- 
-@@ -1121,7 +1122,8 @@ class Collector:
-     def __todict__(self):
-         return dict(self.__options)
- 
--class CLIOptionParserFormatter(optparse.IndentedHelpFormatter):
-+
-+class CLIOptionParserFormatter(IPAFormatter):
-     def format_argument(self, name, help_string):
-         result = []
-         opt_width = self.help_position - self.current_indent - 2
-@@ -1141,7 +1143,8 @@ class CLIOptionParserFormatter(optparse.IndentedHelpFormatter):
-             result.append("\n")
-         return "".join(result)
- 
--class CLIOptionParser(optparse.OptionParser):
-+
-+class CLIOptionParser(IPAOptionParser):
-     """
-     This OptionParser subclass adds an ability to print positional
-     arguments in CLI help. Custom formatter is used to format the argument
-@@ -1151,13 +1154,13 @@ class CLIOptionParser(optparse.OptionParser):
-         self._arguments = []
-         if 'formatter' not in kwargs:
-             kwargs['formatter'] = CLIOptionParserFormatter()
--        optparse.OptionParser.__init__(self, *args, **kwargs)
-+        IPAOptionParser.__init__(self, *args, **kwargs)
- 
-     def format_option_help(self, formatter=None):
-         """
-         Prepend argument help to standard OptionParser's option help
-         """
--        option_help = optparse.OptionParser.format_option_help(self, formatter)
-+        option_help = IPAOptionParser.format_option_help(self, formatter)
- 
-         if isinstance(formatter, CLIOptionParserFormatter):
-             heading = unicode(_("Positional arguments"))
-@@ -1272,7 +1275,7 @@ class cli(backend.Executioner):
-             """Get or create an option group for the given name"""
-             option_group = option_groups.get(group_name)
-             if option_group is None:
--                option_group = optparse.OptionGroup(parser, group_name)
-+                option_group = OptionGroup(parser, group_name)
-                 parser.add_option_group(option_group)
-                 option_groups[group_name] = option_group
-             return option_group
-@@ -1298,7 +1301,7 @@ class cli(backend.Executioner):
-             option_names = ['--%s' % cli_name]
-             if option.cli_short_name:
-                 option_names.append('-%s' % option.cli_short_name)
--            opt = optparse.make_option(*option_names, **kw)
-+            opt = make_option(*option_names, **kw)
-             if option.option_group is None:
-                 parser.add_option(opt)
-             else:
-@@ -1312,7 +1315,7 @@ class cli(backend.Executioner):
-                 group = _get_option_group(unicode(_('Deprecated options')))
-                 for alias in option.deprecated_cli_aliases:
-                     name = '--%s' % alias
--                    group.add_option(optparse.make_option(name, **new_kw))
-+                    group.add_option(make_option(name, **new_kw))
- 
-         for arg in cmd.args():
-             name = self.__get_arg_name(arg, format_name=False)
-@@ -1442,7 +1445,7 @@ class cli(backend.Executioner):
-                     )
- 
- 
--class IPAHelpFormatter(optparse.IndentedHelpFormatter):
-+class IPAHelpFormatter(IPAFormatter):
-     """Formatter suitable for printing IPA command help
- 
-     The default help formatter reflows text to fit the terminal, but it
-diff --git a/ipalib/plugable.py b/ipalib/plugable.py
-index 2e2861df0..a87e6e891 100644
---- a/ipalib/plugable.py
-+++ b/ipalib/plugable.py
-@@ -33,7 +33,6 @@ import sys
- import threading
- import os
- from os import path
--import optparse  # pylint: disable=deprecated-module
- import textwrap
- import collections
- import importlib
-@@ -47,6 +46,7 @@ from ipalib.util import classproperty
- from ipalib.base import ReadOnly, lock, islocked
- from ipalib.constants import DEFAULT_CONFIG
- from ipapython import ipa_log_manager, ipautil
-+from ipapython.config import IPAOptionParser, IPAFormatter
- from ipapython.ipa_log_manager import (
-     LOGGING_FORMAT_FILE,
-     LOGGING_FORMAT_STDERR)
-@@ -526,7 +526,7 @@ class API(ReadOnly):
- 
-     def build_global_parser(self, parser=None, context=None):
-         """
--        Add global options to an optparse.OptionParser instance.
-+        Add global options to an IPAOptionParser instance.
-         """
-         def config_file_callback(option, opt, value, parser):
-             if not os.path.isfile(value):
-@@ -536,7 +536,7 @@ class API(ReadOnly):
-             parser.values.conf = value
- 
-         if parser is None:
--            parser = optparse.OptionParser(
-+            parser = IPAOptionParser(
-                 add_help_option=False,
-                 formatter=IPAHelpFormatter(),
-                 usage='%prog [global-options] COMMAND [command-options]',
-@@ -821,7 +821,7 @@ class API(ReadOnly):
-         return self.__next[plugin]
- 
- 
--class IPAHelpFormatter(optparse.IndentedHelpFormatter):
-+class IPAHelpFormatter(IPAFormatter):
-     def format_epilog(self, epilog):
-         text_width = self.width - self.current_indent
-         indent = " " * self.current_indent
-diff --git a/ipapython/admintool.py b/ipapython/admintool.py
-index fdb4400d8..dff9112eb 100644
---- a/ipapython/admintool.py
-+++ b/ipapython/admintool.py
-@@ -26,7 +26,6 @@ import logging
- import sys
- import os
- import traceback
--from optparse import OptionGroup  # pylint: disable=deprecated-module
- 
- from ipaplatform.osinfo import osinfo
- from ipapython import version
-@@ -113,7 +112,7 @@ class AdminTool:
-         :param parser: The parser to add options to
-         :param debug_option: Add a --debug option as an alias to --verbose
-         """
--        group = OptionGroup(parser, "Logging and output options")
-+        group = config.OptionGroup(parser, "Logging and output options")
-         group.add_option("-v", "--verbose", dest="verbose", default=False,
-             action="store_true", help="print debugging information")
-         if debug_option:
-diff --git a/ipapython/config.py b/ipapython/config.py
-index f53d0f998..7af4dfdeb 100644
---- a/ipapython/config.py
-+++ b/ipapython/config.py
-@@ -18,9 +18,9 @@
- #
- from __future__ import absolute_import
- 
--# pylint: disable=deprecated-module
--from optparse import (
--    Option, Values, OptionParser, IndentedHelpFormatter, OptionValueError)
-+# pylint: disable=deprecated-module, disable=unused-import
-+from optparse import (Option, Values, OptionGroup, OptionParser, SUPPRESS_HELP,
-+                      IndentedHelpFormatter, OptionValueError, make_option)
- # pylint: enable=deprecated-module
- from copy import copy
- from configparser import ConfigParser as SafeConfigParser
-@@ -113,10 +113,14 @@ class IPAOptionParser(OptionParser):
-                  description=None,
-                  formatter=None,
-                  add_help_option=True,
--                 prog=None):
--        OptionParser.__init__(self, usage, option_list, option_class,
--                              version, conflict_handler, description,
--                              formatter, add_help_option, prog)
-+                 prog=None,
-+                 epilog=None):
-+        OptionParser.__init__(self, usage=usage, option_list=option_list,
-+                              option_class=option_class, version=version,
-+                              conflict_handler=conflict_handler,
-+                              description=description, formatter=formatter,
-+                              add_help_option=add_help_option, prog=prog,
-+                              epilog=epilog)
- 
-     def get_safe_opts(self, opts):
-         """
-diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
-index ab212be4e..a048b3c7c 100644
---- a/ipapython/install/cli.py
-+++ b/ipapython/install/cli.py
-@@ -9,12 +9,11 @@ Command line support.
- import collections
- import enum
- import logging
--import optparse  # pylint: disable=deprecated-module
- import signal
- 
- import six
- 
--from ipapython import admintool
-+from ipapython import admintool, config
- from ipapython.ipa_log_manager import standard_logging_setup
- from ipapython.ipautil import (CheckedIPAddress, CheckedIPAddressLoopback,
-                                private_ccache)
-@@ -158,7 +157,7 @@ class ConfigureTool(admintool.AdminTool):
-             try:
-                 opt_group = groups[group_cls]
-             except KeyError:
--                opt_group = groups[group_cls] = optparse.OptionGroup(
-+                opt_group = groups[group_cls] = config.OptionGroup(
-                         parser, "{0} options".format(group_cls.description))
-                 parser.add_option_group(opt_group)
- 
-@@ -232,7 +231,7 @@ class ConfigureTool(admintool.AdminTool):
-                 if not hidden:
-                     help = knob_cls.description
-                 else:
--                    help = optparse.SUPPRESS_HELP
-+                    help = config.SUPPRESS_HELP
- 
-                 opt_group.add_option(
-                     *opt_strs,
-@@ -256,7 +255,7 @@ class ConfigureTool(admintool.AdminTool):
- 
-         # fake option parser to parse positional arguments
-         # (because optparse does not support positional argument parsing)
--        fake_option_parser = optparse.OptionParser()
-+        fake_option_parser = config.IPAOptionParser()
-         self.add_options(fake_option_parser, True)
- 
-         fake_option_map = {option.dest: option
-diff --git a/ipaserver/install/ipa_acme_manage.py b/ipaserver/install/ipa_acme_manage.py
-index dc2359f49..0decab394 100644
---- a/ipaserver/install/ipa_acme_manage.py
-+++ b/ipaserver/install/ipa_acme_manage.py
-@@ -7,14 +7,12 @@ import enum
- import pki.util
- import logging
- 
--from optparse import OptionGroup  # pylint: disable=deprecated-module
--
- from ipalib import api, errors, x509
- from ipalib import _
- from ipalib.facts import is_ipa_configured
- from ipaplatform.paths import paths
- from ipapython.admintool import AdminTool
--from ipapython import cookie, dogtag
-+from ipapython import cookie, dogtag, config
- from ipapython.ipautil import run
- from ipapython.certdb import NSSDatabase, EXTERNAL_CA_TRUST_FLAGS
- from ipaserver.install import cainstance
-@@ -143,7 +141,7 @@ class IPAACMEManage(AdminTool):
-     @classmethod
-     def add_options(cls, parser):
- 
--        group = OptionGroup(parser, 'Pruning')
-+        group = config.OptionGroup(parser, 'Pruning')
-         group.add_option(
-             "--enable", dest="enable", action="store_true",
-             default=False, help="Enable certificate pruning")
-diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py
-index 982e5dfc4..b6af63813 100644
---- a/ipaserver/install/ipa_backup.py
-+++ b/ipaserver/install/ipa_backup.py
-@@ -20,7 +20,6 @@
- from __future__ import absolute_import, print_function
- 
- import logging
--import optparse  # pylint: disable=deprecated-module
- import os
- import shutil
- import sys
-@@ -32,7 +31,7 @@ import six
- from ipaplatform.paths import paths
- from ipaplatform import services
- from ipalib import api, errors
--from ipapython import version
-+from ipapython import version, config
- from ipapython.ipautil import run, write_tmp_file
- from ipapython import admintool, certdb
- from ipapython.dn import DN
-@@ -245,7 +244,7 @@ class Backup(admintool.AdminTool):
- 
-         parser.add_option(
-             "--gpg-keyring", dest="gpg_keyring",
--            help=optparse.SUPPRESS_HELP)
-+            help=config.SUPPRESS_HELP)
-         parser.add_option(
-             "--gpg", dest="gpg", action="store_true",
-             default=False, help="Encrypt the backup")
-diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
-index f6ab736fa..048245237 100644
---- a/ipaserver/install/ipa_cacert_manage.py
-+++ b/ipaserver/install/ipa_cacert_manage.py
-@@ -22,14 +22,13 @@ from __future__ import print_function, absolute_import
- import datetime
- import logging
- import os
--from optparse import OptionGroup  # pylint: disable=deprecated-module
- import gssapi
- 
- from ipalib.constants import (
-     RENEWAL_CA_NAME, RENEWAL_REUSE_CA_NAME, RENEWAL_SELFSIGNED_CA_NAME,
-     IPA_CA_CN)
- from ipalib.install import certmonger, certstore
--from ipapython import admintool, ipautil
-+from ipapython import admintool, ipautil, config
- from ipapython.certdb import (EMPTY_TRUST_FLAGS,
-                               EXTERNAL_CA_TRUST_FLAGS,
-                               TrustFlags,
-@@ -61,7 +60,7 @@ class CACertManage(admintool.AdminTool):
-             "-p", "--password", dest='password',
-             help="Directory Manager password")
- 
--        renew_group = OptionGroup(parser, "Renew options")
-+        renew_group = config.OptionGroup(parser, "Renew options")
-         renew_group.add_option(
-             "--self-signed", dest='self_signed',
-             action='store_true',
-@@ -89,7 +88,7 @@ class CACertManage(admintool.AdminTool):
-                  "certificate chain")
-         parser.add_option_group(renew_group)
- 
--        install_group = OptionGroup(parser, "Install options")
-+        install_group = config.OptionGroup(parser, "Install options")
-         install_group.add_option(
-             "-n", "--nickname", dest='nickname',
-             help="Nickname for the certificate")
-@@ -98,7 +97,7 @@ class CACertManage(admintool.AdminTool):
-             help="Trust flags for the certificate in certutil format")
-         parser.add_option_group(install_group)
- 
--        delete_group = OptionGroup(parser, "Delete options")
-+        delete_group = config.OptionGroup(parser, "Delete options")
-         delete_group.add_option(
-             "-f", "--force", action='store_true',
-             help="Force removing the CA even if chain validation fails")
-diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py
-index 3e4cd67fa..8a09179f7 100644
---- a/ipaserver/install/ipa_kra_install.py
-+++ b/ipaserver/install/ipa_kra_install.py
-@@ -22,13 +22,12 @@ from __future__ import print_function, absolute_import
- import logging
- import sys
- import tempfile
--from optparse import SUPPRESS_HELP  # pylint: disable=deprecated-module
- 
- from textwrap import dedent
- from ipalib import api
- from ipalib.constants import DOMAIN_LEVEL_1
- from ipaplatform.paths import paths
--from ipapython import admintool
-+from ipapython import admintool, config
- from ipaserver.install import service
- from ipaserver.install import cainstance
- from ipaserver.install import custodiainstance
-@@ -73,7 +72,7 @@ class KRAInstall(admintool.AdminTool):
-         parser.add_option(
-             "--uninstall",
-             dest="uninstall", action="store_true", default=False,
--            help=SUPPRESS_HELP)
-+            help=config.SUPPRESS_HELP)
- 
-         parser.add_option(
-             "--pki-config-override", dest="pki_config_override",
-diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py
-index 57ad8dd05..8d75a0e6b 100644
---- a/ipaserver/install/ipa_restore.py
-+++ b/ipaserver/install/ipa_restore.py
-@@ -20,7 +20,6 @@
- from __future__ import absolute_import, print_function
- 
- import logging
--import optparse  # pylint: disable=deprecated-module
- import os
- import shutil
- import sys
-@@ -34,7 +33,7 @@ import six
- from ipaclient.install.client import update_ipa_nssdb
- from ipalib import api, errors
- from ipalib.constants import FQDN
--from ipapython import version, ipautil
-+from ipapython import version, ipautil, config
- from ipapython.ipautil import run, user_input
- from ipapython import admintool, certdb
- from ipapython.dn import DN
-@@ -190,7 +189,7 @@ class Restore(admintool.AdminTool):
-             help="Directory Manager password")
-         parser.add_option(
-             "--gpg-keyring", dest="gpg_keyring",
--            help=optparse.SUPPRESS_HELP)
-+            help=config.SUPPRESS_HELP)
-         parser.add_option(
-             "--data", dest="data_only", action="store_true",
-             default=False, help="Restore only the data")
-diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py
-index e29f00ec3..e9f680b1d 100644
---- a/ipaserver/install/ipa_server_certinstall.py
-+++ b/ipaserver/install/ipa_server_certinstall.py
-@@ -22,12 +22,11 @@ from __future__ import print_function, absolute_import
- import os
- import os.path
- import tempfile
--import optparse  # pylint: disable=deprecated-module
- 
- from ipalib import x509
- from ipalib.install import certmonger
- from ipaplatform.paths import paths
--from ipapython import admintool, dogtag
-+from ipapython import admintool, dogtag, config
- from ipapython.certdb import NSSDatabase, get_ca_nickname
- from ipapython.dn import DN
- from ipapython import ipaldap
-@@ -65,8 +64,8 @@ class ServerCertInstall(admintool.AdminTool):
-             help="The password of the PKCS#12 file")
-         parser.add_option(
-             "--dirsrv_pin", "--http_pin",
--            dest="pin",
--            help=optparse.SUPPRESS_HELP)
-+            dest="pin", sensitive=True,
-+            help=config.SUPPRESS_HELP)
-         parser.add_option(
-             "--cert-name",
-             dest="cert_name", metavar="NAME",
-diff --git a/ipatests/i18n.py b/ipatests/i18n.py
-index 49f5c4c32..57915c286 100644
---- a/ipatests/i18n.py
-+++ b/ipatests/i18n.py
-@@ -22,7 +22,6 @@ from __future__ import print_function
- 
- # WARNING: Do not import ipa modules, this is also used as a
- # stand-alone script (invoked from po Makefile).
--import optparse  # pylint: disable=deprecated-module
- import sys
- import gettext
- import re
-@@ -30,6 +29,7 @@ import os
- import traceback
- import polib
- from collections import namedtuple
-+from ipapython import config
- 
- import six
- 
-@@ -722,9 +722,9 @@ usage ='''
- def main():
-     global verbose, print_traceback, pedantic, show_strings
- 
--    parser = optparse.OptionParser(usage=usage)
-+    parser = config.IPAOptionParser(usage=usage)
- 
--    mode_group = optparse.OptionGroup(parser, 'Operational Mode',
-+    mode_group = config.OptionGroup(parser, 'Operational Mode',
-                                       'You must select one these modes to run in')
- 
-     mode_group.add_option('-g', '--test-gettext', action='store_const', const='test_gettext', dest='mode',
-@@ -748,7 +748,7 @@ def main():
-     parser.add_option('--traceback', action='store_true', dest='print_traceback', default=False,
-                       help='print the traceback when an exception occurs')
- 
--    param_group = optparse.OptionGroup(parser, 'Run Time Parameters',
-+    param_group = config.OptionGroup(parser, 'Run Time Parameters',
-                                        'These may be used to modify the run time defaults')
- 
-     param_group.add_option('--test-lang', action='store', dest='test_lang', default='test',
-diff --git a/makeapi.in b/makeapi.in
-index a801b9253..8fc87d23d 100644
---- a/makeapi.in
-+++ b/makeapi.in
-@@ -38,6 +38,7 @@ from ipalib.parameters import Param
- from ipalib.output import Output
- from ipalib.text import Gettext, NGettext, ConcatenatedLazyText
- from ipalib.capabilities import capabilities
-+from ipapython import config
- 
- API_FILE='API.txt'
- 
-@@ -84,9 +85,7 @@ OUTPUT_IGNORED_ATTRIBUTES = (
- )
- 
- def parse_options():
--    from optparse import OptionParser  # pylint: disable=deprecated-module
--
--    parser = OptionParser()
-+    parser = config.IPAOptionParser()
-     parser.add_option("--validate", dest="validate", action="store_true",
-         default=False, help="Validate the API vs the stored API")
- 
--- 
-2.49.0
-
-
-From f9314562aaae03619eb89ae762bc24182174ad28 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 8 Nov 2024 14:59:20 +0200
-Subject: [PATCH 065/116] ipa tools: remove sensitive material from the
- commandline
-
-When command line tools accept passwords, remove them from the command
-line so that they don't get visible in '/proc/pid/commandline'.
-
-There is no common method to access the original ARGV vector and modify
-it from Python. Since this mostly affects Linux systems where IPA
-services run, we expect use of GNU libc and thus can rely on internal
-glibc symbols. If they aren't available, the code will skip removing
-passwords.
-
-Fixes: CVE-2024-11029
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- .../com.redhat.idm.trust-fetch-domains.in     |  5 ++-
- install/tools/ipa-adtrust-install.in          |  3 +-
- install/tools/ipa-ca-install.in               |  2 +
- install/tools/ipa-compat-manage.in            |  6 ++-
- install/tools/ipa-csreplica-manage.in         | 12 +++---
- install/tools/ipa-managed-entries.in          |  5 ++-
- install/tools/ipa-replica-conncheck.in        |  7 ++--
- install/tools/ipa-replica-manage.in           | 10 +++--
- ipapython/admintool.py                        | 40 +++++++++++++++++++
- ipaserver/install/ipa_migrate.py              | 17 +++++++-
- ipaserver/install/ipa_restore.py              |  2 +-
- ipaserver/install/ipa_server_certinstall.py   |  2 +-
- 12 files changed, 90 insertions(+), 21 deletions(-)
-
-diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains.in b/install/oddjob/com.redhat.idm.trust-fetch-domains.in
-index 45c1f1463..b86be0212 100644
---- a/install/oddjob/com.redhat.idm.trust-fetch-domains.in
-+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains.in
-@@ -15,6 +15,7 @@ import six
- import gssapi
- 
- from ipalib.install.kinit import kinit_keytab, kinit_password
-+from ipapython.admintool import admin_cleanup_global_argv
- 
- if six.PY3:
-     unicode = str
-@@ -52,11 +53,13 @@ def parse_options():
-         "--password",
-         action="store",
-         dest="password",
--        help="Display debugging information",
-+        help="Password for Active Directory administrator",
-+        sensitive=True
-     )
- 
-     options, args = parser.parse_args()
-     safe_options = parser.get_safe_opts(options)
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     # We only use first argument of the passed args but as D-BUS interface
-     # in oddjobd cannot expose optional, we fill in empty slots from IPA side
-diff --git a/install/tools/ipa-adtrust-install.in b/install/tools/ipa-adtrust-install.in
-index e7b0e3692..1efccdb67 100644
---- a/install/tools/ipa-adtrust-install.in
-+++ b/install/tools/ipa-adtrust-install.in
-@@ -35,7 +35,7 @@ from ipaserver.install.installutils import (
-     read_password,
-     check_server_configuration,
-     run_script)
--from ipapython.admintool import ScriptError
-+from ipapython.admintool import ScriptError, admin_cleanup_global_argv
- from ipapython import version
- from ipapython import ipautil
- from ipalib import api, errors, krb_utils
-@@ -93,6 +93,7 @@ def parse_options():
- 
-     options, _args = parser.parse_args()
-     safe_options = parser.get_safe_opts(options)
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     return safe_options, options
- 
-diff --git a/install/tools/ipa-ca-install.in b/install/tools/ipa-ca-install.in
-index 9f3d16669..b437e761f 100644
---- a/install/tools/ipa-ca-install.in
-+++ b/install/tools/ipa-ca-install.in
-@@ -42,6 +42,7 @@ from ipalib.constants import DOMAIN_LEVEL_1
- from ipapython.config import IPAOptionParser
- from ipapython.ipa_log_manager import standard_logging_setup
- from ipaplatform.paths import paths
-+from ipapython.admintool import admin_cleanup_global_argv
- 
- logger = logging.getLogger(os.path.basename(__file__))
- 
-@@ -132,6 +133,7 @@ def parse_options():
- 
-     options, args = parser.parse_args()
-     safe_options = parser.get_safe_opts(options)
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     if args:
-         parser.error("Too many arguments provided")
-diff --git a/install/tools/ipa-compat-manage.in b/install/tools/ipa-compat-manage.in
-index 459f39fc8..9650abd6f 100644
---- a/install/tools/ipa-compat-manage.in
-+++ b/install/tools/ipa-compat-manage.in
-@@ -24,13 +24,13 @@ from __future__ import print_function
- import sys
- from ipaplatform.paths import paths
- try:
--    from optparse import OptionParser  # pylint: disable=deprecated-module
-     from ipapython import ipautil, config
-     from ipaserver.install import installutils
-     from ipaserver.install.ldapupdate import LDAPUpdate
-     from ipalib import api, errors
-     from ipapython.ipa_log_manager import standard_logging_setup
-     from ipapython.dn import DN
-+    from ipapython.admintool import admin_cleanup_global_argv
- except ImportError as e:
-     print("""\
- There was a problem importing one of the required Python modules. The
-@@ -46,7 +46,8 @@ nis_config_dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
- def parse_options():
-     usage = "%prog [options] <enable|disable|status>\n"
-     usage += "%prog [options]\n"
--    parser = OptionParser(usage=usage, formatter=config.IPAFormatter())
-+    parser = config.IPAOptionParser(usage=usage,
-+                                    formatter=config.IPAFormatter())
- 
-     parser.add_option("-d", "--debug", action="store_true", dest="debug",
-                       help="Display debugging information about the update(s)")
-@@ -55,6 +56,7 @@ def parse_options():
- 
-     config.add_standard_options(parser)
-     options, args = parser.parse_args()
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     return options, args
- 
-diff --git a/install/tools/ipa-csreplica-manage.in b/install/tools/ipa-csreplica-manage.in
-index 6f248cc50..2fab27a94 100644
---- a/install/tools/ipa-csreplica-manage.in
-+++ b/install/tools/ipa-csreplica-manage.in
-@@ -32,8 +32,8 @@ from ipaserver.install import (replication, installutils, bindinstance,
- from ipalib import api, errors
- from ipalib.constants import FQDN
- from ipalib.util import has_managed_topology, print_replication_status
--from ipapython import ipautil, ipaldap, version
--from ipapython.admintool import ScriptError
-+from ipapython import ipautil, ipaldap, version, config
-+from ipapython.admintool import admin_cleanup_global_argv, ScriptError
- from ipapython.dn import DN
- 
- logger = logging.getLogger(os.path.basename(__file__))
-@@ -54,11 +54,10 @@ commands = {
- 
- 
- def parse_options():
--    from optparse import OptionParser  # pylint: disable=deprecated-module
--
--    parser = OptionParser(version=version.VERSION)
-+    parser = config.IPAOptionParser(version=version.VERSION)
-     parser.add_option("-H", "--host", dest="host", help="starting host")
--    parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password")
-+    parser.add_option("-p", "--password", dest="dirman_passwd", sensitive=True,
-+                      help="Directory Manager password")
-     parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
-                       help="provide additional information")
-     parser.add_option("-f", "--force", dest="force", action="store_true", default=False,
-@@ -66,6 +65,7 @@ def parse_options():
-     parser.add_option("--from", dest="fromhost", help="Host to get data from")
- 
-     options, args = parser.parse_args()
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     valid_syntax = False
- 
-diff --git a/install/tools/ipa-managed-entries.in b/install/tools/ipa-managed-entries.in
-index e3f121943..ff2fd6a58 100644
---- a/install/tools/ipa-managed-entries.in
-+++ b/install/tools/ipa-managed-entries.in
-@@ -24,7 +24,6 @@ import logging
- import os
- import re
- import sys
--from optparse import OptionParser  # pylint: disable=deprecated-module
- 
- from ipaplatform.paths import paths
- from ipapython import config
-@@ -32,6 +31,7 @@ from ipaserver.install import installutils
- from ipalib import api, errors
- from ipapython.ipa_log_manager import standard_logging_setup
- from ipapython.dn import DN
-+from ipapython.admintool import admin_cleanup_global_argv
- 
- logger = logging.getLogger(os.path.basename(__file__))
- 
-@@ -51,9 +51,10 @@ def parse_options():
-                       action="store_true",
-                       help="List available Managed Entries")
-     parser.add_option("-p", "--password", dest="dirman_password",
--                      help="Directory Manager password")
-+                      sensitive=True, help="Directory Manager password")
- 
-     options, args = parser.parse_args()
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     return options, args
- 
-diff --git a/install/tools/ipa-replica-conncheck.in b/install/tools/ipa-replica-conncheck.in
-index 8eee82483..81b7d13ac 100644
---- a/install/tools/ipa-replica-conncheck.in
-+++ b/install/tools/ipa-replica-conncheck.in
-@@ -23,15 +23,15 @@ from __future__ import print_function
- import logging
- 
- from ipapython import ipachangeconf
--from ipapython.config import IPAOptionParser
-+from ipapython.config import (IPAOptionParser, OptionGroup,
-+                              OptionValueError)
-+from ipapython.admintool import admin_cleanup_global_argv
- from ipapython.dn import DN
- from ipapython import version
- from ipapython import ipautil, certdb
- from ipalib import api, errors, x509
- from ipalib.constants import FQDN
- from ipaserver.install import installutils
--# pylint: disable=deprecated-module
--from optparse import OptionGroup, OptionValueError
- # pylint: enable=deprecated-module
- from ipapython.ipa_log_manager import standard_logging_setup
- import copy
-@@ -189,6 +189,7 @@ def parse_options():
- 
-     options, _args = parser.parse_args()
-     safe_options = parser.get_safe_opts(options)
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     if options.master and options.replica:
-         parser.error("on-master and on-replica options are mutually exclusive!")
-diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in
-index d6e6ef57c..7e5b31a59 100644
---- a/install/tools/ipa-replica-manage.in
-+++ b/install/tools/ipa-replica-manage.in
-@@ -43,6 +43,7 @@ from ipalib.util import (
-     print_replication_status,
-     verify_host_resolvable,
- )
-+from ipapython.admintool import admin_cleanup_global_argv
- from ipapython.ipa_log_manager import standard_logging_setup
- from ipapython.dn import DN
- from ipapython.config import IPAOptionParser
-@@ -84,7 +85,8 @@ class NoRUVsFound(Exception):
- def parse_options():
-     parser = IPAOptionParser(version=version.VERSION)
-     parser.add_option("-H", "--host", dest="host", help="starting host")
--    parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password")
-+    parser.add_option("-p", "--password", dest="dirman_passwd", sensitive=True,
-+                      help="Directory Manager password")
-     parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
-                       help="provide additional information")
-     parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False,
-@@ -95,7 +97,7 @@ def parse_options():
-                       help="DANGER: clean up references to a ghost master")
-     parser.add_option("--binddn", dest="binddn", default=None, type="dn",
-                       help="Bind DN to use with remote server")
--    parser.add_option("--bindpw", dest="bindpw", default=None,
-+    parser.add_option("--bindpw", dest="bindpw", default=None, sensitive=True,
-                       help="Password for Bind DN to use with remote server")
-     parser.add_option("--winsync", dest="winsync", action="store_true", default=False,
-                       help="This is a Windows Sync Agreement")
-@@ -103,13 +105,15 @@ def parse_options():
-                       help="Full path and filename of CA certificate to use with TLS/SSL to the remote server")
-     parser.add_option("--win-subtree", dest="win_subtree", default=None,
-                       help="DN of Windows subtree containing the users you want to sync (default cn=Users,<domain suffix)")
--    parser.add_option("--passsync", dest="passsync", default=None,
-+    parser.add_option("--passsync", dest="passsync",
-+                      default=None, sensitive=True,
-                       help="Password for the IPA system user used by the Windows PassSync plugin to synchronize passwords")
-     parser.add_option("--from", dest="fromhost", help="Host to get data from")
-     parser.add_option("--no-lookup", dest="nolookup", action="store_true", default=False,
-                       help="do not perform DNS lookup checks")
- 
-     options, args = parser.parse_args()
-+    admin_cleanup_global_argv(parser, options, sys.argv)
- 
-     valid_syntax = False
- 
-diff --git a/ipapython/admintool.py b/ipapython/admintool.py
-index dff9112eb..602223ef6 100644
---- a/ipapython/admintool.py
-+++ b/ipapython/admintool.py
-@@ -39,6 +39,45 @@ SERVER_NOT_CONFIGURED = 2
- logger = logging.getLogger(__name__)
- 
- 
-+def admin_cleanup_global_argv(option_parser, options, argv):
-+    """Takes option parser and generated options and scrubs sensitive arguments
-+    from the global program arguments. Note that this only works for GNU GLIBC
-+    as Python has no generic way to get access to the original argv values to
-+    modify them in place.
-+
-+    The code assumes Python behavior, e.g. there are two additional args in the
-+    list (/path/to/python -I ...) than what's passed as 'argv' here.
-+    """
-+    import ctypes
-+    import ctypes.util
-+    try:
-+        _c = ctypes.CDLL(ctypes.util.find_library("c"))
-+        if _c._name is None:
-+            return
-+        _argv = ctypes.POINTER(ctypes.c_voidp).in_dll(_c, "_dl_argv")
-+        # since we run as 'python -I <executable> ...', add two args
-+        _argc = len(argv) + 2
-+        all_options = []
-+        if '_get_all_options' in dir(option_parser):
-+            # OptParse parser
-+            all_options = option_parser._get_all_options()
-+        elif '_actions' in dir(option_parser):
-+            # ArgParse parser
-+            all_options = option_parser._actions
-+
-+        for opt in all_options:
-+            if getattr(opt, 'sensitive', False):
-+                v = getattr(options, opt.dest)
-+                for i in range(0, _argc):
-+                    vi = ctypes.cast(_argv[i],
-+                                     ctypes.c_char_p
-+                                     ).value.decode('utf-8')
-+                    if vi == v:
-+                        ctypes.memset(_argv[i], ord('X'), len(v))
-+    except Exception:
-+        pass
-+
-+
- class ScriptError(Exception):
-     """An exception that records an error message and a return value
-     """
-@@ -148,6 +187,7 @@ class AdminTool:
-             cls._option_parsers[cls] = cls.option_parser
- 
-         options, args = cls.option_parser.parse_args(argv[1:])
-+        admin_cleanup_global_argv(cls.option_parser, options, argv)
- 
-         command_class = cls.get_command_class(options, args)
-         command = command_class(options, args)
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index f35629378..ece473bc8 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -28,6 +28,7 @@ from ipaplatform.paths import paths
- from ipapython.dn import DN
- from ipapython.ipaldap import LDAPClient, LDAPEntry, realm_to_ldapi_uri
- from ipapython.ipa_log_manager import standard_logging_setup
-+from ipapython.admintool import admin_cleanup_global_argv
- from ipaserver.install.ipa_migrate_constants import (
-     DS_CONFIG, DB_OBJECTS, DS_INDEXES, BIND_DN, LOG_FILE_NAME,
-     STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS,
-@@ -284,6 +285,18 @@ class LDIFParser(ldif.LDIFParser):
-             self.mc.process_db_entry(entry_dn=dn, entry_attrs=entry_attrs)
- 
- 
-+class SensitiveStoreAction(argparse._StoreAction):
-+    def __init__(self, *, sensitive, **options):
-+        super(SensitiveStoreAction, self).__init__(**options)
-+        self.sensitive = sensitive
-+
-+    def _get_kwargs(self):
-+        names = super(SensitiveStoreAction, self)._get_kwargs()
-+        sensitive_name = 'sensitive'
-+        names.extend((sensitive_name, getattr(self, sensitive_name)))
-+        return names
-+
-+
- #
- # Migrate IPA to IPA Class
- #
-@@ -344,7 +357,8 @@ class IPAMigrate():
-                             help='Password for the Bind DN.  If a password '
-                                  'is not provided then the user will be '
-                                  'prompted to enter it',
--                            default=None)
-+                            default=None, sensitive=True,
-+                            action=SensitiveStoreAction)
-         parser.add_argument('-j', '--bind-pw-file',
-                             help='A text file containing the clear text '
-                                  'password for the Bind DN', default=None)
-@@ -2128,6 +2142,7 @@ class IPAMigrate():
-         parser = argparse.ArgumentParser(description=desc, allow_abbrev=True)
-         self.add_options(parser)
-         self.validate_options()
-+        admin_cleanup_global_argv(parser, self.args, sys.argv)
- 
-         # Check for dryrun mode
-         if self.args.dryrun or self.args.dryrun_record is not None:
-diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py
-index 8d75a0e6b..539501ab4 100644
---- a/ipaserver/install/ipa_restore.py
-+++ b/ipaserver/install/ipa_restore.py
-@@ -185,7 +185,7 @@ class Restore(admintool.AdminTool):
-         super(Restore, cls).add_options(parser, debug_option=True)
- 
-         parser.add_option(
--            "-p", "--password", dest="password",
-+            "-p", "--password", dest="password", sensitive=True,
-             help="Directory Manager password")
-         parser.add_option(
-             "--gpg-keyring", dest="gpg_keyring",
-diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py
-index e9f680b1d..76ad37ca7 100644
---- a/ipaserver/install/ipa_server_certinstall.py
-+++ b/ipaserver/install/ipa_server_certinstall.py
-@@ -72,7 +72,7 @@ class ServerCertInstall(admintool.AdminTool):
-             help="Name of the certificate to install")
-         parser.add_option(
-             "-p", "--dirman-password",
--            dest="dirman_password",
-+            dest="dirman_password", sensitive=True,
-             help="Directory Manager password")
- 
-     def validate_options(self):
--- 
-2.49.0
-
-
-From d857fcfcc21481cdf06b8cce1685e141921d2fbf Mon Sep 17 00:00:00 2001
-From: Sumit Bose <sbose@redhat.com>
-Date: Wed, 27 Nov 2024 12:16:09 +0100
-Subject: [PATCH 066/116] ipa-otpd: use oidc_child's --client-secret-stdin
- option
-
-To remove the client secret from the command line where it would be
-visible e.g. when calling ps it is now passed via stdin to oidc_child.
-
-Fixes: CVE-2024-11029
-
-Signed-off-by: Sumit Bose <sbose@redhat.com>
----
- daemons/ipa-otpd/oauth2.c | 24 ++++++++++++++----------
- 1 file changed, 14 insertions(+), 10 deletions(-)
-
-diff --git a/daemons/ipa-otpd/oauth2.c b/daemons/ipa-otpd/oauth2.c
-index a33cf5171..52d7d7c9c 100644
---- a/daemons/ipa-otpd/oauth2.c
-+++ b/daemons/ipa-otpd/oauth2.c
-@@ -31,6 +31,7 @@
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/random.h>
-+#include <sys/uio.h>
- 
- #include "internal.h"
- 
-@@ -93,6 +94,7 @@ static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev)
-     (void)vctx; /* Unused */
-     ssize_t io;
-     struct child_ctx *child_ctx;
-+    struct iovec iov[3];
- 
-     child_ctx = verto_get_private(ev);
-     if (child_ctx == NULL) {
-@@ -102,15 +104,18 @@ static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev)
-     }
- 
-     if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
--        /* no input needed */
--        verto_del(ev);
--        return;
--    }
--
-+        io = write(verto_get_fd(ev), child_ctx->item->idp.ipaidpClientSecret,
-+                   strlen(child_ctx->item->idp.ipaidpClientSecret));
-+    } else {
-+        iov[0].iov_base = child_ctx->item->idp.ipaidpClientSecret;
-+        iov[0].iov_len = strlen(child_ctx->item->idp.ipaidpClientSecret);
-+        iov[1].iov_base = "\n";
-+        iov[1].iov_len = 1;
-+        iov[2].iov_base = child_ctx->saved_item->oauth2.device_code_reply;
-+        iov[2].iov_len = strlen(child_ctx->saved_item->oauth2.device_code_reply);
- 
--    io = write(verto_get_fd(ev),
--               child_ctx->saved_item->oauth2.device_code_reply,
--               strlen(child_ctx->saved_item->oauth2.device_code_reply));
-+        io = writev(verto_get_fd(ev), iov, 3);
-+    }
-     otpd_queue_item_free(child_ctx->saved_item);
- 
-     if (io < 0) {
-@@ -429,8 +434,7 @@ int oauth2(struct otpd_queue_item **item, enum oauth2_state oauth2_state)
-     args[args_idx++] = (*item)->idp.ipaidpClientID;
- 
-     if ((*item)->idp.ipaidpClientSecret) {
--        args[args_idx++] = "--client-secret";
--        args[args_idx++] = (*item)->idp.ipaidpClientSecret;
-+        args[args_idx++] = "--client-secret-stdin";
-     }
- 
-     if ((*item)->idp.ipaidpScope) {
--- 
-2.49.0
-
-
-From eef544c1d331bbe80852ebe8b5fc9bad0539b6fa Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 15 Jan 2025 15:39:20 +0100
-Subject: [PATCH 067/116] Fix pylint issue in ipatests/i18n.py
-
-This file should not import ipa modules
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- ipatests/i18n.py | 8 ++++----
- pylintrc         | 3 ++-
- 2 files changed, 6 insertions(+), 5 deletions(-)
-
-diff --git a/ipatests/i18n.py b/ipatests/i18n.py
-index 57915c286..49f5c4c32 100644
---- a/ipatests/i18n.py
-+++ b/ipatests/i18n.py
-@@ -22,6 +22,7 @@ from __future__ import print_function
- 
- # WARNING: Do not import ipa modules, this is also used as a
- # stand-alone script (invoked from po Makefile).
-+import optparse  # pylint: disable=deprecated-module
- import sys
- import gettext
- import re
-@@ -29,7 +30,6 @@ import os
- import traceback
- import polib
- from collections import namedtuple
--from ipapython import config
- 
- import six
- 
-@@ -722,9 +722,9 @@ usage ='''
- def main():
-     global verbose, print_traceback, pedantic, show_strings
- 
--    parser = config.IPAOptionParser(usage=usage)
-+    parser = optparse.OptionParser(usage=usage)
- 
--    mode_group = config.OptionGroup(parser, 'Operational Mode',
-+    mode_group = optparse.OptionGroup(parser, 'Operational Mode',
-                                       'You must select one these modes to run in')
- 
-     mode_group.add_option('-g', '--test-gettext', action='store_const', const='test_gettext', dest='mode',
-@@ -748,7 +748,7 @@ def main():
-     parser.add_option('--traceback', action='store_true', dest='print_traceback', default=False,
-                       help='print the traceback when an exception occurs')
- 
--    param_group = config.OptionGroup(parser, 'Run Time Parameters',
-+    param_group = optparse.OptionGroup(parser, 'Run Time Parameters',
-                                        'These may be used to modify the run time defaults')
- 
-     param_group.add_option('--test-lang', action='store', dest='test_lang', default='test',
-diff --git a/pylintrc b/pylintrc
-index 50278cc76..8fadeffbd 100644
---- a/pylintrc
-+++ b/pylintrc
-@@ -153,4 +153,5 @@ forbidden-imports=
-     ipaplatform/:ipaclient:ipalib:ipaserver,
-     ipapython/:ipaclient:ipalib:ipaserver
-     ipatests/pytest_ipa:ipaserver:ipaclient.install:ipalib.install
--    ipatests/test_integration:ipaserver
-+    ipatests/test_integration:ipaserver,
-+    ipatests/i18n.py:ipapython
--- 
-2.49.0
-
-
-From 175bd7b24bea252ea2c9ef80bbd5c4bc118ba304 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 8 Jan 2025 18:33:47 +0100
-Subject: [PATCH 068/116] ipatests: force the version for uninstall/reinstall
-
-The backup-restore tests ensure that ipa-restore complains if
-a required package is missing. The test scenario removes the
-ipa-server-dns package, tries a restore (expecting a failure),
-then reinstall the ipa-server-dns pkg and retry the restore
-(expecting a success).
-The issue is that reinstallation sometimes pulls a more recent
-version and ipa-restore fails because of the version mismatch.
-
-Ensure the reinstallation pulls the same version.
-
-Fixes: https://pagure.io/freeipa/issue/9723
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/pytest_ipa/integration/tasks.py      | 22 +++++++++++++++++++
- .../test_backup_and_restore.py                |  9 ++++++--
- 2 files changed, 29 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index 677fb7534..4ce33bb47 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -2749,6 +2749,28 @@ def get_package_version(host, pkgname):
-     return get_package_version
- 
- 
-+def get_package_version_and_release(host, pkgname):
-+    """
-+    Get package version-release on remote host
-+    """
-+    platform = get_platform(host)
-+    if platform in ("rhel", "fedora"):
-+        cmd = host.run_command(
-+            ["rpm", "-qa", "--qf", "%{VERSION}-%{RELEASE}", pkgname]
-+        )
-+        get_package_version = cmd.stdout_text
-+        if not get_package_version:
-+            raise ValueError(
-+                "get_package_version: "
-+                "pkgname package is not installed"
-+            )
-+    else:
-+        raise ValueError(
-+            "get_package_version: unknown platform %s" % platform
-+        )
-+    return get_package_version
-+
-+
- def get_openldap_client_version(host):
-     """Get openldap-clients version on remote host"""
-     return get_package_version(host, 'openldap-clients')
-diff --git a/ipatests/test_integration/test_backup_and_restore.py b/ipatests/test_integration/test_backup_and_restore.py
-index 7e788793b..9d6b2503b 100644
---- a/ipatests/test_integration/test_backup_and_restore.py
-+++ b/ipatests/test_integration/test_backup_and_restore.py
-@@ -332,6 +332,8 @@ class BaseBackupAndRestoreWithDNS(IntegrationTest):
-                                      '--uninstall',
-                                      '-U'])
- 
-+            version = tasks.get_package_version_and_release(
-+                self.master, '*ipa-server-dns')
-             tasks.uninstall_packages(self.master, ['*ipa-server-dns'])
- 
-             dirman_password = self.master.config.dirman_password
-@@ -341,7 +343,7 @@ class BaseBackupAndRestoreWithDNS(IntegrationTest):
-                 raiseonerr=False)
-             assert 'Please install the package' in result.stderr_text
- 
--            tasks.install_packages(self.master, ['*ipa-server-dns'])
-+            tasks.install_packages(self.master, ['*ipa-server-dns-' + version])
-             if reinstall:
-                 tasks.install_master(self.master, setup_dns=True)
-             self.master.run_command(['ipa-restore', backup_path],
-@@ -945,6 +947,8 @@ class TestBackupAndRestoreTrust(IntegrationTest):
-                                      '--uninstall',
-                                      '-U'])
- 
-+            version = tasks.get_package_version_and_release(
-+                self.master, '*ipa-server-trust-ad')
-             tasks.uninstall_packages(self.master, ['*ipa-server-trust-ad'])
- 
-             dirman_password = self.master.config.dirman_password
-@@ -954,7 +958,8 @@ class TestBackupAndRestoreTrust(IntegrationTest):
-                 raiseonerr=False)
-             assert 'Please install the package' in result.stderr_text
- 
--            tasks.install_packages(self.master, ['*ipa-server-trust-ad'])
-+            tasks.install_packages(
-+                self.master, ['*ipa-server-trust-ad-' + version])
-             self.master.run_command(['ipa-restore', backup_path],
-                                     stdin_text=dirman_password + '\nyes')
- 
--- 
-2.49.0
-
-
-From 45f96a0f978dfda0e2faa8360182a1dfd3122b94 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 10 Jan 2025 13:22:29 +0100
-Subject: [PATCH 069/116] ipatests: skip test_ipahealthcheck_ds_configcheck for
- recent versions
-
-389-ds removed the parameter nsslapd-logging-hr-timestamps-enabled
-in 2.5.3 and above. Skip the test that exercises the corresponding
-healthcheck.
-
-Fixes: https://pagure.io/freeipa/issue/9730
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 12 +++++++++---
- 1 file changed, 9 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index cc51a5a6a..6b6f15aa4 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -18,7 +18,7 @@ import uuid
- 
- import pytest
- 
--from ipalib import x509
-+from ipalib import errors, x509
- from ipapython.dn import DN
- from ipapython.ipaldap import realm_to_serverid
- from ipapython.certdb import NSS_SQL_FILES
-@@ -1146,8 +1146,14 @@ class TestIpaHealthCheck(IntegrationTest):
-         )
-         entry = ldap.get_entry(dn)
-         entry.single_value["nsslapd-logging-hr-timestamps-enabled"] = 'off'
--        ldap.update_entry(entry)
--
-+        try:
-+            ldap.update_entry(entry)
-+        except errors.DatabaseError as e:
-+            expected_msg = "Unknown attribute " \
-+                           "nsslapd-logging-hr-timestamps-enabled"
-+            if expected_msg in e.message:
-+                pytest.skip(
-+                    "389-ds removed nsslapd-logging-hr-timestamps-enabled")
-         yield
- 
-         entry = ldap.get_entry(dn)
--- 
-2.49.0
-
-
-From ec94ee72714296c86ba1227a5a945a7ed0bc7fac Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 16 Jan 2025 15:43:17 +0100
-Subject: [PATCH 070/116] ipatests: restart dirsrv after time jumps
-
-The test for ipa-healthcheck is moving the date in the future.
-Restart the dirsrv instance because the LDAP server is
-sensitive to large time jumps.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 6b6f15aa4..7c3f5857a 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -1634,6 +1634,11 @@ class TestIpaHealthCheck(IntegrationTest):
-             grace_date = datetime.strftime(grace_date, "%Y-%m-%d 00:00:01 Z")
-             self.master.run_command(['date', '-s', grace_date])
- 
-+            # Restart dirsrv as it doesn't like time jumps
-+            instance = realm_to_serverid(self.master.domain.realm)
-+            cmd = ["systemctl", "restart", "dirsrv@{}".format(instance)]
-+            self.master.run_command(cmd)
-+
-             for check in ("IPACertmongerExpirationCheck",
-                           "IPACertfileExpirationCheck",):
-                 execute_expiring_check(check)
--- 
-2.49.0
-
-
-From d86db9d2c107c66372f422f1d628624b1a55ad45 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 17 Jan 2025 09:44:22 +0200
-Subject: [PATCH 071/116] ipa-otpd: do not pass OIDC client secret if there is
- none to pass
-
-If there is no client secret specified for the OIDC client, don't push
-it to oidc_child via stdin. oidc_child does only expect client secret if
---client-secret-stdin option was specified and we already specify it
-only if client secret is not empty.
-
-In addition, if client secret is empty (it is a public OIDC client),
-then strlen(NULL) would crash in glibc internals. Avoid that!
-
-Fixes: https://pagure.io/freeipa/issue/9734
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- daemons/ipa-otpd/oauth2.c | 29 +++++++++++++++++++----------
- 1 file changed, 19 insertions(+), 10 deletions(-)
-
-diff --git a/daemons/ipa-otpd/oauth2.c b/daemons/ipa-otpd/oauth2.c
-index 52d7d7c9c..0eb43b237 100644
---- a/daemons/ipa-otpd/oauth2.c
-+++ b/daemons/ipa-otpd/oauth2.c
-@@ -104,17 +104,26 @@ static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev)
-     }
- 
-     if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
--        io = write(verto_get_fd(ev), child_ctx->item->idp.ipaidpClientSecret,
--                   strlen(child_ctx->item->idp.ipaidpClientSecret));
-+        if (child_ctx->item->idp.ipaidpClientSecret != NULL) {
-+            io = write(verto_get_fd(ev), child_ctx->item->idp.ipaidpClientSecret,
-+                       strlen(child_ctx->item->idp.ipaidpClientSecret));
-+        } else {
-+            io = 0;
-+        }
-     } else {
--        iov[0].iov_base = child_ctx->item->idp.ipaidpClientSecret;
--        iov[0].iov_len = strlen(child_ctx->item->idp.ipaidpClientSecret);
--        iov[1].iov_base = "\n";
--        iov[1].iov_len = 1;
--        iov[2].iov_base = child_ctx->saved_item->oauth2.device_code_reply;
--        iov[2].iov_len = strlen(child_ctx->saved_item->oauth2.device_code_reply);
--
--        io = writev(verto_get_fd(ev), iov, 3);
-+        int idx = 0;
-+        if (child_ctx->item->idp.ipaidpClientSecret != NULL) {
-+            iov[idx].iov_base = child_ctx->item->idp.ipaidpClientSecret;
-+            iov[idx].iov_len = strlen(child_ctx->item->idp.ipaidpClientSecret);
-+	    idx++;
-+            iov[idx].iov_base = "\n";
-+            iov[idx].iov_len = 1;
-+	    idx++;
-+        }
-+        iov[idx].iov_base = child_ctx->saved_item->oauth2.device_code_reply;
-+        iov[idx].iov_len = strlen(child_ctx->saved_item->oauth2.device_code_reply);
-+        idx++;
-+        io = writev(verto_get_fd(ev), iov, idx);
-     }
-     otpd_queue_item_free(child_ctx->saved_item);
- 
--- 
-2.49.0
-
-
-From 431a5804949417257b204125ff0a898b98dd2a90 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 17 Jan 2025 12:33:54 +0200
-Subject: [PATCH 072/116] Migrate Keycloak tests to JDK 21 and Keycloak 26
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../pytest_ipa/integration/create_bridge.py   |  2 +-
- .../pytest_ipa/integration/create_keycloak.py | 28 +++++++++----------
- ipatests/test_integration/test_idp.py         |  4 +--
- ipatests/test_integration/test_sso.py         |  4 +--
- 4 files changed, 18 insertions(+), 20 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/create_bridge.py b/ipatests/pytest_ipa/integration/create_bridge.py
-index 618c645fe..5dd2f305c 100644
---- a/ipatests/pytest_ipa/integration/create_bridge.py
-+++ b/ipatests/pytest_ipa/integration/create_bridge.py
-@@ -147,7 +147,7 @@ def setup_keycloak_scim_plugin(host, bridge_server):
-     # Login to keycloak as admin
-     kcadmin_sh = "/opt/keycloak/bin/kcadm.sh"
-     kcadmin = [kcadmin_sh, "config", "credentials", "--server",
--               f"https://{host.hostname}:8443/auth/",
-+               f"https://{host.hostname}:8443",
-                "--realm", "master", "--user", "admin",
-                "--password", password]
-     tasks.run_repeatedly(host, kcadmin, timeout=60)
-diff --git a/ipatests/pytest_ipa/integration/create_keycloak.py b/ipatests/pytest_ipa/integration/create_keycloak.py
-index 1340b9571..addade759 100644
---- a/ipatests/pytest_ipa/integration/create_keycloak.py
-+++ b/ipatests/pytest_ipa/integration/create_keycloak.py
-@@ -6,10 +6,10 @@ from ipaplatform.paths import paths
- from ipatests.pytest_ipa.integration import tasks
- 
- 
--def setup_keycloakserver(host, version='17.0.0'):
-+def setup_keycloakserver(host, version='26.1.0'):
-     dir = "/opt/keycloak"
-     password = host.config.admin_password
--    tasks.install_packages(host, ["unzip", "java-11-openjdk-headless",
-+    tasks.install_packages(host, ["unzip", "java-21-openjdk-headless",
-                                   "openssl", "maven", "wget",
-                                   "firefox", "xorg-x11-server-Xvfb"])
-     #  add keycloak system user/group and folder
-@@ -33,7 +33,7 @@ def setup_keycloakserver(host, version='17.0.0'):
- 
-     key = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.key")
-     crt = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.crt")
--    keystore = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.store")
-+    keystore = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.jks")
- 
-     host.run_command(["ipa-getcert", "request", "-K",
-                       "HTTP/{0}".format(host.hostname),
-@@ -49,14 +49,13 @@ def setup_keycloakserver(host, version='17.0.0'):
- 
-     # Setup keycloak service and config files
-     contents = textwrap.dedent("""
--    KEYCLOAK_ADMIN=admin
--    KEYCLOAK_ADMIN_PASSWORD={admin_pswd}
--    KC_HOSTNAME={host}:8443
-+    KC_BOOTSTRAP_ADMIN_USERNAME=admin
-+    KC_BOOTSTRAP_ADMIN_PASSWORD={admin_pswd}
-+    KC_HOSTNAME=https://{host}:8443/
-     KC_HTTPS_CERTIFICATE_FILE={crt}
-     KC_HTTPS_CERTIFICATE_KEY_FILE={key}
-     KC_HTTPS_TRUST_STORE_FILE={store}
-     KC_HTTPS_TRUST_STORE_PASSWORD={store_pswd}
--    KC_HTTP_RELATIVE_PATH=/auth
-     """).format(admin_pswd=password, host=host.hostname, crt=crt, key=key,
-                 store=keystore, store_pswd=password)
-     host.put_file_contents("/etc/sysconfig/keycloak", contents)
-@@ -84,14 +83,13 @@ def setup_keycloakserver(host, version='17.0.0'):
- 
-     # Run build stage first
-     env_vars = textwrap.dedent("""
--    export KEYCLOAK_ADMIN=admin
--    export KC_HOSTNAME={hostname}:8443
-+    export KC_BOOTSTRAP_ADMIN_USERNAME=admin
-+    export KC_HOSTNAME=https://{hostname}:8443/
-     export KC_HTTPS_CERTIFICATE_FILE=/etc/pki/tls/certs/keycloak.crt
-     export KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/pki/tls/private/keycloak.key
--    export KC_HTTPS_TRUST_STORE_FILE=/etc/pki/tls/private/keycloak.store
-+    export KC_HTTPS_TRUST_STORE_FILE=/etc/pki/tls/private/keycloak.jks
-     export KC_HTTPS_TRUST_STORE_PASSWORD={STORE_PASS}
--    export KEYCLOAK_ADMIN_PASSWORD={ADMIN_PASS}
--    export KC_HTTP_RELATIVE_PATH=/auth
-+    export KC_BOOTSTRAP_ADMIN_PASSWORD={ADMIN_PASS}
-     """).format(hostname=host.hostname, STORE_PASS=password,
-                 ADMIN_PASS=password)
- 
-@@ -112,7 +110,7 @@ def setup_keycloakserver(host, version='17.0.0'):
-     host.run_command([kcadmin_sh, "config", "truststore",
-                       "--trustpass", password, keystore])
-     kcadmin = [kcadmin_sh, "config", "credentials", "--server",
--               "https://{0}:8443/auth/".format(host.hostname),
-+               "https://{0}:8443/".format(host.hostname),
-                "--realm", "master", "--user", "admin",
-                "--password", password
-                ]
-@@ -133,7 +131,7 @@ def setup_keycloak_client(host):
-     password = host.config.admin_password
-     host.run_command(["/opt/keycloak/bin/kcreg.sh",
-                       "config", "credentials", "--server",
--                      "https://{0}:8443/auth/".format(host.hostname),
-+                      "https://{0}:8443/".format(host.hostname),
-                       "--realm", "master", "--user", "admin",
-                       "--password", password]
-                      )
-@@ -163,7 +161,7 @@ def setup_keycloak_client(host):
- def uninstall_keycloak(host):
-     key = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.key")
-     crt = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.crt")
--    keystore = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.store")
-+    keystore = os.path.join(paths.OPENSSL_PRIVATE_DIR, "keycloak.jks")
- 
-     host.run_command(["systemctl", "stop", "keycloak"], raiseonerr=False)
-     host.run_command(["getcert", "stop-tracking", "-k", key, "-f", crt],
-diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py
-index ca2fcecb2..76edc9458 100644
---- a/ipatests/test_integration/test_idp.py
-+++ b/ipatests/test_integration/test_idp.py
-@@ -122,7 +122,7 @@ class TestIDPKeycloak(IntegrationTest):
-         tasks.kinit_admin(self.master)
-         cmd = ["ipa", "idp-add", "keycloakidp", "--provider=keycloak",
-                "--client-id=ipa_oidc_client", "--org=master",
--               "--base-url={0}:8443/auth".format(self.client.hostname)]
-+               "--base-url={0}:8443".format(self.client.hostname)]
-         self.master.run_command(cmd, stdin_text="{0}\n{0}".format(
-             self.client.config.admin_password))
-         tasks.user_add(self.master, 'keycloakuser',
-@@ -282,7 +282,7 @@ class TestIDPKeycloak(IntegrationTest):
-         user = "backupuser"
-         cmd = ["ipa", "idp-add", "testidp", "--provider=keycloak",
-                "--client-id=ipa_oidc_client", "--org=master",
--               "--base-url={0}:8443/auth".format(self.client.hostname)]
-+               "--base-url={0}:8443".format(self.client.hostname)]
-         self.master.run_command(cmd, stdin_text="{0}\n{0}".format(
-             self.client.config.admin_password))
- 
-diff --git a/ipatests/test_integration/test_sso.py b/ipatests/test_integration/test_sso.py
-index 9708e9fa0..57c5a96ba 100644
---- a/ipatests/test_integration/test_sso.py
-+++ b/ipatests/test_integration/test_sso.py
-@@ -18,7 +18,7 @@ from selenium.webdriver.support import expected_conditions as EC
- options = Options()
- options.headless = True
- driver = webdriver.Firefox(executable_path="/opt/geckodriver", options=options)
--verification_uri = "https://{hostname}:8443/auth/realms/master/account/#/"
-+verification_uri = "https://{hostname}:8443/realms/master/account/#/"
- driver.get(verification_uri)
- 
- try:
-@@ -60,7 +60,7 @@ def keycloak_add_user(host, kcadm_pass, username, password=None):
-     domain = host.domain.name
-     kcadmin_sh = "/opt/keycloak/bin/kcadm.sh"
-     kcadmin = [kcadmin_sh, "config", "credentials", "--server",
--               f"https://{host.hostname}:8443/auth/",
-+               f"https://{host.hostname}:8443",
-                "--realm", "master", "--user", "admin",
-                "--password", kcadm_pass]
- 
--- 
-2.49.0
-
-
-From 4ea78404a4202456b33ee58723b590f5b6f2f5aa Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 21 Jan 2025 14:30:11 +0200
-Subject: [PATCH 073/116] Reintroduce test_idp to gating tests
-
-test_idp should complete in ~27 minutes.
-
-Related: https://pagure.io/freeipa/issue/9734
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/prci_definitions/gating.yaml | 16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index 416a99cc8..f17af796c 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -11,6 +11,10 @@ topologies:
-     name: master_1repl_1client
-     cpu: 4
-     memory: 8000
-+  master_2repl_1client: &master_2repl_1client
-+    name: master_2repl_1client
-+    cpu: 5
-+    memory: 10750
-   ad_master_2client: &ad_master_2client
-     name: ad_master_2client
-     cpu: 4
-@@ -334,3 +338,15 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 600
-         topology: *master_1repl
-+
-+  fedora-latest-ipa-4-12/test_external_idp:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 100
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_idp.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *master_2repl_1client
--- 
-2.49.0
-
-
-From 9f30edef463237ba48efe45406626eb325bf6c39 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Wed, 22 Jan 2025 13:19:43 -0500
-Subject: [PATCH 074/116] Apply certmonger_timeout to start_tracking and
- request_cert
-
-We've seen that with some slow HSMs the default DBus timeout
-the HSM doesn't respond quickly enough to certmonger start
-tracking requests which fails the entire installation.
-
-A first attempt was made to bump up the default to 30 seconds
-which turned out to not be long enough.
-
-There is already a certmonger timeout defined in the API but it
-is 300 seconds so I was hesitant to use it. It could delay the
-actual failure of a blown install by 5 minutes. But it also gives
-the end user the flexibility to be able to control success over
-an installation so we'll go ahead and use it.
-
-Fixes: https://pagure.io/freeipa/issue/9725
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Mohammad Rizwan Yusuf <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- client/man/default.conf.5           | 10 ++++++++--
- ipalib/install/certmonger.py        |  7 +++++--
- ipaserver/install/cainstance.py     |  5 ++++-
- ipaserver/install/dogtaginstance.py | 18 ++++++++++++++++++
- ipaserver/install/service.py        |  4 +++-
- 5 files changed, 38 insertions(+), 6 deletions(-)
-
-diff --git a/client/man/default.conf.5 b/client/man/default.conf.5
-index 3846de50c..e0aec21f7 100644
---- a/client/man/default.conf.5
-+++ b/client/man/default.conf.5
-@@ -20,7 +20,7 @@
- .SH "NAME"
- default.conf \- IPA configuration file
- .SH "SYNOPSIS"
--/etc/ipa/default.conf, ~/.ipa/default.conf, /etc/ipa/server.conf, /etc/ipa/cli.conf
-+/etc/ipa/default.conf, ~/.ipa/default.conf, /etc/ipa/server.conf, /etc/ipa/cli.conf, /etc/ipa/installer.conf, /etc/ipa/cli_installer.conf
- .SH "DESCRIPTION"
- The \fIdefault.conf \fRconfiguration file is used to set system\-wide defaults to be applied when running IPA clients and servers.
- 
-@@ -75,7 +75,7 @@ Specifies the hostname of the dogtag CA server. The default is the hostname of t
- Specifies the insecure CA end user port. The default is 8080.
- .TP
- .B certmonger_wait_timeout <seconds>
--The time to wait for a certmonger request to complete during installation. The default value is 300 seconds.
-+The time to wait for a certmonger request to complete during installation. The default value is 300 seconds. To tune create/add to /etc/ipa/installer.conf.
- .TP
- .B context <context>
- Specifies the context that IPA is being executed in. IPA may operate differently depending on the context. The current defined contexts are cli, server and dns. Additionally this value is used to load /etc/ipa/\fBcontext\fR.conf to provide context\-specific configuration. For example, if you want to always perform client requests in verbose mode but do not want to have verbose enabled on the server, add the verbose option to \fI/etc/ipa/cli.conf\fR.
-@@ -263,6 +263,12 @@ system\-wide IPA client configuration file
- .TP
- .I /etc/ipa/server.conf
- system\-wide IPA server configuration file
-+.TP
-+.I /etc/ipa/installer.conf
-+IPA configuration used while installing an IPA server or replica
-+.TP
-+.I /etc/ipa/cli_installer.conf
-+IPA configuration used while installing an IPA client
- .SH "EXAMPLES"
- .TP
- An example of a context-specific configuration file is \fB/etc/ipa/dns.conf\fR to be used to increase debug output of the IPA DNSSEC daemons.
-diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py
-index efc1ba4f4..675b2c96c 100644
---- a/ipalib/install/certmonger.py
-+++ b/ipalib/install/certmonger.py
-@@ -477,7 +477,8 @@ def request_cert(
-         request_parameters['cert-perms'] = perms[0]
-         request_parameters['key-perms'] = perms[1]
- 
--    result = cm.obj_if.add_request(request_parameters, timeout=30)
-+    result = cm.obj_if.add_request(request_parameters,
-+                                   timeout=api.env.certmonger_wait_timeout)
-     try:
-         if result[0]:
-             request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
-@@ -581,7 +582,9 @@ def start_tracking(
-     if nss_user:
-         params['nss-user'] = nss_user
- 
--    result = cm.obj_if.add_request(params, timeout=30)
-+    logger.debug("start tracking %s", params)
-+    result = cm.obj_if.add_request(params,
-+                                   timeout=api.env.certmonger_wait_timeout)
-     try:
-         if result[0]:
-             request = _cm_dbus_object(cm.bus, cm, result[1], DBUS_CM_REQUEST_IF,
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index e03a8c863..76718036d 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -513,7 +513,10 @@ class CAInstance(DogtagInstance):
-         if ra_only:
-             runtime = None
-         else:
--            runtime = 180
-+            if self.tokenname:
-+                runtime = "HSM dependent"
-+            else:
-+                runtime = 180
- 
-         try:
-             self.start_creation(runtime=runtime)
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index 32a52dbed..002053ed7 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -575,6 +575,24 @@ class DogtagInstance(service.Service):
-             except RuntimeError as e:
-                 logger.error(
-                     "certmonger failed to start tracking certificate: %s", e)
-+            except dbus.exceptions.DBusException as e:
-+                if e._dbus_error_name == "org.freedesktop.DBus.Error.NoReply":
-+                    logger.error(
-+                        "Timeout encountered starting tracking of '%s'."
-+                        "This timeout can be tuned using "
-+                        "certmonger_wait_timeout in /etc/ipa/installer.conf.",
-+                        nickname
-+                    )
-+                if self.hsm_enabled:
-+                    logger.error(
-+                        "On an initial install failure this may leave "
-+                        "certificates and keys on the HSM token. These "
-+                        "need to be manually cleaned per your HSM-specific "
-+                        "documentation before installing IPA again. On a "
-+                        "replica install no clean-up should be done (it will "
-+                        "destroy your installation."
-+                    )
-+                raise
- 
-     def stop_tracking_certificates(self):
-         """
-diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
-index cf0f64ab9..7755a4f2f 100644
---- a/ipaserver/install/service.py
-+++ b/ipaserver/install/service.py
-@@ -59,6 +59,8 @@ def print_msg(message, output_fd=sys.stdout):
- 
- def format_seconds(seconds):
-     """Format a number of seconds as an English minutes+seconds message"""
-+    if type(seconds) is not int:
-+        return seconds
-     parts = []
-     minutes, seconds = divmod(seconds, 60)
-     if minutes:
-@@ -660,7 +662,7 @@ class Service:
-                 else:
-                     end_message = "Done configuring %s." % self.service_desc
- 
--        if runtime is not None and runtime > 0:
-+        if runtime is not None or (type(runtime) is int and runtime > 0):
-             self.print_msg('%s. Estimated time: %s' % (start_message,
-                                                       format_seconds(runtime)))
-         else:
--- 
-2.49.0
-
-
-From e2be556c19b7e91716f062b4e0ba67307cdcaa69 Mon Sep 17 00:00:00 2001
-From: Francisco Trivino <ftrivino@redhat.com>
-Date: Mon, 27 Jan 2025 10:11:49 +0100
-Subject: [PATCH 075/116] ipatests: increase delays for WebUI host test
-
-Previously, it waited 0.5 seconds before checking if the record was present,
-but this delay was often insufficient, causing intermittent failures.
-
-Fixes: https://pagure.io/freeipa/issue/9721
-Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipatests/test_webui/test_host.py | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_webui/test_host.py b/ipatests/test_webui/test_host.py
-index 2619fd460..94dbd296a 100644
---- a/ipatests/test_webui/test_host.py
-+++ b/ipatests/test_webui/test_host.py
-@@ -766,17 +766,17 @@ class test_host(host_tasks):
-         self.fill_search_filter(self.pkey2)
-         actions = ActionChains(self.driver)
-         actions.send_keys(Keys.ENTER).perform()
--        self.wait(0.5)
-+        self.wait(1.5)
-         self.assert_record(self.pkey2)
-         # negative search filter
-         self.fill_search_filter(self.pkey3)
-         actions = ActionChains(self.driver)
-         actions.send_keys(Keys.ENTER).perform()
--        self.wait(0.5)
-+        self.wait(1.5)
-         self.assert_record(self.pkey4, negative=True)
-         # cleanup
-         self.fill_search_filter('')
-         actions = ActionChains(self.driver)
-         actions.send_keys(Keys.ENTER).perform()
--        self.wait(0.5)
-+        self.wait(1.5)
-         self.delete_record([self.pkey2, self.pkey3])
--- 
-2.49.0
-
-
-From d070a9abc5029a81a6eda257f1c6ad59e6b9ae60 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 30 Jan 2025 07:53:23 -0500
-Subject: [PATCH 076/116] Write out the PKI admin certificate as a PEM file
-
-It was being written out in base-64 only. The PKI team gave
-us a heads-up that that may not always be supported. So use
-a standard PEM file instead.
-
-Fixes: https://pagure.io/freeipa/issue/9735
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaserver/install/krainstance.py | 6 +-----
- 1 file changed, 1 insertion(+), 5 deletions(-)
-
-diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
-index 8452c896a..8d95a23ea 100644
---- a/ipaserver/install/krainstance.py
-+++ b/ipaserver/install/krainstance.py
-@@ -23,7 +23,6 @@ import logging
- import os
- import shutil
- import tempfile
--import base64
- 
- from ipalib import api
- from ipalib import x509
-@@ -230,10 +229,7 @@ class KRAInstance(DogtagInstance):
-             parentdir = os.path.dirname(paths.ADMIN_CERT_PATH)
-             if not os.path.exists(parentdir):
-                 os.makedirs(parentdir)
--            with open(paths.ADMIN_CERT_PATH, "wb") as admin_path:
--                admin_path.write(
--                    base64.b64encode(cert.public_bytes(x509.Encoding.DER))
--                )
-+            x509.write_certificate(cert, paths.ADMIN_CERT_PATH)
- 
-         # Generate configuration file
-         config = self._create_spawn_config(cfg)
--- 
-2.49.0
-
-
-From b083f7f741f6c27c2a1e80a72ec053282e32c23f Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 30 Jan 2025 17:52:09 -0500
-Subject: [PATCH 077/116] Add a message where the ipa service restarted at end
- of install
-
-There is a noticeable near the end of an IPA server installation
-after the client installation is complete and before the
-"Setup complete" message is displayed.
-
-A few minor things are done here but the big time sink is
-enabling ipa.service and restarting it because the CA takes
-so long to start. In my testing it was between 40 and 60 seconds.
-
-So add a little message so folks don't think the client installer
-hung up at the end.
-
-Fixes: https://pagure.io/freeipa/issue/9741
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaserver/install/server/install.py | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
-index 47db13142..c39c807a9 100644
---- a/ipaserver/install/server/install.py
-+++ b/ipaserver/install/server/install.py
-@@ -1072,7 +1072,12 @@ def install(installer):
-     # Everything installed properly, activate ipa service.
-     sstore.delete_state('installation', 'complete')
-     sstore.backup_state('installation', 'complete', True)
-+
-+    service.print_msg("Enabling and restarting the IPA service")
-+    start = time.time()
-     services.knownservices.ipa.enable()
-+    dur = time.time() - start
-+    logger.debug("Service enablement duration: %0.3f", dur)
- 
-     print("======================================="
-           "=======================================")
--- 
-2.49.0
-
-
-From d0dd2e8393f4b49edc67f8036a19d8b2d4e28938 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 31 Jan 2025 13:09:22 +0100
-Subject: [PATCH 078/116] ipatests: on rhel10 do not install firefox
-
-Firefox will be installed as a pre-requisite as it either comes
-as a flatpak or a tar file. The flatpak is not compatible with
-geckodriver as explained in geckodriver release notes
-(https://github.com/mozilla/geckodriver/releases/tag/v0.35.0)
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
----
- ipatests/pytest_ipa/integration/create_keycloak.py | 10 +++++++---
- 1 file changed, 7 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/create_keycloak.py b/ipatests/pytest_ipa/integration/create_keycloak.py
-index addade759..7f0850ca7 100644
---- a/ipatests/pytest_ipa/integration/create_keycloak.py
-+++ b/ipatests/pytest_ipa/integration/create_keycloak.py
-@@ -9,9 +9,13 @@ from ipatests.pytest_ipa.integration import tasks
- def setup_keycloakserver(host, version='26.1.0'):
-     dir = "/opt/keycloak"
-     password = host.config.admin_password
--    tasks.install_packages(host, ["unzip", "java-21-openjdk-headless",
--                                  "openssl", "maven", "wget",
--                                  "firefox", "xorg-x11-server-Xvfb"])
-+    packages = ["unzip", "java-21-openjdk-headless", "openssl", "maven", "wget"]
-+    # For RHEL 10 we don't install firefox as it is not shipped any more
-+    # as a rpm. The infra handles the installation from a zip file
-+    if not (tasks.get_platform(host) == "rhel"
-+       and tasks.get_platform_version(host)[0] == 10):
-+        packages.extend(["firefox", "xorg-x11-server-Xvfb"])
-+    tasks.install_packages(host, packages)
-     #  add keycloak system user/group and folder
-     url = "https://github.com/keycloak/keycloak/releases/download/{0}/keycloak-{0}.zip".format(version)  # noqa: E501
-     host.run_command(["wget", url, "-O", "{0}-{1}.zip".format(dir, version)])
--- 
-2.49.0
-
-
-From 826ba67e4b3237ccda081fa3f288d35a564f4890 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 30 Jan 2025 14:38:58 -0500
-Subject: [PATCH 079/116] Suppress spurious failure messages when uninstalling
- ACME
-
-We fork out to pki-server to uninstall the ACME service and
-the results are very coarse, currently just a pass/fail. This
-can lead to displaying very long tracebacks as an error
-message.
-
-So check for the existence of the acme path and only invoke
-the uninstaller if it exists.
-
-Fixes: https://pagure.io/freeipa/issue/9740
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaserver/install/dogtaginstance.py | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index 002053ed7..d5796c75a 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -336,6 +336,8 @@ class DogtagInstance(service.Service):
-         if self.subsystem == "ACME":
-             if pki_version < pki.util.Version("11.0.0"):
-                 return
-+            elif not os.path.exists(os.path.join(paths.PKI_TOMCAT, 'acme')):
-+                return
-             elif (
-                 pki.util.Version("11.0.0") <= pki_version
-                 < pki.util.Version("11.6.0")
-@@ -365,7 +367,6 @@ class DogtagInstance(service.Service):
- 
-         try:
-             ipautil.run(args)
--
-         except ipautil.CalledProcessError as e:
-             logger.critical("failed to uninstall %s instance %s",
-                             self.subsystem, e)
--- 
-2.49.0
-
-
-From 47ce0982249ee7ce12b38eae5ce3ee6a9b5df52e Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 4 Feb 2025 12:54:48 -0500
-Subject: [PATCH 080/116] Configure the pki-tomcatd service systemd timeout
-
-IPA defines a startup timeout that is primarily used
-during installation to extend service start-up timeouts
-on slower systems.
-
-This tends to work ok when runing pki-spawn but can fail when
-systemd is starting the tomcat service.
-
-Use the value of startup_timeout to set TimeoutStartSec in
-the pki-tomcat systemd override file ipa.conf. This will
-preserve the necessary startup_timeout for all future restarts.
-
-This was seen with a very slow HSM where installation was successful
-(pki-spawn) but pki-tomcatd startup timed out at the end of the
-installation.
-
-To increase the value in installation one needs to create the file
-/etc/ipa/installer.conf with contents:
-
-[global]
-startup_timeout = 300 (or whatever)
-
-Fixes: https://pagure.io/freeipa/issue/9743
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/install/cainstance.py | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index 76718036d..c8ecde8f2 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -713,7 +713,12 @@ class CAInstance(DogtagInstance):
-             f.write('[Service]\n')
-             f.write('Environment=LC_ALL=C.UTF-8\n')
-             f.write('ExecStartPost={}\n'.format(paths.IPA_PKI_WAIT_RUNNING))
-+            f.write('TimeoutStartSec=%d\n' % api.env.startup_timeout)
-         tasks.systemd_daemon_reload()
-+        logger.info(
-+            "Set start up timeout of pki-tomcatd service to %d seconds",
-+            api.env.startup_timeout
-+        )
- 
-     def safe_backup_config(self):
-         """
--- 
-2.49.0
-
-
-From 22cbc5ed4889d6c66e2916d5acde582b1868fbc9 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 10 Feb 2025 10:45:39 -0500
-Subject: [PATCH 081/116] Align startup_timeout with the systemd default and
- document it
-
-We had it set to 120 seconds while the systemd default is 90.
-They should be the same because the first one that times out "wins".
-
-Move where during the installation we create the systemd override
-file so that the timeout will be applied across all subsequent
-server starts during and post installation.
-
-Fixes: https://pagure.io/freeipa/issue/9743
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- client/man/default.conf.5       | 2 +-
- ipalib/constants.py             | 5 +++--
- ipaserver/install/cainstance.py | 3 ++-
- 3 files changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/client/man/default.conf.5 b/client/man/default.conf.5
-index e0aec21f7..461c60134 100644
---- a/client/man/default.conf.5
-+++ b/client/man/default.conf.5
-@@ -191,7 +191,7 @@ Specifies the IPA Server hostname.
- Skip client vs. server API version checking. Can lead to errors/strange behavior when newer clients talk to older servers. Use with caution.
- .TP
- .B startup_timeout <time in seconds>
--Controls the amount of time waited when starting a service. The default value is 120 seconds.
-+Controls the amount of time waited when starting a service. The default value is 90 seconds, the same as the default systemd startup timeout. If configuring a CA the startup_timeout value will be added as an override for TimeoutStartSec in systemd. If installation times out when starting the CA create /etc/ipa/installer.conf with this value set.
- .TP
- .B startup_traceback <boolean>
- If the IPA server fails to start and this value is True the server will attempt to generate a python traceback to make identifying the underlying problem easier.
-diff --git a/ipalib/constants.py b/ipalib/constants.py
-index c90caa221..2e4c9a833 100644
---- a/ipalib/constants.py
-+++ b/ipalib/constants.py
-@@ -189,8 +189,9 @@ DEFAULT_CONFIG = (
- 
-     # Time to wait for a service to start, in seconds.
-     # Note that systemd has a DefaultTimeoutStartSec of 90 seconds. Higher
--    # values are not effective unless systemd is reconfigured, too.
--    ('startup_timeout', 120),
-+    # values are not effective unless systemd is reconfigured, too. Or you
-+    # can update the systemd service file with its own TimeoutStartSec.
-+    ('startup_timeout', 90),
-     # How long http connection should wait for reply [seconds].
-     ('http_timeout', 30),
-     # How long to wait for an entry to appear on a replica
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index c8ecde8f2..3466c3088 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -453,6 +453,7 @@ class CAInstance(DogtagInstance):
-             if promote:
-                 self.step("destroying installation admin user",
-                           self.teardown_admin)
-+            self.step("updating IPA configuration", update_ipa_conf)
-             # Materialize config changes and new ACLs
-             self.step("starting certificate server instance",
-                       self.start_instance)
-@@ -480,7 +481,6 @@ class CAInstance(DogtagInstance):
-                 self.step("configure certificate renewals", self.configure_renewal)
-                 self.step("Configure HTTP to proxy connections",
-                           self.http_proxy)
--                self.step("updating IPA configuration", update_ipa_conf)
-                 self.step("enabling CA instance", self.__enable_instance)
-                 if not promote:
-                     if self.clone:
-@@ -2453,6 +2453,7 @@ def update_ipa_conf(ca_host=None):
-     parser.set('global', 'enable_ra', 'True')
-     parser.set('global', 'ra_plugin', 'dogtag')
-     parser.set('global', 'dogtag_version', '10')
-+    parser.set('global', 'startup_timeout', api.env.startup_timeout)
-     if ca_host is None:
-         parser.remove_option('global', 'ca_host')
-     else:
--- 
-2.49.0
-
-
-From 46ef43b2a68139c991883633137c0061f20222a7 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Mon, 29 Apr 2024 11:36:52 +0200
-Subject: [PATCH 082/116] Add DNS over TLS support
-
-Add DNS over TLS support using Unbound as a local resolver. This
-includes new options on both server and client side.
-
-* `--dns-over-tls`: enable DNS over TLS support. This option is present
-  on both client and server. It deploys Unbound and configures BIND on
-the server to receive DoT requests.
-* `--dot-forwarder`: the upstream DNS server with DoT support. It must
-  be specified in the format `1.2.3.4#dns.server.test`
-* `--dns-over-tls-key` and `--dns-over-tls-cert`: in case user prefers
-  to have the DoT certificate in BIND generated by themselves. If these
-  are empty, IPA CA is used instead to request a new certificate.
-
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
-Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
----
- client/man/ipa-client-install.1            |   3 +
- client/share/Makefile.am                   |   1 +
- client/share/unbound.conf.template         |  10 ++
- install/share/bind.named.conf.template     |   4 +
- install/tools/ipa-dns-install.in           |  50 +++++-
- install/tools/man/ipa-dns-install.1        |  16 ++
- install/tools/man/ipa-replica-install.1    |  16 ++
- install/tools/man/ipa-server-install.1     |  16 ++
- ipaclient/install/client.py                |  89 +++++++++-
- ipaplatform/base/paths.py                  |   4 +
- ipaplatform/base/services.py               |   2 +-
- ipapython/ipautil.py                       |  13 ++
- ipaserver/install/bindinstance.py          |  48 +++++-
- ipaserver/install/dns.py                   | 182 ++++++++++++++++++++-
- ipaserver/install/server/__init__.py       |  60 ++++++-
- ipaserver/install/server/install.py        |  24 ++-
- ipaserver/install/server/replicainstall.py |   2 +
- 17 files changed, 507 insertions(+), 33 deletions(-)
- create mode 100644 client/share/unbound.conf.template
-
-diff --git a/client/man/ipa-client-install.1 b/client/man/ipa-client-install.1
-index 725b11422..e6f641254 100644
---- a/client/man/ipa-client-install.1
-+++ b/client/man/ipa-client-install.1
-@@ -201,6 +201,9 @@ Use \fIIP_ADDRESS\fR in DNS A/AAAA record for this host. May be specified multip
- .TP
- \fB\-\-all\-ip\-addresses\fR
- Create DNS A/AAAA record for each IP address on this host.
-+.TP
-+\fB\-\-dns\-over\-tls\fR
-+Configure DNS over TLS.
- 
- .SS "SSSD OPTIONS"
- .TP
-diff --git a/client/share/Makefile.am b/client/share/Makefile.am
-index bf631a22f..52f3c4dd4 100644
---- a/client/share/Makefile.am
-+++ b/client/share/Makefile.am
-@@ -5,6 +5,7 @@ dist_app_DATA =				\
- 	freeipa.template		\
- 	sshd_ipa.conf.template	\
- 	ssh_ipa.conf.template	\
-+	unbound.conf.template	\
- 	$(NULL)
- 
- epnconfdir = $(IPA_SYSCONF_DIR)
-diff --git a/client/share/unbound.conf.template b/client/share/unbound.conf.template
-new file mode 100644
-index 000000000..166036f65
---- /dev/null
-+++ b/client/share/unbound.conf.template
-@@ -0,0 +1,10 @@
-+server:
-+    tls-cert-bundle: $TLS_CERT_BUNDLE_PATH
-+    tls-upstream: yes
-+    interface: 127.0.0.55
-+    log-servfail: yes
-+forward-zone:
-+    name: "."
-+    forward-tls-upstream: yes
-+    forward-first: no
-+    $FORWARD_ADDRS
-diff --git a/install/share/bind.named.conf.template b/install/share/bind.named.conf.template
-index 01b77c5ae..b64a6a4b0 100644
---- a/install/share/bind.named.conf.template
-+++ b/install/share/bind.named.conf.template
-@@ -21,6 +21,8 @@ options {
- 
- 	managed-keys-directory "$MANAGED_KEYS_DIR";
- 
-+	$NAMED_DNS_OVER_TLS_OPTIONS_CONF
-+
- 	/* user customizations of options */
- 	include "$NAMED_CUSTOM_OPTIONS_CONF";
- 
-@@ -49,6 +51,8 @@ ${NAMED_ZONE_COMMENT}};
- include "$RFC1912_ZONES";
- include "$ROOT_KEY";
- 
-+$NAMED_DNS_OVER_TLS_CONF
-+
- /* user customization */
- include "$NAMED_CUSTOM_CONF";
- 
-diff --git a/install/tools/ipa-dns-install.in b/install/tools/ipa-dns-install.in
-index f1a90e7ac..0b0cd2be5 100644
---- a/install/tools/ipa-dns-install.in
-+++ b/install/tools/ipa-dns-install.in
-@@ -27,6 +27,7 @@ import sys
- 
- from ipaserver.install import bindinstance
- from ipaserver.install import installutils
-+from ipaplatform import services
- from ipapython import version
- from ipalib import api
- from ipaplatform.paths import paths
-@@ -56,6 +57,24 @@ def parse_options():
-     parser.add_option("--auto-forwarders", dest="auto_forwarders",
-                       action="store_true", default=False,
-                       help="Use DNS forwarders configured in /etc/resolv.conf")
-+    parser.add_option("--dns-over-tls", dest="dns_over_tls",
-+                      help="Configure DNS over TLS", default=False,
-+                      action="store_true")
-+    parser.add_option("--dot-forwarder", dest="dot_forwarders",
-+                      action="append",
-+                      default=[],
-+                      help="Add a DNS over TLS forwarder. "
-+                      "This option can be used multiple times")
-+    parser.add_option("--dns-over-tls-cert", dest="dns_over_tls_cert",
-+                      help="Certificate to use for DNS over TLS. "
-+                      "If empty, a new certificate will be requested "
-+                      "from IPA CA")
-+    parser.add_option("--dns-over-tls-key", dest="dns_over_tls_key",
-+                      help="Key for certificate specified "
-+                      "in --dns-over-tls-cert")
-+    parser.add_option("--dns-policy", dest="dns_policy",
-+                      choices=("relaxed", "enforced"), default="relaxed",
-+                      help="Policy for encrypted DNS enforcement")
-     parser.add_option("--forward-policy", dest="forward_policy",
-                       choices=("first", "only"), default=None,
-                       help="DNS forwarding policy for global forwarders")
-@@ -102,12 +121,37 @@ def parse_options():
-     elif options.auto_reverse and options.no_reverse:
-         parser.error("You cannot specify a --auto-reverse option together with --no-reverse")
- 
-+    if options.dns_policy == "enforced" and not options.dns_over_tls:
-+        parser.error("If encrypted DNS policy is enforced, "
-+                     "--dns-over-tls must be specified.")
-+
-+    unbound = services.knownservices["unbound"]
-+    if options.dns_over_tls and not unbound.is_installed():
-+        parser.error(
-+            "To enable DNS over TLS, package ipa-server-encrypted-dns "
-+            "must be installed."
-+        )
-+
-+    if not options.dns_over_tls and options.dot_forwarders:
-+        parser.error("You cannot specify a "
-+                     "--dot-forwarder option without --dns-over-tls")
-+    elif options.dns_over_tls and not options.dot_forwarders:
-+        parser.error(
-+            "You must specify --dot-forwarder "
-+            "when enabling DNS over TLS")
-+    elif bool(options.dns_over_tls_key) != bool(options.dns_over_tls_cert):
-+        parser.error(
-+            "You cannot specify a --dns-over-tls-key option "
-+            "without the --dns-over-tls-cert option and vice versa")
-+
-     if options.unattended:
-         if (not options.forwarders
--            and not options.no_forwarders
--            and not options.auto_forwarders):
-+                and not options.no_forwarders
-+                and not options.auto_forwarders
-+                and not options.dot_forwarders):
-             parser.error("You must specify at least one option: "
--                "--forwarder or --no-forwarders or --auto-forwarders")
-+                         "--forwarder or --no-forwarders or --auto-forwarders"
-+                         " or --dot-forwarder")
- 
-     if options.kasp_db_file and not os.path.isfile(options.kasp_db_file):
-         parser.error("File %s does not exist" % options.kasp_db_file)
-diff --git a/install/tools/man/ipa-dns-install.1 b/install/tools/man/ipa-dns-install.1
-index 029001eca..6008d2028 100644
---- a/install/tools/man/ipa-dns-install.1
-+++ b/install/tools/man/ipa-dns-install.1
-@@ -69,6 +69,22 @@ Allow creatin of (reverse) zone even if the zone is already resolvable. Using th
- .TP
- \fB\-U\fR, \fB\-\-unattended\fR
- An unattended installation that will never prompt for user input
-+.TP
-+\fB\-\-dns\-over\-tls\fR
-+Configure DNS over TLS.
-+.TP
-+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+.TP
-+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
-+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
-+.TP
-+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
-+Key for the certificate specified in --dns-over-tls-key.
-+.TP
-+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
-+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
-+unencrypted DNS queries will be allowed.
- .SH "DEPRECATED OPTIONS"
- .TP
- \fB\-p\fR \fIDM_PASSWORD\fR, \fB\-\-ds\-password\fR=\fIDM_PASSWORD\fR
-diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1
-index 3b1d25ba6..c55d21253 100644
---- a/install/tools/man/ipa-replica-install.1
-+++ b/install/tools/man/ipa-replica-install.1
-@@ -223,6 +223,22 @@ Do not automatically create DNS SSHFP records.
- .TP
- \fB\-\-no\-dnssec\-validation\fR
- Disable DNSSEC validation on this server.
-+.TP
-+\fB\-\-dns\-over\-tls\fR
-+Configure DNS over TLS.
-+.TP
-+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+.TP
-+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
-+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
-+.TP
-+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
-+Key for the certificate specified in --dns-over-tls-key.
-+.TP
-+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
-+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
-+unencrypted DNS queries will be allowed.
- 
- .SS "SID GENERATION OPTIONS"
- .TP
-diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
-index 215a77d6b..84d82531c 100644
---- a/install/tools/man/ipa-server-install.1
-+++ b/install/tools/man/ipa-server-install.1
-@@ -252,6 +252,22 @@ Disable DNSSEC validation on this server.
- .TP
- \fB\-\-allow\-zone\-overlap\fR
- Allow creation of (reverse) zone even if the zone is already resolvable. Using this option is discouraged as it result in later problems with domain name resolution.
-+.TP
-+\fB\-\-dns\-over\-tls\fR
-+Configure DNS over TLS.
-+.TP
-+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+.TP
-+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
-+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
-+.TP
-+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
-+Key for the certificate specified in --dns-over-tls-key.
-+.TP
-+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
-+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
-+unencrypted DNS queries will be allowed.
- 
- .SS "SID GENERATION OPTIONS"
- .TP
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 47a371f62..9e4d3bbe7 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -1002,6 +1002,11 @@ def configure_sssd_conf(
- 
-     if options.dns_updates:
-         domain.set_option('dyndns_update', True)
-+        if options.dns_over_tls:
-+            server_ip = str(list(dnsutil.resolve_ip_addresses(
-+                cli_server[0]))[0])
-+            domain.set_option('dyndns_server', 'dns+tls://{}:853#{}'
-+                              .format(server_ip, cli_server[0]))
-         if options.all_ip_addresses:
-             domain.set_option('dyndns_iface', '*')
-         else:
-@@ -1409,8 +1414,9 @@ def get_local_ipaddresses(iface=None):
-     return ips
- 
- 
--def do_nsupdate(update_txt):
-+def do_nsupdate(update_txt, options, server):
-     logger.debug("Writing nsupdate commands to %s:", UPDATE_FILE)
-+
-     logger.debug("%s", update_txt)
- 
-     with open(UPDATE_FILE, "w") as f:
-@@ -1418,12 +1424,20 @@ def do_nsupdate(update_txt):
- 
-     result = False
-     try:
--        ipautil.run([paths.NSUPDATE, '-g', UPDATE_FILE])
-+        if options.dns_over_tls:
-+            ipautil.run([paths.NSUPDATE, '-p', '853', '-S',
-+                         '-H', server, '-g', UPDATE_FILE])
-+        else:
-+            ipautil.run([paths.NSUPDATE, '-g', UPDATE_FILE])
-         result = True
-     except CalledProcessError as e:
-         logger.debug('nsupdate (GSS-TSIG) failed: %s', str(e))
-         try:
--            ipautil.run([paths.NSUPDATE, UPDATE_FILE])
-+            if options.dns_over_tls:
-+                ipautil.run([paths.NSUPDATE, '-p', '853', '-S',
-+                             '-H', server, '-g', UPDATE_FILE])
-+            else:
-+                ipautil.run([paths.NSUPDATE, UPDATE_FILE])
-             try:
-                 sssdconfig = SSSDConfig.SSSDConfig()
-                 sssdconfig.import_config()
-@@ -1525,6 +1539,8 @@ def update_dns(server, hostname, options):
-     no_matching_interface_for_ip_address_warning(update_ips)
- 
-     update_txt = "debug\n"
-+    if options.dns_over_tls:
-+        update_txt += "server %s 853" % server
-     update_txt += ipautil.template_str(DELETE_TEMPLATE_A,
-                                        dict(HOSTNAME=hostname))
-     update_txt += ipautil.template_str(DELETE_TEMPLATE_AAAA,
-@@ -1538,7 +1554,7 @@ def update_dns(server, hostname, options):
-             template = ADD_TEMPLATE_AAAA
-         update_txt += ipautil.template_str(template, sub_dict)
- 
--    if not do_nsupdate(update_txt):
-+    if not do_nsupdate(update_txt, options, server):
-         logger.error("Failed to update DNS records.")
-     verify_dns_update(hostname, update_ips)
- 
-@@ -1654,6 +1670,54 @@ def client_dns(server, hostname, options):
-                        hostname, ex)
-         dns_ok = False
- 
-+    # Setup DNS over TLS
-+    if options.dns_over_tls:
-+        # setup and enable Unbound as resolver
-+        server_ip = str(list(dnsutil.resolve_ip_addresses(server))[0])
-+        forward_addr = "forward-addr: %s#%s" % (server_ip, server)
-+        ipautil.copy_template_file(
-+            paths.UNBOUND_CONF_SRC,
-+            paths.UNBOUND_CONF,
-+            dict(
-+                TLS_CERT_BUNDLE_PATH=os.path.join(
-+                    paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
-+                FORWARD_ADDRS=forward_addr
-+            )
-+        )
-+        sr = services.knownservices["systemd-resolved"]
-+        if sr.is_running():
-+            sr.stop()
-+            sr.disable()
-+
-+        nm = services.knownservices["NetworkManager"]
-+        if nm.is_enabled():
-+            with open(paths.NETWORK_MANAGER_IPA_CONF, "w") as f:
-+                dns_none = [
-+                    "# auto-generated by IPA installer",
-+                    "[main]",
-+                    "dns=none\n"
-+                ]
-+                f.write("\n".join(dns_none))
-+            nm.reload_or_restart()
-+
-+        # Overwrite resolv.conf to point to Unbound
-+        cfg = [
-+            "# auto-generated by IPA installer",
-+            "search .",
-+            "nameserver 127.0.0.55\n"
-+        ]
-+        fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
-+        fstore.backup_file(paths.RESOLV_CONF)
-+        with open(paths.RESOLV_CONF, 'w') as f:
-+            f.write('\n'.join(cfg))
-+            os.chmod(paths.RESOLV_CONF, 0o644)
-+
-+        services.knownservices.unbound.enable()
-+        services.knownservices.unbound.restart()
-+        logger.info("DNS encryption support was enabled. "
-+                    "Unbound is configured to listen on 127.0.0.55:53 and "
-+                    "forward to upstream DoT servers.")
-+
-     if (
-         options.dns_updates or options.all_ip_addresses or
-         options.ip_addresses or not dns_ok
-@@ -1661,6 +1725,7 @@ def client_dns(server, hostname, options):
-         update_dns(server, hostname, options)
- 
- 
-+
- def check_ip_addresses(options):
-     if options.ip_addresses:
-         for ip in options.ip_addresses:
-@@ -1672,7 +1737,7 @@ def check_ip_addresses(options):
-     return True
- 
- 
--def update_ssh_keys(hostname, ssh_dir, create_sshfp):
-+def update_ssh_keys(hostname, ssh_dir, options, server):
-     if not os.path.isdir(ssh_dir):
-         return
- 
-@@ -1718,10 +1783,12 @@ def update_ssh_keys(hostname, ssh_dir, create_sshfp):
-         logger.warning("Failed to upload host SSH public keys.")
-         return
- 
--    if create_sshfp:
-+    if options.create_sshfp:
-         ttl = 1200
- 
-         update_txt = 'debug\n'
-+        if options.dns_over_tls:
-+            update_txt += "server %s 853" % server
-         update_txt += 'update delete %s. IN SSHFP\nshow\nsend\n' % hostname
-         for pubkey in pubkeys:
-             sshfp = pubkey.fingerprint_dns_sha1()
-@@ -1734,7 +1801,7 @@ def update_ssh_keys(hostname, ssh_dir, create_sshfp):
-                     hostname, ttl, sshfp)
-         update_txt += 'show\nsend\n'
- 
--        if not do_nsupdate(update_txt):
-+        if not do_nsupdate(update_txt, options, server):
-             logger.warning("Could not update DNS SSHFP records.")
- 
- 
-@@ -3163,7 +3230,7 @@ def _install(options, tdict):
-     if not options.on_master:
-         client_dns(cli_server[0], hostname, options)
- 
--    update_ssh_keys(hostname, paths.SSH_CONFIG_DIR, options.create_sshfp)
-+    update_ssh_keys(hostname, paths.SSH_CONFIG_DIR, options, cli_server[0])
- 
-     try:
-         os.remove(CCACHE_FILE)
-@@ -3988,6 +4055,12 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
-         if value < 1:
-             raise ValueError("expects an integer greater than 0.")
- 
-+    dns_over_tls = knob(
-+        None,
-+        description="Configure DNS over TLS",
-+    )
-+    dns_over_tls = enroll_only(dns_over_tls)
-+
-     request_cert = knob(
-         None,
-         deprecated=True,
-diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
-index b339d2202..b2da94992 100644
---- a/ipaplatform/base/paths.py
-+++ b/ipaplatform/base/paths.py
-@@ -102,6 +102,8 @@ class BasePathNamespace:
-     NAMED_ROOT_KEY = "/etc/named.root.key"
-     NAMED_MANAGED_KEYS_DIR = "/var/named/dynamic"
-     NAMED_CRYPTO_POLICY_FILE = None
-+    UNBOUND_CONF_SRC = '/usr/share/ipa/client/unbound.conf.template'
-+    UNBOUND_CONF = "/etc/unbound/conf.d/zzz-ipa.conf"
-     NSLCD_CONF = "/etc/nslcd.conf"
-     NSS_LDAP_CONF = "/etc/nss_ldap.conf"
-     NSSWITCH_CONF = "/etc/nsswitch.conf"
-@@ -225,6 +227,8 @@ class BasePathNamespace:
-     OPENSSL_DIR = "/etc/pki/tls"
-     OPENSSL_CERTS_DIR = "/etc/pki/tls/certs"
-     OPENSSL_PRIVATE_DIR = "/etc/pki/tls/private"
-+    BIND_DNS_OVER_TLS_CRT = "/etc/pki/tls/certs/bind_dot.crt"
-+    BIND_DNS_OVER_TLS_KEY = "/etc/pki/tls/private/bind_dot.key"
-     PK12UTIL = "/usr/bin/pk12util"
-     SOFTHSM2_UTIL = "/usr/bin/softhsm2-util"
-     SSLGET = "/usr/bin/sslget"
-diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py
-index 275422e30..fcb626aa3 100644
---- a/ipaplatform/base/services.py
-+++ b/ipaplatform/base/services.py
-@@ -53,7 +53,7 @@ wellknownservices = [
-     'named', 'ods_enforcerd', 'ods_signerd', 'gssproxy',
-     'nfs-utils', 'sssd', 'NetworkManager', 'ipa-custodia',
-     'ipa-dnskeysyncd', 'ipa-otpd', 'ipa-ods-exporter',
--    'systemd-resolved',
-+    'systemd-resolved', 'unbound',
- ]
- 
- # The common ports for these services. This is used to wait for the
-diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
-index c237d59fb..681f60655 100644
---- a/ipapython/ipautil.py
-+++ b/ipapython/ipautil.py
-@@ -266,6 +266,19 @@ class CheckedIPAddressLoopback(CheckedIPAddress):
-                   file=sys.stderr)
- 
- 
-+class IPAddressDoTForwarder(str):
-+    """IPv4 or IPv6 address with added hostname as needed for DNS over TLS
-+    configuration. Example: 1.2.3.4#dns.hostname.test
-+    """
-+    def __init__(self, addr):
-+        addr_split = addr.split("#")
-+        if len(addr_split) != 2 or not valid_ip(addr_split[0]):
-+            raise ValueError(
-+                "DoT forwarder must be in the format "
-+                "of '1.2.3.4#dns.example.test'."
-+            )
-+
-+
- def valid_ip(addr):
-     return netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr)
- 
-diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
-index 939f5f21f..4f4ab9bbc 100644
---- a/ipaserver/install/bindinstance.py
-+++ b/ipaserver/install/bindinstance.py
-@@ -28,6 +28,7 @@ import re
- import shutil
- import sys
- import time
-+import textwrap
- 
- import ldap
- import six
-@@ -50,7 +51,7 @@ from ipapython.admintool import ScriptError
- import ipalib
- from ipalib import api, errors
- from ipalib.constants import IPA_CA_RECORD
--from ipalib.install import dnsforwarders
-+from ipalib.install import dnsforwarders, certmonger
- from ipaplatform import services
- from ipaplatform.tasks import tasks
- from ipaplatform.constants import constants
-@@ -668,14 +669,20 @@ class BindInstance(service.Service):
- 
-     def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders,
-               forward_policy, reverse_zones, zonemgr=None,
--              no_dnssec_validation=False):
-+              no_dnssec_validation=False, dns_over_tls=False,
-+              dns_over_tls_cert=None, dns_over_tls_key=None,
-+              dns_policy=None):
-         """Setup bindinstance for installation
-         """
-         self.setup_templating(
-             fqdn=fqdn,
-             realm_name=realm_name,
-             domain_name=domain_name,
--            no_dnssec_validation=no_dnssec_validation
-+            no_dnssec_validation=no_dnssec_validation,
-+            dns_over_tls=dns_over_tls,
-+            dns_over_tls_cert=dns_over_tls_cert,
-+            dns_over_tls_key=dns_over_tls_key,
-+            dns_policy=dns_policy
-         )
-         self.ip_addresses = ip_addresses
-         self.forwarders = forwarders
-@@ -688,7 +695,9 @@ class BindInstance(service.Service):
-             self.zonemgr = normalize_zonemgr(zonemgr)
- 
-     def setup_templating(
--        self, fqdn, realm_name, domain_name, no_dnssec_validation=None
-+        self, fqdn, realm_name, domain_name, no_dnssec_validation=None,
-+        dns_over_tls=None, dns_over_tls_cert=None, dns_over_tls_key=None,
-+        dns_policy=None
-     ):
-         """Setup bindinstance for templating
-         """
-@@ -698,6 +707,10 @@ class BindInstance(service.Service):
-         self.host = fqdn.split(".")[0]
-         self.suffix = ipautil.realm_to_suffix(self.realm)
-         self.no_dnssec_validation = no_dnssec_validation
-+        self.dns_over_tls = dns_over_tls
-+        self.dns_over_tls_cert = dns_over_tls_cert
-+        self.dns_over_tls_key = dns_over_tls_key
-+        self.dns_policy = dns_policy
-         self._setup_sub_dict()
- 
-     @property
-@@ -872,6 +885,24 @@ class BindInstance(service.Service):
-         else:
-             crypto_policy = "// not available"
- 
-+        if self.dns_over_tls:
-+            named_tls_conf = textwrap.dedent("""\
-+                tls local-tls {{
-+                    \tkey-file "{}";
-+                    \tcert-file "{}";
-+                }};
-+            """).format(self.dns_over_tls_key, self.dns_over_tls_cert)
-+            unencrypted_iface = ("127.0.0.1" if self.dns_policy == "enforced"
-+                                 else "any")
-+            named_tls_options = textwrap.dedent("""\
-+                \tlisten-on { %s; };
-+                \tlisten-on tls local-tls { any; };
-+                \tlisten-on-v6 tls local-tls { any; };
-+            """ % unencrypted_iface)
-+        else:
-+            named_tls_options = ""
-+            named_tls_conf = ""
-+
-         self.sub_dict = dict(
-             FQDN=self.fqdn,
-             SERVER_ID=ipaldap.realm_to_serverid(self.realm),
-@@ -891,6 +922,8 @@ class BindInstance(service.Service):
-             NAMED_DATA_DIR=constants.NAMED_DATA_DIR,
-             NAMED_ZONE_COMMENT=constants.NAMED_ZONE_COMMENT,
-             NAMED_DNSSEC_VALIDATION=self._get_dnssec_validation(),
-+            NAMED_DNS_OVER_TLS_OPTIONS_CONF=named_tls_options,
-+            NAMED_DNS_OVER_TLS_CONF=named_tls_conf,
-         )
- 
-     def __setup_dns_container(self):
-@@ -1344,6 +1377,11 @@ class BindInstance(service.Service):
- 
-         self.named_conflict.unmask()
- 
-+        certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_CRT)
-+        certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_KEY)
-+        services.knownservices.unbound.disable()
-+        services.knownservices.unbound.stop()
-+
-         ipautil.remove_file(paths.NAMED_CONF_BAK)
-         ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
-         ipautil.remove_file(paths.NAMED_CUSTOM_OPTIONS_CONF)
-@@ -1357,6 +1395,8 @@ class BindInstance(service.Service):
-                 pass
-         except ValueError:
-             pass
-+        ipautil.remove_file(paths.BIND_DNS_OVER_TLS_CRT)
-+        ipautil.remove_file(paths.BIND_DNS_OVER_TLS_KEY)
-         ipautil.remove_keytab(self.keytab)
- 
-         ipautil.remove_ccache(run_as=self.service_user)
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 47d79af9c..29ca0d2ff 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -20,14 +20,18 @@ from subprocess import CalledProcessError
- from ipalib import api
- from ipalib import errors
- from ipalib import util
--from ipalib.install import hostname, sysrestore
-+from ipalib import x509
-+from ipalib.install import hostname, sysrestore, certmonger
- from ipalib.install.service import enroll_only, prepare_only
- from ipalib.install import dnsforwarders
-+from ipalib.constants import FQDN
- from ipaplatform.paths import paths
- from ipaplatform.constants import constants
- from ipaplatform import services
-+from ipapython import admintool
- from ipapython import ipautil
- from ipapython import dnsutil
-+from ipapython.certdb import EXTERNAL_CA_TRUST_FLAGS
- from ipapython.dn import DN
- from ipapython.dnsutil import check_zone_overlap
- from ipapython.install import typing
-@@ -37,7 +41,9 @@ from ipapython.ipautil import user_input
- from ipaserver.install.installutils import get_server_ip_address
- from ipaserver.install.installutils import read_dns_forwarders
- from ipaserver.install.installutils import update_hosts_file
-+from ipaserver.install.installutils import default_subject_base
- from ipaserver.install import bindinstance
-+from ipaserver.install import certs
- from ipaserver.install import dnskeysyncinstance
- from ipaserver.install import odsexporterinstance
- from ipaserver.install import opendnssecinstance
-@@ -108,6 +114,73 @@ def _disable_dnssec():
-             conn.update_entry(entry)
- 
- 
-+def _setup_dns_over_tls(options):
-+    if os.path.isfile(paths.IPA_CA_CRT) and not options.dns_over_tls_cert:
-+        # request certificate for DNS over TLS, using IPA CA
-+        cert = paths.BIND_DNS_OVER_TLS_CRT
-+        key = paths.BIND_DNS_OVER_TLS_KEY
-+        certmonger.request_and_wait_for_cert(
-+            certpath=(cert, key),
-+            principal='DNS/%s@%s' % (FQDN, api.env.realm),
-+            subject=str(DN(('CN', FQDN), default_subject_base(api.env.realm))),
-+            storage="FILE"
-+        )
-+        constants.NAMED_USER.chown(cert, gid=constants.NAMED_GROUP.gid)
-+        constants.NAMED_USER.chown(key, gid=constants.NAMED_GROUP.gid)
-+
-+    # setup and enable Unbound as resolver
-+    forward_addrs = ["# forward-addr: specify here forwarders"]
-+    if options.dot_forwarders:
-+        forward_addrs = ["forward-addr: %s" % fw
-+                         for fw in options.dot_forwarders]
-+    ipautil.copy_template_file(
-+        paths.UNBOUND_CONF_SRC,
-+        paths.UNBOUND_CONF,
-+        dict(
-+            TLS_CERT_BUNDLE_PATH=os.path.join(
-+                paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
-+            FORWARD_ADDRS="\n".join(forward_addrs)
-+        )
-+    )
-+
-+    sr = services.knownservices["systemd-resolved"]
-+    if sr.is_running():
-+        sr.stop()
-+        sr.disable()
-+
-+    api.Command.dnsserver_mod(
-+        FQDN,
-+        idnsforwarders="127.0.0.55",
-+        idnsforwardpolicy="first"
-+    )
-+
-+    nm = services.knownservices["NetworkManager"]
-+    if nm.is_enabled():
-+        with open(paths.NETWORK_MANAGER_IPA_CONF, "w") as f:
-+            dns_none = [
-+                "# auto-generated by IPA installer",
-+                "[main]",
-+                "dns=none\n"
-+            ]
-+            f.write("\n".join(dns_none))
-+        nm.reload_or_restart()
-+
-+    # Overwrite resolv.conf to point to IPA
-+    cfg = [
-+        "# auto-generated by IPA installer",
-+        "search .",
-+        "nameserver 127.0.0.1\n"
-+    ]
-+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
-+    fstore.backup_file(paths.RESOLV_CONF)
-+    with open(paths.RESOLV_CONF, 'w') as f:
-+        f.write('\n'.join(cfg))
-+        os.chmod(paths.RESOLV_CONF, 0o644)
-+
-+    services.knownservices.unbound.enable()
-+    services.knownservices.unbound.restart()
-+
-+
- def package_check(exception):
-     if not os.path.isfile(paths.IPA_DNS_INSTALL):
-         raise exception(
-@@ -287,9 +360,14 @@ def install_check(standalone, api, replica, options, hostname):
- 
-     if options.no_forwarders:
-         options.forwarders = []
--    elif options.forwarders or options.auto_forwarders:
-+    elif (options.forwarders
-+          or options.dot_forwarders or options.auto_forwarders):
-         if not options.forwarders:
--            options.forwarders = []
-+            if options.dot_forwarders:
-+                options.forwarders = [fw.split("#")[0]
-+                                      for fw in options.dot_forwarders]
-+            else:
-+                options.forwarders = []
-         if options.auto_forwarders:
-             options.forwarders.extend(dnsforwarders.get_nameservers())
-     elif standalone or not replica:
-@@ -330,11 +408,46 @@ def install(standalone, replica, options, api=api):
-         # otherwise this is done by server/replica installer
-         update_hosts_file(ip_addresses, api.env.host, fstore)
- 
-+    if os.path.isfile(paths.IPA_CA_CRT) and not options.dns_over_tls_cert:
-+        dot_cert = paths.BIND_DNS_OVER_TLS_CRT
-+        dot_key = paths.BIND_DNS_OVER_TLS_KEY
-+    elif options.dns_over_tls_cert and options.dns_over_tls_key:
-+        # Check certificate validity first
-+        with certs.NSSDatabase() as tmpdb:
-+            tmpdb.create_db()
-+            ca_certs = x509.load_certificate_list_from_file(
-+                options.dns_over_tls_cert)
-+            nicknames = []
-+            for ca_cert in ca_certs:
-+                nicknames.append(str(DN(ca_cert.subject)))
-+                tmpdb.add_cert(
-+                    ca_cert, str(DN(ca_cert.subject)), EXTERNAL_CA_TRUST_FLAGS)
-+            try:
-+                for nick in nicknames:
-+                    tmpdb.verify_ca_cert_validity(nick)
-+            except ValueError as e:
-+                raise admintool.ScriptError(
-+                    "Not a valid CA certificate: %s" % e)
-+        dot_cert = options.dns_over_tls_cert
-+        dot_key = options.dns_over_tls_key
-+    else:
-+        raise RuntimeError(
-+            "Certificate for DNS over TLS not specified "
-+            "and IPA CA is not present."
-+        )
-+
-+    if not options.forwarders and options.dot_forwarders:
-+        options.forwaders = [fw.split("#")[0] for fw in options.dot_forwarders]
-+
-     bind = bindinstance.BindInstance(fstore, api=api)
-     bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
-                options.forwarders, options.forward_policy,
-                reverse_zones, zonemgr=options.zonemgr,
--               no_dnssec_validation=options.no_dnssec_validation)
-+               no_dnssec_validation=options.no_dnssec_validation,
-+               dns_over_tls=options.dns_over_tls,
-+               dns_over_tls_cert=dot_cert,
-+               dns_over_tls_key=dot_key,
-+               dns_policy=options.dns_policy)
- 
-     if standalone and not options.unattended:
-         print("")
-@@ -343,6 +456,11 @@ def install(standalone, replica, options, api=api):
-         print("")
- 
-     bind.create_instance()
-+
-+    if options.dns_over_tls:
-+        print("Setting up DNS over TLS")
-+        _setup_dns_over_tls(options)
-+
-     print("Restarting the web server to pick up resolv.conf changes")
-     services.knownservices.httpd.restart(capture_output=True)
- 
-@@ -370,6 +488,12 @@ def install(standalone, replica, options, api=api):
-     bind.update_system_records()
- 
-     if standalone:
-+        if options.dns_over_tls and options.dns_policy == "enforced":
-+            dns_port = "853"
-+        elif options.dns_over_tls:
-+            dns_port = "53, 853"
-+        else:
-+            dns_port = "53"
-         print("==============================================================================")
-         print("Setup complete")
-         print("")
-@@ -378,14 +502,22 @@ def install(standalone, replica, options, api=api):
-         print("")
-         print("\tYou must make sure these network ports are open:")
-         print("\t\tTCP Ports:")
--        print("\t\t  * 53: bind")
-+        print(f"\t\t  * {dns_port}: bind")
-         print("\t\tUDP Ports:")
--        print("\t\t  * 53: bind")
-+        print(f"\t\t  * {dns_port}: bind")
-     elif not standalone and replica:
-         print("")
-         bind.check_global_configuration()
-         print("")
- 
-+    if options.dns_over_tls:
-+        policy = "enforced" if options.dns_policy == "enforced" else "relaxed"
-+        print("")
-+        print(("DNS encryption support was enabled "
-+               "with policy '{}'.".format(policy)))
-+        print(("Unbound is configured to listen on 127.0.0.55:53 and "
-+               "forward to upstream DoT servers."))
-+
- 
- def uninstall_check(options):
-     # test if server is DNSSEC key master
-@@ -424,6 +556,10 @@ class DNSForwardPolicy(enum.Enum):
-     FIRST = 'first'
- 
- 
-+class EncryptedDNSPolicy(enum.Enum):
-+    RELAXED = 'relaxed'
-+    ENFORCED = 'enforced'
-+
- @group
- class DNSInstallInterface(hostname.HostNameInstallInterface):
-     """
-@@ -536,6 +672,40 @@ class DNSInstallInterface(hostname.HostNameInstallInterface):
-     )
-     no_dnssec_validation = enroll_only(no_dnssec_validation)
- 
-+    dns_over_tls = knob(
-+        None,
-+        description="Configure DNS over TLS",
-+    )
-+    dns_over_tls = enroll_only(dns_over_tls)
-+
-+    dot_forwarders = knob(
-+        typing.List[ipautil.IPAddressDoTForwarder], None,
-+        description=("Add a DNS over TLS forwarder. "
-+                     "This option can be used multiple times"),
-+        cli_names='--dot-forwarder',
-+    )
-+    dot_forwarders = enroll_only(dot_forwarders)
-+
-+    dns_over_tls_cert = knob(
-+        str, None,
-+        description=("Certificate to use for DNS over TLS. "
-+                     "If empty, a new certificate will be "
-+                     "requested from IPA CA"),
-+    )
-+    dns_over_tls_cert = enroll_only(dns_over_tls_cert)
-+
-+    dns_over_tls_key = knob(
-+        str, None,
-+        description="Key for certificate specified in --dns-over-tls-cert",
-+    )
-+    dns_over_tls_key = enroll_only(dns_over_tls_key)
-+
-+    dns_policy = knob(
-+        EncryptedDNSPolicy, 'relaxed',
-+        description=("Encrypted DNS policy"),
-+    )
-+    dns_policy = enroll_only(dns_policy)
-+
-     dnssec_master = False
-     disable_dnssec_master = False
-     kasp_db_file = None
-diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
-index 857b08f9f..c6a88585a 100644
---- a/ipaserver/install/server/__init__.py
-+++ b/ipaserver/install/server/__init__.py
-@@ -21,6 +21,7 @@ from ipalib.install.service import (enroll_only,
- from ipapython.install import typing
- from ipapython.install.core import group, knob, extend_knob
- from ipapython.install.common import step
-+from ipaplatform import services
- 
- from .install import validate_admin_password, validate_dm_password
- from .install import get_min_idstart
-@@ -442,6 +443,18 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
-                 raise RuntimeError(
-                     "You cannot specify a --no-dnssec-validation option "
-                     "without the --setup-dns option")
-+            if self.dot_forwarders:
-+                raise RuntimeError(
-+                    "You cannot specify a --dot-forwarder option "
-+                    "without the --setup-dns option")
-+            if self.dns_over_tls_cert:
-+                raise RuntimeError(
-+                    "You cannot specify a --dns-over-tls-cert option "
-+                    "without the --setup-dns option")
-+            if self.dns_over_tls_key:
-+                raise RuntimeError(
-+                    "You cannot specify a --dns-over-tls-key option "
-+                    "without the --setup-dns option")
-         elif self.forwarders and self.no_forwarders:
-             raise RuntimeError(
-                 "You cannot specify a --forwarder option together with "
-@@ -458,7 +471,32 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
-             raise RuntimeError(
-                 "You cannot specify a --auto-reverse option together with "
-                 "--no-reverse")
--
-+        elif self.dot_forwarders and not self.dns_over_tls:
-+            raise RuntimeError(
-+                "You cannot specify a --dot-forwarder option "
-+                "without the --dns-over-tls option")
-+        elif (self.dns_over_tls
-+              and not services.knownservices["unbound"].is_installed()):
-+            raise RuntimeError(
-+                "To enable DNS over TLS, package ipa-server-encrypted-dns "
-+                "must be installed."
-+            )
-+        elif self.dns_policy == "enforced" and not self.dns_over_tls:
-+            raise RuntimeError(
-+                "You cannot specify a --dns-policy option "
-+                "without the --dns-over-tls option")
-+        elif self.dns_over_tls_cert and not self.dns_over_tls:
-+            raise RuntimeError(
-+                "You cannot specify a --dns-over-tls-cert option "
-+                "without the --dns-over-tls option")
-+        elif self.dns_over_tls_key and not self.dns_over_tls:
-+            raise RuntimeError(
-+                "You cannot specify a --dns-over-tls-key option "
-+                "without the --dns-over-tls option")
-+        elif bool(self.dns_over_tls_key) != bool(self.dns_over_tls_cert):
-+            raise RuntimeError(
-+                "You cannot specify a --dns-over-tls-key option "
-+                "without the --dns-over-tls-cert option and vice versa")
-         if not self.setup_adtrust:
-             if self.add_agents:
-                 raise RuntimeError(
-@@ -504,12 +542,18 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
-                         "In unattended mode you need to provide at least -r, "
-                         "-p and -a options")
-                 if self.setup_dns:
--                    if (not self.forwarders and
--                            not self.no_forwarders and
--                            not self.auto_forwarders):
-+                    if (not self.forwarders
-+                            and not self.no_forwarders
-+                            and not self.auto_forwarders
-+                            and not self.dot_forwarders):
-                         raise RuntimeError(
-                             "You must specify at least one of --forwarder, "
--                            "--auto-forwarders, or --no-forwarders options")
-+                            "--auto-forwarders, --dot-forwarder or "
-+                            "--no-forwarders options")
-+                    elif self.dns_over_tls and not self.dot_forwarders:
-+                        raise RuntimeError(
-+                            "You must specify --dot-forwarder "
-+                            "when enabling DNS over TLS")
- 
-             any_ignore_option_true = any(
-                 [self.ignore_topology_disconnect, self.ignore_last_of_role])
-@@ -541,10 +585,12 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
-             if self.setup_dns:
-                 if (not self.forwarders and
-                         not self.no_forwarders and
--                        not self.auto_forwarders):
-+                        not self.auto_forwarders
-+                        and not self.dot_forwarders):
-                     raise RuntimeError(
-                         "You must specify at least one of --forwarder, "
--                        "--auto-forwarders, or --no-forwarders options")
-+                        "--auto-forwarders, --dot-forwarder, "
-+                        "or --no-forwarders options")
- 
- 
- ServerMasterInstallInterface = installs_master(ServerInstallInterface)
-diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
-index c39c807a9..4354683f1 100644
---- a/ipaserver/install/server/install.py
-+++ b/ipaserver/install/server/install.py
-@@ -1018,6 +1018,10 @@ def install(installer):
- 
-     if options.setup_dns:
-         dns.install(False, False, options)
-+    elif options.dns_over_tls:
-+        service.print_msg("Warning: --dns-over-tls option "
-+                          "specified without --setup-dns, ignoring")
-+        options.dns_over_tls = False
- 
-     # Always call adtrust installer to configure SID generation
-     # if --setup-adtrust is not specified, only the SID part is executed
-@@ -1089,12 +1093,16 @@ def install(installer):
-     print("\t\t  * 80, 443: HTTP/HTTPS")
-     print("\t\t  * 389, 636: LDAP/LDAPS")
-     print("\t\t  * 88, 464: kerberos")
--    if options.setup_dns:
--        print("\t\t  * 53: bind")
-+    if options.dns_over_tls and options.dns_policy == "enforced":
-+        dns_port = "853"
-+    elif options.dns_over_tls:
-+        dns_port = "53, 853"
-+    else:
-+        dns_port = "53"
-+    print(f"\t\t  * {dns_port}: bind")
-     print("\t\tUDP Ports:")
-     print("\t\t  * 88, 464: kerberos")
--    if options.setup_dns:
--        print("\t\t  * 53: bind")
-+    print(f"\t\t  * {dns_port}: bind")
-     if not options.no_ntp:
-         print("\t\t  * 123: ntp")
-     print("")
-@@ -1109,6 +1117,14 @@ def install(installer):
-         print("\t   and servers for correct operation. You should consider "
-               "enabling chronyd.")
- 
-+    if options.dns_over_tls:
-+        policy = "enforced" if options.dns_policy == "enforced" else "relaxed"
-+        print("")
-+        print(("DNS encryption support was enabled "
-+               "with policy '{}'.".format(policy)))
-+        print(("Unbound is configured to listen on 127.0.0.55:53 and "
-+               "forward to upstream DoT servers."))
-+
-     print("")
-     if setup_ca and not options.token_name:
-         print(("Be sure to back up the CA certificates stored in " +
-diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
-index eeaaacb65..1f2c81f85 100644
---- a/ipaserver/install/server/replicainstall.py
-+++ b/ipaserver/install/server/replicainstall.py
-@@ -722,6 +722,8 @@ def ensure_enrolled(installer):
-             args.extend(("--ntp-server", server))
-     if installer.ntp_pool:
-         args.extend(("--ntp-pool", installer.ntp_pool))
-+    if installer.dns_over_tls and not installer.setup_dns:
-+        args.append("--dns-over-tls")
- 
-     try:
-         # Call client install script
--- 
-2.49.0
-
-
-From 186d5f65dc57dba3bb027fa4c5c4cb1603ce305a Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Wed, 11 Dec 2024 13:38:28 +0100
-Subject: [PATCH 083/116] ipatests: add tests for DNS over TLS
-
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
-Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
----
- ipatests/test_integration/test_edns.py | 257 +++++++++++++++++++++++++
- 1 file changed, 257 insertions(+)
- create mode 100644 ipatests/test_integration/test_edns.py
-
-diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
-new file mode 100644
-index 000000000..b42570ffa
---- /dev/null
-+++ b/ipatests/test_integration/test_edns.py
-@@ -0,0 +1,257 @@
-+#
-+# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
-+#
-+"""This covers tests for DNS over TLS related feature"""
-+
-+from __future__ import absolute_import
-+import textwrap
-+
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+from ipatests.test_integration.test_dns import TestDNS
-+from ipatests.pytest_ipa.integration.firewall import Firewall
-+from ipaplatform.paths import paths
-+
-+
-+class TestDNSOverTLS(IntegrationTest):
-+    """Tests for DNS over TLS feature."""
-+
-+    topology = 'line'
-+    num_replicas = 1
-+    num_clients = 1
-+
-+    @classmethod
-+    def install(cls, mh):
-+        Firewall(cls.master).enable_service("dns-over-tls")
-+        Firewall(cls.replicas[0]).enable_service("dns-over-tls")
-+        tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
-+        tasks.install_packages(cls.replicas[0], ['*ipa-server-encrypted-dns'])
-+        tasks.install_packages(cls.clients[0], ['*ipa-client-encrypted-dns'])
-+
-+    def test_install_dnsovertls_invalid_ca(self):
-+        """
-+        This test checks that the installers throws an error
-+        when invalid cert is specified.
-+        """
-+        bad_ca_cnf = textwrap.dedent("""
-+        [ req ]
-+        x509_extensions = v3_ca
-+        [ v3_ca ]
-+        basicConstraints = critical,CA:false
-+        """)
-+        self.master.put_file_contents("/bad_ca.cnf", bad_ca_cnf)
-+        self.master.run_command(["openssl", "req", "-newkey", "rsa:2048",
-+                                 "-nodes", "-keyout",
-+                                 "/etc/pki/tls/certs/privkey-invalid.pem",
-+                                 "-x509", "-days", "36500", "-out",
-+                                 "/etc/pki/tls/certs/certificate-invalid.pem",
-+                                 "-subj",
-+                                 ("/C=ES/ST=Andalucia/L=Sevilla/O=CompanyName/"
-+                                  "OU=IT/CN=www.example.com/"
-+                                  "emailAddress=email@example.com"),
-+                                 "-config", "/bad_ca.cnf"])
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
-+            "--dns-over-tls-cert",
-+            "/etc/pki/tls/certs/certificate-invalid.pem",
-+            "--dns-over-tls-key",
-+            "/etc/pki/tls/certs/privkey-invalid.pem"
-+        ]
-+        res = tasks.install_master(self.master, extra_args=args,
-+                                   raiseonerr=False)
-+        assert "Not a valid CA certificate: " in res.stderr_text
-+        tasks.uninstall_master(self.master)
-+
-+    def test_install_dnsovertls_without_setup_dns_master(self):
-+        """
-+        This test installs an IPA server using the --dns-over-tls option
-+        without using setup-dns option, and captures warnings that appear.
-+        """
-+        self.master.run_command(["ipa-server-install", "--uninstall", "-U"])
-+        args = [
-+            "--dns-over-tls",
-+        ]
-+        res = tasks.install_master(
-+            self.master, extra_args=args, setup_dns=False)
-+        assert ("Warning: --dns-over-tls option specified without "
-+                "--setup-dns, ignoring") in res.stdout_text
-+        tasks.uninstall_master(self.master)
-+
-+    def test_install_dnsovertls_master(self):
-+        """
-+        This tests installs IPA server with --dns-over-tls option.
-+        """
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
-+        ]
-+        return tasks.install_master(self.master, extra_args=args)
-+
-+    def test_install_dnsovertls_client(self):
-+        """
-+        This tests installs IPA client with --dns-over-tls option.
-+        """
-+        self.clients[0].put_file_contents(
-+            paths.RESOLV_CONF,
-+            "nameserver %s" % self.master.ip
-+        )
-+        args = [
-+            "--dns-over-tls"
-+        ]
-+        return tasks.install_client(self.master,
-+                                    self.clients[0],
-+                                    nameservers=None,
-+                                    extra_args=args)
-+
-+    def test_install_dnsovertls_replica(self):
-+        """
-+        This tests installs IPA replica with --dns-over-tls option.
-+        """
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
-+        ]
-+        return tasks.install_replica(self.master, self.replicas[0],
-+                                     setup_dns=True, extra_args=args)
-+
-+    def test_queries_encrypted(self):
-+        """
-+        This test performs queries from each of the hosts
-+        and ensures they were routed to 1.1.1.1#853 (eDNS).
-+        """
-+        unbound_log_cfg = textwrap.dedent("""
-+        server:
-+            verbosity: 3
-+            log-queries: yes
-+        """)
-+        # Test servers first (querying to local Unbound)
-+        for server in [self.master, self.replicas[0]]:
-+            server.put_file_contents("/etc/unbound/conf.d/log.conf",
-+                                     unbound_log_cfg)
-+            server.run_command(["systemctl", "restart", "unbound"])
-+            server.run_command(["journalctl", "--flush", "--rotate",
-+                                "--vacuum-time=1s"])
-+            server.run_command(["dig", "freeipa.org"])
-+            server.run_command(["journalctl", "-u", "unbound",
-+                                "--grep=1.1.1.1#853"])
-+            server.run_command(["journalctl", "--flush", "--rotate",
-+                                "--vacuum-time=1s"])
-+        # Now, test the client (redirects query to master)
-+        self.clients[0].run_command(["dig", "redhat.com"])
-+        self.master.run_command(["journalctl", "-u", "unbound",
-+                                 "--grep=1.1.1.1#853"])
-+
-+    def test_uninstall_all(self):
-+        """
-+        This test ensures that all hosts can be uninstalled correctly.
-+        """
-+        tasks.uninstall_client(self.clients[0])
-+        tasks.uninstall_replica(self.master, self.replicas[0])
-+        tasks.uninstall_master(self.master)
-+
-+    def test_install_dnsovertls_master_external_ca(self):
-+        """
-+        This test ensures that IPA server can be installed
-+        with DoT using an external CA.
-+        """
-+        self.master.run_command(["openssl", "req", "-newkey", "rsa:2048",
-+                                 "-nodes", "-keyout",
-+                                 "/etc/pki/tls/certs/privkey.pem", "-x509",
-+                                 "-days", "36500", "-out",
-+                                 "/etc/pki/tls/certs/certificate.pem", "-subj",
-+                                 ("/C=ES/ST=Andalucia/L=Sevilla/O=CompanyName/"
-+                                  "OU=IT/CN={}/"
-+                                  "emailAddress=email@example.com")
-+                                 .format(self.master.hostname)])
-+        self.master.run_command(["chown", "named:named",
-+                                 "/etc/pki/tls/certs/privkey.pem",
-+                                 "/etc/pki/tls/certs/certificate.pem"])
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
-+            "--dns-over-tls-cert", "/etc/pki/tls/certs/certificate.pem",
-+            "--dns-over-tls-key", "/etc/pki/tls/certs/privkey.pem"
-+        ]
-+        return tasks.install_master(self.master, extra_args=args)
-+
-+    def test_enrollments_external_ca(self):
-+        """
-+        Test that replicas and clients can be deployed when the master
-+        uses an external CA.
-+        """
-+        tasks.copy_files(self.master, self.clients[0],
-+                         ["/etc/pki/tls/certs/certificate.pem"])
-+        self.clients[0].run_command(["mv",
-+                                     "/etc/pki/tls/certs/certificate.pem",
-+                                     "/etc/pki/ca-trust/source/anchors/"])
-+        self.clients[0].run_command(["update-ca-trust", "extract"])
-+        self.test_install_dnsovertls_client()
-+        self.test_install_dnsovertls_replica()
-+        self.test_queries_encrypted()
-+
-+    def test_install_dnsovertls_with_invalid_ipaddress_master(self):
-+        """
-+        This test installs an IPA server using the --dns-over-tls
-+        option with an invalid IP address.
-+        """
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "198.168.0.0.1#example-dns.test",
-+        ]
-+        res = tasks.install_master(self.master, extra_args=args,
-+                                   raiseonerr=False)
-+        assert ("--dot-forwarder invalid: DoT forwarder must be in "
-+                "the format of '1.2.3.4#dns.example.test'") in res.stderr_text
-+        tasks.uninstall_master(self.master)
-+
-+    def test_validate_DoT_options_master(self):
-+        """
-+        Tests that DoT options are displayed correctly on master.
-+        """
-+        cmdout = self.master.run_command(
-+            ['ipa-server-install', '--help'])
-+        assert '''--dot-forwarder=DOT_FORWARDERS
-+                        Add a DNS over TLS forwarder. This option can be used
-+                        multiple times''' in cmdout.stdout_text  # noqa: E501
-+        assert '''--dns-over-tls-cert=DNS_OVER_TLS_CERT
-+                        Certificate to use for DNS over TLS. If empty, a new
-+                        certificate will be requested from IPA CA''' in cmdout.stdout_text  # noqa: E501
-+        assert '''--dns-over-tls-key=DNS_OVER_TLS_KEY
-+                        Key for certificate specified in --dns-over-tls-cert''' in cmdout.stdout_text  # noqa: E501
-+        assert '''--dns-over-tls      Configure DNS over TLS''' in cmdout.stdout_text  # noqa: E501
-+
-+    def test_validate_DoT_options_replica(self):
-+        """
-+        Tests that DoT options are displayed correctly on replica.
-+        """
-+        cmdout = self.replicas[0].run_command(
-+            ['ipa-server-install', '--help'])
-+        assert '''--dot-forwarder=DOT_FORWARDERS
-+                        Add a DNS over TLS forwarder. This option can be used
-+                        multiple times''' in cmdout.stdout_text
-+        assert '''--dns-over-tls-cert=DNS_OVER_TLS_CERT
-+                        Certificate to use for DNS over TLS. If empty, a new
-+                        certificate will be requested from IPA CA''' in cmdout.stdout_text  # noqa: E501
-+        assert '''--dns-over-tls-key=DNS_OVER_TLS_KEY
-+                        Key for certificate specified in --dns-over-tls-cert''' in cmdout.stdout_text  # noqa: E501
-+        assert '''--dns-over-tls      Configure DNS over TLS''' in cmdout.stdout_text  # noqa: E501
-+
-+    def test_validate_DoT_options_client(self):
-+        """
-+        Tests that DoT options are displayed correctly on client.
-+        """
-+        cmdout = self.clients[0].run_command(
-+            ['ipa-client-install', '--help'])
-+        assert '''--dns-over-tls      Configure DNS over TLS''' in cmdout.stdout_text  # noqa: E501
-+
-+
-+class TestDNS_DoT(TestDNS):
-+    @classmethod
-+    def install(cls, mh):
-+        tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
-+        args = [
-+            "--dns-over-tls",
-+            "--dot-forwarder", "1.1.1.1#cloudflare-dns.com"
-+        ]
-+        tasks.install_master(cls.master, extra_args=args)
--- 
-2.49.0
-
-
-From a32b8fda893ae00bcd8efd91339dc8dbe35fc3cd Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Wed, 11 Dec 2024 13:35:29 +0100
-Subject: [PATCH 084/116] spec: add unbound requirement and template file
-
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
-Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
----
- freeipa.spec.in | 27 +++++++++++++++++++++++++++
- 1 file changed, 27 insertions(+)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 4b91aa96f..b539f51f8 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -628,6 +628,7 @@ Requires: openssl-pkcs11 >= %{openssl_pkcs11_version}
- # See https://bugzilla.redhat.com/show_bug.cgi?id=1825812
- # RHEL 8.3+ and Fedora 32+ have 2.1
- Requires: opendnssec >= 2.1.6-5
-+Recommends: %{name}-server-encrypted-dns
- %{?systemd_requires}
- 
- Provides: %{alt_name}-server-dns = %{version}
-@@ -642,6 +643,15 @@ IPA integrated DNS server with support for automatic DNSSEC signing.
- Integrated DNS server is BIND 9. OpenDNSSEC provides key management.
- 
- 
-+%package server-encrypted-dns
-+Summary: support for encrypted DNS in IPA integrated DNS server
-+Requires: %{name}-client-encrypted-dns
-+
-+%description server-encrypted-dns
-+Provides support for enabling DNS over TLS in the IPA integrated DNS
-+server.
-+
-+
- %package server-trust-ad
- Summary: Virtual package to install packages required for Active Directory trusts
- Requires: %{name}-server = %{version}-%{release}
-@@ -722,6 +732,7 @@ Requires: libnfsidmap
- Requires: (nfs-utils or nfsv4-client-utils)
- Requires: sssd-tools >= %{sssd_version}
- Requires(post): policycoreutils
-+Recommends: %{name}-client-encrypted-dns
- 
- # https://pagure.io/freeipa/issue/8530
- Recommends: libsss_sudo
-@@ -763,6 +774,14 @@ If your network uses IPA for authentication, this package should be
- installed on every client machine.
- This package provides command-line tools for IPA administrators.
- 
-+%package client-encrypted-dns
-+Summary: Enable encrypted DNS support for clients
-+Requires: unbound
-+
-+%description client-encrypted-dns
-+This package enables support for installing clients with encrypted DNS
-+via DNS over TLS.
-+
- %package client-samba
- Summary: Tools to configure Samba on IPA client
- Group: System Environment/Base
-@@ -1724,6 +1743,10 @@ fi
- %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket
- %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service
- 
-+%files server-encrypted-dns
-+%doc README.md Contributors.txt
-+%license COPYING
-+
- %files server-trust-ad
- %doc README.md Contributors.txt
- %license COPYING
-@@ -1783,6 +1806,10 @@ fi
- %attr(600,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn.conf
- %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn/expire_msg.template
- 
-+%files client-encrypted-dns
-+%doc README.md Contributors.txt
-+%license COPYING
-+
- %files -n python3-ipaclient
- %doc README.md Contributors.txt
- %license COPYING
--- 
-2.49.0
-
-
-From f443b91ddb2e0e6e61d56b2d19fef789e6cb21a8 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Tue, 11 Feb 2025 10:13:01 +0100
-Subject: [PATCH 085/116] PRCI: add definitions for DNS over TLS tests
-
-Signed-off-by: Antonio Torres <antorres@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index f0283615d..6229a3739 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -2153,3 +2153,15 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
-         topology: *master_1repl
-+
-+  fedora-latest-ipa-4-12/test_edns:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 100
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_edns.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 14400
-+        topology: *master_2repl_1client
-\ No newline at end of file
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index c2871c473..4d01d936d 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2325,3 +2325,16 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
-         topology: *master_1repl
-+
-+  fedora-latest-ipa-4-12/test_edns:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 100
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_edns.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 14400
-+        topology: *master_2repl_1client
-\ No newline at end of file
--- 
-2.49.0
-
-
-From f65ec981f8b395d0e5401453cec7cb3c3b1de017 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Wed, 12 Feb 2025 09:48:58 +0100
-Subject: [PATCH 086/116] dns: only disable unbound when DoT is enabled
-
-Ensure unbound is only stopped and disabled when DNS over TLS was
-enabled during installation.
-
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/bindinstance.py | 11 +++++++----
- ipaserver/install/dns.py          |  3 +++
- 2 files changed, 10 insertions(+), 4 deletions(-)
-
-diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
-index 4f4ab9bbc..0cc1f1325 100644
---- a/ipaserver/install/bindinstance.py
-+++ b/ipaserver/install/bindinstance.py
-@@ -689,6 +689,8 @@ class BindInstance(service.Service):
-         self.forward_policy = forward_policy
-         self.reverse_zones = reverse_zones
- 
-+        self.sstore.backup_state("dns_over_tls", "enabled", dns_over_tls)
-+
-         if not zonemgr:
-             self.zonemgr = 'hostmaster.%s' % normalize_zone(self.domain)
-         else:
-@@ -1377,10 +1379,11 @@ class BindInstance(service.Service):
- 
-         self.named_conflict.unmask()
- 
--        certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_CRT)
--        certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_KEY)
--        services.knownservices.unbound.disable()
--        services.knownservices.unbound.stop()
-+        if self.sstore.restore_state("dns_over_tls", "enabled"):
-+            if not self.sstore.restore_state("dns_over_tls", "external_crt"):
-+                certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_CRT)
-+            services.knownservices["unbound"].disable()
-+            services.knownservices["unbound"].stop()
- 
-         ipautil.remove_file(paths.NAMED_CONF_BAK)
-         ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 29ca0d2ff..88aff19bc 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -457,6 +457,9 @@ def install(standalone, replica, options, api=api):
- 
-     bind.create_instance()
- 
-+    bind.sstore.backup_state("dns_over_tls",
-+                             "external_crt",
-+                             bool(options.dns_over_tls_cert))
-     if options.dns_over_tls:
-         print("Setting up DNS over TLS")
-         _setup_dns_over_tls(options)
--- 
-2.49.0
-
-
-From 7fd4b940abd2084fd6ec7de73dfd68551fce73fe Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Wed, 29 Jan 2025 10:07:45 -0500
-Subject: [PATCH 087/116] ipa-migrate - do not migrate tombstone entries,
- ignore MidairCollisions, and krbpwdpolicyreference
-
-Replication related entries should not be migrated.  The main reason is
-that we do not allow entries to be added that have an RDN of nsuniqueid
-(only the server can internally add them).
-
-Most midair collisions are transient issues and can be ignored for
-migration purposes. In migration tests this only happens when an
-attribute does not exist in the local server.  This happens frequently
-with COS attributes.
-
-We should also ignore 'krbpwdpolicyreference' as it's an attribute that is
-set by COS and does not need to be migrated.
-
-Fixes: https://pagure.io/freeipa/issue/9737
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py           | 8 ++++++++
- ipaserver/install/ipa_migrate_constants.py | 1 +
- 2 files changed, 9 insertions(+)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index ece473bc8..5ba140ce3 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -1462,6 +1462,10 @@ class IPAMigrate():
-             if DN(exclude_dn) in DN(entry_dn):
-                 return
- 
-+        # Skip tombstones
-+        if 'nsTombstone' in entry_attrs['objectClass']:
-+            return
-+
-         # Determine entry type: user, group, hbac, etc
-         entry_type = self.get_entry_type(entry_dn, entry_attrs)
-         if entry_type is None:
-@@ -1568,6 +1572,10 @@ class IPAMigrate():
-                     stats['custom'] += 1
-                 else:
-                     DB_OBJECTS[entry_type]['count'] += 1
-+            except errors.MidairCollision as e:
-+                # Typically means no such attribute, ok to ignore
-+                self.log_debug(f'Failed to update "{local_dn}" error: '
-+                               f'{str(e)} - ok to ignore')
-             except errors.ExecutionError as e:
-                 self.log_error(f'Failed to update "{local_dn}" error: '
-                                f'{str(e)}')
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index e8192fb1a..09856f07c 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -71,6 +71,7 @@ IGNORE_ATTRS = [
-     'serverhostname',
-     'krbpasswordexpiration',
-     'krblastadminunlock',
-+    'krbpwdpolicyreference',  # COS attribute
- ]
- 
- # For production mode, bring everything over
--- 
-2.49.0
-
-
-From c6bac9bcb4fb6c5b4f45dd765be4af03b1c6f291 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Wed, 12 Feb 2025 16:45:16 -0500
-Subject: [PATCH 088/116] Validate the default e-mail domain in the config
- plugin
-
-There was zero validation at all. Re-use the Email validator
-since that will be used to validate the eventual e-mail
-address set in a user.
-
-Fixes: https://pagure.io/freeipa/issue/9680
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/plugins/config.py                | 13 +++++
- ipatests/test_xmlrpc/test_config_plugin.py | 57 ++++++++++++++++++++++
- 2 files changed, 70 insertions(+)
-
-diff --git a/ipaserver/plugins/config.py b/ipaserver/plugins/config.py
-index e2aa5cdb1..adf21ea0c 100644
---- a/ipaserver/plugins/config.py
-+++ b/ipaserver/plugins/config.py
-@@ -36,6 +36,7 @@ from .selinuxusermap import validate_selinuxuser
- from ipalib import _
- from ipapython.admintool import ScriptError
- from ipapython.dn import DN
-+from ipapython.ipavalidate import Email
- from ipaserver.plugins.privilege import principal_has_privilege
- from ipaserver.install.adtrust import set_and_check_netbios_name
- 
-@@ -116,6 +117,17 @@ def validate_search_records_limit(ugettext, value):
-         return _('must be at least 10')
-     return None
- 
-+
-+def validate_emaildomain(ugettext, value):
-+    """Do some basic e-mail domain validation.
-+
-+       Test using a sample user@domain.
-+    """
-+    test = "test@{}".format(value)
-+    if not Email(test):
-+        return _('Invalid e-mail domain')
-+    return None
-+
- @register()
- class config(LDAPObject):
-     """
-@@ -188,6 +200,7 @@ class config(LDAPObject):
-             doc=_('Default group for new users'),
-         ),
-         Str('ipadefaultemaildomain?',
-+            validate_emaildomain,
-             cli_name='emaildomain',
-             label=_('Default e-mail domain'),
-             doc=_('Default e-mail domain'),
-diff --git a/ipatests/test_xmlrpc/test_config_plugin.py b/ipatests/test_xmlrpc/test_config_plugin.py
-index 3862246a1..3be2e066b 100644
---- a/ipatests/test_xmlrpc/test_config_plugin.py
-+++ b/ipatests/test_xmlrpc/test_config_plugin.py
-@@ -340,4 +340,61 @@ class test_config(Declarative):
-             ),
-             expected=errors.EmptyModlist(),
-         ),
-+
-+        dict(
-+            desc='Set invalid default e-mail domain, no TLD',
-+            command=(
-+                'config_mod', [], {'ipadefaultemaildomain': 'foo'},
-+            ),
-+            expected=errors.ValidationError(
-+                name='emaildomain',
-+                error='Invalid e-mail domain'),
-+        ),
-+
-+        dict(
-+            desc='Set invalid default e-mail domain, trailing dots',
-+            command=(
-+                'config_mod', [], {'ipadefaultemaildomain': 'foo.com...'},
-+            ),
-+            expected=errors.ValidationError(
-+                name='emaildomain',
-+                error='Invalid e-mail domain'),
-+        ),
-+
-+        dict(
-+            desc='Set invalid default e-mail domain, with an @',
-+            command=(
-+                'config_mod', [], {'ipadefaultemaildomain': '@foo.com'},
-+            ),
-+            expected=errors.ValidationError(
-+                name='emaildomain',
-+                error='Invalid e-mail domain'),
-+        ),
-+
-+
-+        dict(
-+            desc='Set valid default e-mail domain',
-+            command=(
-+                'config_mod', [], {'ipadefaultemaildomain': 'foo.com'},
-+            ),
-+            expected={
-+                'result': lambda d: d['ipadefaultemaildomain'] == ('foo.com',),
-+                'summary': None,
-+                'value': None,
-+            },
-+        ),
-+
-+        dict(
-+            desc='Reset default e-mail domain',
-+            command=(
-+                'config_mod', [], {'ipadefaultemaildomain': api.env.domain},
-+            ),
-+            expected={
-+                'result': lambda d: (
-+                    d['ipadefaultemaildomain'] == (api.env.domain,),
-+                ),
-+                'summary': None,
-+                'value': None,
-+            },
-+        ),
-     ]
--- 
-2.49.0
-
-
-From 1c789f5ffde5d443fa2ce6ccfc4eb55f9a8afb4c Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 25 Feb 2025 10:24:56 +0100
-Subject: [PATCH 089/116] ipatests: use "sos report" instead of "sosreport"
- command
-
-The "soscommand" has been deprecated and "sos report" should be
-used instead. The redirector was removed in sos 4.9.
-
-Fixes: https://pagure.io/freeipa/issue/9752
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 7c3f5857a..05a0adb24 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -1405,7 +1405,7 @@ class TestIpaHealthCheck(IntegrationTest):
-         msg = "[plugin:ipa] collecting path '{}'".format(HEALTHCHECK_LOG)
-         cmd = self.master.run_command(
-             [
--                "sosreport",
-+                "sos", "report",
-                 "-o",
-                 "ipa",
-                 "--case-id",
-@@ -1508,7 +1508,7 @@ class TestIpaHealthCheck(IntegrationTest):
-         caseid = "123456"
-         self.master.run_command(
-             [
--                "sosreport",
-+                "sos", "report",
-                 "-o",
-                 "ipa",
-                 "--case-id",
--- 
-2.49.0
-
-
-From bf4de61784d8f39298ca952e739ecc44929fa5da Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 11 Feb 2025 14:31:08 -0500
-Subject: [PATCH 090/116] Use new(er) PKI connection API in
- ipa-pki-wait-running
-
-Since PKI v10.8.0 the PKIConnection object can longer be
-created for a specific subsystem. The client that consumes
-the connection specifies the subsystem instead.
-
-Use this new API unconditionally as we require PKI >= 10.10.0
-and what is available in supported repos is generally 11.5.0+.
-
-See https://github.com/dogtagpki/pki/wiki/PKI-10.8-Python-Changes
-
-Fixes: https://pagure.io/freeipa/issue/9691
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- install/tools/ipa-pki-wait-running.in | 17 ++++++++---------
- 1 file changed, 8 insertions(+), 9 deletions(-)
-
-diff --git a/install/tools/ipa-pki-wait-running.in b/install/tools/ipa-pki-wait-running.in
-index 9ca6e974e..fe7bb3c85 100644
---- a/install/tools/ipa-pki-wait-running.in
-+++ b/install/tools/ipa-pki-wait-running.in
-@@ -55,25 +55,24 @@ def check_installed(subsystem):
-         return False
- 
- 
--def get_conn(hostname, subsystem):
-+def get_client(hostname):
-     """Create a connection object
-     """
-     conn = PKIConnection(
-         hostname=hostname,
--        subsystem=subsystem,
-         cert_paths=paths.IPA_CA_CRT
-     )
-+    client = SystemStatusClient(conn, subsystem=SUBSYSTEM)
-     logger.info(
--        "Created connection %s://%s:%s/%s",
--        conn.protocol, conn.hostname, conn.port, conn.subsystem
-+        "Created connection %s%s",
-+        client.connection.serverURI, client.get_status_url
-     )
--    return conn
-+    return client
- 
- 
--def get_status(conn, timeout):
-+def get_status(client, timeout):
-     """Get status from subsystem and return parsed (status, error)
-     """
--    client = SystemStatusClient(conn)
-     response = client.get_status(timeout=timeout)
-     status = None
-     error = None
-@@ -102,11 +101,11 @@ def main():
-     api.bootstrap(confdir=paths.ETC_IPA, log=None)
-     timeout = api.env.startup_timeout
- 
--    conn = get_conn(api.env.host, subsystem=SUBSYSTEM)
-+    client = get_client(api.env.host)
-     end = curtime() + timeout
-     while curtime() < end:
-         try:
--            status, error = get_status(conn, CONNECTION_TIMEOUT)
-+            status, error = get_status(client, CONNECTION_TIMEOUT)
-         except (ConnectionError, Timeout) as e:
-             logger.info("Connection failed: %s", e)
-         except RequestException as e:
--- 
-2.49.0
-
-
-From c87b695ab0211e9cd337e4d148c3cc0ee56a232c Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 11 Feb 2025 13:52:27 -0500
-Subject: [PATCH 091/116] Fix some memory errors identified by a static
- analyzer
-
-Fix a couple of leaks and a double free.
-
-Fixes: https://pagure.io/freeipa/issue/9698
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- client/ipa-rmkeytab.c      | 9 +++++++--
- daemons/ipa-otpd/forward.c | 3 +--
- 2 files changed, 8 insertions(+), 4 deletions(-)
-
-diff --git a/client/ipa-rmkeytab.c b/client/ipa-rmkeytab.c
-index 78032418a..d1c9ee660 100644
---- a/client/ipa-rmkeytab.c
-+++ b/client/ipa-rmkeytab.c
-@@ -111,6 +111,9 @@ remove_principal(krb5_context context, krb5_keytab ktid, const char *principal,
- 
- done:
- 
-+    if (entry.principal)
-+        krb5_free_principal(context, entry.principal);
-+
-     return rval;
- }
- 
-@@ -119,7 +122,7 @@ remove_realm(krb5_context context, krb5_keytab ktid, const char *realm, int debu
- {
-     krb5_error_code krberr;
-     krb5_keytab_entry entry;
--    krb5_kt_cursor kt_cursor;
-+    krb5_kt_cursor kt_cursor = NULL;
-     char * entry_princ_s = NULL;
-     int rval = 0;
-     bool realm_found = false;
-@@ -153,6 +156,7 @@ remove_realm(krb5_context context, krb5_keytab ktid, const char *realm, int debu
-             rval = CURSOR_ERROR;
-             goto done;
-         }
-+        kt_cursor = NULL;
- 
-         if (strstr(entry_princ_s, realm) != NULL) {
-             realm_found = true;
-@@ -176,7 +180,8 @@ remove_realm(krb5_context context, krb5_keytab ktid, const char *realm, int debu
-     }
- 
- done:
--    krb5_kt_end_seq_get(context, ktid, &kt_cursor);
-+    if (kt_cursor != NULL)
-+        krb5_kt_end_seq_get(context, ktid, &kt_cursor);
-     krb5_free_unparsed_name(context, entry_princ_s);
-     return rval;
- }
-diff --git a/daemons/ipa-otpd/forward.c b/daemons/ipa-otpd/forward.c
-index 77241ec3b..0b779ce00 100644
---- a/daemons/ipa-otpd/forward.c
-+++ b/daemons/ipa-otpd/forward.c
-@@ -47,8 +47,7 @@ static void forward_cb(krb5_error_code retval, const krad_packet *request,
-         otpd_log_req(item->req, "forward end: %s", krad_code_num2name(code));
-     } else {
-         const char *err_msg = krb5_get_error_message(ctx.kctx, retval);
--        otpd_log_req(item->req, "forward end: %s",
--                     krb5_get_error_message(ctx.kctx, retval));
-+        otpd_log_req(item->req, "forward end: %s", err_msg);
-         krb5_free_error_message(ctx.kctx, err_msg);
-     }
- 
--- 
-2.49.0
-
-
-From 8a9c5629214cd71e88a5ac9c1b639faad9fc4ec1 Mon Sep 17 00:00:00 2001
-From: Frederik Himpe <frederik@frehi.be>
-Date: Tue, 25 Feb 2025 13:49:23 +0100
-Subject: [PATCH 092/116] Make name of nobody group configurable and use
- nogroup on Debian
-
-Fixes: https://pagure.io/freeipa/issue/9753
-
-Signed-off-by: Frederik Himpe <frederik@frehi.be>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaplatform/base/constants.py        | 1 +
- ipaplatform/debian/constants.py      | 1 +
- ipaserver/install/adtrustinstance.py | 6 ++++--
- 3 files changed, 6 insertions(+), 2 deletions(-)
-
-diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py
-index f1ef7efff..4c8038a84 100644
---- a/ipaplatform/base/constants.py
-+++ b/ipaplatform/base/constants.py
-@@ -124,6 +124,7 @@ class BaseConstantsNamespace:
-     NAMED_OPTIONS_VAR = "OPTIONS"
-     NAMED_OPENSSL_ENGINE = None
-     NAMED_ZONE_COMMENT = ""
-+    NOBODY_GROUP = Group("nobody")
-     PKI_USER = User("pkiuser")
-     PKI_GROUP = Group("pkiuser")
-     # ntpd init variable used for daemon options
-diff --git a/ipaplatform/debian/constants.py b/ipaplatform/debian/constants.py
-index 7216694ad..f8ee8cf9f 100644
---- a/ipaplatform/debian/constants.py
-+++ b/ipaplatform/debian/constants.py
-@@ -29,5 +29,6 @@ class DebianConstantsNamespace(BaseConstantsNamespace):
-     ODS_USER = User("opendnssec")
-     ODS_GROUP = Group("opendnssec")
-     SECURE_NFS_VAR = "NEED_GSSD"
-+    NOBODY_GROUP = Group("nogroup")
- 
- constants = DebianConstantsNamespace()
-diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
-index fd5a5a282..df2586ef1 100644
---- a/ipaserver/install/adtrustinstance.py
-+++ b/ipaserver/install/adtrustinstance.py
-@@ -123,9 +123,11 @@ def make_netbios_name(s):
- def map_Guests_to_nobody():
-     env = {'LC_ALL': 'C'}
-     args = [paths.NET, '-s', '/dev/null', 'groupmap', 'add',
--            'sid=S-1-5-32-546', 'unixgroup=nobody', 'type=builtin']
-+            'sid=S-1-5-32-546',
-+            'unixgroup="' + constants.NOBODY_GROUP + '"', 'type=builtin']
- 
--    logger.debug("Map BUILTIN\\Guests to a group 'nobody'")
-+    logger.debug("Map BUILTIN\\Guests to a group '%s'",
-+                 constants.NOBODY_GROUP)
-     ipautil.run(args, env=env, raiseonerr=False, capture_error=True)
- 
- 
--- 
-2.49.0
-
-
-From 9c6ff33c5213a23749e26f6c8a59ff6809b4e654 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 30 Jan 2025 11:29:37 -0500
-Subject: [PATCH 093/116] Remove migration from mod_nss to mod_ssl
-
-mod_nss was the original crypto engine used in Apache with IPA.
-Support was dropped in IPA release 4.7.0 in 4596674 and looks
-like it was introduced into Fedora in F28.
-
-It's time to remove this upgrade code.
-
-This retains the HTTPD_NSS_CONF definition so that
-contrib/copy-schema-to-ca-RHEL6.py can still work.
-
-Fixes: https://pagure.io/freeipa/issue/9739
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/install/cainstance.py     |  9 +---
- ipaserver/install/httpinstance.py   | 73 +----------------------------
- ipaserver/install/krainstance.py    |  5 +-
- ipaserver/install/server/upgrade.py | 14 ------
- 4 files changed, 5 insertions(+), 96 deletions(-)
-
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index 3466c3088..2c9fbe04f 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -266,13 +266,8 @@ class InconsistentCRLGenConfigException(Exception):
- class CAInstance(DogtagInstance):
-     """
-     When using a dogtag CA the DS database contains just the
--    server cert for DS. The mod_nss database will contain the RA agent
--    cert that will be used to do authenticated requests against dogtag.
--
--    This is done because we use python-nss and will inherit the opened
--    NSS database in mod_python. In nsslib.py we do an nssinit but this will
--    return success if the database is already initialized. It doesn't care
--    if the database is different or not.
-+    server cert for DS. The RA agent cert that will be used
-+    to do authenticated requests against dogtag.
- 
-     external is a state machine:
-        0 = not an externally signed CA
-diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
-index d893e741b..b196b0088 100644
---- a/ipaserver/install/httpinstance.py
-+++ b/ipaserver/install/httpinstance.py
-@@ -25,7 +25,6 @@ import os
- import glob
- import shlex
- import shutil
--import tempfile
- 
- from augeas import Augeas
- import dbus
-@@ -119,7 +118,6 @@ class HTTPInstance(service.Service):
- 
-         self.step("stopping httpd", self.__stop)
-         self.step("backing up ssl.conf", self.backup_ssl_conf)
--        self.step("disabling nss.conf", self.disable_nss_conf)
-         self.step("configuring mod_ssl certificate paths",
-                   self.configure_mod_ssl_certs)
-         self.step("setting mod_ssl protocol list",
-@@ -222,34 +220,10 @@ class HTTPInstance(service.Service):
-         tasks.configure_http_gssproxy_conf(IPAAPI_USER)
-         services.knownservices.gssproxy.restart()
- 
--    def get_mod_nss_nickname(self):
--        cert = directivesetter.get_directive(paths.HTTPD_NSS_CONF,
--                                             'NSSNickname')
--        nickname = directivesetter.unquote_directive_value(cert,
--                                                           quote_char="'")
--        return nickname
--
-     def backup_ssl_conf(self):
-         self.fstore.backup_file(paths.HTTPD_SSL_CONF)
-         self.fstore.backup_file(paths.HTTPD_SSL_SITE_CONF)
- 
--    def disable_nss_conf(self):
--        """
--        Backs up the original nss.conf file and replace it with the empty one.
--        Empty file avoids recreation of nss.conf in case the package is
--        reinstalled.
--
--        There is no safe way to co-exist since there is no safe port
--        to make mod_nss use, disable it completely.
--        """
--        if os.path.exists(paths.HTTPD_NSS_CONF):
--            # check that we don't have a backup already
--            # (mod_nss -> mod_ssl upgrade scenario)
--            if not self.fstore.has_file(paths.HTTPD_NSS_CONF):
--                self.fstore.backup_file(paths.HTTPD_NSS_CONF)
--
--        open(paths.HTTPD_NSS_CONF, 'w').close()
--
-     def set_mod_ssl_protocol(self):
-         tasks.configure_httpd_protocol()
- 
-@@ -275,8 +249,6 @@ class HTTPInstance(service.Service):
- 
-         ocsp_dir = aug.get(ocsp_path)
- 
--        # there is SSLOCSPEnable directive in nss.conf file, comment it
--        # otherwise just do nothing
-         if ocsp_dir is not None:
-             ocsp_state = aug.get(ocsp_arg)
-             aug.remove(ocsp_arg)
-@@ -285,7 +257,6 @@ class HTTPInstance(service.Service):
-             aug.save()
- 
-     def __add_include(self):
--        """This should run after __set_mod_nss_port so is already backed up"""
-         if installutils.update_file(paths.HTTPD_SSL_SITE_CONF,
-                                     '</VirtualHost>',
-                                     'Include {path}\n'
-@@ -538,7 +509,7 @@ class HTTPInstance(service.Service):
-                              'external-helper', helper)
- 
-         for f in [paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF,
--                  paths.HTTPD_SSL_SITE_CONF, paths.HTTPD_NSS_CONF]:
-+                  paths.HTTPD_SSL_SITE_CONF]:
-             try:
-                 self.fstore.restore_file(f)
-             except ValueError as error:
-@@ -570,8 +541,6 @@ class HTTPInstance(service.Service):
-         for filename in remove_files:
-             ipautil.remove_file(filename)
- 
--        ipautil.remove_file(paths.HTTPD_NSS_CONF, only_if_empty=True)
--
-         for d in (
-             paths.SYSTEMD_SYSTEM_HTTPD_D_DIR,
-             paths.HTTPD_ALIAS_DIR
-@@ -648,43 +617,3 @@ class HTTPInstance(service.Service):
-                     service_dn,
-                     timeout=api.env.replication_wait_timeout
-                 )
--
--    def migrate_to_mod_ssl(self):
--        """For upgrades only, migrate from mod_nss to mod_ssl"""
--        db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR)
--        nickname = self.get_mod_nss_nickname()
--        with tempfile.NamedTemporaryFile() as temp:
--            pk12_password = ipautil.ipa_generate_password()
--            pk12_pwdfile = ipautil.write_tmp_file(pk12_password)
--            db.export_pkcs12(temp.name, pk12_pwdfile.name, nickname)
--            certs.install_pem_from_p12(temp.name,
--                                       pk12_password,
--                                       paths.HTTPD_CERT_FILE)
--
--            passwd_fname = paths.HTTPD_PASSWD_FILE_FMT.format(
--                            host=api.env.host)
--            with open(passwd_fname, 'wb') as passwd_file:
--                os.fchmod(passwd_file.fileno(), 0o600)
--                passwd_file.write(
--                    ipautil.ipa_generate_password().encode('utf-8'))
--
--            certs.install_key_from_p12(temp.name,
--                                       pk12_password,
--                                       paths.HTTPD_KEY_FILE,
--                                       out_passwd_fname=passwd_fname)
--
--        self.backup_ssl_conf()
--        self.configure_mod_ssl_certs()
--        self.set_mod_ssl_protocol()
--        self.set_mod_ssl_logdir()
--        self.__add_include()
--
--        self.cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE)
--
--        if self.ca_is_configured:
--            db.untrack_server_cert(nickname)
--            self.start_tracking_certificates()
--
--        # remove nickname and CA certs from NSS db
--
--        self.disable_nss_conf()
-diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
-index 8d95a23ea..12335f49a 100644
---- a/ipaserver/install/krainstance.py
-+++ b/ipaserver/install/krainstance.py
-@@ -60,9 +60,8 @@ class KRAInstance(DogtagInstance):
-     """
-     We assume that the CA has already been installed, and we use the
-     same tomcat instance to host both the CA and KRA.
--    The mod_nss database will contain the RA agent cert that will be used
--    to do authenticated requests against dogtag.  The RA agent cert will
--    be the same for both the CA and KRA.
-+    The same RA agent certificate used with the CA will be used for the
-+    KRA.
-     """
- 
-     # Mapping of nicknames for tracking requests, and the profile to
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index fb71df81a..f26a08aef 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -1088,19 +1088,6 @@ def remove_ds_ra_cert(subject_base):
-     sysupgrade.set_upgrade_state('ds', 'remove_ra_cert', True)
- 
- 
--def migrate_to_mod_ssl(http):
--    logger.info('[Migrating from mod_nss to mod_ssl]')
--
--    if sysupgrade.get_upgrade_state('ssl.conf', 'migrated_to_mod_ssl'):
--        logger.info("Already migrated to mod_ssl")
--        return
--
--    http.migrate_to_mod_ssl()
--
--    sysupgrade.set_upgrade_state('ssl.conf', 'migrated_to_mod_ssl', True)
--
--
--
- def update_ipa_httpd_service_conf(http):
-     logger.info('[Updating HTTPD service IPA configuration]')
-     http.update_httpd_service_ipa_conf()
-@@ -1874,7 +1861,6 @@ def upgrade_configuration():
-     http.stop()
-     update_ipa_httpd_service_conf(http)
-     update_ipa_http_wsgi_conf(http)
--    migrate_to_mod_ssl(http)
-     update_http_keytab(http)
-     http.configure_gssproxy()
-     http.start()
--- 
-2.49.0
-
-
-From 5cb30d27fe1565249347e133eca3695517a5bf61 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 11 Feb 2025 10:13:20 -0500
-Subject: [PATCH 094/116] Remove the migration of the RA cert from mod_nss to
- mod_ssl
-
-Originally the RA certificate was stored in the Apache NSS
-database so it was available natively to the framework. It
-was subsequently moved into PEM files even prior to the
-switch from mod_nss to mod_ssl.
-
-This code was introduced with 4.5.0 and was last seen in Fedora
-in 27 in 2017. We don't support direct in-place upgrades from
-such an old version so we should be safe from bricking a user's
-installation.
-
-Fixes: https://pagure.io/freeipa/issue/9739
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- .../updates/90-post_upgrade_plugins.update    |  2 -
- .../install/plugins/update_ra_cert_store.py   | 73 -------------------
- 2 files changed, 75 deletions(-)
- delete mode 100644 ipaserver/install/plugins/update_ra_cert_store.py
-
-diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
-index bb48f483c..9a9d80a92 100644
---- a/install/updates/90-post_upgrade_plugins.update
-+++ b/install/updates/90-post_upgrade_plugins.update
-@@ -21,8 +21,6 @@ plugin: update_pacs
- plugin: update_service_principalalias
- plugin: update_fix_duplicate_cacrt_in_ldap
- plugin: update_upload_cacrt
--# update_ra_cert_store has to be executed after update_ca_renewal_master
--plugin: update_ra_cert_store
- plugin: update_mapping_Guests_to_nobody
- plugin: fix_kra_people_entry
- plugin: update_pwpolicy
-diff --git a/ipaserver/install/plugins/update_ra_cert_store.py b/ipaserver/install/plugins/update_ra_cert_store.py
-deleted file mode 100644
-index 620f2e2dd..000000000
---- a/ipaserver/install/plugins/update_ra_cert_store.py
-+++ /dev/null
-@@ -1,73 +0,0 @@
--#
--# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
--#
--
--from __future__ import absolute_import
--
--import logging
--import os
--import tempfile
--
--from ipalib import Registry
--from ipalib import Updater
--from ipalib.install import certmonger
--from ipaplatform.paths import paths
--from ipapython.certdb import NSSDatabase
--from ipaserver.install import cainstance
--
--logger = logging.getLogger(__name__)
--
--register = Registry()
--
--
--@register()
--class update_ra_cert_store(Updater):
--    """
--    Moves the ipaCert store from /etc/httpd/alias RA_AGENT_PEM, RA_AGENT_KEY
--    files
--    """
--
--    def execute(self, **options):
--        ra_nick = 'ipaCert'
--        ca_enabled = self.api.Command.ca_is_enabled()['result']
--        if not ca_enabled:
--            return False, []
--
--        try:
--            certdb = NSSDatabase(nssdir=paths.HTTPD_ALIAS_DIR)
--        except ValueError as e:
--            logger.warning("Problem opening NSS database in "
--                           "%s. Skipping check for existing RA "
--                           "agent certificate: %s", paths.HTTPD_ALIAS_DIR, e)
--            return False, []
--
--        if not certdb.has_nickname(ra_nick):
--            # Nothign to do
--            return False, []
--        elif os.path.exists(paths.RA_AGENT_PEM):
--            # even though the certificate file exists, we will overwrite it
--            # as it's probabably something wrong anyway
--            logger.warning(
--                "A certificate with the nickname 'ipaCert' exists in "
--                "the old '%s' NSS database as well as in the new "
--                "PEM file '%s'",
--                paths.HTTPD_ALIAS_DIR, paths.RA_AGENT_PEM)
--
--        _fd, p12file = tempfile.mkstemp(dir=certdb.secdir)
--        # no password is necessary as we will be saving it in clear anyway
--        certdb.export_pkcs12(ra_nick, p12file, pkcs12_passwd='')
--
--        # stop tracking the old cert and remove it
--        certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname=ra_nick)
--        certdb.delete_key_and_cert(ra_nick)
--        if os.path.exists(paths.OLD_KRA_AGENT_PEM):
--            os.remove(paths.OLD_KRA_AGENT_PEM)
--
--        # get the private key and certificate from the file and start
--        # tracking it in certmonger
--        ca = cainstance.CAInstance()
--        ca.import_ra_cert(p12file)
--
--        os.remove(p12file)
--
--        return False, []
--- 
-2.49.0
-
-
-From 25848a8d49a77280f7b00cb81271ddb94aef7d48 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 26 Feb 2025 14:11:44 +0100
-Subject: [PATCH 095/116] vault: remove PKIConnection deprecation warning
-
-PKIConnection should be created without the subsystem and
-the susbystem has to be provided in the AccountClient as
-described in https://github.com/dogtagpki/pki/wiki/PKI-10.8-Python-Changes
-
-Fixes: https://pagure.io/freeipa/issue/9754
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipaserver/plugins/dogtag.py | 1 -
- ipaserver/plugins/vault.py  | 9 ++++++---
- 2 files changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
-index ee6d0e347..00fe1ad64 100644
---- a/ipaserver/plugins/dogtag.py
-+++ b/ipaserver/plugins/dogtag.py
-@@ -1591,7 +1591,6 @@ class kra(Backend):
-             'https',
-             self.kra_host,
-             str(self.kra_port),
--            'kra',
-             cert_paths=paths.IPA_CA_CRT
-         )
- 
-diff --git a/ipaserver/plugins/vault.py b/ipaserver/plugins/vault.py
-index 0bcc2a1ce..46463430d 100644
---- a/ipaserver/plugins/vault.py
-+++ b/ipaserver/plugins/vault.py
-@@ -843,7 +843,8 @@ class vault_del(LDAPDelete):
- 
-         with self.api.Backend.kra.get_client() as kra_client:
-             # pylint: disable=used-before-assignment
--            kra_account = pki.account.AccountClient(kra_client.connection)
-+            kra_account = pki.account.AccountClient(kra_client.connection,
-+                                                    subsystem='kra')
-             # pylint: enable=used-before-assignment
-             kra_account.login()
- 
-@@ -1081,7 +1082,8 @@ class vault_archive_internal(PKQuery):
- 
-         # connect to KRA
-         with self.api.Backend.kra.get_client() as kra_client:
--            kra_account = pki.account.AccountClient(kra_client.connection)
-+            kra_account = pki.account.AccountClient(kra_client.connection,
-+                                                    subsystem='kra')
-             kra_account.login()
- 
-             client_key_id = self.obj.get_key_id(vault['dn'])
-@@ -1163,7 +1165,8 @@ class vault_retrieve_internal(PKQuery):
- 
-         # connect to KRA
-         with self.api.Backend.kra.get_client() as kra_client:
--            kra_account = pki.account.AccountClient(kra_client.connection)
-+            kra_account = pki.account.AccountClient(kra_client.connection,
-+                                                    subsystem='kra')
-             kra_account.login()
- 
-             client_key_id = self.obj.get_key_id(vault['dn'])
--- 
-2.49.0
-
-
-From 460281f4508864ef25b3b9992e5922e7947a3109 Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Tue, 18 Feb 2025 15:36:12 +0100
-Subject: [PATCH 096/116] Replace fips-mode-setup
-
-RHEL10 no longer support fips-setup-mode, this has been
-replaced in the healthcheck tool, but also needs to be replaced here.
-
-Fixes: https://pagure.io/freeipa/issue/9750
-
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaplatform/base/paths.py                        |  1 -
- ipatests/test_integration/test_ipahealthcheck.py | 15 ++++++---------
- 2 files changed, 6 insertions(+), 10 deletions(-)
-
-diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
-index b2da94992..6a62d7bd0 100644
---- a/ipaplatform/base/paths.py
-+++ b/ipaplatform/base/paths.py
-@@ -28,7 +28,6 @@ class BasePathNamespace:
-     BIN_HOSTNAMECTL = "/bin/hostnamectl"
-     CRYPTO_POLICY_OPENSSLCNF_FILE = None
-     ECHO = "/bin/echo"
--    FIPS_MODE_SETUP = "/bin/fips-mode-setup"
-     GZIP = "/bin/gzip"
-     LS = "/bin/ls"
-     SYSTEMCTL = "/bin/systemctl"
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 05a0adb24..9f4017e35 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -377,21 +377,18 @@ class TestIpaHealthCheck(IntegrationTest):
-                                             failures_only=False)
-         assert returncode == 0
- 
--        cmd = self.master.run_command(
--            [paths.FIPS_MODE_SETUP, "--is-enabled"], raiseonerr=False
--        )
--        returncode = cmd.returncode
-+        is_fips_enabled = tasks.is_fips_enabled(self.master)
- 
-         assert "fips" in check[0]["kw"]
- 
-         if check[0]["kw"]["fips"] == "disabled":
--            assert returncode == 2
-+            assert not is_fips_enabled
-         elif check[0]["kw"]["fips"] == "enabled":
--            assert returncode == 0
--        elif check[0]["kw"]["fips"] == f"missing {paths.FIPS_MODE_SETUP}":
--            assert returncode == 127
-+            assert is_fips_enabled
-         else:
--            assert returncode == 1
-+            raise ValueError("File %s doesn't exist or contains unexpected "
-+                             "value, this is a kernel issue!"
-+                             % paths.PROC_FIPS_ENABLED)
- 
-     def test_ipa_healthcheck_after_certupdate(self):
-         """
--- 
-2.49.0
-
-
-From 3e8348a252114005678c1155c70bb806e9efc4f0 Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Mon, 24 Feb 2025 11:32:59 +0100
-Subject: [PATCH 097/116] Skip for unpatched freeipa-healthcheck
-
-The patch is not yet live, therefore we should expect an failure.
-Once the patched version goes out, the test should work properly.
-
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 10 ++++++++++
- 1 file changed, 10 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 9f4017e35..d72808f0f 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -370,6 +370,16 @@ class TestIpaHealthCheck(IntegrationTest):
- 
-         https://pagure.io/freeipa/issue/8951
-         """
-+        healthcheck_version = tasks.get_healthcheck_version(self.master)
-+        if (
-+            parse_version(healthcheck_version) < parse_version("0.17")
-+            and osinfo.id == 'rhel'
-+            and osinfo.version_number == (10,0)
-+        ):
-+            # Patch: https://github.com/freeipa/freeipa-healthcheck/pull/349
-+            pytest.xfail("Patch is unavailable for RHEL 10.0 and "
-+                         "freeipa-healtheck version 0.16 or less")
-+
-         returncode, check = run_healthcheck(self.master,
-                                             source="ipahealthcheck.meta.core",
-                                             check="MetaCheck",
--- 
-2.49.0
-
-
-From fbdf7551de524d4509c77f03bcb59d9b616c179f Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Fri, 31 Jan 2025 10:54:13 -0500
-Subject: [PATCH 098/116] Log failed auth attempts over LDAP when a user is
- locked
-
-The KDC logs when a user who is locked out with
-Client's credentials have been revoked
-
-The LDAP server did not. This adds a message so administrators
-can tell when a user is locked out.
-
-ALERT - ipalockout_postop - User uid=tuser,cn=users,cn=accounts,dc=example,dc=test is locked out. Too many failed authentication attempts.
-
-Fixes: https://pagure.io/freeipa/issue/9742
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- daemons/ipa-slapi-plugins/common/util.h       |  3 ++
- .../ipa-lockout/ipa_lockout.c                 |  1 +
- ipatests/test_integration/test_commands.py    | 37 +++++++++++++++++++
- 3 files changed, 41 insertions(+)
-
-diff --git a/daemons/ipa-slapi-plugins/common/util.h b/daemons/ipa-slapi-plugins/common/util.h
-index db7cf7181..8a9ab7999 100644
---- a/daemons/ipa-slapi-plugins/common/util.h
-+++ b/daemons/ipa-slapi-plugins/common/util.h
-@@ -67,6 +67,9 @@
-                     "[file %s, line %d]: " fmt, \
-                     __FILE__, __LINE__, ##__VA_ARGS__)
- 
-+#define LOG_ALERT(fmt, ...) \
-+    slapi_log_error(SLAPI_LOG_ALERT, log_func, fmt, ##__VA_ARGS__)
-+
- #define LOG_PWDPOLICY(fmt, ...) \
-     slapi_log_error(SLAPI_LOG_PWDPOLICY, log_func, fmt, ##__VA_ARGS__)
- 
-diff --git a/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
-index 366018094..ecb6d6df5 100644
---- a/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
-+++ b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
-@@ -552,6 +552,7 @@ static int ipalockout_postop(Slapi_PBlock *pb)
-                 if ((lockout_duration == 0) ||
-                     (time_now < timegm(&tm) + lockout_duration)) {
-                     /* Within lockout duration */
-+                    LOG_ALERT("User %s is locked out. Too many failed authentication attempts.\n", dn);
-                     goto done;
-                 }
-             }
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index fd34defe5..9c65b7c6b 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -24,6 +24,7 @@ from cryptography import x509
- from datetime import datetime, timedelta
- 
- from ipalib.constants import IPAAPI_USER
-+from ipalib.errors import DatabaseError
- 
- from ipaplatform.paths import paths
- 
-@@ -1379,6 +1380,42 @@ class TestIPACommand(IntegrationTest):
-         assert "DEBUG" in pkispawnlog
-         assert "INFO" in pkispawnlog
- 
-+    def test_password_lock_ldap_logs(self):
-+        """
-+        Test that when a user fails LDAP authentication while in lockout
-+        that it is logged.
-+        """
-+        user = 'ldapuser'
-+        password = 'Secret123'
-+        bad_password = 'foo'
-+        basedn = self.master.domain.basedn
-+        binddn = DN(f"uid={user},cn=users,cn=accounts,{basedn}")
-+
-+        tasks.kinit_admin(self.master)
-+        tasks.create_active_user(
-+            self.master, user, password=password
-+        )
-+
-+        serverid = realm_to_serverid(self.master.domain.realm)
-+        log_file = '/var/log/dirsrv/slapd-{}/errors'.format(serverid)
-+
-+        logsize = len(self.master.get_file_contents(log_file))
-+
-+        # Lock out the user on master
-+        for _i in range(0, 7):
-+            tasks.kinit_user(self.master, user, bad_password, raiseonerr=False)
-+
-+        conn = self.master.ldap_connect()
-+        try:
-+            conn.simple_bind(binddn, f"{password}")
-+        except DatabaseError:
-+            # This is expected
-+            pass
-+
-+        error_log = self.master.get_file_contents(log_file)[logsize:]
-+
-+        assert b'Too many failed authentication attempts' in error_log
-+
-     def test_reset_password_unlock(self):
-         """
-         Test that when a user is also unlocked when their password
--- 
-2.49.0
-
-
-From 100737fff5a0039cd883a92400d1495dd5bf7658 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 7 Mar 2025 09:01:35 +0100
-Subject: [PATCH 099/116] WebUI: fix the tooltip for Search Size limit
-
-The tooltip for IPA Server > Configuration > Search size limit
-is using the doc from ipasearchtimelimit instead of
-ipasearchrecordslimit.
-
-Use the right tooltip to properly display:
-Maximum number of records to search (-1 or 0 is unlimited)
-
-Fixes: https://pagure.io/freeipa/issue/9758
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- install/ui/src/freeipa/serverconfig.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/install/ui/src/freeipa/serverconfig.js b/install/ui/src/freeipa/serverconfig.js
-index e81e48cfb..d7f67e885 100644
---- a/install/ui/src/freeipa/serverconfig.js
-+++ b/install/ui/src/freeipa/serverconfig.js
-@@ -47,7 +47,7 @@ return {
-                     fields: [
-                         {
-                             name: 'ipasearchrecordslimit',
--                            tooltip: '@mc-opt:config_mod:ipasearchtimelimit:doc'
-+                            tooltip: '@mc-opt:config_mod:ipasearchrecordslimit:doc'
-                         },
-                         {
-                             name: 'ipasearchtimelimit',
--- 
-2.49.0
-
-
-From d03164fc104588e88ad75483e7233b7fccacabb6 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 6 Mar 2025 15:58:53 +0100
-Subject: [PATCH 100/116] ipatests: adapt error code and message for samba 4.22
-
-When establishing trust with an unreachable AD domain controller,
-the error code and message have changed with samba 4.22.
-
-Update the test to be compatible with any version of samba.
-
-Fixes: https://pagure.io/freeipa/issue/9751
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/test_integration/test_trust.py | 14 ++++++++++++--
- 1 file changed, 12 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
-index f71ec377b..4086cb30a 100644
---- a/ipatests/test_integration/test_trust.py
-+++ b/ipatests/test_integration/test_trust.py
-@@ -1072,8 +1072,18 @@ class TestTrust(BaseTestTrust):
-                 paths.VAR_LOG_HTTPD_ERROR,
-                 encoding='utf-8'
-             )
--            assert 'CIFS server communication error: code "3221225653", ' \
--                   'message "{Device Timeout}' in httpd_error_log
-+
-+            # The error code and message changed in samba 4.22
-+            old_msg = 'CIFS server communication error: code "3221225653", ' \
-+                      'message "{Device Timeout}'
-+            new_msg = 'CIFS server communication error: code "3221226021", ' \
-+                      'message "The object was not found."'
-+            result = self.master.run_command(["smbstatus", "-V"]).stdout_text
-+            version = result.split()[1]
-+            if tasks.parse_version(version) < tasks.parse_version('4.22.0rc4'):
-+                assert old_msg in httpd_error_log
-+            else:
-+                assert new_msg in httpd_error_log
- 
-             # Check that trust is successfully established with --server option
-             tasks.establish_trust_with_ad(
--- 
-2.49.0
-
-
-From 3553cd3ddcbe7014c790d0df1b5b3de86bb64065 Mon Sep 17 00:00:00 2001
-From: Erik Belko <ebelko@redhat.com>
-Date: Thu, 16 Jan 2025 14:32:11 +0100
-Subject: [PATCH 101/116] man: fix formatting and syntax issues
-
-Signed-off-by: Erik Belko <ebelko@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- client/man/ipa-client-install.1 | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/client/man/ipa-client-install.1 b/client/man/ipa-client-install.1
-index e6f641254..6833991b8 100644
---- a/client/man/ipa-client-install.1
-+++ b/client/man/ipa-client-install.1
-@@ -234,9 +234,9 @@ uninstall.
- 
- .SS "PKINIT OPTIONS"
- .TP
--\fB\-\-pkinit\-identity=\fIDENTITY\fR
-+\fB\-\-pkinit\-identity=\fIIDENTITY\fR
- Identity string for PKINIT authentication to use to join the IPA realm,
--for example 'FILE:/path/to/cert.pem,/path/to/key.pem'. See krb5.conf(5)
-+for example \fIFILE:/path/to/cert.pem,/path/to/key.pem\fR. See krb5.conf(5)
- for more information. The option is mutually exclusive with
- \fB\-\-password\fR and \fB\-\-keytab\fR.
- .TP
--- 
-2.49.0
-
-
-From 7b038741b816e2ccda33cfbed8d64b9827d3565e Mon Sep 17 00:00:00 2001
-From: Vasily Parfenov <wasly_brain@yahoo.com>
-Date: Tue, 4 Mar 2025 13:40:35 +0100
-Subject: [PATCH 102/116] man: fix incorrect groff syntax in man pages
-
-Updating cases in man pages which had incorrect groff syntax
-
-Signed-off-by: Erik Belko <ebelko@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- client/man/default.conf.5 | 4 ++--
- client/man/epn.conf.5     | 2 +-
- client/man/ipa.1          | 2 +-
- 3 files changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/client/man/default.conf.5 b/client/man/default.conf.5
-index 461c60134..7d0bdcf9c 100644
---- a/client/man/default.conf.5
-+++ b/client/man/default.conf.5
-@@ -47,7 +47,7 @@ Valid lines consist of an option name, an equals sign and a value. Spaces surrou
- 
- Values should not be quoted, the quotes will not be stripped.
- 
--.RS L
-+.RS 4
-     # Wrong \- don't include quotes
-     verbose = "True"
- 
-@@ -273,7 +273,7 @@ IPA configuration used while installing an IPA client
- .TP
- An example of a context-specific configuration file is \fB/etc/ipa/dns.conf\fR to be used to increase debug output of the IPA DNSSEC daemons.
- .TP
--.RS L
-+.RS 4
- [global]
- debug = True
- .RE
-diff --git a/client/man/epn.conf.5 b/client/man/epn.conf.5
-index 5df409ad5..9fad6048a 100644
---- a/client/man/epn.conf.5
-+++ b/client/man/epn.conf.5
-@@ -34,7 +34,7 @@ Valid lines consist of an option name, an equals sign and a value. Spaces surrou
- 
- Values should not be quoted, the quotes will not be stripped.
- 
--.RS L
-+.RS 4
-     # Wrong \- don't include quotes
-     verbose = "True"
- 
-diff --git a/client/man/ipa.1 b/client/man/ipa.1
-index c404c5be3..6d383b605 100644
---- a/client/man/ipa.1
-+++ b/client/man/ipa.1
-@@ -146,7 +146,7 @@ May 21 11:31:33 master1.ipa1.test /usr/bin/ipa[247422]: [IPA.API] [autobind]: us
- executable name and PID (`/mod_wsgi` for HTTP end-point)
- .TP
- \fB[IPA.API]\fR
--marker to allow searches with \fBjournalctl -g IPA.API\R
-+marker to allow searches with \fBjournalctl -g IPA.API\fR
- .TP
- \fBusername@REALM\fR
- authenticated Kerberos principal or \fB[autobind]\fR marker for LDAP-based operations done as root
--- 
-2.49.0
-
-
-From 1e47185289fbbe29eedca82a4872d1d075b26c26 Mon Sep 17 00:00:00 2001
-From: Frederik Himpe <frederik@frehi.be>
-Date: Thu, 27 Feb 2025 11:28:30 +0100
-Subject: [PATCH 103/116] Make path of Samba lock directory configurable and
- use /run/samba on Debian
-
-Signed-off-by: Frederik Himpe <frederik@frehi.be>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaplatform/base/paths.py            | 1 +
- ipaplatform/debian/paths.py          | 1 +
- ipaserver/install/adtrustinstance.py | 2 +-
- 3 files changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
-index 6a62d7bd0..f794aae6d 100644
---- a/ipaplatform/base/paths.py
-+++ b/ipaplatform/base/paths.py
-@@ -350,6 +350,7 @@ class BasePathNamespace:
-     KRA_CS_CFG_PATH = "/var/lib/pki/pki-tomcat/conf/kra/CS.cfg"
-     KRACERT_P12 = "/root/kracert.p12"
-     SAMBA_DIR = "/var/lib/samba"
-+    SAMBA_LOCKDIR = "/var/lib/samba/lock"
-     SSSD_DB = "/var/lib/sss/db"
-     SSSD_MC_GROUP = "/var/lib/sss/mc/group"
-     SSSD_MC_PASSWD = "/var/lib/sss/mc/passwd"
-diff --git a/ipaplatform/debian/paths.py b/ipaplatform/debian/paths.py
-index 7a8099680..229f185f0 100644
---- a/ipaplatform/debian/paths.py
-+++ b/ipaplatform/debian/paths.py
-@@ -44,6 +44,7 @@ class DebianPathNamespace(BasePathNamespace):
-     OPENSSL_DIR = "/usr/lib/ssl"
-     OPENSSL_CERTS_DIR = "/usr/lib/ssl/certs"
-     OPENSSL_PRIVATE_DIR = "/usr/lib/ssl/private"
-+    SAMBA_LOCKDIR = "/run/samba"
-     ETC_DEBIAN_VERSION = "/etc/debian_version"
-     # Old versions of freeipa wrote all trusted certificates to a single
-     # file, which is not supported by ca-certificates.
-diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
-index df2586ef1..bc3a282e6 100644
---- a/ipaserver/install/adtrustinstance.py
-+++ b/ipaserver/install/adtrustinstance.py
-@@ -962,7 +962,7 @@ class ADTRUSTInstance(service.Service):
-         # in /var/lib/samba and /var/lib/samba/private
-         for smbpath in (paths.SAMBA_DIR,
-                         os.path.join(paths.SAMBA_DIR, "private"),
--                        os.path.join(paths.SAMBA_DIR, "lock")):
-+                        paths.SAMBA_LOCKDIR):
-             if os.path.isdir(smbpath):
-                 tdb_files = [
-                     os.path.join(smbpath, tdb_file)
--- 
-2.49.0
-
-
-From 9b566fe458fb36eb5eb3212b01bc6ba48ac8349a Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 11 Mar 2025 15:55:11 +0100
-Subject: [PATCH 104/116] Leapp upgrade: skip systemctl calls
-
-During LEAPP upgrade, the system is booted in a special mode
-without systemd. As a consequence, any scriptlet calling
-systemctl fails and may break the upgrade.
-
-Skip the call to systemctl if a LEAPP upgrade is in progress
-(this is easily checked using the env variable $LEAPP_IPU_IN_PROGRESS
-that is set for instance to LEAPP_IPU_IN_PROGRESS=8to9).
-
-Fixes: RHEL-82089
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in | 14 ++++++++++----
- 1 file changed, 10 insertions(+), 4 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index b539f51f8..143ee5c83 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1241,8 +1241,11 @@ if [ $1 = 0 ]; then
- # NOTE: systemd specific section
-     /bin/systemctl --quiet stop ipa.service || :
-     /bin/systemctl --quiet disable ipa.service || :
--    /bin/systemctl reload-or-try-restart dbus
--    /bin/systemctl reload-or-try-restart oddjobd
-+    # Skip systemctl calls when leapp upgrade is in progress
-+    if [ -z "$LEAPP_IPU_IN_PROGRESS" ] ; then
-+        /bin/systemctl reload-or-try-restart dbus
-+        /bin/systemctl reload-or-try-restart oddjobd
-+    fi
- # END
- fi
- 
-@@ -1306,8 +1309,11 @@ fi
- %preun server-trust-ad
- if [ $1 -eq 0 ]; then
-     %{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
--    /bin/systemctl reload-or-try-restart dbus
--    /bin/systemctl reload-or-try-restart oddjobd
-+    # Skip systemctl calls when leapp upgrade is in progress
-+    if [ -z "$LEAPP_IPU_IN_PROGRESS" ] ; then
-+        /bin/systemctl reload-or-try-restart dbus
-+        /bin/systemctl reload-or-try-restart oddjobd
-+    fi
- fi
- 
- # ONLY_CLIENT
--- 
-2.49.0
-
-
-From 883f69db280071cf8003eff977f6f061651c7a7d Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 11 Mar 2025 18:32:43 +0100
-Subject: [PATCH 105/116] ipatest: make test_cert more robust to replication
- delays
-
-The test TestCAShowErrorHandling::test_ca_show_error_handling is
-adding a subca on the replica, then checks the entry is present on the
-master.
-If the replication is a bit slow, the call on the master may fail to
-return the newly created subca.
-The test should wait for replication to complete before calling
-ipa ca-find.
-
-Fixes: https://pagure.io/freeipa/issue/9762
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_cert.py | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 91598b655..c642caaf0 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -548,6 +548,8 @@ class TestCAShowErrorHandling(IntegrationTest):
-             'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA 1'
-         ])
-         assert 'Created CA "{}"'.format(lwca) in result.stdout_text
-+        # wait for replication to propagate the change
-+        tasks.wait_for_replication(self.replicas[0].ldap_connect())
-         result = self.master.run_command(['ipa', 'ca-find'])
-         assert 'Name: {}'.format(lwca) in result.stdout_text
-         result = self.master.run_command(
--- 
-2.49.0
-
-
-From 653b4b6971b1778988718840a301c10b3e35e700 Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Thu, 6 Mar 2025 09:32:01 +0100
-Subject: [PATCH 106/116] Disable --raw and --structured together
-
-Disables --raw and --structured for dnsrecord-* command.
-This is being shown in help for structured, as raw is implemented in
-almost every command, therefore people are more likely to view
-structured. Also contains tests, even though this is newly noted, this
-combination has never worked in the past.
-
-Fixes: https://pagure.io/freeipa/issue/9756
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaclient/remote_plugins/2_114/dns.py   | 15 +++++++----
- ipaclient/remote_plugins/2_156/dns.py   | 15 +++++++----
- ipaclient/remote_plugins/2_164/dns.py   | 15 +++++++----
- ipaclient/remote_plugins/2_49/dns.py    | 15 +++++++----
- ipaserver/plugins/dns.py                | 28 ++++++++++++++++++++
- ipatests/test_xmlrpc/test_dns_plugin.py | 35 +++++++++++++++++++++++++
- 6 files changed, 103 insertions(+), 20 deletions(-)
-
-diff --git a/ipaclient/remote_plugins/2_114/dns.py b/ipaclient/remote_plugins/2_114/dns.py
-index 626042000..2f414927b 100644
---- a/ipaclient/remote_plugins/2_114/dns.py
-+++ b/ipaclient/remote_plugins/2_114/dns.py
-@@ -2625,7 +2625,8 @@ class dnsrecord_add(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -2991,7 +2992,8 @@ class dnsrecord_del(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -3405,7 +3407,8 @@ class dnsrecord_find(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4290,7 +4293,8 @@ class dnsrecord_mod(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4363,7 +4367,8 @@ class dnsrecord_show(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-diff --git a/ipaclient/remote_plugins/2_156/dns.py b/ipaclient/remote_plugins/2_156/dns.py
-index 4ebad93e7..9ce8a7eef 100644
---- a/ipaclient/remote_plugins/2_156/dns.py
-+++ b/ipaclient/remote_plugins/2_156/dns.py
-@@ -2540,7 +2540,8 @@ class dnsrecord_add(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -2861,7 +2862,8 @@ class dnsrecord_del(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -3230,7 +3232,8 @@ class dnsrecord_find(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4065,7 +4068,8 @@ class dnsrecord_mod(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4138,7 +4142,8 @@ class dnsrecord_show(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-diff --git a/ipaclient/remote_plugins/2_164/dns.py b/ipaclient/remote_plugins/2_164/dns.py
-index f5adb4d54..284ef2cda 100644
---- a/ipaclient/remote_plugins/2_164/dns.py
-+++ b/ipaclient/remote_plugins/2_164/dns.py
-@@ -2548,7 +2548,8 @@ class dnsrecord_add(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -2869,7 +2870,8 @@ class dnsrecord_del(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -3238,7 +3240,8 @@ class dnsrecord_find(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4073,7 +4076,8 @@ class dnsrecord_mod(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4146,7 +4150,8 @@ class dnsrecord_show(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-diff --git a/ipaclient/remote_plugins/2_49/dns.py b/ipaclient/remote_plugins/2_49/dns.py
-index 4b543a2c2..1610f4af1 100644
---- a/ipaclient/remote_plugins/2_49/dns.py
-+++ b/ipaclient/remote_plugins/2_49/dns.py
-@@ -2233,7 +2233,8 @@ class dnsrecord_add(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -2594,7 +2595,8 @@ class dnsrecord_del(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -3013,7 +3015,8 @@ class dnsrecord_find(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4025,7 +4028,8 @@ class dnsrecord_mod(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-@@ -4094,7 +4098,8 @@ class dnsrecord_show(Method):
-         parameters.Flag(
-             'structured',
-             label=_(u'Structured'),
--            doc=_(u'Parse all raw DNS records and return them in a structured way'),
-+            doc=_(u'Parse all raw DNS records and return them in a '
-+                  u'structured way. Can not be used with --raw.'),
-             default=False,
-             autofill=True,
-         ),
-diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py
-index 0d6260cd6..ff2d3ff8a 100644
---- a/ipaserver/plugins/dns.py
-+++ b/ipaserver/plugins/dns.py
-@@ -3587,6 +3587,12 @@ class dnsrecord_add(LDAPCreate):
- 
-     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
-         assert isinstance(dn, DN)
-+
-+        if options.get('structured') and options.get('raw'):
-+            raise errors.MutuallyExclusiveError(
-+                reason=_("cannot use structured together with raw")
-+            )
-+
-         precallback_attrs = []
-         processed_attrs = []
-         for option, option_val in options.items():
-@@ -3729,6 +3735,12 @@ class dnsrecord_mod(LDAPUpdate):
- 
-     def pre_callback(self, ldap, dn, entry_attrs, attrs_list,  *keys, **options):
-         assert isinstance(dn, DN)
-+
-+        if options.get('structured') and options.get('raw'):
-+            raise errors.MutuallyExclusiveError(
-+                reason=_("cannot use structured together with raw")
-+            )
-+
-         if options.get('rename') and self.obj.is_pkey_zone_record(*keys):
-             # zone rename is not allowed
-             raise errors.ValidationError(name='rename',
-@@ -3883,6 +3895,7 @@ class dnsrecord_del(LDAPUpdate):
- 
-     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
-         assert isinstance(dn, DN)
-+
-         try:
-             old_entry = ldap.get_entry(dn, _record_attributes)
-         except errors.NotFound:
-@@ -3983,6 +3996,16 @@ class dnsrecord_show(LDAPRetrieve):
-         dnsrecord.structured_flag,
-     )
- 
-+    def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
-+        assert isinstance(dn, DN)
-+
-+        if options.get('structured') and options.get('raw'):
-+            raise errors.MutuallyExclusiveError(
-+                reason=_("cannot use structured together with raw")
-+            )
-+
-+        return dn
-+
-     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-         assert isinstance(dn, DN)
-         if self.obj.is_pkey_zone_record(*keys):
-@@ -4013,6 +4036,11 @@ class dnsrecord_find(LDAPSearch):
-                      dnszoneidnsname, *args, **options):
-         assert isinstance(base_dn, DN)
- 
-+        if options.get('structured') and options.get('raw'):
-+            raise errors.MutuallyExclusiveError(
-+                reason=_("cannot use structured together with raw")
-+            )
-+
-         # validate if zone is master zone
-         self.obj.check_zone(dnszoneidnsname, **options)
- 
-diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py
-index 39d42e306..803b0a957 100644
---- a/ipatests/test_xmlrpc/test_dns_plugin.py
-+++ b/ipatests/test_xmlrpc/test_dns_plugin.py
-@@ -3426,6 +3426,41 @@ class test_dns(Declarative):
-             },
-         ),
- 
-+        dict(
-+            desc="Ensure --raw and --structure does not work "
-+                 "for ipa dnsrecord-add",
-+            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            expected=errors.MutuallyExclusiveError(
-+                reason=u"cannot use structured together with raw"
-+            ),
-+        ),
-+
-+        dict(
-+            desc="Ensure --raw and --structure does not work "
-+                 "for ipa dnsrecord-mod",
-+            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            expected=errors.MutuallyExclusiveError(
-+                reason=u"cannot use structured together with raw"
-+            ),
-+        ),
-+
-+        dict(
-+            desc="Ensure --raw and --structure does not work "
-+                 "for ipa dnsrecord-show",
-+            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            expected=errors.MutuallyExclusiveError(
-+                reason=u"cannot use structured together with raw"
-+            ),
-+        ),
-+
-+        dict(
-+            desc="Ensure --raw and --structure does not work "
-+                 "for ipa dnsrecord-find",
-+            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            expected=errors.MutuallyExclusiveError(
-+                reason=u"cannot use structured together with raw"
-+            ),
-+        ),
-     ]
- 
- 
--- 
-2.49.0
-
-
-From f906e3625491e9b6fc67fdd5ac6b429531658be1 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 28 Feb 2025 14:57:25 +0200
-Subject: [PATCH 107/116] config-mod: allow disabling subordinate ID
- integration
-
-When full 32-bit ID range usage is required, subordinate ID support have
-to be disabled. However, even if ID range for subordinate IDs were to be
-removed, it will be restored during the next data upgrade.
-
-Change upgrade code to only apply subID range creation when subID
-support is enabled.
-
-Do not allow allocating subIDs if their use is disabled.
-
-Allow full 32-bit uidNumber/gidNumber values in JSON payload.
-
-Fixes: https://pagure.io/freeipa/issue/9757
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- API.txt                                       |  2 +-
- doc/api/config_mod.md                         |  2 +-
- doc/designs/subordinate-ids.md                | 22 +++++++++
- install/share/Makefile.am                     |  1 +
- install/share/subid-generators.uldif          | 38 ++++++++++++++++
- install/updates/73-subid.update               | 37 ---------------
- .../updates/90-post_upgrade_plugins.update    |  1 +
- ipalib/messages.py                            | 13 ++++++
- ipaplatform/base/paths.py                     |  1 +
- ipaserver/install/ipa_subids.py               |  5 +++
- .../install/plugins/update_subid_support.py   | 45 +++++++++++++++++++
- ipaserver/plugins/config.py                   | 34 +++++++++++++-
- ipaserver/plugins/subid.py                    | 11 +++++
- ipaserver/plugins/user.py                     |  4 +-
- 14 files changed, 174 insertions(+), 42 deletions(-)
- create mode 100644 install/share/subid-generators.uldif
- create mode 100644 ipaserver/install/plugins/update_subid_support.py
-
-diff --git a/API.txt b/API.txt
-index 61e8e463a..f19e3bf34 100644
---- a/API.txt
-+++ b/API.txt
-@@ -1083,7 +1083,7 @@ option: Flag('all', autofill=True, cli_name='all', default=False)
- option: Str('ca_renewal_master_server?', autofill=False)
- option: Str('delattr*', cli_name='delattr')
- option: Flag('enable_sid?', autofill=True, default=False)
--option: StrEnum('ipaconfigstring*', autofill=False, cli_name='ipaconfigstring', values=[u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs', u'EnforceLDAPOTP'])
-+option: StrEnum('ipaconfigstring*', autofill=False, cli_name='ipaconfigstring', values=[u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs', u'EnforceLDAPOTP', u'SubID:Disable'])
- option: Str('ipadefaultemaildomain?', autofill=False, cli_name='emaildomain')
- option: Str('ipadefaultloginshell?', autofill=False, cli_name='defaultshell')
- option: Str('ipadefaultprimarygroup?', autofill=False, cli_name='defaultgroup')
-diff --git a/doc/api/config_mod.md b/doc/api/config_mod.md
-index b3203c350..e18dd55c7 100644
---- a/doc/api/config_mod.md
-+++ b/doc/api/config_mod.md
-@@ -27,7 +27,7 @@ No arguments.
- * ipauserobjectclasses : :ref:`Str<Str>`
- * ipapwdexpadvnotify : :ref:`Int<Int>`
- * ipaconfigstring : :ref:`StrEnum<StrEnum>`
-- * Values: ('AllowNThash', 'KDC:Disable Last Success', 'KDC:Disable Lockout', 'KDC:Disable Default Preauth for SPNs', 'EnforceLDAPOTP')
-+ * Values: ('AllowNThash', 'KDC:Disable Last Success', 'KDC:Disable Lockout', 'KDC:Disable Default Preauth for SPNs', 'EnforceLDAPOTP', 'SubID:Disable')
- * ipaselinuxusermaporder : :ref:`Str<Str>`
- * ipaselinuxusermapdefault : :ref:`Str<Str>`
- * ipakrbauthzdata : :ref:`StrEnum<StrEnum>`
-diff --git a/doc/designs/subordinate-ids.md b/doc/designs/subordinate-ids.md
-index b280df1a9..dac1c3292 100644
---- a/doc/designs/subordinate-ids.md
-+++ b/doc/designs/subordinate-ids.md
-@@ -64,6 +64,18 @@ and don't auto-map or auto-assign subordinate ids by default. Instead
- we give the admin several options to assign them manually, semi-manual,
- or automatically.
- 
-+For deployments where there is a need to consume IDs above 2^31 for normal UID
-+and GID assignments, one has to disable subordinate ID feature. This should be
-+done with `ipa config-mod --addattr ipaconfigstring=SubID:Disable` command.
-+After it is done, subordinate ID range can be removed with `ipa idrange-del`
-+command and on the IPA server one have to run `ipa-server-upgrade` command to
-+make sure internal DNA plugin configuration is removed as well.
-+Finally, a new local ID range can be added to cover required part of the
-+2^31..2^32-1 space. The range must have RID bases to make sure FreeIPA will
-+generate SIDs properly to users and groups created with IDs from this range.
-+
-+**NOTE**: Disabling subordinate ID feature can only be done if no subordinate
-+IDs were already allocated.
- 
- ### Revision 1 limitation
- 
-@@ -340,6 +352,16 @@ subordinate id entries for new users:
- $ ipa config-mod --user-default-subid=true
- ```
- 
-+Subordinate ID feature can be disabled completely. This is done with `ipa
-+config-mod --addattr ipaconfigstring=SubID:Disable` command. After it is done,
-+subordinate ID range can be removed with `ipa idrange-del` command and on the
-+IPA server one have to run `ipa-server-upgrade` command to make sure internal
-+DNA plugin configuration is removed as well. Finally, a new local ID range can
-+be added to cover the required part of the full 32-bit ID space.
-+
-+**NOTE**: Disabling subordinate ID feature can only be done if no subordinate
-+IDs were already allocated.
-+
- Subordinate ids are managed by a new plugin class. The ``subid-add``
- and ``subid-del`` commands are hidden from command line. New subordinate
- ids are generated and auto-assigned with ``subid-generate``.
-diff --git a/install/share/Makefile.am b/install/share/Makefile.am
-index 4029297b7..d8d270ca9 100644
---- a/install/share/Makefile.am
-+++ b/install/share/Makefile.am
-@@ -90,6 +90,7 @@ dist_app_DATA =				\
- 	vault.ldif			\
- 	kdcproxy-enable.uldif		\
- 	kdcproxy-disable.uldif		\
-+	subid-generators.uldif		\
- 	ipa-httpd.conf.template		\
- 	ipa-httpd-wsgi.conf.template	\
- 	gssapi.login			\
-diff --git a/install/share/subid-generators.uldif b/install/share/subid-generators.uldif
-new file mode 100644
-index 000000000..118077382
---- /dev/null
-+++ b/install/share/subid-generators.uldif
-@@ -0,0 +1,38 @@
-+# DNA plugin and idrange configuration
-+dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
-+default: objectClass: nsContainer
-+default: objectClass: top
-+default: cn: subordinate-ids
-+
-+dn: cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
-+default: objectclass: top
-+default: objectclass: extensibleObject
-+default: cn: Subordinate IDs
-+default: dnaType: ipasubuidnumber
-+default: dnaType: ipasubgidnumber
-+default: dnaNextValue: eval($SUBID_RANGE_START)
-+default: dnaMaxValue: eval($SUBID_RANGE_MAX)
-+default: dnaMagicRegen: -1
-+default: dnaFilter: (objectClass=ipaSubordinateId)
-+default: dnaScope: $SUFFIX
-+default: dnaThreshold: eval($SUBID_DNA_THRESHOLD)
-+default: dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
-+default: dnaExcludeScope: cn=provisioning,$SUFFIX
-+default: dnaInterval: eval($SUBID_COUNT)
-+add: aci: (targetattr = "dnaNextRange || dnaNextValue || dnaMaxValue")(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
-+add: aci: (targetattr = "cn || dnaMaxValue || dnaNextRange || dnaNextValue  || dnaThreshold || dnaType || objectclass")(version 3.0;acl "permission:Read DNA Range";allow (read, search, compare) groupdn = "ldap:///cn=Read DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
-+
-+dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
-+default: objectClass: top
-+default: objectClass: ipaIDrange
-+default: objectClass: ipaTrustedADDomainRange
-+default: cn: ${REALM}_subid_range
-+default: ipaBaseID: $SUBID_RANGE_START
-+default: ipaIDRangeSize: $SUBID_RANGE_SIZE
-+# HACK: RIDs to work around adtrust sidgen issue
-+default: ipaBaseRID: eval($SUBID_BASE_RID)
-+default: ipaNTTrustedDomainSID: S-1-5-21-738065-838566-$DOMAIN_HASH
-+# HACK: "ipa-local-subid" range type causes issues with older SSSD clients
-+# see https://github.com/SSSD/sssd/issues/5571
-+default: ipaRangeType: ipa-ad-trust
-+
-diff --git a/install/updates/73-subid.update b/install/updates/73-subid.update
-index 3c030b41e..18bca60bc 100644
---- a/install/updates/73-subid.update
-+++ b/install/updates/73-subid.update
-@@ -67,40 +67,3 @@ dn: cn=subids,cn=accounts,$SUFFIX
- add: aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(targetattr="description || ipaowner || ipauniqueid")(targattrfilters = "add=objectClass:(|(objectClass=top)(objectClass=ipasubordinateid)(objectClass=ipasubordinateidentry)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(ipasubuidnumber=-1) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(ipasubgidnumber=-1) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "selfservice: Add subordinate id";allow (add, write) userattr = "ipaowner#SELFDN" and groupdn="ldap:///cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX";)
- add: aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(targetattr="description || ipaowner || ipauniqueid")(targattrfilters = "add=objectClass:(|(objectClass=top)(objectClass=ipasubordinateid)(objectClass=ipasubordinateidentry)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=1)(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=1)(ipasubgidnumber=-1)) && ipasubgidcount:(ipasubgidcount=eval($SUBID_COUNT)), del=ipasubuidnumber:(!(ipasubuidnumber=*)) && ipasubuidcount:(!(ipasubuidcount=*)) && ipasubgidnumber:(!(ipasubgidnumber=*)) && ipasubgidcount:(!(ipasubgidcount=*))")(version 3.0;acl "Add subordinate ids to any user";allow (add, write) groupdn="ldap:///cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX";)
- 
--# DNA plugin and idrange configuration
--dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
--default: objectClass: nsContainer
--default: objectClass: top
--default: cn: subordinate-ids
--
--dn: cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
--default: objectclass: top
--default: objectclass: extensibleObject
--default: cn: Subordinate IDs
--default: dnaType: ipasubuidnumber
--default: dnaType: ipasubgidnumber
--default: dnaNextValue: eval($SUBID_RANGE_START)
--default: dnaMaxValue: eval($SUBID_RANGE_MAX)
--default: dnaMagicRegen: -1
--default: dnaFilter: (objectClass=ipaSubordinateId)
--default: dnaScope: $SUFFIX
--default: dnaThreshold: eval($SUBID_DNA_THRESHOLD)
--default: dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
--default: dnaExcludeScope: cn=provisioning,$SUFFIX
--default: dnaInterval: eval($SUBID_COUNT)
--add: aci: (targetattr = "dnaNextRange || dnaNextValue || dnaMaxValue")(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
--add: aci: (targetattr = "cn || dnaMaxValue || dnaNextRange || dnaNextValue  || dnaThreshold || dnaType || objectclass")(version 3.0;acl "permission:Read DNA Range";allow (read, search, compare) groupdn = "ldap:///cn=Read DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
--
--dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
--default: objectClass: top
--default: objectClass: ipaIDrange
--default: objectClass: ipaTrustedADDomainRange
--default: cn: ${REALM}_subid_range
--default: ipaBaseID: $SUBID_RANGE_START
--default: ipaIDRangeSize: $SUBID_RANGE_SIZE
--# HACK: RIDs to work around adtrust sidgen issue
--default: ipaBaseRID: eval($SUBID_BASE_RID)
--default: ipaNTTrustedDomainSID: S-1-5-21-738065-838566-$DOMAIN_HASH
--# HACK: "ipa-local-subid" range type causes issues with older SSSD clients
--# see https://github.com/SSSD/sssd/issues/5571
--default: ipaRangeType: ipa-ad-trust
-diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
-index 9a9d80a92..7c3bba3e0 100644
---- a/install/updates/90-post_upgrade_plugins.update
-+++ b/install/updates/90-post_upgrade_plugins.update
-@@ -34,6 +34,7 @@ plugin: update_dnsforward_emptyzones
- plugin: update_managed_post
- plugin: update_managed_permissions
- plugin: update_read_replication_agreements_permission
-+plugin: update_subid_support
- plugin: update_idrange_baserid
- plugin: update_passync_privilege_update
- plugin: update_dnsserver_configuration_into_ldap
-diff --git a/ipalib/messages.py b/ipalib/messages.py
-index 732de7cb9..6a70bbc75 100644
---- a/ipalib/messages.py
-+++ b/ipalib/messages.py
-@@ -506,6 +506,19 @@ class MissingTargetAttributesinPermission(PublicMessage):
-                "are set.")
- 
- 
-+class ServerUpgradeRequired(PublicMessage):
-+    """
-+    **13033** Server upgrade required
-+    """
-+    errno = 13033
-+    type = "warning"
-+    format = _(
-+        "Change of the state of '%(feature)s' feature requires to run "
-+        "'ipa-server-upgrade' command on IPA server %(server)s "
-+        "to apply configuration changes."
-+    )
-+
-+
- def iter_messages(variables, base):
-     """Return a tuple with all subclasses
-     """
-diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
-index f794aae6d..a5bca789b 100644
---- a/ipaplatform/base/paths.py
-+++ b/ipaplatform/base/paths.py
-@@ -302,6 +302,7 @@ class BasePathNamespace:
-     NIS_UPDATE_ULDIF = "/usr/share/ipa/nis-update.uldif"
-     SCHEMA_COMPAT_ULDIF = "/usr/share/ipa/updates/91-schema_compat.update"
-     SCHEMA_COMPAT_POST_ULDIF = "/usr/share/ipa/schema_compat_post.uldif"
-+    SUBID_GENERATORS_ULDIF = "/usr/share/ipa/subid-generators.uldif"
-     IPA_JS_PLUGINS_DIR = "/usr/share/ipa/ui/js/plugins"
-     UPDATES_DIR = "/usr/share/ipa/updates/"
-     DICT_WORDS = "/usr/share/dict/words"
-diff --git a/ipaserver/install/ipa_subids.py b/ipaserver/install/ipa_subids.py
-index 1537047c3..8c542e4ea 100644
---- a/ipaserver/install/ipa_subids.py
-+++ b/ipaserver/install/ipa_subids.py
-@@ -116,6 +116,11 @@ class IPASubids(AdminTool):
-         api.finalize()
-         api.Backend.ldap2.connect()
-         self.ldap2 = api.Backend.ldap2
-+
-+        if api.Object.config.is_config_option_present('SubID:Disable'):
-+            print("Support for subordinate IDs is disabled.")
-+            return 2
-+
-         subid_generate = api.Command.subid_generate
- 
-         dry_run = self.safe_options.dry_run
-diff --git a/ipaserver/install/plugins/update_subid_support.py b/ipaserver/install/plugins/update_subid_support.py
-new file mode 100644
-index 000000000..54852d203
---- /dev/null
-+++ b/ipaserver/install/plugins/update_subid_support.py
-@@ -0,0 +1,45 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+import logging
-+from ipalib import Registry, Updater, errors
-+from ipaserver.install import ldapupdate
-+from ipaplatform.paths import paths
-+from ipapython.dn import DN
-+
-+logger = logging.getLogger(__name__)
-+
-+register = Registry()
-+
-+
-+@register()
-+class update_subid_support(Updater):
-+    """
-+    Conditionally add SubID ranges when subID support is enabled
-+    """
-+
-+    dna_plugin_dn = DN(
-+        ('cn', 'Distributed Numeric Assignment Plugin'),
-+        ('cn', 'plugins'),
-+        ('cn', 'config')
-+    )
-+
-+    def execute(self, **options):
-+        subid_disabled = self.api.Object.config.is_config_option_present(
-+            'SubID:Disable')
-+        if not subid_disabled:
-+            ld = ldapupdate.LDAPUpdate(api=self.api)
-+            ld.update([paths.SUBID_GENERATORS_ULDIF])
-+        else:
-+            # make sure to remove DNA configuration
-+            conn = self.api.Backend.ldap2
-+            try:
-+                subid_dna_config = DN(
-+                    ('cn', 'Subordinate IDs'), self.dna_plugin_dn
-+                )
-+                entry = conn.get_entry(subid_dna_config)
-+                conn.delete_entry(entry)
-+            except errors.NotFound:
-+                pass
-+
-+        return False, []
-diff --git a/ipaserver/plugins/config.py b/ipaserver/plugins/config.py
-index adf21ea0c..c509c2c13 100644
---- a/ipaserver/plugins/config.py
-+++ b/ipaserver/plugins/config.py
-@@ -33,7 +33,7 @@ from .baseldap import (
-     LDAPUpdate,
-     LDAPRetrieve)
- from .selinuxusermap import validate_selinuxuser
--from ipalib import _
-+from ipalib import _, messages
- from ipapython.admintool import ScriptError
- from ipapython.dn import DN
- from ipapython.ipavalidate import Email
-@@ -261,7 +261,7 @@ class config(LDAPObject):
-             values=(u'AllowNThash',
-                     u'KDC:Disable Last Success', u'KDC:Disable Lockout',
-                     u'KDC:Disable Default Preauth for SPNs',
--                    u'EnforceLDAPOTP'),
-+                    u'EnforceLDAPOTP', u'SubID:Disable'),
-         ),
-         Str('ipaselinuxusermaporder',
-             label=_('SELinux user map order'),
-@@ -521,6 +521,12 @@ class config(LDAPObject):
-         for domain in submitted_domains:
-             self._validate_single_domain(attr_name, domain, known_domains)
- 
-+    def is_config_option_present(self, option):
-+        dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'), self.api.env.basedn)
-+        configentry = self.api.Backend.ldap2.get_entry(dn, ['ipaconfigstring'])
-+        configstring = configentry['ipaconfigstring']
-+        return (option.lower() in map(str.lower, configstring))
-+
- 
- @register()
- class config_mod(LDAPUpdate):
-@@ -695,6 +701,30 @@ class config_mod(LDAPUpdate):
-                 raise errors.ValidationError(name=failedattr,
-                     error=_('SELinux user map default user not in order list'))
- 
-+        if 'ipaconfigstring' in entry_attrs:
-+            configstring = entry_attrs['ipaconfigstring']
-+            if 'SubID:Disable'.lower() in map(str.lower, configstring):
-+                # Check if SubIDs already allocated
-+                try:
-+                    result = self.api.Command.subid_stats()
-+                    stats = result['result']
-+                except errors.PublicError:
-+                    stats = {'assigned_subids': 0}
-+                if stats["assigned_subids"] > 0:
-+                    error_message = _("Subordinate ID feature can not be "
-+                                      "disabled when there are subIDs "
-+                                      "already in use.")
-+                    raise errors.ValidationError(name='configuration state',
-+                                                 error=error_message)
-+                # SubID:Disable enforces disabling default subid generation
-+                entry_attrs['ipauserdefaultsubordinateid'] = False
-+                self.add_message(
-+                    messages.ServerUpgradeRequired(
-+                        feature='Subordinate ID',
-+                        server=_('<all IPA servers>')
-+                    )
-+                )
-+
-         if 'ca_renewal_master_server' in options:
-             new_master = options['ca_renewal_master_server']
- 
-diff --git a/ipaserver/plugins/subid.py b/ipaserver/plugins/subid.py
-index 132c85c7f..2be2cdeff 100644
---- a/ipaserver/plugins/subid.py
-+++ b/ipaserver/plugins/subid.py
-@@ -265,6 +265,12 @@ class subid(LDAPObject):
- 
-     def handle_subordinate_ids(self, ldap, dn, entry_attrs):
-         """Handle ipaSubordinateId object class"""
-+
-+        if self.api.Object.config.is_config_option_present('SubID:Disable'):
-+            raise errors.ValidationError(
-+                name="configuration state",
-+                error=_("Support for subordinate IDs is disabled"))
-+
-         new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
-         new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
- 
-@@ -577,6 +583,11 @@ class subid_stats(LDAPQuery):
-         return int(entry.single_value["numSubordinates"])
- 
-     def execute(self, *keys, **options):
-+        if self.api.Object.config.is_config_option_present('SubID:Disable'):
-+            raise errors.ValidationError(
-+                name="configuration state",
-+                error=_("Support for subordinate IDs is disabled"))
-+
-         ldap = self.obj.backend
-         dna_remaining = self.get_remaining_dna(ldap, **options)
-         baseid, rangesize = self.get_idrange(ldap, **options)
-diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py
-index a3e9c2903..875f2b4ba 100644
---- a/ipaserver/plugins/user.py
-+++ b/ipaserver/plugins/user.py
-@@ -718,7 +718,9 @@ class user_add(baseuser_add):
-         default_subid = config.single_value.get(
-             'ipaUserDefaultSubordinateId', False
-         )
--        if default_subid:
-+        subid_disabled = self.api.Object.config.is_config_option_present(
-+            'SubID:Disable')
-+        if default_subid and not subid_disabled:
-             result = self.api.Command.subid_generate(
-                 ipaowner=entry_attrs.single_value['uid'],
-                 version=options['version']
--- 
-2.49.0
-
-
-From b8b91dfe71d7d049f5e55a8195cb37f87837bbce Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 12 Mar 2025 21:52:56 +0200
-Subject: [PATCH 108/116] update_dna_shared_config: do not fail when config is
- not found
-
-The helper function was supposed to return a DN or None.
-
-Related: https://pagure.io/freeipa/issue/9757
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/plugins/update_dna_shared_config.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/plugins/update_dna_shared_config.py b/ipaserver/install/plugins/update_dna_shared_config.py
-index 955bee5dd..42ee86d8b 100644
---- a/ipaserver/install/plugins/update_dna_shared_config.py
-+++ b/ipaserver/install/plugins/update_dna_shared_config.py
-@@ -49,7 +49,7 @@ class update_dna_shared_config(Updater):
-         except errors.NotFound:
-             logger.error("Could not find DNA config entry: %s",
-                          dna_config_base)
--            return False, ()
-+            return None
-         else:
-             logger.debug('Found DNA config %s', dna_config_base)
- 
--- 
-2.49.0
-
-
-From 65cb358c01568e9a11899dbfe21eaeb916af3cdf Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 28 Feb 2025 15:34:12 +0200
-Subject: [PATCH 109/116] baseuser: allow uidNumber and gidNumber of 32-bit
- range
-
-JSON format allows to encode integers up to 2^53-1. Linux systems allow
-for 32-bit IDs. Permit setting full 32-bit uidNumber and gidNumber
-through IPA API. Administrators already can set 32-bit IDs via LDAP.
-
-ID Range also needs to permit larger sizes of RID bases. SIDGEN plugin
-already treats RID bases as 1..MAX_UINT32.
-
-Fixes: https://pagure.io/freeipa/issue/9757
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/plugins/baseuser.py | 4 +++-
- ipaserver/plugins/idrange.py  | 4 ++++
- 2 files changed, 7 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
-index b66016305..22393b8f6 100644
---- a/ipaserver/plugins/baseuser.py
-+++ b/ipaserver/plugins/baseuser.py
-@@ -26,7 +26,7 @@ import six
- from ipalib import api, errors, constants
- from ipalib import (
-     Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
--from ipalib.parameters import Principal, Certificate
-+from ipalib.parameters import Principal, Certificate, MAX_UINT32
- from ipalib.plugable import Registry
- from .baseldap import (
-     DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
-@@ -348,11 +348,13 @@ class baseuser(LDAPObject):
-             label=_('UID'),
-             doc=_('User ID Number (system will assign one if not provided)'),
-             minvalue=1,
-+            maxvalue=MAX_UINT32,
-         ),
-         Int('gidnumber?',
-             label=_('GID'),
-             doc=_('Group ID Number'),
-             minvalue=1,
-+            maxvalue=MAX_UINT32,
-         ),
-         Str('street?',
-             cli_name='street',
-diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
-index ec061a455..26a3bb666 100644
---- a/ipaserver/plugins/idrange.py
-+++ b/ipaserver/plugins/idrange.py
-@@ -235,10 +235,14 @@ class idrange(LDAPObject):
-         Int('ipabaserid?',
-             cli_name='rid_base',
-             label=_('First RID of the corresponding RID range'),
-+            minvalue=1,
-+            maxvalue=Int.MAX_UINT32
-         ),
-         Int('ipasecondarybaserid?',
-             cli_name='secondary_rid_base',
-             label=_('First RID of the secondary RID range'),
-+            minvalue=1,
-+            maxvalue=Int.MAX_UINT32
-         ),
-         Str('ipanttrusteddomainsid?',
-             cli_name='dom_sid',
--- 
-2.49.0
-
-
-From 015d26bab4296dc18e97dd10054a3f668282ef88 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 5 Mar 2025 12:49:27 +0200
-Subject: [PATCH 110/116] ipatests: add a test to use full 32-bit ID range
- space
-
-The test reconfigures IPA deployment to disable subordinate IDs support
-and then configures an additional ID range to cover upper half of the
-2^32 ID space. It then makes sure that a user with an UID/GID from that
-ID range can be created and used.
-
-Fixes: https://pagure.io/freeipa/issue/9757
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_32bit_idranges.py   | 104 ++++++++++++++++++
- 1 file changed, 104 insertions(+)
- create mode 100644 ipatests/test_integration/test_32bit_idranges.py
-
-diff --git a/ipatests/test_integration/test_32bit_idranges.py b/ipatests/test_integration/test_32bit_idranges.py
-new file mode 100644
-index 000000000..e76e117e5
---- /dev/null
-+++ b/ipatests/test_integration/test_32bit_idranges.py
-@@ -0,0 +1,104 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+
-+from __future__ import absolute_import
-+
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+
-+
-+class Test32BitIdRanges(IntegrationTest):
-+    topology = "line"
-+
-+    def test_remove_subid_range(self):
-+        """
-+        Test that allocating subid will fail after disabling global option
-+        """
-+        master = self.master
-+        tasks.kinit_admin(master)
-+
-+        idrange = f"{master.domain.realm}_subid_range"
-+        master.run_command(
-+            ["ipa", "config-mod", "--addattr", "ipaconfigstring=SubID:Disable"]
-+        )
-+        master.run_command(["ipa", "idrange-del", idrange])
-+
-+        tasks.user_add(master, 'subiduser')
-+        result = master.run_command(
-+            ["ipa", "subid-generate", "--owner", "subiduser"], raiseonerr=False
-+        )
-+        assert result.returncode > 0
-+        assert "Support for subordinate IDs is disabled" in result.stderr_text
-+        tasks.user_del(master, 'subiduser')
-+
-+    def test_invoke_upgrader(self):
-+        """Test that ipa-server-upgrade does not add subid ranges back"""
-+
-+        master = self.master
-+        master.run_command(['ipa-server-upgrade'], raiseonerr=True)
-+        idrange = f"{master.domain.realm}_subid_range"
-+        result = master.run_command(
-+            ["ipa", "idrange-show", idrange], raiseonerr=False
-+        )
-+        assert result.returncode > 0
-+        assert f"{idrange}: range not found" in result.stderr_text
-+
-+        result = tasks.ldapsearch_dm(
-+            master,
-+            'cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,'
-+            'cn=plugins,cn=config',
-+            ['dnaType'],
-+            scope='base',
-+            raiseonerr=False
-+        )
-+        assert result.returncode == 32
-+        output = result.stdout_text.lower()
-+        assert "dnatype: " not in output
-+
-+    def test_create_user_with_32bit_id(self):
-+        """Test that ID range above 2^31 can be used to assign IDs
-+           to users and groups. Also check that SIDs generated properly.
-+        """
-+
-+        master = self.master
-+        idrange = f"{master.domain.realm}_upper_32bit_range"
-+        id_base = 1 << 31
-+        id_length = (1 << 31) - 2
-+        uid = id_base + 1
-+        gid = id_base + 1
-+        master.run_command(
-+            [
-+                "ipa",
-+                "idrange-add",
-+                idrange,
-+                "--base-id", str(id_base),
-+                "--range-size", str(id_length),
-+                "--rid-base", str(int(id_base >> 3)),
-+                "--secondary-rid-base", str(int(id_base >> 3) + id_length),
-+                "--type=ipa-local"
-+            ]
-+        )
-+
-+        # We added new ID range, SIDGEN will only take it after
-+        # restarting a directory server instance.
-+        tasks.restart_ipa_server(master)
-+
-+        # Clear SSSD cache to pick up new ID range
-+        tasks.clear_sssd_cache(master)
-+
-+        tasks.user_add(master, "user", extra_args=[
-+            "--uid", str(uid), "--gid", str(gid)
-+        ])
-+
-+        result = master.run_command(
-+            ["ipa", "user-show", "user", "--all", "--raw"], raiseonerr=False
-+        )
-+        assert result.returncode == 0
-+        assert "ipaNTSecurityIdentifier:" in result.stdout_text
-+
-+        result = master.run_command(
-+            ["id", "user"], raiseonerr=False
-+        )
-+        assert result.returncode == 0
-+        assert str(uid) in result.stdout_text
--- 
-2.49.0
-
-
-From 01f23216ab5b383710dad086a01bb73b2da383d1 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 17 Mar 2025 16:21:23 +0100
-Subject: [PATCH 111/116] idrange: use minvalue=0 for baserid and
- secondarybaserid
-
-With the support of 32 bit idrange, the minvalue was set to 1
-but this introduces a regression in the command ipa trust-add
-as the range for AD trust is added with baserid=0
-
-Lower the minvalue to 0
-
-Fixes: https://pagure.io/freeipa/issue/9765
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/plugins/idrange.py | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
-index 26a3bb666..d155fb46d 100644
---- a/ipaserver/plugins/idrange.py
-+++ b/ipaserver/plugins/idrange.py
-@@ -235,13 +235,13 @@ class idrange(LDAPObject):
-         Int('ipabaserid?',
-             cli_name='rid_base',
-             label=_('First RID of the corresponding RID range'),
--            minvalue=1,
-+            minvalue=0,
-             maxvalue=Int.MAX_UINT32
-         ),
-         Int('ipasecondarybaserid?',
-             cli_name='secondary_rid_base',
-             label=_('First RID of the secondary RID range'),
--            minvalue=1,
-+            minvalue=0,
-             maxvalue=Int.MAX_UINT32
-         ),
-         Str('ipanttrusteddomainsid?',
--- 
-2.49.0
-
-
-From d4f4c7c1b1ed113b33298eae5c4cca9884383335 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 17 Mar 2025 16:42:08 +0100
-Subject: [PATCH 112/116] gating tests: add
- test_ipahealthcheck.py::TestIpaHealthCheckWithADtrust
-
-This way we have at least one gating test that calls
-ipa trust-add.
-
-Related: test_ipahealthcheck.py::TestIpaHealthCheckWithADtrust
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/prci_definitions/gating.yaml | 16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index f17af796c..cbcbb094d 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -19,6 +19,10 @@ topologies:
-     name: ad_master_2client
-     cpu: 4
-     memory: 10596
-+  adroot_adchild_adtree_master_1client: &adroot_adchild_adtree_master_1client
-+    name: adroot_adchild_adtree_master_1client
-+    cpu: 8
-+    memory: 14466
-   ipaserver: &ipaserver
-     name: ipaserver
-     cpu: 2
-@@ -350,3 +354,15 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 3600
-         topology: *master_2repl_1client
-+
-+  fedora-latest-ipa-4-12/test_ipahealthcheck_adtrust:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunADTests
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_ipahealthcheck.py::TestIpaHealthCheckWithADtrust
-+        template: *ci-ipa-4-12-latest
-+        timeout: 4800
-+        topology: *adroot_adchild_adtree_master_1client
--- 
-2.49.0
-
-
-From 2f389150781f822f3eab13a67c6d5d11d8b36926 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 19 Mar 2025 14:27:15 +0100
-Subject: [PATCH 113/116] ipatests: use newer fedora 41 box
-
-Regenerated March 19 with latest pkgs
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/prci_definitions/gating.yaml                         | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml        | 2 +-
- .../prci_definitions/nightly_ipa-4-12_latest_selinux.yaml     | 2 +-
- ipatests/prci_definitions/temp_commit.yaml                    | 4 ++--
- 4 files changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index cbcbb094d..94910bc8c 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -39,7 +39,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 6229a3739..b54de5406 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -55,7 +55,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 4d01d936d..348891595 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -55,7 +55,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
-index dcc2c52dc..653abb3fc 100644
---- a/ipatests/prci_definitions/temp_commit.yaml
-+++ b/ipatests/prci_definitions/temp_commit.yaml
-@@ -60,8 +60,8 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f40
--          version: 0.0.1
-+          name: freeipa/ci-ipa-4-12-f41
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
--- 
-2.49.0
-
-
-From 47770b8626c353b95d4ae89a0fb7e23b3791d3ea Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Wed, 22 Jan 2025 16:03:37 +0530
-Subject: [PATCH 114/116] ipatests: Tests to check data in journal log
-
-This testcase checks that ipa administrative user
-password is not displayed in journal log.
-
-Related: https://issues.redhat.com/browse/RHEL-67190
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/pytest_ipa/integration/tasks.py   |  10 ++
- ipatests/test_integration/test_commands.py | 116 +++++++++++++++++----
- 2 files changed, 104 insertions(+), 22 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index 4ce33bb47..dccfaf30e 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -3004,3 +3004,13 @@ def copy_files(source_host, dest_host, filelist):
-         dest_host.transport.mkdir_recursive(os.path.dirname(file))
-         data = source_host.get_file_contents(file)
-         dest_host.transport.put_file_contents(file, data)
-+
-+
-+def check_journal_does_not_contain_secret(host, cmd):
-+    """
-+    Helper to check journal logs doesnt reveal secrets
-+    """
-+    journalctl_cmd = ['journalctl', '-t', cmd, '-n1', '-o', 'json-pretty']
-+    result = host.run_command(journalctl_cmd, raiseonerr=False)
-+    assert (host.config.admin_password not in result.stdout_text)
-+    assert (host.config.dirman_password not in result.stdout_text)
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 9c65b7c6b..47ef23256 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -39,6 +39,7 @@ from ipaplatform.tasks import tasks as platform_tasks
- from ipatests.create_external_ca import ExternalCA
- from ipatests.test_ipalib.test_x509 import good_pkcs7, badcert
- from ipapython.ipautil import realm_to_suffix, ipa_generate_password
-+from ipatests.test_integration.test_topology import find_segment
- from ipaserver.install.installutils import realm_to_serverid
- from pkg_resources import parse_version
- 
-@@ -1662,28 +1663,77 @@ class TestIPACommand(IntegrationTest):
-         assert result.returncode == 1
-         assert 'cannot be deleted or disabled' in result.stderr_text
- 
--    def test_ipa_cacert_manage_prune(self):
--        """Test for ipa-cacert-manage prune"""
--
--        certfile = os.path.join(self.master.config.test_dir, 'cert.pem')
--        self.master.put_file_contents(certfile, isrgrootx1)
--        result = self.master.run_command(
--            [paths.IPA_CACERT_MANAGE, 'install', certfile])
--
--        certs_before_prune = self.master.run_command(
--            [paths.IPA_CACERT_MANAGE, 'list'], raiseonerr=False
--        ).stdout_text
-+    def test_ipa_systemd_journal(self):
-+        """
-+        This testcase checks that administrative user credentials
-+        is not leaked to journald log
-+        """
-+        tasks.kinit_admin(self.master)
-+        tasks.kinit_admin(self.replicas[0])
-+        tasks.kinit_admin(self.clients[0])
-+        cmds = [
-+            ['/usr/sbin/ipa-adtrust-install', '-a',
-+             self.master.config.admin_password, '-U'],
-+            ['/usr/sbin/ipa-replica-manage', 'del',
-+             f"dummyhost.{self.master.domain.name}", '-p',
-+             self.master.config.dirman_password],
-+            ['/usr/sbin/ipa-csreplica-manage', 'del',
-+             f"dummyhost.{self.master.domain.name}", '-p',
-+             self.master.config.dirman_password],
-+            ['/usr/sbin/ipa-kra-install', '-p',
-+             self.master.config.dirman_password, '-U'],
-+            ['/usr/sbin/ipa-server-certinstall', '-k', '--pin',
-+             self.master.config.dirman_password, '-p',
-+             self.master.config.dirman_password, paths.KDC_CERT,
-+             paths.KDC_KEY]
-+        ]
-+        for cmd in cmds:
-+            self.master.run_command(cmd, raiseonerr=False)
-+            tasks.check_journal_does_not_contain_secret(
-+                self.master, cmd[0]
-+            )
-+        for cmd in cmds:
-+            self.replicas[0].run_command(cmd, raiseonerr=False)
-+            tasks.check_journal_does_not_contain_secret(
-+                self.replicas[0], cmd[0]
-+            )
-+        tasks.check_journal_does_not_contain_secret(
-+            self.clients[0], 'python3'
-+        )
-+        # Backup and restore IPA and check secrets are not leaked.
-+        backup_path = tasks.get_backup_dir(self.master)
-+        restore_cmd = (
-+            ['/usr/sbin/ipa-restore', '-p',
-+             self.master.config.dirman_password,
-+             backup_path, '-U']
-+        )
-+        self.master.run_command(restore_cmd)
- 
--        assert isrgrootx1_nick in certs_before_prune
-+        # re-initializing topology after restore
-+        for topo_suffix in 'domain', 'ca':
-+            topo_name = find_segment(self.master, self.replicas[0], topo_suffix)
-+            arg = ['ipa', 'topologysegment-reinitialize',
-+                   topo_suffix, topo_name]
-+            if topo_name.split('-to-', maxsplit=1)[0] != self.master.hostname:
-+                arg.append('--left')
-+            else:
-+                arg.append('--right')
-+            self.replicas[0].run_command(arg)
- 
--        # Jump in time to make sure the cert is expired
--        self.master.run_command(['date', '-s', '+15Years'])
--        result = self.master.run_command(
--            [paths.IPA_CACERT_MANAGE, 'prune'], raiseonerr=False
--        ).stdout_text
--        self.master.run_command(['date', '-s', '-15Years'])
-+        # wait sometime for re-initialization
-+        tasks.wait_for_replication(self.replicas[0].ldap_connect())
- 
--        assert isrgrootx1_nick in result
-+        tasks.check_journal_does_not_contain_secret(
-+            self.master, restore_cmd[0]
-+        )
-+        # Checking for secrets in IPA server install
-+        tasks.check_journal_does_not_contain_secret(
-+            self.master, '/usr/sbin/ipa-server-install'
-+        )
-+        # Checking for secrets in IPA replica install
-+        tasks.check_journal_does_not_contain_secret(
-+            self.replicas[0], '/usr/sbin/ipa-replica-install'
-+        )
- 
- 
- class TestIPACommandWithoutReplica(IntegrationTest):
-@@ -1719,10 +1769,9 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         self.master.run_command(['ipa', 'user-show', 'ipauser1'])
- 
-     def test_basesearch_compat_tree(self):
--        """Test ldapsearch against compat tree is working
--
-+        """
-+        Test ldapsearch against compat tree is working
-         This to ensure that ldapsearch with base scope is not failing.
--
-         related: https://bugzilla.redhat.com/show_bug.cgi?id=1958909
-         """
-         version = self.master.run_command(
-@@ -1920,6 +1969,29 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         assert old_err_msg not in dirsrv_error_log
-         assert re.search(new_err_msg, dirsrv_error_log)
- 
-+    def test_ipa_cacert_manage_prune(self):
-+        """Test for ipa-cacert-manage prune"""
-+
-+        certfile = os.path.join(self.master.config.test_dir, 'cert.pem')
-+        self.master.put_file_contents(certfile, isrgrootx1)
-+        result = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'install', certfile])
-+
-+        certs_before_prune = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'list'], raiseonerr=False
-+        ).stdout_text
-+
-+        assert isrgrootx1_nick in certs_before_prune
-+
-+        # Jump in time to make sure the cert is expired
-+        self.master.run_command(['date', '-s', '+15Years'])
-+        result = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'prune'], raiseonerr=False
-+        ).stdout_text
-+        self.master.run_command(['date', '-s', '-15Years'])
-+
-+        assert isrgrootx1_nick in result
-+
- 
- class TestIPAautomount(IntegrationTest):
-     @classmethod
--- 
-2.49.0
-
-
-From ac308ab8f5685465e755b4ba7e5d428fe38bea4d Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Mon, 17 Mar 2025 09:26:44 +0100
-Subject: [PATCH 115/116] Disallow removal of dogtag and ipa-dnskeysyncd
- services on IPA servers
-
-Also removes dogtagldap from unremovable services
-
-Fixes: https://pagure.io/freeipa/issue/9764
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/plugins/service.py                |  2 +-
- ipatests/test_xmlrpc/test_service_plugin.py | 26 +++++++++++++++++++++
- 2 files changed, 27 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py
-index 075a1be8a..f50406472 100644
---- a/ipaserver/plugins/service.py
-+++ b/ipaserver/plugins/service.py
-@@ -323,7 +323,7 @@ def check_required_principal(ldap, principal):
-     try:
-         host_is_master(ldap, principal.hostname)
-     except errors.ValidationError:
--        service_types = {'http', 'ldap', 'dns', 'dogtagldap'}
-+        service_types = {'http', 'ldap', 'dns', 'dogtag', 'ipa-dnskeysyncd'}
-         if principal.service_name.lower() in service_types:
-             raise errors.ValidationError(
-                 name='principal',
-diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py
-index a3b245679..4aeeb9d89 100644
---- a/ipatests/test_xmlrpc/test_service_plugin.py
-+++ b/ipatests/test_xmlrpc/test_service_plugin.py
-@@ -864,6 +864,32 @@ class test_service(Declarative):
-             ),
-         ),
- 
-+        dict(
-+            desc=('Delete the current host (master?) %s dogtag service,'
-+                  ' should be caught' % api.env.host),
-+            command=('service_del', ['dogtag/%s' % api.env.host], {}),
-+            expected=errors.ValidationError(
-+                name='principal',
-+                error='dogtag/%s@%s is required by the IPA master' % (
-+                    api.env.host,
-+                    api.env.realm
-+                )
-+            ),
-+        ),
-+
-+        dict(
-+            desc=('Delete the current host (master?) %s ipa-dnskeysyncd'
-+                  ' service, should be caught' % api.env.host),
-+            command=('service_del', ['ipa-dnskeysyncd/%s' % api.env.host], {}),
-+            expected=errors.ValidationError(
-+                name='principal',
-+                error='ipa-dnskeysyncd/%s@%s is required by the IPA master' % (
-+                    api.env.host,
-+                    api.env.realm
-+                )
-+            ),
-+        ),
-+
- 
-         dict(
-             desc='Disable the current host (master?) %s HTTP service, should be caught' % api.env.host,
--- 
-2.49.0
-
-
-From 722a5a4e0f0c6948252d385da4ffef7c03338aec Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 8 Aug 2024 16:48:19 -0400
-Subject: [PATCH 116/116] Don't require certificates to have unique
- ipaCertSubject
-
-In the wild a public CA issued a new subordinate CA certificate
-with an identical subject to another, with a new private key.
-This was uninstallable using ipa-cacert-manage because it would
-fail with "subject public key info mismatch" during verification
-because a different certificate with the same subject but
-different public key was installed.
-
-I'm not sure of the reasoning to prevent this situation but I
-see it as giving users flexibility. This may be hurtful to them
-but they can always remove any affected certs.
-
-This is backwards compatible with older releases from the client
-perspective. Older servers will choke on the duplicates and
-won't be able to manage these.
-
-A new serial number option is added for displaying the list of
-certificates and for use when deleting one with a duplicate subject.
-
-ipa-cacert-manage delete on systems without this patch will
-successfully remove ALL of the requested certificates. There is no
-way to distinguish. At least it won't break anything and the
-deleted certificates can be re-added.
-
-Fixes: https://pagure.io/freeipa/issue/9652
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- install/restart_scripts/renew_ca_cert.in    |   4 +-
- install/tools/man/ipa-cacert-manage.1       |  11 ++
- install/updates/10-uniqueness.update        |  21 +--
- ipaclient/install/client.py                 |   6 +-
- ipaclient/install/ipa_certupdate.py         |   2 +-
- ipalib/install/certstore.py                 |  46 +++++-
- ipaplatform/debian/tasks.py                 |   2 +-
- ipaplatform/redhat/tasks.py                 |   2 +-
- ipapython/certdb.py                         | 110 ++++++++------
- ipaserver/install/certs.py                  |   7 +-
- ipaserver/install/installutils.py           |   3 +-
- ipaserver/install/ipa_cacert_manage.py      |  82 +++++++----
- ipaserver/install/ipa_server_certinstall.py |   5 +-
- ipaserver/install/krbinstance.py            |   2 +-
- ipaserver/install/service.py                |   4 +-
- ipatests/test_integration/test_commands.py  | 151 +++++++++++++++++++-
- 16 files changed, 351 insertions(+), 107 deletions(-)
-
-diff --git a/install/restart_scripts/renew_ca_cert.in b/install/restart_scripts/renew_ca_cert.in
-index cbb2c89a8..814acdaa7 100644
---- a/install/restart_scripts/renew_ca_cert.in
-+++ b/install/restart_scripts/renew_ca_cert.in
-@@ -168,7 +168,7 @@ def _main():
-                     ca_certs = []
- 
-                 realm_nickname = get_ca_nickname(api.env.realm)
--                for ca_cert, ca_nick, ca_flags in ca_certs:
-+                for ca_cert, ca_nick, ca_flags, _serial in ca_certs:
-                     try:
-                         if ca_nick == realm_nickname:
-                             ca_nick = 'caSigningCert cert-pki-ca'
-@@ -180,7 +180,7 @@ def _main():
- 
-                 # Pass Dogtag's self-tests
-                 for ca_nick in db.find_root_cert(nickname)[-2:-1]:
--                    ca_flags = dict(cc[1:] for cc in ca_certs)[ca_nick]
-+                    ca_flags = dict(cc[1:3] for cc in ca_certs)[ca_nick]
-                     usages = ca_flags.usages or set()
-                     ca_flags_modified = TrustFlags(ca_flags.has_key,
-                         True, True,
-diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1
-index 8913fe5d2..1e15d47a5 100644
---- a/install/tools/man/ipa-cacert-manage.1
-+++ b/install/tools/man/ipa-cacert-manage.1
-@@ -57,6 +57,14 @@ Important: this does not replace IPA CA but adds the provided certificate as a k
- Please do not forget to run ipa-certupdate on the master, all the replicas and all the clients after this command in order to update IPA certificates databases.
- .sp
- The supported formats for the certificate files are DER, PEM and PKCS#7 format.
-+.sp
-+CA certificates with the same subject but different private keys maybe installed simultaneously with the following restrictions from NSS:
-+.IP \[bu]
-+The certificates cannot have different NSS trust flags.
-+.IP \[bu]
-+The nickname is not configurable between different certificates of the same subject. It will always be the same (even if you try).
-+.sp
-+Additionally CA certificates with the same subject should include the Authority Key Identifier extension in order to identify the public key of the certificate issuer (CA) that signed the certificate (it may be itself). Similarly it should have a Subject Key Identifier extension. This is used to create the trust chain not through subjects but by using the SKID and AKID which is what allows duplicate certificate subjects to be resolved correctly. Without an AKID multiple certificates of the same subject will not resolve as expected.
- .RE
- .TP
- \fBdelete\fR
-@@ -153,6 +161,9 @@ p \- not trusted
- .TP
- \fB\-f\fR, \fB\-\-force\fR
- Force a CA certificate to be removed even if chain validation fails.
-+.TP
-+\fB\-s\fR \fISERIAL_NUMBER\fR, \fB\-\-serial\fR=\fISERIAL_NUMBER\fR
-+Serial number of the certificate to delete (decimal). This is needed to determine which certificate to remove if there are multiple certificates stored with the same name.
- .SH "EXIT STATUS"
- 0 if the command was successful
- 
-diff --git a/install/updates/10-uniqueness.update b/install/updates/10-uniqueness.update
-index 699de3b4d..fa17911f2 100644
---- a/install/updates/10-uniqueness.update
-+++ b/install/updates/10-uniqueness.update
-@@ -15,23 +15,6 @@ default:nsslapd-pluginId: NSUniqueAttr
- default:nsslapd-pluginVersion: 1.1.0
- default:nsslapd-pluginVendor: Fedora Project
- 
--dn: cn=certificate store subject uniqueness,cn=plugins,cn=config
--default:objectClass: top
--default:objectClass: nsSlapdPlugin
--default:objectClass: extensibleObject
--default:cn: certificate store subject uniqueness
--default:nsslapd-pluginDescription: Enforce unique attribute values
--default:nsslapd-pluginPath: libattr-unique-plugin
--default:nsslapd-pluginInitfunc: NSUniqueAttr_Init
--default:nsslapd-pluginType: preoperation
--default:nsslapd-pluginEnabled: on
--default:uniqueness-attribute-name: ipaCertSubject
--default:uniqueness-subtrees: cn=certificates,cn=ipa,cn=etc,$SUFFIX
--default:nsslapd-plugin-depends-on-type: database
--default:nsslapd-pluginId: NSUniqueAttr
--default:nsslapd-pluginVersion: 1.1.0
--default:nsslapd-pluginVendor: Fedora Project
--
- dn: cn=certificate store issuer/serial uniqueness,cn=plugins,cn=config
- default:objectClass: top
- default:objectClass: nsSlapdPlugin
-@@ -128,3 +111,7 @@ default:nsslapd-plugin-depends-on-type: database
- default:nsslapd-pluginId: NSUniqueAttr
- default:nsslapd-pluginVersion: 1.1.0
- default:nsslapd-pluginVendor: Fedora Project
-+
-+# A unique ipaCertSubject is no longer required
-+dn: cn=certificate store subject uniqueness,cn=plugins,cn=config
-+deleteentry: cn=certificate store subject uniqueness,cn=plugins,cn=config
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 9e4d3bbe7..372daa51e 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -3200,15 +3200,15 @@ def _install(options, tdict):
-         ca_certs = certstore.make_compat_ca_certs(ca_certs, cli_realm,
-                                                   ca_subject)
-     ca_certs_trust = [(c, n, certstore.key_policy_to_trust_flags(t, True, u))
--                      for (c, n, t, u) in ca_certs]
-+                      for (c, n, t, u, s) in ca_certs]
- 
-     x509.write_certificate_list(
--        [c for c, n, t, u in ca_certs if t is not False],
-+        [c for c, n, t, u, s in ca_certs if t is not False],
-         paths.KDC_CA_BUNDLE_PEM,
-         mode=0o644
-     )
-     x509.write_certificate_list(
--        [c for c, n, t, u in ca_certs if t is not False],
-+        [c for c, n, t, u, s in ca_certs if t is not False],
-         paths.CA_BUNDLE_PEM,
-         mode=0o644
-     )
-diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py
-index bc70254e2..88618a9c2 100644
---- a/ipaclient/install/ipa_certupdate.py
-+++ b/ipaclient/install/ipa_certupdate.py
-@@ -276,7 +276,7 @@ def update_db(path, certs):
-     for name, flags in db.list_certs():
-         if flags.ca:
-             db.delete_cert(name)
--    for cert, nickname, trusted, eku in certs:
-+    for cert, nickname, trusted, eku, _serial in certs:
-         trust_flags = certstore.key_policy_to_trust_flags(trusted, True, eku)
-         try:
-             db.add_cert(cert, nickname, trust_flags)
-diff --git a/ipalib/install/certstore.py b/ipalib/install/certstore.py
-index 8b182958c..fb4f09a2b 100644
---- a/ipalib/install/certstore.py
-+++ b/ipalib/install/certstore.py
-@@ -179,10 +179,9 @@ def update_ca_cert(ldap, base_dn, cert, trusted=None, ext_key_usage=None,
-         # We are adding a new cert, validate it
-         if entry.single_value['ipaCertSubject'].lower() != subject.lower():
-             raise ValueError("subject name mismatch")
--        if entry.single_value['ipaPublicKey'] != public_key:
--            raise ValueError("subject public key info mismatch")
-         entry['ipaCertIssuerSerial'].append(issuer_serial)
-         entry['cACertificate;binary'].append(cert)
-+        entry['ipaPublicKey'].append(public_key)
- 
-     # Update key trust
-     if trusted is not None:
-@@ -224,6 +223,38 @@ def update_ca_cert(ldap, base_dn, cert, trusted=None, ext_key_usage=None,
-     clean_old_config(ldap, base_dn, dn, config_ipa, config_compat)
- 
- 
-+def delete_ca_cert(ldap, base_dn, cert):
-+    """
-+    Remove a CA certificate in the certificate store.
-+    """
-+    subject, issuer_serial, _public_key = _parse_cert(cert)
-+
-+    filter = ldap.make_filter({'ipaCertSubject': subject})
-+    result, _truncated = ldap.find_entries(
-+        base_dn=DN(('cn', 'certificates'), ('cn', 'ipa'), ('cn', 'etc'),
-+                   base_dn),
-+        filter=filter,
-+        attrs_list=['cn', 'ipaCertSubject', 'ipaCertIssuerSerial',
-+                    'ipaPublicKey', 'ipaKeyTrust', 'ipaKeyExtUsage',
-+                    'ipaConfigString', 'cACertificate;binary'])
-+    entry = result[0]
-+
-+    for old_cert in entry['cACertificate;binary']:
-+        # Check if we are adding a new cert
-+        if old_cert == cert:
-+            break
-+    else:
-+        raise ValueError("certificate not found")
-+
-+    entry['ipaCertIssuerSerial'].remove(issuer_serial)
-+    entry['cACertificate;binary'].remove(cert)
-+
-+    if len(entry['ipaCertIssuerSerial']) == 0:
-+        ldap.delete_entry(entry.dn)
-+    else:
-+        ldap.update_entry(entry)
-+
-+
- def put_ca_cert(ldap, base_dn, cert, nickname, trusted=None,
-                 ext_key_usage=None, config_ipa=False, config_compat=False):
-     """
-@@ -309,11 +340,14 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
- 
-             for cert in entry.get('cACertificate;binary', []):
-                 try:
--                    _parse_cert(cert)
-+                    _subject, issuer_serial, _pkinfo = _parse_cert(cert)
-                 except ValueError:
-                     certs = []
-                     break
--                certs.append((cert, nickname, trusted, ext_key_usage))
-+                serial_number = issuer_serial.split(';')[1]
-+                certs.append(
-+                    (cert, nickname, trusted, ext_key_usage, serial_number)
-+                )
-     except errors.NotFound:
-         try:
-             ldap.get_entry(container_dn, [''])
-@@ -381,9 +415,9 @@ def get_ca_certs_nss(ldap, base_dn, compat_realm, compat_ipa_ca,
- 
-     certs = get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca,
-                          filter_subject=filter_subject)
--    for cert, nickname, trusted, ext_key_usage in certs:
-+    for cert, nickname, trusted, ext_key_usage, _serial_number in certs:
-         trust_flags = key_policy_to_trust_flags(trusted, True, ext_key_usage)
--        nss_certs.append((cert, nickname, trust_flags))
-+        nss_certs.append((cert, nickname, trust_flags, _serial_number))
- 
-     return nss_certs
- 
-diff --git a/ipaplatform/debian/tasks.py b/ipaplatform/debian/tasks.py
-index a7b5cdf38..8a50c66bc 100644
---- a/ipaplatform/debian/tasks.py
-+++ b/ipaplatform/debian/tasks.py
-@@ -126,7 +126,7 @@ used by ca-certificates and is provided for information only.\
-             logger.error("Could not create %s", path)
-             raise
- 
--        for cert, nickname, trusted, _ext_key_usage in ca_certs:
-+        for cert, nickname, trusted, _ext_key_usage, _serial in ca_certs:
-             if not trusted:
-                 continue
- 
-diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
-index 4fb620807..d3eda0172 100644
---- a/ipaplatform/redhat/tasks.py
-+++ b/ipaplatform/redhat/tasks.py
-@@ -329,7 +329,7 @@ class RedHatTaskNamespace(BaseTaskNamespace):
-                 raise
- 
-             has_eku = set()
--            for cert, nickname, trusted, _ext_key_usage in ca_certs:
-+            for cert, nickname, trusted, _ext_key_usage, _serial in ca_certs:
-                 try:
-                     subject = cert.subject_bytes
-                     issuer = cert.issuer_bytes
-diff --git a/ipapython/certdb.py b/ipapython/certdb.py
-index ec8f63905..3314c3a03 100644
---- a/ipapython/certdb.py
-+++ b/ipapython/certdb.py
-@@ -633,7 +633,7 @@ class NSSDatabase:
-                 pkcs12_password_file.close()
- 
-     def import_files(self, files, import_keys=False, key_password=None,
--                     key_nickname=None):
-+                     key_nickname=None, trust_flags=EMPTY_TRUST_FLAGS):
-         """
-         Import certificates and a single private key from multiple files
- 
-@@ -809,7 +809,7 @@ class NSSDatabase:
- 
-         for cert in extracted_certs:
-             nickname = str(DN(cert.subject))
--            self.add_cert(cert, nickname, EMPTY_TRUST_FLAGS)
-+            self.add_cert(cert, nickname, trust_flags)
- 
-         if extracted_key:
-             with tempfile.NamedTemporaryFile() as in_file, \
-@@ -867,6 +867,27 @@ class NSSDatabase:
-         cert, _start = find_cert_from_txt(result.output, start=0)
-         return cert
- 
-+    def get_all_certs(self, nickname):
-+        """
-+        :param nickname: nickname of the certificate in the NSS database
-+        :returns: list of bytes of all certificates for the nickname
-+        """
-+        args = ['-L', '-n', nickname, '-a']
-+        try:
-+            result = self.run_certutil(args, capture_output=True)
-+        except ipautil.CalledProcessError:
-+            raise RuntimeError("Failed to get %s" % nickname)
-+        certs = []
-+
-+        st = 0
-+        while True:
-+            try:
-+                cert, st = find_cert_from_txt(result.output, start=st)
-+            except RuntimeError:
-+                break
-+            certs.append(cert)
-+        return certs
-+
-     def has_nickname(self, nickname):
-         try:
-             self.get_cert(nickname)
-@@ -990,53 +1011,58 @@ class NSSDatabase:
-             raise ValueError('invalid for server %s' % hostname)
- 
-     def verify_ca_cert_validity(self, nickname, minpathlen=None):
--        cert = self.get_cert(nickname)
--        self._verify_cert_validity(cert)
-+        def verify_ca_cert(cert, nickname, minpathlen):
-+            self._verify_cert_validity(cert)
- 
--        if not cert.subject:
--            raise ValueError("has empty subject")
-+            if not cert.subject:
-+                raise ValueError("has empty subject")
- 
--        try:
--            bc = cert.extensions.get_extension_for_class(
-+            try:
-+                bc = cert.extensions.get_extension_for_class(
-                     cryptography.x509.BasicConstraints)
--        except cryptography.x509.ExtensionNotFound:
--            raise ValueError("missing basic constraints")
--
--        if not bc.value.ca:
--            raise ValueError("not a CA certificate")
--        if minpathlen is not None:
--            # path_length is None means no limitation
--            pl = bc.value.path_length
--            if pl is not None and pl < minpathlen:
--                raise ValueError(
--                    "basic contraint pathlen {}, must be at least {}".format(
--                        pl, minpathlen
-+            except cryptography.x509.ExtensionNotFound:
-+                raise ValueError("missing basic constraints")
-+
-+            if not bc.value.ca:
-+                raise ValueError("not a CA certificate")
-+            if minpathlen is not None:
-+                # path_length is None means no limitation
-+                pl = bc.value.path_length
-+                if pl is not None and pl < minpathlen:
-+                    raise ValueError(
-+                        "basic contraint pathlen {}, "
-+                        "must be at least {}".format(
-+                            pl, minpathlen
-+                        )
-                     )
--                )
- 
--        try:
--            ski = cert.extensions.get_extension_for_class(
-+            try:
-+                ski = cert.extensions.get_extension_for_class(
-                     cryptography.x509.SubjectKeyIdentifier)
--        except cryptography.x509.ExtensionNotFound:
--            raise ValueError("missing subject key identifier extension")
--        else:
--            if len(ski.value.digest) == 0:
--                raise ValueError("subject key identifier must not be empty")
-+            except cryptography.x509.ExtensionNotFound:
-+                raise ValueError("missing subject key identifier extension")
-+            else:
-+                if len(ski.value.digest) == 0:
-+                    raise ValueError("subject key identifier must not be empty")
- 
--        try:
--            self.run_certutil(
--                [
--                    '-V',       # check validity of cert and attrs
--                    '-n', nickname,
--                    '-u', 'L',  # usage; 'L' means "SSL CA"
--                    '-e',       # check signature(s); this checks
--                                # key sizes, sig algorithm, etc.
--                ],
--                capture_output=True)
--        except ipautil.CalledProcessError as e:
--            # certutil output in case of error is
--            # 'certutil: certificate is invalid: <ERROR_STRING>\n'
--            raise ValueError(e.output)
-+            try:
-+                self.run_certutil(
-+                    [
-+                        '-V',       # check validity of cert and attrs
-+                        '-n', nickname,
-+                        '-u', 'L',  # usage; 'L' means "SSL CA"
-+                        '-e',       # check signature(s); this checks
-+                                    # key sizes, sig algorithm, etc.
-+                    ],
-+                    capture_output=True)
-+            except ipautil.CalledProcessError as e:
-+                # certutil output in case of error is
-+                # 'certutil: certificate is invalid: <ERROR_STRING>\n'
-+                raise ValueError(e.output)
-+
-+        certlist = self.get_all_certs(nickname)
-+        for cert in certlist:
-+            verify_ca_cert(cert, nickname, minpathlen)
- 
-     def verify_kdc_cert_validity(self, nickname, realm):
-         nicknames = self.get_trust_chain(nickname)
-diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
-index f8be1ef06..a3e38cdaa 100644
---- a/ipaserver/install/certs.py
-+++ b/ipaserver/install/certs.py
-@@ -373,7 +373,7 @@ class CertDB:
-             except RuntimeError:
-                 break
- 
--    def get_cert_from_db(self, nickname):
-+    def get_cert_from_db(self, nickname, all=False):
-         """
-         Retrieve a certificate from the current NSS database for nickname.
-         """
-@@ -386,7 +386,10 @@ class CertDB:
-             if token:
-                 args.extend(['-h', token])
-             result = self.run_certutil(args, capture_output=True)
--            return x509.load_pem_x509_certificate(result.raw_output)
-+            if all:
-+                return x509.load_certificate_list(result.raw_output)
-+            else:
-+                return x509.load_pem_x509_certificate(result.raw_output)
-         except ipautil.CalledProcessError:
-             return None
- 
-diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
-index 3a31f8a98..f6f06c9a1 100644
---- a/ipaserver/install/installutils.py
-+++ b/ipaserver/install/installutils.py
-@@ -901,7 +901,8 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files,
- 
-         if ca_cert_files:
-             try:
--                nssdb.import_files(ca_cert_files)
-+                nssdb.import_files(ca_cert_files,
-+                                   trust_flags=EXTERNAL_CA_TRUST_FLAGS)
-             except RuntimeError as e:
-                 raise ScriptError(str(e))
- 
-diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
-index 048245237..6a03fa74e 100644
---- a/ipaserver/install/ipa_cacert_manage.py
-+++ b/ipaserver/install/ipa_cacert_manage.py
-@@ -101,6 +101,9 @@ class CACertManage(admintool.AdminTool):
-         delete_group.add_option(
-             "-f", "--force", action='store_true',
-             help="Force removing the CA even if chain validation fails")
-+        delete_group.add_option(
-+            "-s", "--serial",
-+            help="Serial number of the certificate to delete (decimal)")
-         parser.add_option_group(delete_group)
- 
-     def validate_options(self):
-@@ -413,6 +416,11 @@ class CACertManage(admintool.AdminTool):
-                     "Nickname can only be used if only a single "
-                     "certificate is loaded")
- 
-+            for nickname, trust_flags in imported:
-+                if trust_flags.has_key:
-+                    continue
-+                tmpdb.trust_root_cert(nickname, EXTERNAL_CA_TRUST_FLAGS)
-+
-             # If a nickname was provided re-import the cert
-             if options.nickname:
-                 (nickname, trust_flags) = imported[0]
-@@ -421,7 +429,7 @@ class CACertManage(admintool.AdminTool):
-                 tmpdb.add_cert(cert, options.nickname, EXTERNAL_CA_TRUST_FLAGS)
-                 imported = tmpdb.list_certs()
- 
--            for ca_cert, ca_nickname, ca_trust_flags in ca_certs:
-+            for ca_cert, ca_nickname, ca_trust_flags, _serial in ca_certs:
-                 tmpdb.add_cert(ca_cert, ca_nickname, ca_trust_flags)
- 
-             for nickname, trust_flags in imported:
-@@ -461,10 +469,11 @@ class CACertManage(admintool.AdminTool):
- 
-             for nickname, _trust_flags in imported:
-                 try:
--                    cert = tmpdb.get_cert(nickname)
--                    certstore.put_ca_cert_nss(
--                        api.Backend.ldap2, api.env.basedn, cert, nickname,
--                        trust_flags)
-+                    certlist = tmpdb.get_all_certs(nickname)
-+                    for cert in certlist:
-+                        certstore.put_ca_cert_nss(
-+                            api.Backend.ldap2, api.env.basedn, cert, nickname,
-+                            trust_flags)
-                 except ValueError as e:
-                     raise admintool.ScriptError(
-                         "Failed to install the certificate: %s" % e)
-@@ -476,8 +485,8 @@ class CACertManage(admintool.AdminTool):
-                                               api.env.basedn,
-                                               api.env.realm,
-                                               False)
--        for _ca_cert, ca_nickname, _ca_trust_flags in ca_certs:
--            print(ca_nickname)
-+        for _ca_cert, ca_nickname, _ca_trust_flags, serial in ca_certs:
-+            print(f"{ca_nickname}  {serial}")
- 
-     def _delete_by_nickname(self, nicknames, options):
-         conn = api.Backend.ldap2
-@@ -489,9 +498,25 @@ class CACertManage(admintool.AdminTool):
- 
-         ipa_ca_nickname = get_ca_nickname(api.env.realm)
- 
-+        # Count the number of times the nickname appears in case we
-+        # have a duplicate. If a serial number is provided we can skip
-+        # this.
-+        cert_count = 0
-+        if not options.serial:
-+            for nickname in nicknames:
-+                for _ca_cert, ca_nickname, _ca_trust_flags, _serial in ca_certs:
-+                    if ca_nickname == nickname:
-+                        cert_count += 1
-+            if cert_count > 1:
-+                raise admintool.ScriptError(
-+                    'Multiple matching certificates found (%d). Use the '
-+                    '--serial option to specify which one to remove.' %
-+                    cert_count
-+                )
-+
-         for nickname in nicknames:
-             found = False
--            for _ca_cert, ca_nickname, _ca_trust_flags in ca_certs:
-+            for _ca_cert, ca_nickname, _ca_trust_flags, _serial in ca_certs:
-                 if ca_nickname == nickname:
-                     if ca_nickname == ipa_ca_nickname:
-                         raise admintool.ScriptError(
-@@ -508,13 +533,17 @@ class CACertManage(admintool.AdminTool):
- 
-         with certs.NSSDatabase() as tmpdb:
-             tmpdb.create_db()
--            for ca_cert, ca_nickname, ca_trust_flags in ca_certs:
-+            for ca_cert, ca_nickname, ca_trust_flags, serial in ca_certs:
-+                if nickname == ca_nickname:
-+                    if options.serial and options.serial == serial:
-+                        continue
-                 tmpdb.add_cert(ca_cert, ca_nickname, ca_trust_flags)
-             loaded = tmpdb.list_certs()
-             logger.debug("loaded raw certs '%s'", loaded)
- 
--            for nickname in nicknames:
--                tmpdb.delete_cert(nickname)
-+            if not options.serial:
-+                for nickname in nicknames:
-+                    tmpdb.delete_cert(nickname)
- 
-             for ca_nickname, _trust_flags in loaded:
-                 if ca_nickname in nicknames:
-@@ -526,8 +555,8 @@ class CACertManage(admintool.AdminTool):
-                 try:
-                     tmpdb.verify_ca_cert_validity(ca_nickname)
-                 except ValueError as e:
--                    msg = "Verifying \'%s\' failed. Removing part of the " \
--                          "chain? %s" % (nickname, e)
-+                    msg = "Verifying removal of \'%s\' failed. Removing " \
-+                          "part of the chain? %s" % (nickname, e)
-                     if options.force:
-                         print(msg)
-                         continue
-@@ -535,15 +564,20 @@ class CACertManage(admintool.AdminTool):
-                 else:
-                     logger.debug("Verified %s", ca_nickname)
- 
--        for _ca_cert, ca_nickname, _ca_trust_flags in ca_certs:
-+        for ca_cert, ca_nickname, _ca_trust_flags, serial in ca_certs:
-             if ca_nickname in nicknames:
--                container_dn = DN(('cn', 'certificates'), ('cn', 'ipa'),
--                                  ('cn', 'etc'), api.env.basedn)
--                dn = DN(('cn', nickname), container_dn)
-+                if options.serial and options.serial != serial:
-+                    continue
-                 logger.debug("Deleting %s", ca_nickname)
--                conn.delete_entry(dn)
-+                certstore.delete_ca_cert(conn, api.env.basedn, ca_cert)
-+
-                 return
- 
-+        raise admintool.ScriptError(
-+            "Certificate with name %s and serial number %s not found"
-+            % (ca_nickname, options.serial)
-+        )
-+
-     def delete(self):
-         nickname = self.args[1]
-         self._delete_by_nickname([nickname], self.options)
-@@ -556,17 +590,17 @@ class CACertManage(admintool.AdminTool):
-                                               False)
- 
-         now = datetime.datetime.now(tz=datetime.timezone.utc)
--        for ca_cert, ca_nickname, _ca_trust_flags in ca_certs:
-+        for ca_cert, ca_nickname, _ca_trust_flags, _serial in ca_certs:
-             if ca_cert.not_valid_after_utc < now:
-                 expired_certs.append(ca_nickname)
- 
--
-+        del_options = self.options
-+        del_options.force = True
-         if expired_certs:
--            self._delete_by_nickname(expired_certs, self.options)
--
-             print("Expired certificates deleted:")
--            for nickname in expired_certs:
--                print(nickname)
-+            for ca_cert in expired_certs:
-+                self._delete_by_nickname([ca_cert], del_options)
-+                print(ca_cert)
-             print("Run ipa-certupdate on enrolled machines to apply changes.")
-         else:
-             print("No certificates were deleted")
-diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py
-index 76ad37ca7..6eaf9d197 100644
---- a/ipaserver/install/ipa_server_certinstall.py
-+++ b/ipaserver/install/ipa_server_certinstall.py
-@@ -276,8 +276,9 @@ class ServerCertInstall(admintool.AdminTool):
-             # import all the CA certs from nssdb into the temp db
-             for nickname, flags in nssdb.list_certs():
-                 if not flags.has_key:
--                    cert = nssdb.get_cert_from_db(nickname)
--                    tempnssdb.add_cert(cert, nickname, flags)
-+                    certs = nssdb.get_cert_from_db(nickname, all=True)
-+                    for cert in certs:
-+                        tempnssdb.add_cert(cert, nickname, flags)
- 
-             # now get the server certs from tempnssdb and check their validity
-             try:
-diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
-index 99995ea0b..a98875538 100644
---- a/ipaserver/install/krbinstance.py
-+++ b/ipaserver/install/krbinstance.py
-@@ -536,7 +536,7 @@ class KrbInstance(service.Service):
-                                           self.api.env.basedn,
-                                           self.api.env.realm,
-                                           False)
--        ca_certs = [c for c, _n, t, _u in ca_certs if t is not False]
-+        ca_certs = [c for c, _n, t, _u, _s in ca_certs if t is not False]
-         x509.write_certificate_list(ca_certs, paths.CACERT_PEM, mode=0o644)
- 
-     def issue_selfsigned_pkinit_certs(self):
-diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
-index 7755a4f2f..5e5c60b4b 100644
---- a/ipaserver/install/service.py
-+++ b/ipaserver/install/service.py
-@@ -541,7 +541,7 @@ class Service:
-             pass
-         else:
-             with open(cafile, 'wb') as fd:
--                for cert, _unused1, _unused2, _unused3 in ca_certs:
-+                for cert, _unused1, _unused2, _unused3, _unused4 in ca_certs:
-                     fd.write(cert.public_bytes(x509.Encoding.PEM))
- 
-     def export_ca_certs_nssdb(self, db, ca_is_configured, conn=None):
-@@ -561,7 +561,7 @@ class Service:
-         except errors.NotFound:
-             pass
-         else:
--            for cert, nickname, trust_flags in ca_certs:
-+            for cert, nickname, trust_flags, _serial in ca_certs:
-                 db.add_cert(cert, nickname, trust_flags)
- 
-     def is_configured(self):
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 47ef23256..3c883b8bb 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -123,6 +123,82 @@ letsencryptauthorityr3 = (
- )
- le_r3_nick = "CN=R3,O=Let's Encrypt,C=US"
- 
-+# Certificates for reproducing duplicate ipaCertSubject values.
-+# The trick to creating the second intermediate is for the validity
-+# period to be different. In this case the second CA certificate
-+# was issued 3 years+1day after the original.
-+originalsubjectchain = (
-+    b'-----BEGIN CERTIFICATE-----\n'
-+    b'MIIDcjCCAlqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwRDEeMBwGA1UECgwVQ2Vy\n'
-+    b'dGlmaWNhdGUgU2hhY2sgTHRkMSIwIAYDVQQDDBlDZXJ0aWZpY2F0ZSBTaGFjayBS\n'
-+    b'b290IENBMB4XDTIxMDgwNzE4MDQyNloXDTQxMDgwMTE4MDQyNlowTDEeMBwGA1UE\n'
-+    b'CgwVQ2VydGlmaWNhdGUgU2hhY2sgTHRkMSowKAYDVQQDDCFDZXJ0aWZpY2F0ZSBT\n'
-+    b'aGFjayBJbnRlcm1lZGlhdGUgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n'
-+    b'AoIBAQC2RNo7atuVWC/6tDCGforNFvvSFdUwqHxltFmg61i2hmdHAjTaYI1ZJdgB\n'
-+    b'y7ApGc8RYc7tfaNrUNA8Chd/9Cu4eW2KuTnAozxytXQneNXloK2xb9iLIhETa1FC\n'
-+    b'Hw5BbrmJSWjiVYQsM6bzeiFsKJs4qnP1T9iFHuqmggTtCTPajoYhn6ZKfK3pmB8P\n'
-+    b'6XRcp5O9vUhNHJWdpuUjOL32fsBEpV0vKWlsemqDhJrhzj3+YCKt6xrSdpK64HUW\n'
-+    b'Kf3YM/K4G6vU5M8DgSFex6T1u2vCsQYJ4Mv8LVCho8awTZoBsimy1tiM0V7GmmBE\n'
-+    b'0Uck/U0381NBpNYdv7eyF682SbihAgMBAAGjZjBkMB0GA1UdDgQWBBTtHQCp1dBF\n'
-+    b'ypsegtWcXhXDdopIgDAfBgNVHSMEGDAWgBRJuz/14J1ZXqvpOuikJJ62NtuiGTAS\n'
-+    b'BgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF\n'
-+    b'AAOCAQEAkCBm6u+k/x4QoqqwOJvy8sjq7bUCh73qNPAFlqVSSB8UdCyu21EaXCj8\n'
-+    b'dbZa3GNRGk6JACTEUVQ1SD8SkC1E1/IWuEzYOKOP6FmTFbC4V5zU9LAnGFJapS6Q\n'
-+    b'CGwU2F44oflBbfOodFznqKPPuENX0gmm4ddvoT915WUOvVLKLuVujkU/ffGKAc8U\n'
-+    b'RxRIJ3W2Ybjs9ANg7JqB3Ny8i5QAGHzjRVwU+IgTrJCYPS2DrRYtN3glKBTlyKyR\n'
-+    b'xMy0PVKwVo/ItDO3fZ0fsAiIO+4pI51A0lFge5Bg/DzsotZxcWhdTelWjYI9JNca\n'
-+    b'y2GPzV1wlxK+ui1uLCWEvKbPtaCfeQ==\n'
-+    b'-----END CERTIFICATE-----\n'
-+    b'-----BEGIN CERTIFICATE-----\n'
-+    b'MIIDeTCCAmGgAwIBAgIUUbo+eGRT5jiS2eIoEzRhXaUx4gwwDQYJKoZIhvcNAQEL\n'
-+    b'BQAwRDEeMBwGA1UECgwVQ2VydGlmaWNhdGUgU2hhY2sgTHRkMSIwIAYDVQQDDBlD\n'
-+    b'ZXJ0aWZpY2F0ZSBTaGFjayBSb290IENBMB4XDTIxMDgwNzE4MDQyNloXDTQxMDgw\n'
-+    b'MjE4MDQyNlowRDEeMBwGA1UECgwVQ2VydGlmaWNhdGUgU2hhY2sgTHRkMSIwIAYD\n'
-+    b'VQQDDBlDZXJ0aWZpY2F0ZSBTaGFjayBSb290IENBMIIBIjANBgkqhkiG9w0BAQEF\n'
-+    b'AAOCAQ8AMIIBCgKCAQEArh41PPmI6rg7nz3cRqsbCqGgD3+vAD4DNs/Cnp+vhM//\n'
-+    b'7Di8FuMoyyLDpD+RdT/Vkvh2Xhp+OcjYSFLX8xeFRy0blfzel2Tq7PiD83BwewsG\n'
-+    b'BOarlhkbQGxlGxkr4Fi6z0kNNAfbE2ZzBIs4XSppm7xl4YJyLQD0FkzdrU+zrZuK\n'
-+    b'3ELQzk3UWfSSrnbYABY2LBgkny5m7y/kJOMyqn+/T1CUthXD3OpGtyQm2kuEooDZ\n'
-+    b'xP1eq30gS8oGYAw2nR/8vJPuyeZaMxM4eNLuc35uq8/6pI+xNEpzGt7xAk1ul/xc\n'
-+    b'ewOY2kjh4KJCNK/nCjALzxqhNRHhnH8bA6xtOcgdBwIDAQABo2MwYTAdBgNVHQ4E\n'
-+    b'FgQUSbs/9eCdWV6r6TropCSetjbbohkwHwYDVR0jBBgwFoAUSbs/9eCdWV6r6Tro\n'
-+    b'pCSetjbbohkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\n'
-+    b'hvcNAQELBQADggEBAC35stv/1WZhWblRTZP3XHhH0usHRGTUY7zNSrgS5sb3ERsf\n'
-+    b'hgbmFbomra5jKaBqffToOZKLEo+n3tfIPokus35NUQn7ox/6qPp0rJEK8dfLx9jA\n'
-+    b'0VTqREbgaAf5xLaX874++OTiM1sPVYG3Egsb1A/YCtDek8mZkKk21g+DZlFMOSDl\n'
-+    b'Hw+c3gZUnv6bIT8P09z+9yca2Lvg/dpj2ln3PbOykXzwuGSoNxjUt2OSdCbwyN+f\n'
-+    b'hO4NFtDvx74Ggi5bcTrz0ZKO6g8SQotii7cSKAdpIWDpXl8cfsK3SRbkCsg+Fg1S\n'
-+    b'kMJEFyDEkKu8Qe6zwKXIAoeKULLO6ADgFVH9CmM=\n'
-+    b'-----END CERTIFICATE-----\n'
-+)
-+interm_nick = "CN=Certificate Shack Intermediate CA,O=Certificate Shack Ltd"
-+intermediate_serial = "4096"
-+
-+duplicatesubject = (
-+    b'-----BEGIN CERTIFICATE-----\n'
-+    b'MIIDcjCCAlqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwRDEeMBwGA1UECgwVQ2Vy\n'
-+    b'dGlmaWNhdGUgU2hhY2sgTHRkMSIwIAYDVQQDDBlDZXJ0aWZpY2F0ZSBTaGFjayBS\n'
-+    b'b290IENBMB4XDTI0MDgwODE4MDQyNloXDTQ0MDgwMjE4MDQyNlowTDEeMBwGA1UE\n'
-+    b'CgwVQ2VydGlmaWNhdGUgU2hhY2sgTHRkMSowKAYDVQQDDCFDZXJ0aWZpY2F0ZSBT\n'
-+    b'aGFjayBJbnRlcm1lZGlhdGUgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n'
-+    b'AoIBAQCzUmUBEO/w1wslS8H304/qfsbeIJX0C5Tm8K2H9JXoauFFej1GZoHqeE+x\n'
-+    b'YQvSMuMFcKks3ps9+9yVKuBPtMwbmXsqwlQXORU8DuKhtRzKIOj7nEGw6AQIsfkG\n'
-+    b'Q4DjD1ytXliyM7vVfxYD+P1CFDK4NR+K1JLdi3WkYOdCelOQMwNspN/ebiqvwonl\n'
-+    b'2asQ6+a13Y0ln1AdrLBvqtR5Z+Gq5+tiC5tA+LKea0e3neQGKjfp/BNPJ+ooNHPR\n'
-+    b'86iKDjBKAabvfrHLG2t6oo9+N4xRBGtPYQh9LOQPZ4OedciCo1s2zs+F+4/6co6T\n'
-+    b'DsbQt7NJKQ3BJKosvZBhC62lc4evAgMBAAGjZjBkMB0GA1UdDgQWBBTvALT5i2gq\n'
-+    b'8yq2Uh8lZGgMoKVClzAfBgNVHSMEGDAWgBRJuz/14J1ZXqvpOuikJJ62NtuiGTAS\n'
-+    b'BgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF\n'
-+    b'AAOCAQEAVjx1aGNK08/Nhf0JYMxMb9Dqg5m7LNOVBs1jurPtwS3uN+84997GRqIQ\n'
-+    b'i+gp/tQVF2YT/RAmt+X0aDLFiSkBcOk87zoFRkR7PZrhhtPo6pSVMN7ngD4/dmp9\n'
-+    b'ESbiI8+iF5ZxqI7c3o2N/LtZpi+hWSCJ/xwbOl05jpNQ6ddl+UzDpJ0oNsyndiJA\n'
-+    b'yciaCvluK027J4xNym166lqwm6CqiOkm8R/G6NJrEH2Xs5XBCyfeH9V0pkXDbrUe\n'
-+    b'Ldqc9ys7l7/MGZi6Qg2nA7J8ErCkrI6eZOocJktSF6SRfXd1NqiqCiNZZQjD6XKZ\n'
-+    b'4fMKTKPX6Q2k10iriAIn4RgVjzM05A==\n'
-+    b'-----END CERTIFICATE-----\n'
-+)
-+duplicate_serial = "4097"
-+
- 
- class TestIPACommand(IntegrationTest):
-     """
-@@ -827,6 +903,12 @@ class TestIPACommand(IntegrationTest):
-                 paths.IPA_CACERT_MANAGE,
-                 'install',
-                 filename])
-+        # remove the subject of good_pkcs7 we just added to avoid
-+        # future failures.
-+        self.master.run_command([
-+            paths.IPA_CACERT_MANAGE,
-+            'delete',
-+            'CN=Certificate Authority,O=EXAMPLE.COM'])
- 
-         for contents in (badcert,):
-             self.master.put_file_contents(filename, contents)
-@@ -1160,7 +1242,7 @@ class TestIPACommand(IntegrationTest):
-             raiseonerr=False
-         )
-         assert result.returncode != 0
--        assert "Verifying \'%s\' failed. Removing part of the " \
-+        assert "Verifying removal of \'%s\' failed. Removing part of the " \
-                "chain? certutil: certificate is invalid: Peer's " \
-                "Certificate issuer is not recognized." \
-                % isrgrootx1_nick in result.stderr_text
-@@ -1735,6 +1817,68 @@ class TestIPACommand(IntegrationTest):
-             self.replicas[0], '/usr/sbin/ipa-replica-install'
-         )
- 
-+    def test_ipa_cacert_manage_duplicate_certsubject(self):
-+        """Test for ipa-cacert-manage install with duplicated
-+           certificate subjects. This relies on the behavior
-+           of NSS to show the certificates separately rather than
-+           lumping the duplicates together. This requires different
-+           validity periods, say 3 years + 1 day.
-+        """
-+
-+        certfile = os.path.join(self.master.config.test_dir, 'chain.pem')
-+        self.master.put_file_contents(certfile, originalsubjectchain)
-+        result = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'install', certfile])
-+
-+        certs = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'list'], raiseonerr=False
-+        ).stdout_text
-+
-+        assert f"{interm_nick}  {intermediate_serial}" in certs
-+
-+        certfile = os.path.join(self.master.config.test_dir, 'interm.pem')
-+        self.master.put_file_contents(certfile, duplicatesubject)
-+        result = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'install', certfile])
-+
-+        certs = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'list'], raiseonerr=False
-+        ).stdout_text
-+
-+        # If the duplicate subject certificates are not sufficiently
-+        # different in validity period, or prior to the this fix,
-+        # the test will fail because only one of the duplicately named
-+        # subject certificates will be visible: the second one (4097).
-+        assert f"{interm_nick}  {intermediate_serial}" in certs
-+        assert f"{interm_nick}  {duplicate_serial}" in certs
-+
-+        # Make sure we can install the new certs systemwide
-+        # No assertions needed, it will work or it won't
-+        self.master.run_command(["ipa-certupdate"])
-+
-+        # delete one of the duplicate subjects, no serial number
-+        result = self.master.run_command(
-+            ['ipa-cacert-manage', 'delete', interm_nick],
-+            raiseonerr=False
-+        )
-+        assert result.returncode == 1
-+        assert 'Multiple matching certificates' in result.stderr_text
-+
-+        # delete one of the duplicate subjects by the serial number
-+        result = self.master.run_command(
-+            ['ipa-cacert-manage', 'delete', interm_nick,
-+             '--serial', intermediate_serial,],
-+            raiseonerr=False
-+        )
-+        assert result.returncode == 0
-+
-+        certs = self.master.run_command(
-+            [paths.IPA_CACERT_MANAGE, 'list'], raiseonerr=False
-+        ).stdout_text
-+
-+        assert f"{interm_nick}  {intermediate_serial}" not in certs
-+        assert f"{interm_nick}  {duplicate_serial}" in certs
-+
- 
- class TestIPACommandWithoutReplica(IntegrationTest):
-     """
-@@ -1970,7 +2114,10 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         assert re.search(new_err_msg, dirsrv_error_log)
- 
-     def test_ipa_cacert_manage_prune(self):
--        """Test for ipa-cacert-manage prune"""
-+        """Test for ipa-cacert-manage prune
-+
-+           This twiddles with time so should be run last in the class.
-+        """
- 
-         certfile = os.path.join(self.master.config.test_dir, 'cert.pem')
-         self.master.put_file_contents(certfile, isrgrootx1)
--- 
-2.49.0
-

diff --git a/freeipa-4-12-2-post-updates-2.patch b/freeipa-4-12-2-post-updates-2.patch
deleted file mode 100644
index f5da27c..0000000
--- a/freeipa-4-12-2-post-updates-2.patch
+++ /dev/null
@@ -1,5397 +0,0 @@
-From e1d517032afa2a8258c1ff8bd6bfdd4175b42327 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Mon, 17 Feb 2025 10:21:53 +0100
-Subject: [PATCH 01/53] dns: don't populate forwarders with DoT forwarders
-
-DNS over TLS setup overrides global forwarder to point to Unbound, so no
-need to setup regular forwarders.
-
-Resolves: https://pagure.io/freeipa/issue/9748
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/dns.py | 12 ++----------
- 1 file changed, 2 insertions(+), 10 deletions(-)
-
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 88aff19bc..470e19159 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -360,14 +360,9 @@ def install_check(standalone, api, replica, options, hostname):
- 
-     if options.no_forwarders:
-         options.forwarders = []
--    elif (options.forwarders
--          or options.dot_forwarders or options.auto_forwarders):
-+    elif options.forwarders or options.auto_forwarders:
-         if not options.forwarders:
--            if options.dot_forwarders:
--                options.forwarders = [fw.split("#")[0]
--                                      for fw in options.dot_forwarders]
--            else:
--                options.forwarders = []
-+            options.forwarders = []
-         if options.auto_forwarders:
-             options.forwarders.extend(dnsforwarders.get_nameservers())
-     elif standalone or not replica:
-@@ -436,9 +431,6 @@ def install(standalone, replica, options, api=api):
-             "and IPA CA is not present."
-         )
- 
--    if not options.forwarders and options.dot_forwarders:
--        options.forwaders = [fw.split("#")[0] for fw in options.dot_forwarders]
--
-     bind = bindinstance.BindInstance(fstore, api=api)
-     bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
-                options.forwarders, options.forward_policy,
--- 
-2.49.0
-
-
-From 3f7d84677775bd9e237b28b08fe961a157b8b14e Mon Sep 17 00:00:00 2001
-From: Aleksandr Sharov <asharov@redhat.com>
-Date: Sat, 8 Mar 2025 14:55:09 +0100
-Subject: [PATCH 02/53] Add a check into ipa-cert-fix tool to avoid updating
- certs if CA is close to being expired.
-
-Fixes: https://pagure.io/freeipa/issue/9760
-Signed-off-by: Aleksandr Sharov <asharov@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/ipa_cert_fix.py | 11 +++++++++++
- 1 file changed, 11 insertions(+)
-
-diff --git a/ipaserver/install/ipa_cert_fix.py b/ipaserver/install/ipa_cert_fix.py
-index 8e02d1e75..960d7b9e0 100644
---- a/ipaserver/install/ipa_cert_fix.py
-+++ b/ipaserver/install/ipa_cert_fix.py
-@@ -69,6 +69,7 @@ logger = logging.getLogger(__name__)
- 
- 
- cert_nicknames = {
-+    'ca_issuing': 'caSigningCert cert-pki-ca',
-     'sslserver': 'Server-Cert cert-pki-ca',
-     'subsystem': 'subsystemCert cert-pki-ca',
-     'ca_ocsp_signing': 'ocspSigningCert cert-pki-ca',
-@@ -137,6 +138,16 @@ class IPACertFix(AdminTool):
-             print("Nothing to do.")
-             return 0
- 
-+        if any(key == 'ca_issuing' for key, _ in certs):
-+            logger.debug("CA signing cert is expired, exiting!")
-+            print(
-+                "The CA signing certificate is expired or will expire within "
-+                "the next two weeks.\n\nipa-cert-fix cannot proceed, please "
-+                "refer to the ipa-cacert-manage tool to renew the CA "
-+                "certificate before proceeding."
-+            )
-+            return 1
-+
-         print(msg)
- 
-         print_intentions(certs, extra_certs, non_renewed)
--- 
-2.49.0
-
-
-From cdc03d7b6233f736c51c10aa07225aac9715e4c0 Mon Sep 17 00:00:00 2001
-From: Aleksandr Sharov <asharov@redhat.com>
-Date: Sat, 8 Mar 2025 15:04:57 +0100
-Subject: [PATCH 03/53] Test fix for the update
-
-Fixes: https://pagure.io/freeipa/issue/9760
-Signed-off-by: Aleksandr Sharov <asharov@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipa_cert_fix.py | 9 +++++++--
- 1 file changed, 7 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index 15d8a8157..d11fd3d61 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -301,13 +301,18 @@ class TestIpaCertFix(IntegrationTest):
-         valid. If CA cert expired, ipa-cert-fix won't work.
- 
-         related: https://pagure.io/freeipa/issue/8721
-+
-+        If CA cert is close to expiry, there's no reason to issue new certs
-+        with short validity period. So, ipa-cert-fix should fail in this case.
-+
-+        related: https://pagure.io/freeipa/issue/9760
-         """
-         result = self.master.run_command(['ipa-cert-fix', '-v'],
-                                          stdin_text='yes\n',
-                                          raiseonerr=False)
-         # check that pki-server cert-fix command fails
--        err_msg = ("ERROR: CalledProcessError(Command "
--                   "['pki-server', 'cert-fix'")
-+        err_msg = ("CA signing cert is expired, exiting!")
-+        assert result.returncode == 1
-         assert err_msg in result.stderr_text
- 
- 
--- 
-2.49.0
-
-
-From b8fd9dbfb2199b2ab82563be2085a4e7d765eb0b Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 27 Mar 2025 16:26:20 -0400
-Subject: [PATCH 04/53] Address deprecation warning in ipa-replica-manage
-
-Running ipa-replica-manage results in a deprecation warning:
-
-/usr/lib/python3.12/site-packages/ipaclient/install/ipadiscovery.py:20:
-DeprecationWarning: ipaclient.install.ipadiscovery is deprecated, use ipacli
-ent.discovery
-
-Switch to the new usage.
-
-Fixes: https://pagure.io/freeipa/issue/9771
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- install/tools/ipa-replica-manage.in | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in
-index 7e5b31a59..c8ab74a80 100644
---- a/install/tools/ipa-replica-manage.in
-+++ b/install/tools/ipa-replica-manage.in
-@@ -30,7 +30,7 @@ from xmlrpc.client import MAXINT
- 
- import ldap
- 
--from ipaclient.install import ipadiscovery
-+import ipaclient.discovery as ipadiscovery
- from ipapython import ipautil
- from ipaserver.install import replication, dsinstance, installutils
- from ipaserver.install import bindinstance, cainstance
--- 
-2.49.0
-
-
-From d3e9e35ef73729956c649f2ee0d0ff3963f99e4e Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Fri, 28 Mar 2025 10:33:15 +0100
-Subject: [PATCH 05/53] Correct dnsrecord_* tests for --raw --structured
-
-Fixes typo in the tests,  --raw --structured is only checked if rest of
-the command is correct as well, therefore test changes were required.
-
-Fixes: https://pagure.io/freeipa/issue/9768
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_xmlrpc/test_dns_plugin.py | 32 ++++++++++++++-----------
- 1 file changed, 18 insertions(+), 14 deletions(-)
-
-diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py
-index 803b0a957..864d5287f 100644
---- a/ipatests/test_xmlrpc/test_dns_plugin.py
-+++ b/ipatests/test_xmlrpc/test_dns_plugin.py
-@@ -3416,20 +3416,11 @@ class test_dns(Declarative):
-             },
-         ),
- 
--        dict(
--            desc='Delete zone %r' % zone1,
--            command=('dnszone_del', [zone1], {}),
--            expected={
--                'value': [zone1_absolute_dnsname],
--                'summary': u'Deleted DNS zone "%s"' % zone1_absolute,
--                'result': {'failed': []},
--            },
--        ),
--
-         dict(
-             desc="Ensure --raw and --structure does not work "
-                  "for ipa dnsrecord-add",
--            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            command=('dnsrecord_add', [zone1, name1],
-+                     {'arecord': arec2, u'raw': True, u'structured': True}),
-             expected=errors.MutuallyExclusiveError(
-                 reason=u"cannot use structured together with raw"
-             ),
-@@ -3438,7 +3429,8 @@ class test_dns(Declarative):
-         dict(
-             desc="Ensure --raw and --structure does not work "
-                  "for ipa dnsrecord-mod",
--            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            command=('dnsrecord_mod', [zone1, name1],
-+                     {'arecord': arec1, u'raw': True, u'structured': True}),
-             expected=errors.MutuallyExclusiveError(
-                 reason=u"cannot use structured together with raw"
-             ),
-@@ -3447,7 +3439,8 @@ class test_dns(Declarative):
-         dict(
-             desc="Ensure --raw and --structure does not work "
-                  "for ipa dnsrecord-show",
--            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            command=('dnsrecord_show', [zone1, name1],
-+                     {u'raw': True, u'structured': True}),
-             expected=errors.MutuallyExclusiveError(
-                 reason=u"cannot use structured together with raw"
-             ),
-@@ -3456,11 +3449,22 @@ class test_dns(Declarative):
-         dict(
-             desc="Ensure --raw and --structure does not work "
-                  "for ipa dnsrecord-find",
--            command=('dnrecord_add', [], {u'raw': True, u'structured': True}),
-+            command=('dnsrecord_find', [zone1],
-+                     {u'raw': True, u'structured': True}),
-             expected=errors.MutuallyExclusiveError(
-                 reason=u"cannot use structured together with raw"
-             ),
-         ),
-+
-+        dict(
-+            desc='Delete zone %r' % zone1,
-+            command=('dnszone_del', [zone1], {}),
-+            expected={
-+                'value': [zone1_absolute_dnsname],
-+                'summary': u'Deleted DNS zone "%s"' % zone1_absolute,
-+                'result': {'failed': []},
-+            },
-+        ),
-     ]
- 
- 
--- 
-2.49.0
-
-
-From d77c4597a841729cb7c890bb57cc548a70c8724b Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Tue, 1 Apr 2025 12:31:18 +0530
-Subject: [PATCH 06/53] ipatests: Fix for ipa-healthcheck test in FIPS Mode
-
-Fix https://github.com/freeipa/freeipa-healthcheck/pull/349
-was added for RHEL10 only causing the tests to
-fail in RHEL10.1.
-Hence the if condition has been changed in the testcode.
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index d72808f0f..8d1b9fce3 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -374,10 +374,10 @@ class TestIpaHealthCheck(IntegrationTest):
-         if (
-             parse_version(healthcheck_version) < parse_version("0.17")
-             and osinfo.id == 'rhel'
--            and osinfo.version_number == (10,0)
-+            and osinfo.version_number >= (10,0)
-         ):
-             # Patch: https://github.com/freeipa/freeipa-healthcheck/pull/349
--            pytest.xfail("Patch is unavailable for RHEL 10.0 and "
-+            pytest.xfail("Patch is unavailable for RHEL 10.0 and above"
-                          "freeipa-healtheck version 0.16 or less")
- 
-         returncode, check = run_healthcheck(self.master,
--- 
-2.49.0
-
-
-From 1aac0a5f7e0702e23e0ba6dad726734b5d75710d Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Mon, 31 Mar 2025 11:50:41 +0200
-Subject: [PATCH 07/53] ipa-sidgen: fix memory leak in ipa_sidgen_add_post_op
-
-Also remove unused "search_pb" variable and its associated free
-functions.
-
-Fixes: https://pagure.io/freeipa/issue/9772
-Signed-off-by: Julien Rische <jrische@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c | 4 +---
- 1 file changed, 1 insertion(+), 3 deletions(-)
-
-diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
-index 99e6b850b..35ecef228 100644
---- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
-+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
-@@ -81,7 +81,6 @@ static int ipa_sidgen_add_post_op(Slapi_PBlock *pb)
-     const char *dn_str;
-     Slapi_DN *dn = NULL;
-     struct ipa_sidgen_ctx *ctx;
--    Slapi_PBlock *search_pb = NULL;
-     char *errmsg = NULL;
- 
-     ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
-@@ -152,9 +151,8 @@ static int ipa_sidgen_add_post_op(Slapi_PBlock *pb)
- 
-     ret = 0;
- done:
--    slapi_free_search_results_internal(search_pb);
--    slapi_pblock_destroy(search_pb);
-     slapi_sdn_free(&dn);
-+    slapi_entry_free(entry);
- 
-     if (ret != 0) {
-         if (errmsg == NULL) {
--- 
-2.49.0
-
-
-From add20a7d03e66ad3aa34433c2449f6e3621f1d97 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 9 Apr 2025 17:54:41 +0200
-Subject: [PATCH 08/53] Remove ipa-nis-manage on fedora 42+
-
-Remove NIS server integration from the migration and
-management tools on fedora 42+
-
-Fixes: https://pagure.io/freeipa/issue/9363
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- freeipa.spec.in                            | 18 +++++++++
- install/tools/ipa-compat-manage.in         | 17 +++++++--
- install/updates/10-enable-betxn.update     |  2 +-
- ipaserver/install/ipa_migrate.py           | 43 ++++++++++++----------
- ipaserver/install/ldapupdate.py            |  1 +
- ipatests/test_cmdline/test_cli.py          |  1 -
- ipatests/test_integration/test_commands.py | 10 +++++
- 7 files changed, 67 insertions(+), 25 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 143ee5c83..01193a39e 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -63,6 +63,11 @@
-     %global modulename ipa
- %endif
- 
-+# Remove NIS support for f42+
-+%if 0%{?fedora} <= 41
-+    %global with_nis 1
-+%endif
-+
- %if 0%{?rhel}
- %global package_name ipa
- %global alt_name freeipa
-@@ -1191,6 +1196,15 @@ mkdir -p %{buildroot}%{_sysconfdir}/cron.d
- # ONLY_CLIENT
- %endif
- 
-+%if ! %{with nis}
-+rm %{buildroot}/%{_sbindir}/ipa-nis-manage
-+rm %{buildroot}/%{_mandir}/man1/ipa-nis-manage.1*
-+rm %{buildroot}%{python3_sitelib}/ipaserver/install/plugins/update_nis.py
-+rm %{buildroot}%{_usr}/share/ipa/nis.uldif
-+rm %{buildroot}%{_usr}/share/ipa/nis-update.uldif
-+rm %{buildroot}%{_usr}/share/ipa/updates/50-nis.update
-+%endif
-+
- %if ! %{ONLY_CLIENT}
- 
- %post server
-@@ -1540,7 +1554,9 @@ fi
- %{_sbindir}/ipa-ldap-updater
- %{_sbindir}/ipa-otptoken-import
- %{_sbindir}/ipa-compat-manage
-+%if %{with nis}
- %{_sbindir}/ipa-nis-manage
-+%endif
- %{_sbindir}/ipa-managed-entries
- %{_sbindir}/ipactl
- %{_sbindir}/ipa-advise
-@@ -1616,7 +1632,9 @@ fi
- %{_mandir}/man1/ipa-ca-install.1*
- %{_mandir}/man1/ipa-kra-install.1*
- %{_mandir}/man1/ipa-compat-manage.1*
-+%if %{with nis}
- %{_mandir}/man1/ipa-nis-manage.1*
-+%endif
- %{_mandir}/man1/ipa-managed-entries.1*
- %{_mandir}/man1/ipa-ldap-updater.1*
- %{_mandir}/man8/ipactl.8*
-diff --git a/install/tools/ipa-compat-manage.in b/install/tools/ipa-compat-manage.in
-index 9650abd6f..301e51c67 100644
---- a/install/tools/ipa-compat-manage.in
-+++ b/install/tools/ipa-compat-manage.in
-@@ -25,6 +25,7 @@ import sys
- from ipaplatform.paths import paths
- try:
-     from ipapython import ipautil, config
-+    from ipapython.ipaldap import realm_to_serverid
-     from ipaserver.install import installutils
-     from ipaserver.install.ldapupdate import LDAPUpdate
-     from ipalib import api, errors
-@@ -152,9 +153,19 @@ def main():
-         try:
-             entry = get_entry(nis_config_dn)
-             # We can't disable schema compat if the NIS plugin is enabled
--            if entry is not None and entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'on':
--                print("The NIS plugin is configured, cannot disable compatibility.", file=sys.stderr)
--                print("Run 'ipa-nis-manage disable' first.", file=sys.stderr)
-+            if (
-+                entry is not None
-+                and entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'on'
-+            ):
-+                instance = realm_to_serverid(api.env.realm)
-+                print(
-+                    "The NIS plugin is configured, cannot "
-+                    "disable compatibility.", file=sys.stderr,
-+                )
-+                print(
-+                    f"Run \"dsconf {instance} plugin set --enabled off "
-+                    "'NIS Server'\" first.", file=sys.stderr,
-+                )
-                 retval = 2
-         except errors.ExecutionError as lde:
-             print("An error occurred while talking to the server.")
-diff --git a/install/updates/10-enable-betxn.update b/install/updates/10-enable-betxn.update
-index 1f89341c7..e6fde183d 100644
---- a/install/updates/10-enable-betxn.update
-+++ b/install/updates/10-enable-betxn.update
-@@ -46,4 +46,4 @@ dn: cn=Schema Compatibility, cn=plugins, cn=config
- onlyifexist: nsslapd-pluginbetxn: on
- 
- dn: cn=NIS Server, cn=plugins, cn=config
--onlyifexist: nsslapd-pluginbetxn: on
-+${NIS}onlyifexist: nsslapd-pluginbetxn: on
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index 5ba140ce3..95ef0ac5a 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -1975,26 +1975,29 @@ class IPAMigrate():
-                     stats['config_processed'] += 1
- 
-             # Slapi NIS Plugin
--            if DN(NIS_PLUGIN['dn']) == DN(entry['dn']):
--                # Parent plugin entry
--                self.process_config_entry(
--                    entry['dn'], entry['attrs'], NIS_PLUGIN,
--                    add_missing=True)
--                stats['config_processed'] += 1
--            elif DN(NIS_PLUGIN['dn']) in DN(entry['dn']):
--                # Child NIS plugin entry
--                nis_dn = entry['dn']
--                lc_remote_realm = self.remote_realm.lower()
--                lc_realm = self.realm.lower()
--                nis_dn = nis_dn.replace(lc_remote_realm, lc_realm)
--                if 'nis-domain' in entry['attrs']:
--                    value = entry['attrs']['nis-domain'][0]
--                    value = value.replace(lc_remote_realm, lc_realm)
--                    entry['attrs']['nis-domain'][0] = value
--                # Process the entry
--                self.process_config_entry(nis_dn, entry['attrs'], NIS_PLUGIN,
--                                          add_missing=True)
--                stats['config_processed'] += 1
-+            # If the file nis-update.uldif does not exist, no support for NIS
-+            if os.path.exists(paths.NIS_ULDIF):
-+                if DN(NIS_PLUGIN['dn']) == DN(entry['dn']):
-+                    # Parent plugin entry
-+                    self.process_config_entry(
-+                        entry['dn'], entry['attrs'], NIS_PLUGIN,
-+                        add_missing=True)
-+                    stats['config_processed'] += 1
-+                elif DN(NIS_PLUGIN['dn']) in DN(entry['dn']):
-+                    # Child NIS plugin entry
-+                    nis_dn = entry['dn']
-+                    lc_remote_realm = self.remote_realm.lower()
-+                    lc_realm = self.realm.lower()
-+                    nis_dn = nis_dn.replace(lc_remote_realm, lc_realm)
-+                    if 'nis-domain' in entry['attrs']:
-+                        value = entry['attrs']['nis-domain'][0]
-+                        value = value.replace(lc_remote_realm, lc_realm)
-+                        entry['attrs']['nis-domain'][0] = value
-+                    # Process the entry
-+                    self.process_config_entry(nis_dn, entry['attrs'],
-+                                              NIS_PLUGIN,
-+                                              add_missing=True)
-+                    stats['config_processed'] += 1
- 
-     #
-     # Migration
-diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
-index cd20d1fc9..c3b59732a 100644
---- a/ipaserver/install/ldapupdate.py
-+++ b/ipaserver/install/ldapupdate.py
-@@ -110,6 +110,7 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
-         NAMED_UID=named_uid,
-         NAMED_GID=named_gid,
-+        NIS='' if os.path.exists(paths.NIS_ULDIF) else '#',
-     )
- 
- 
-diff --git a/ipatests/test_cmdline/test_cli.py b/ipatests/test_cmdline/test_cli.py
-index ae0d059ce..718798d68 100644
---- a/ipatests/test_cmdline/test_cli.py
-+++ b/ipatests/test_cmdline/test_cli.py
-@@ -385,7 +385,6 @@ IPA_CLIENT_NOT_CONFIGURED = b'IPA client is not configured on this system'
-           '/usr/share/ipa/updates/05-pre_upgrade_plugins.update'],
-          2, None, IPA_NOT_CONFIGURED),
-         (['ipa-managed-entries'], 2, None, IPA_NOT_CONFIGURED),
--        (['ipa-nis-manage'], 2, None, IPA_NOT_CONFIGURED),
-         (['ipa-pkinit-manage'], 2, None, IPA_NOT_CONFIGURED),
-         (['ipa-replica-manage', 'list'], 1, IPA_NOT_CONFIGURED, None),
-         (['ipa-server-certinstall'], 2, None, IPA_NOT_CONFIGURED),
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 3c883b8bb..fa6da78e9 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -26,6 +26,7 @@ from datetime import datetime, timedelta
- from ipalib.constants import IPAAPI_USER
- from ipalib.errors import DatabaseError
- 
-+from ipaplatform.osinfo import osinfo
- from ipaplatform.paths import paths
- 
- from ipapython.dn import DN
-@@ -1353,6 +1354,9 @@ class TestIPACommand(IntegrationTest):
-         serverid = realm_to_serverid(self.master.domain.realm)
-         return ("dirsrv@%s.service" % serverid)
- 
-+    @pytest.mark.skipif((osinfo.id == 'fedora'
-+                        and osinfo.version_number > (41,)),
-+                        reason="NIS server support removed")
-     def test_ipa_nis_manage_enable(self):
-         """
-         This testcase checks if ipa-nis-manage enable
-@@ -1389,6 +1393,9 @@ class TestIPACommand(IntegrationTest):
-         )
-         assert status_msg in result.stdout_text
- 
-+    @pytest.mark.skipif((osinfo.id == 'fedora'
-+                        and osinfo.version_number > (41,)),
-+                        reason="NIS server support removed")
-     def test_ipa_nis_manage_disable(self):
-         """
-         This testcase checks if ipa-nis-manage disable
-@@ -1424,6 +1431,9 @@ class TestIPACommand(IntegrationTest):
-         assert result.returncode == 4
-         assert status_msg in result.stdout_text
- 
-+    @pytest.mark.skipif((osinfo.id == 'fedora'
-+                        and osinfo.version_number > (41,)),
-+                        reason="NIS server support removed")
-     def test_ipa_nis_manage_enable_incorrect_password(self):
-         """
-         This testcase checks if ipa-nis-manage enable
--- 
-2.49.0
-
-
-From d8c7c322d28e8f1dd99fc39c3108ac630e8416ea Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 3 Apr 2025 17:19:18 +0200
-Subject: [PATCH 09/53] PRCI: switch testing from f41 to f42
-
-Fedora 42 will be available soon and Fedora 40 will be EOL May 13 2025.
-Start testing on fedora 42.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipatests/prci_definitions/gating.yaml                       | 4 ++--
- ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml      | 6 +++---
- .../prci_definitions/nightly_ipa-4-12_latest_selinux.yaml   | 6 +++---
- ipatests/prci_definitions/temp_commit.yaml                  | 4 ++--
- 4 files changed, 10 insertions(+), 10 deletions(-)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index 94910bc8c..fcb477e34 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -38,8 +38,8 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.2
-+          name: freeipa/ci-ipa-4-12-f42
-+          version: 0.0.1
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index b54de5406..50540f360 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -54,8 +54,8 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.2
-+          name: freeipa/ci-ipa-4-12-f42
-+          version: 0.0.1
-         timeout: 1800
-         topology: *build
- 
-@@ -2164,4 +2164,4 @@ jobs:
-         test_suite: test_integration/test_edns.py
-         template: *ci-ipa-4-12-latest
-         timeout: 14400
--        topology: *master_2repl_1client
-\ No newline at end of file
-+        topology: *master_2repl_1client
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 348891595..05167f6d7 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -54,8 +54,8 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.2
-+          name: freeipa/ci-ipa-4-12-f42
-+          version: 0.0.1
-         timeout: 1800
-         topology: *build
- 
-@@ -2337,4 +2337,4 @@ jobs:
-         test_suite: test_integration/test_edns.py
-         template: *ci-ipa-4-12-latest
-         timeout: 14400
--        topology: *master_2repl_1client
-\ No newline at end of file
-+        topology: *master_2repl_1client
-diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
-index 653abb3fc..62f6f0c07 100644
---- a/ipatests/prci_definitions/temp_commit.yaml
-+++ b/ipatests/prci_definitions/temp_commit.yaml
-@@ -60,8 +60,8 @@ jobs:
-         git_repo: '{git_repo}'
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
--          name: freeipa/ci-ipa-4-12-f41
--          version: 0.0.2
-+          name: freeipa/ci-ipa-4-12-f42
-+          version: 0.0.1
-         timeout: 1800
-         topology: *build
- 
--- 
-2.49.0
-
-
-From 3aa35fe16bb730cc3899312bedc8e335da4e145f Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 26 Mar 2025 17:48:48 +0100
-Subject: [PATCH 10/53] ipatests: fix test_idp
-
-The test is calling su -c "sudo yum list yum" keycloakuser
-to ensure that keycloakuser is allowed to execute the yum command.
-
-On fedora 42 and above, the yum package does not exist any more.
-Replace with a call to yum list sssd-client as the package
-sssd-client is always installed on our test machines.
-
-Fixes: https://pagure.io/freeipa/issue/9769
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipatests/test_integration/test_idp.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py
-index 76edc9458..a29333ef2 100644
---- a/ipatests/test_integration/test_idp.py
-+++ b/ipatests/test_integration/test_idp.py
-@@ -217,7 +217,7 @@ class TestIDPKeycloak(IntegrationTest):
-             assert "User keycloakuser may run the following commands" in test
-             assert "/usr/bin/yum" in test
-             kinit_idp(self.client, 'keycloakuser', self.client)
--            test_sudo = 'su -c "sudo yum list yum" keycloakuser'
-+            test_sudo = 'su -c "sudo yum list sssd-client" keycloakuser'
-             self.client.run_command(test_sudo)
-             list_fail = self.master.run_command(cmd).stdout_text
-             assert "User keycloakuser is not allowed to run sudo" in list_fail
--- 
-2.49.0
-
-
-From d25aeaf8e5e7d25070fcdae9f19b4d145a5dd464 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 28 Mar 2025 17:41:28 +0100
-Subject: [PATCH 11/53] azure pipeline: replace ubuntu-20.04 with 24.04
-
-Azure is deprecating the Ubuntu-20.04 Apr 15th, see announcement
-https://devblogs.microsoft.com/devops/upcoming-updates-for-azure-pipelines-agents-images/#ubuntu
-
-With the Ubuntu 24.04 image, we need to change permissions of /etc/shadow
-in order to allow sudo commands.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- ipatests/azure/azure-pipelines.yml            | 46 +++++++++++++------
- ipatests/azure/templates/variables-common.yml |  2 +-
- 2 files changed, 32 insertions(+), 16 deletions(-)
-
-diff --git a/ipatests/azure/azure-pipelines.yml b/ipatests/azure/azure-pipelines.yml
-index aea25026c..6f18b1018 100644
---- a/ipatests/azure/azure-pipelines.yml
-+++ b/ipatests/azure/azure-pipelines.yml
-@@ -10,14 +10,22 @@ variables:
- # platform specific variables, links to
- - template: ${{ parameters.VARIABLES_FILE }}
- 
-+resources:
-+  containers:
-+  - container: 'builder'
-+    image: $(DOCKER_BUILD_IMAGE)
-+    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+
- jobs:
- - job: Build
-   pool:
-     vmImage: $(VM_IMAGE)
--  container:
--    image: $(DOCKER_BUILD_IMAGE)
--    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+  container: builder
-   steps:
-+    - script: |
-+        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
-+      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-     - template: templates/${{ variables.BUILD_TEMPLATE }}
-@@ -57,10 +65,12 @@ jobs:
- - job: Lint
-   pool:
-     vmImage: $(VM_IMAGE)
--  container:
--    image: $(DOCKER_BUILD_IMAGE)
--    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+  container: builder
-   steps:
-+    - script: |
-+        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
-+      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_LINT_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-@@ -79,10 +89,12 @@ jobs:
- - job: Docs
-   pool:
-     vmImage: $(VM_IMAGE)
--  container:
--    image: $(DOCKER_BUILD_IMAGE)
--    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+  container: builder
-   steps:
-+    - script: |
-+        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
-+      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-     - template: templates/${{ variables.BUILD_DOCS_TEMPLATE }}
-@@ -95,10 +107,12 @@ jobs:
- - job: Tox
-   pool:
-     vmImage: $(VM_IMAGE)
--  container:
--    image: $(DOCKER_BUILD_IMAGE)
--    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+  container: builder
-   steps:
-+    - script: |
-+        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
-+      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_TOX_TEMPLATE }}
-     - script: |
-@@ -118,10 +132,12 @@ jobs:
- - job: WebUI_Unit_Tests
-   pool:
-     vmImage: $(VM_IMAGE)
--  container:
--    image: $(DOCKER_BUILD_IMAGE)
--    options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged --env container=docker
-+  container: builder
-   steps:
-+    - script: |
-+        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
-+      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_WEBUI_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-diff --git a/ipatests/azure/templates/variables-common.yml b/ipatests/azure/templates/variables-common.yml
-index 5b3c2ca49..336019c86 100644
---- a/ipatests/azure/templates/variables-common.yml
-+++ b/ipatests/azure/templates/variables-common.yml
-@@ -6,7 +6,7 @@ variables:
-   # https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1604-REA    DME.md
-   # Ubuntu-18.04 - 3.6.9
-   # https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-REA    DME.md
--  VM_IMAGE: 'ubuntu-20.04'
-+  VM_IMAGE: 'ubuntu-24.04'
-   MAX_CONTAINER_ENVS: 5
-   IPA_TESTS_ENV_WORKING_DIR: $(Build.Repository.LocalPath)/ipa_envs
-   IPA_TESTS_SCRIPTS: 'ipatests/azure/scripts'
--- 
-2.49.0
-
-
-From c73ec36cb7a0b691ed790542463a0d2f7b6d2b6a Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 2 Apr 2025 11:31:04 +0200
-Subject: [PATCH 12/53] azure pipeline: skip step disabling conflicting
- apparmor profile
-
-aa-disable /etc/apparmor.d/usr.sbin.chronyd fails on Ubuntu 24.04
-https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2079019
-
-Skip the step for now
-
-Fix the top command (replace n 1 with -n 1)
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- ipatests/azure/templates/test-jobs.yml | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/azure/templates/test-jobs.yml b/ipatests/azure/templates/test-jobs.yml
-index dba527474..1df009899 100644
---- a/ipatests/azure/templates/test-jobs.yml
-+++ b/ipatests/azure/templates/test-jobs.yml
-@@ -41,7 +41,7 @@ steps:
-     printf "AppArmor status\n"
-     sudo aa-status
-     printf "Disable AppArmor conflicting profiles\n"
--    sudo aa-disable /etc/apparmor.d/usr.sbin.chronyd
-+    sudo aa-disable /etc/apparmor.d/usr.sbin.chronyd ||:
-     printf "Recheck AppArmor status\n"
-     sudo aa-status
-   displayName: Disable AppArmor conflicting profiles on Host
-@@ -74,7 +74,7 @@ steps:
- 
- - script: |
-     set -eu
--    sudo top -b -o +%MEM n 1
-+    sudo top -b -o +%MEM -n 1
-   displayName: Show Host's top
- 
- - script: |
--- 
-2.49.0
-
-
-From 4d8e341e0693ab05ec80bef13768d33da40ff584 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 2 Apr 2025 17:12:47 +0200
-Subject: [PATCH 13/53] Azure CI: use podman instead of docker through
- emulation
-
-- The scaled containers are named name_i instead of name-i
-(with _ instead of -)
-- podman-compose doesn't have the option -p project_id
-Use the env var COMPOSE_PROJECT_NAME instead
-- Do not add all capabilities in docker-compose.yml
-Drop the CAP_SYS_TIME capability, otherwise chronyd fails to start
-in the container
-- Use absolute path to mount volumes in the containers
-When using a relative path which is a symbolic link, podman is
-confused. Use only absolute paths.
-- Use default network in podman-compose
-- Start podman.socket and set the env var DOCKER_HOST to
-communicate with it
-- Ubuntu 24.04 uses cgroup v2 and has different files for
-memory control files.
-- test_sudo: force idrange to be compatible with containers
-The user defined on the host has a subuid/subgid range of
-65536 ids. Create ipa server with an idrange below
-65536, otherwise sudo commands fail.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- ipatests/azure/Dockerfiles/docker-compose.yml | 24 +++-----
- ipatests/azure/azure-pipelines.yml            | 25 +-------
- ipatests/azure/scripts/azure-run-tests.sh     | 57 +++++++++++++++----
- ipatests/azure/scripts/setup_containers.py    | 12 ++--
- .../azure/templates/prepare-build-fedora.yml  |  8 ++-
- .../templates/setup-test-environment.yml      | 18 ++----
- ipatests/azure/templates/test-jobs.yml        | 38 ++++++++++---
- ipatests/test_integration/test_sudo.py        |  7 ++-
- 8 files changed, 110 insertions(+), 79 deletions(-)
-
-diff --git a/ipatests/azure/Dockerfiles/docker-compose.yml b/ipatests/azure/Dockerfiles/docker-compose.yml
-index dc3707eff..8dd9c5996 100644
---- a/ipatests/azure/Dockerfiles/docker-compose.yml
-+++ b/ipatests/azure/Dockerfiles/docker-compose.yml
-@@ -5,6 +5,8 @@ services:
-     build: .
-     cap_add:
-     - ALL
-+    cap_drop:
-+    - CAP_SYS_TIME
-     security_opt:
-     - apparmor:unconfined
-     - seccomp:./seccomp.json
-@@ -12,17 +14,16 @@ services:
-     memswap_limit: "${IPA_TESTS_SERVER_MEMSWAP_LIMIT}"
-     volumes:
-     - /sys/fs/cgroup/systemd:/sys/fs/cgroup/systemd
--    - ./ipa-test-config.yaml:/root/.ipa/ipa-test-config.yaml:ro
-+    - ${PROJECT_DIR}/ipa-test-config.yaml:/root/.ipa/ipa-test-config.yaml:ro
-     - ${BUILD_REPOSITORY_LOCALPATH}:${IPA_TESTS_REPO_PATH}
- 
--    networks:
--    - ${IPA_NETWORK}
--
-   replica:
-     image: ${IPA_DOCKER_IMAGE}
-     build: .
-     cap_add:
-     - ALL
-+    cap_drop:
-+    - CAP_SYS_TIME
-     security_opt:
-     - apparmor:unconfined
-     - seccomp:./seccomp.json
-@@ -31,14 +32,14 @@ services:
-     volumes:
-     - /sys/fs/cgroup/systemd:/sys/fs/cgroup/systemd
-     - ${BUILD_REPOSITORY_LOCALPATH}:${IPA_TESTS_REPO_PATH}:ro
--    networks:
--    - ${IPA_NETWORK}
- 
-   client:
-     image: ${IPA_DOCKER_IMAGE}
-     build: .
-     cap_add:
-     - ALL
-+    cap_drop:
-+    - CAP_SYS_TIME
-     security_opt:
-     - apparmor:unconfined
-     - seccomp:./seccomp.json
-@@ -50,15 +51,4 @@ services:
-     # nfs server
-     - ./exports:/exports
-     - /lib/modules:/lib/modules:ro
--    networks:
--    - ${IPA_NETWORK}
- 
--networks:
--  ipanet:
--    driver: bridge
--    enable_ipv6: true
--    ipam:
--      driver: default
--      config:
--      - subnet: ${IPA_IPV6_SUBNET}
--    internal: ${IPA_NETWORK_INTERNAL}
-diff --git a/ipatests/azure/azure-pipelines.yml b/ipatests/azure/azure-pipelines.yml
-index 6f18b1018..78777d460 100644
---- a/ipatests/azure/azure-pipelines.yml
-+++ b/ipatests/azure/azure-pipelines.yml
-@@ -22,10 +22,6 @@ jobs:
-     vmImage: $(VM_IMAGE)
-   container: builder
-   steps:
--    - script: |
--        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
--        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
--      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-     - template: templates/${{ variables.BUILD_TEMPLATE }}
-@@ -41,8 +37,9 @@ jobs:
-         cp -pr dist container/
-         cp $(IPA_TESTS_DOCKERFILES)/$(DOCKER_DOCKERFILE) container/Dockerfile
-         cd container
--        docker build -t freeipa-azure-builder .
--        docker save freeipa-azure-builder | gzip > '$(builddir)/freeipa-azure-builder-container.tar.gz'
-+        podman build -t freeipa-azure-builder .
-+        podman image save -o $(builddir)/freeipa-azure-builder-container.tar freeipa-azure-builder:latest
-+        gzip $(builddir)/freeipa-azure-builder-container.tar
-       displayName: Create container image for test
-     - template: templates/publish-build.yml
-       parameters:
-@@ -67,10 +64,6 @@ jobs:
-     vmImage: $(VM_IMAGE)
-   container: builder
-   steps:
--    - script: |
--        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
--        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
--      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_LINT_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-@@ -91,10 +84,6 @@ jobs:
-     vmImage: $(VM_IMAGE)
-   container: builder
-   steps:
--    - script: |
--        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
--        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
--      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-     - template: templates/${{ variables.BUILD_DOCS_TEMPLATE }}
-@@ -109,10 +98,6 @@ jobs:
-     vmImage: $(VM_IMAGE)
-   container: builder
-   steps:
--    - script: |
--        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
--        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
--      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_TOX_TEMPLATE }}
-     - script: |
-@@ -134,10 +119,6 @@ jobs:
-     vmImage: $(VM_IMAGE)
-   container: builder
-   steps:
--    - script: |
--        CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
--        /usr/bin/docker exec $CONTAINERID bash -c "chmod a+r /etc/shadow"
--      target: host
-     - template: templates/${{ variables.PREPARE_BUILD_TEMPLATE }}
-     - template: templates/${{ variables.PREPARE_WEBUI_TEMPLATE }}
-     - template: templates/${{ variables.AUTOCONF_TEMPLATE }}
-diff --git a/ipatests/azure/scripts/azure-run-tests.sh b/ipatests/azure/scripts/azure-run-tests.sh
-index 2252d8c6c..476b4236d 100755
---- a/ipatests/azure/scripts/azure-run-tests.sh
-+++ b/ipatests/azure/scripts/azure-run-tests.sh
-@@ -7,6 +7,8 @@ if [ $# -ne 1 ]; then
-     exit 1
- fi
- 
-+# docker uses - but podman uses _ as separator when building scaled container names
-+CONTAINER_SEP="_"
- PROJECT_ID="$1"
- BUILD_REPOSITORY_LOCALPATH="${BUILD_REPOSITORY_LOCALPATH:-$(realpath .)}"
- 
-@@ -41,7 +43,7 @@ IPA_TESTS_CLIENTS="${!IPA_TESTS_CLIENTS_VARNAME:-0}"
- IPA_TESTS_REPLICAS_VARNAME="IPA_TESTS_REPLICAS_${PROJECT_ID}"
- IPA_TESTS_REPLICAS="${!IPA_TESTS_REPLICAS_VARNAME:-0}"
- 
--IPA_TESTS_CONTROLLER="${PROJECT_ID}-master-1"
-+IPA_TESTS_CONTROLLER="${PROJECT_ID}${CONTAINER_SEP}master${CONTAINER_SEP}1"
- IPA_TESTS_LOGSDIR="${IPA_TESTS_REPO_PATH}/ipa_envs/${IPA_TESTS_ENV_NAME}/${CI_RUNNER_LOGS_DIR}"
- 
- # path to azure scripts inside container
-@@ -49,6 +51,8 @@ IPA_TESTS_SCRIPTS_IN="${IPA_TESTS_REPO_PATH}/${IPA_TESTS_SCRIPTS}"
- # path to azure scripts outside of container
- IPA_TESTS_SCRIPTS_OUT="${BUILD_REPOSITORY_LOCALPATH}/${IPA_TESTS_SCRIPTS}"
- 
-+IPA_TESTS_NETWORK_VARNAME="IPA_TESTS_NETWORK_${PROJECT_ID}"
-+IPA_NETWORK=${!IPA_TESTS_NETWORK_VARNAME:-default}
- IPA_TESTS_NETWORK_INTERNAL_VARNAME="IPA_TESTS_NETWORK_INTERNAL_${PROJECT_ID}"
- IPA_NETWORK_INTERNAL="${!IPA_TESTS_NETWORK_INTERNAL_VARNAME:-false}"
- 
-@@ -77,7 +81,7 @@ IPA_TESTS_CLIENT_MEMSWAP_LIMIT="${!IPA_TESTS_CLIENT_MEMSWAP_LIMIT_VARNAME:-768m}
- IPA_TESTS_DOMAIN="${IPA_TESTS_DOMAIN:-ipa.test}"
- # bash4
- IPA_TESTS_REALM="${IPA_TESTS_DOMAIN^^}"
--
-+DOCKER_HOST=$(eval "echo unix://${XDG_RUNTIME_DIR}/podman/podman.sock")
- 
- # for base tests only 1 master is needed even if another was specified
- if [ "$IPA_TESTS_TYPE" == "base" ]; then
-@@ -97,14 +101,14 @@ IPA_INSTALLED_PKGS_DIR="${project_dir}/installed_packages"
- BASH_CMD="/bin/bash --noprofile --norc"
- 
- function containers() {
--    local _containers="${PROJECT_ID}-master-1"
-+    local _containers="${PROJECT_ID}${CONTAINER_SEP}master${CONTAINER_SEP}1"
-     # build list of replicas
-     for i in $(seq 1 1 "$IPA_TESTS_REPLICAS"); do
--        _containers+=" ${PROJECT_ID}-replica-${i}"
-+        _containers+=" ${PROJECT_ID}${CONTAINER_SEP}replica${CONTAINER_SEP}${i}"
-     done
-     # build list of clients
-     for i in $(seq 1 1 "$IPA_TESTS_CLIENTS"); do
--        _containers+=" ${PROJECT_ID}-client-${i}"
-+        _containers+=" ${PROJECT_ID}${CONTAINER_SEP}client${CONTAINER_SEP}${i}"
-     done
-     printf "$_containers"
- }
-@@ -112,7 +116,7 @@ function containers() {
- function compose_execute() {
-     # execute given command within every container of compose
-     for container in $(containers); do
--        docker exec -t \
-+        podman exec -t \
-             "$container" \
-             "$@" \
-         2>&1 | \
-@@ -144,11 +148,15 @@ IPA_TESTS_CLIENT_MEM_LIMIT="$IPA_TESTS_CLIENT_MEM_LIMIT" \
- IPA_TESTS_SERVER_MEMSWAP_LIMIT="$IPA_TESTS_SERVER_MEMSWAP_LIMIT" \
- IPA_TESTS_REPLICA_MEMSWAP_LIMIT="$IPA_TESTS_REPLICA_MEMSWAP_LIMIT" \
- IPA_TESTS_CLIENT_MEMSWAP_LIMIT="$IPA_TESTS_CLIENT_MEMSWAP_LIMIT" \
--docker compose -p "$PROJECT_ID" up \
-+COMPOSE_PROJECT_NAME="$PROJECT_ID" \
-+PROJECT_DIR="$project_dir" \
-+podman-compose up \
-     --scale replica="$IPA_TESTS_REPLICAS" \
-     --scale client="$IPA_TESTS_CLIENTS" \
-     --force-recreate --remove-orphans -d
- 
-+podman network inspect ${PROJECT_ID}_${IPA_NETWORK}
-+
- popd
- 
- IPA_TESTS_CLIENTS="$IPA_TESTS_CLIENTS" \
-@@ -159,10 +167,12 @@ IPA_TESTS_ENV_NAME="$IPA_TESTS_ENV_NAME" \
- IPA_TEST_CONFIG_TEMPLATE="${BUILD_REPOSITORY_LOCALPATH}/ipatests/azure/templates/ipa-test-config-template.yaml" \
- IPA_TESTS_REPO_PATH="$IPA_TESTS_REPO_PATH" \
- IPA_TESTS_DOMAIN="$IPA_TESTS_DOMAIN" \
-+DOCKER_HOST="$DOCKER_HOST" \
-+IPA_NETWORK="${IPA_NETWORK:-ipanet}" \
- python3 setup_containers.py || \
-     { mkdir -p "$SYSTEMD_BOOT_LOG";
-       for container in $(containers); do
--          docker exec -t "$container" \
-+          podman exec -t "$container" \
-               $BASH_CMD -eu \
-               -c 'journalctl -b --no-pager' > "${SYSTEMD_BOOT_LOG}/systemd_boot_${container}.log";
-       done
-@@ -173,7 +183,7 @@ python3 setup_containers.py || \
- mkdir -p "$IPA_INSTALLED_PKGS_DIR"
- 
- # controller
--docker exec -t \
-+podman exec -t \
-     --env IPA_TESTS_SCRIPTS="${IPA_TESTS_SCRIPTS_IN}" \
-     --env IPA_PLATFORM="$IPA_PLATFORM" \
-     "$IPA_TESTS_CONTROLLER" \
-@@ -185,9 +195,30 @@ docker exec -t \
-      installed_packages \
-      " > "${IPA_INSTALLED_PKGS_DIR}/packages_controller_${IPA_TESTS_CONTROLLER}.log"
- 
-+# display user namespace
-+podman exec -t \
-+    --env IPA_TESTS_SCRIPTS="${IPA_TESTS_SCRIPTS_IN}" \
-+    --env IPA_PLATFORM="$IPA_PLATFORM" \
-+    "$IPA_TESTS_CONTROLLER" \
-+    $BASH_CMD -eu \
-+    -c \
-+    "cat /proc/self/uid_map /proc/self/gid_map"
-+
-+# workaround for /etc/shadow missing read permissions
-+# workers
-+for container in $(containers); do
-+    podman exec -t \
-+        --env IPA_TESTS_SCRIPTS="${IPA_TESTS_SCRIPTS_IN}" \
-+        --env IPA_PLATFORM="$IPA_PLATFORM" \
-+        "$container" \
-+        $BASH_CMD -eu \
-+        -c \
-+        "chmod a+r /etc/shadow"
-+done
-+
- # workers
- for container in $(containers); do
--    docker exec -t \
-+    podman exec -t \
-         --env IPA_TESTS_SCRIPTS="${IPA_TESTS_SCRIPTS_IN}" \
-         --env IPA_PLATFORM="$IPA_PLATFORM" \
-         "$container" \
-@@ -204,7 +235,7 @@ done
- tests_runner="${IPA_TESTS_SCRIPTS_IN}/azure-run-${IPA_TESTS_TYPE}-tests.sh"
- 
- tests_result=1
--{ docker exec -t \
-+{ podman exec -t \
-     --env IPA_TESTS_SCRIPTS="${IPA_TESTS_SCRIPTS_IN}" \
-     --env IPA_PLATFORM="$IPA_PLATFORM" \
-     --env IPA_TESTS_DOMAIN="$IPA_TESTS_DOMAIN" \
-@@ -257,7 +288,9 @@ IPA_TESTS_CLIENT_MEM_LIMIT="$IPA_TESTS_CLIENT_MEM_LIMIT" \
- IPA_TESTS_SERVER_MEMSWAP_LIMIT="$IPA_TESTS_SERVER_MEMSWAP_LIMIT" \
- IPA_TESTS_REPLICA_MEMSWAP_LIMIT="$IPA_TESTS_REPLICA_MEMSWAP_LIMIT" \
- IPA_TESTS_CLIENT_MEMSWAP_LIMIT="$IPA_TESTS_CLIENT_MEMSWAP_LIMIT" \
--docker compose -p "$PROJECT_ID" down
-+COMPOSE_PROJECT_NAME="$PROJECT_ID" \
-+PROJECT_DIR="$project_dir" \
-+podman-compose down
- popd
- 
- exit $tests_result
-diff --git a/ipatests/azure/scripts/setup_containers.py b/ipatests/azure/scripts/setup_containers.py
-index 9d2f1142e..477b314e8 100644
---- a/ipatests/azure/scripts/setup_containers.py
-+++ b/ipatests/azure/scripts/setup_containers.py
-@@ -125,7 +125,7 @@ class ContainersGroup:
-         # initialize containers
-         self.containers = [
-             Container(
--                name=f"{self.prefix}-{self.role}-{c}",
-+                name=f"{self.prefix}_{self.role}_{c}",
-                 hostname=f"{self.role}{c}.{self.domain}",
-                 network=f"{IPA_TESTS_ENV_ID}_{IPA_NETWORK}",
-             )
-@@ -187,7 +187,7 @@ class ContainersGroup:
-                     "127.0.0.1 localhost",
-                     "::1 localhost",
-                     f"{cont.ip} {cont.hostname}",
--                    f"{cont.ipv6} {cont.hostname}",
-+                    # f"{cont.ipv6} {cont.hostname}",
-                 ]
-             )
-             cmd = ["/bin/bash", "-c", f"echo -e '{hosts}' > /etc/hosts"]
-@@ -218,7 +218,8 @@ class ContainersGroup:
-         cmd = [
-             "/bin/bash",
-             "-c",
--            f"echo -e '{nameservers}' > /etc/resolv.conf",
-+            f"echo -e '{nameservers}' > /etc/resolv.conf"
-+            f"; chmod 0644 /etc/resolv.conf",
-         ]
-         self.execute_all(cmd)
- 
-@@ -363,7 +364,7 @@ class Controller(Container):
- 
-             for container in containers_group.containers:
-                 hosts.append(f"{container.ip} {container.hostname}")
--                hosts.append(f"{container.ipv6} {container.hostname}")
-+                # hosts.append(f"{container.ipv6} {container.hostname}")
- 
-         cmd = [
-             "/bin/bash",
-@@ -391,6 +392,9 @@ class Controller(Container):
-             template = Template(f.read(), trim_blocks=True, lstrip_blocks=True)
- 
-         logging.info(template.render(config))
-+        logging.info(
-+            "Saving the test config in %s",
-+            os.path.join(IPA_TESTS_ENV_DIR, IPA_TEST_CONFIG))
- 
-         with open(os.path.join(IPA_TESTS_ENV_DIR, IPA_TEST_CONFIG), "w") as f:
-             f.write(template.render(config))
-diff --git a/ipatests/azure/templates/prepare-build-fedora.yml b/ipatests/azure/templates/prepare-build-fedora.yml
-index 9f6b92b70..3a74d7f2a 100644
---- a/ipatests/azure/templates/prepare-build-fedora.yml
-+++ b/ipatests/azure/templates/prepare-build-fedora.yml
-@@ -1,4 +1,10 @@
- steps:
-+- script: |
-+    CONTAINERID=`echo '$(Agent.ContainerMapping)' | jq -r '.builder.id'`
-+    /usr/bin/docker exec $CONTAINERID bash -c "dnf reinstall -y shadow-utils; chmod a+r /etc/shadow"
-+  target: host
-+  displayName: Workaround unreadable /etc/shadow
-+
- - script: |
-     set -e
-     sudo rm -rf /var/cache/dnf/*
-@@ -14,7 +20,7 @@ steps:
-         git \
-         automake \
-         libtool \
--        docker \
-+        podman \
-         python3-paramiko \
-         python3-pyyaml \
- 
-diff --git a/ipatests/azure/templates/setup-test-environment.yml b/ipatests/azure/templates/setup-test-environment.yml
-index 584abb990..3448c662e 100644
---- a/ipatests/azure/templates/setup-test-environment.yml
-+++ b/ipatests/azure/templates/setup-test-environment.yml
-@@ -2,16 +2,6 @@ parameters:
-   imageName: 'freeipa-azure-builder:latest'
- 
- steps:
--- script: |
--    set -e
--    echo '{ "ipv6": true, "fixed-cidr-v6": "2001:db8::/64" }' > docker-daemon.json
--    sudo mkdir -p /etc/docker
--    sudo cp docker-daemon.json /etc/docker/daemon.json
--    sudo chown root:root /etc/docker/daemon.json
--    sudo systemctl restart docker
--    sudo modprobe ip6_tables
--  displayName: Configure containerization to allow IPv6 network
--
- - script: |
-     set -e
-     sudo modprobe {nfs,nfsd}
-@@ -31,7 +21,9 @@ steps:
- 
- - script: |
-     set -e
--    docker load --input $(Build.Repository.LocalPath)/freeipa-azure-builder-container.tar.gz
--    docker images
--    docker inspect freeipa-azure-builder:latest
-+    podman load --input $(Build.Repository.LocalPath)/freeipa-azure-builder-container.tar.gz
-+    # Work around podman load bug that names loaded image as latest:latest
-+    podman image tag localhost/latest:latest freeipa-azure-builder:latest ||:
-+    podman images
-+    podman inspect freeipa-azure-builder:latest
-   displayName: Import pre-built container to the engine
-diff --git a/ipatests/azure/templates/test-jobs.yml b/ipatests/azure/templates/test-jobs.yml
-index 1df009899..4037e5077 100644
---- a/ipatests/azure/templates/test-jobs.yml
-+++ b/ipatests/azure/templates/test-jobs.yml
-@@ -1,6 +1,7 @@
- steps:
- - script: |
-     set -e
-+    export DOCKER_HOST=unix://$(XDG_RUNTIME_DIR)/podman/podman.sock
-     env | sort
-   displayName: Print Host Enviroment
- 
-@@ -11,6 +12,7 @@ steps:
- 
- - script: |
-     set -e
-+    sudo apt-get purge --auto-remove docker-ce-cli
-     sudo apt-get update
-     sudo apt-get install -y \
-         apparmor-utils \
-@@ -18,7 +20,8 @@ steps:
-         moreutils \
-         rng-tools \
-         systemd-coredump \
--        python3-docker
-+        podman podman-docker python3-pip python3-venv
-+    sudo pip3 install podman-compose
-   displayName: Install Host's tests requirements
- 
- - script: |
-@@ -26,6 +29,11 @@ steps:
-     sudo systemctl
-   displayName: Show Host's systemd status
- 
-+- script: |
-+    set -e
-+    systemctl --user enable --now podman.socket ||:
-+  displayName: Enable podman emulation of docker
-+
- - script: |
-     set -e
-     # most of the time systemd killed hostnamed with SIGKILL on timeout
-@@ -82,13 +90,27 @@ steps:
-     sudo ps -auxf
-   displayName: Show Host's processes
- 
-+- script: |
-+    set -eu
-+    id
-+    cat /proc/$$/subuid_map ||:
-+    cat /proc/$$/subgid_map ||:
-+    cat /etc/subuid ||:
-+    cat /etc/subgid ||:
-+  displayName: Show Host's subuid settings
-+
-+- script: |
-+    set -e
-+    sudo mkdir -p /sys/fs/cgroup/systemd
-+  displayName: Create /sys/fs/cgroup/systemd for the volume
-+
- - template: run-test.yml
- 
- - script: |
-     set -eux
-     free -m
--    cat /sys/fs/cgroup/memory/memory.memsw.max_usage_in_bytes
--    cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
-+    cat /sys/fs/cgroup/systemd/memory.swap.peak
-+    cat /sys/fs/cgroup/systemd/memory.peak
-     cat /proc/sys/vm/swappiness
-   condition: succeededOrFailed()
-   displayName: Host's memory statistics
-@@ -150,20 +172,20 @@ steps:
-     # continue in container
-     HOST_JOURNAL="/var/log/host_journal"
-     CONTAINER_COREDUMP="dump_cores"
--    docker create --privileged \
-+    podman create --privileged \
-         -v "$(realpath coredumpctl.time.mark)":/coredumpctl.time.mark:ro \
-         -v /var/lib/systemd/coredump:/var/lib/systemd/coredump:ro \
-         -v /var/log/journal:"$HOST_JOURNAL":ro \
-         -v "${BUILD_REPOSITORY_LOCALPATH}":"${IPA_TESTS_REPO_PATH}" \
-         --name "$CONTAINER_COREDUMP" freeipa-azure-builder
--    docker start "$CONTAINER_COREDUMP"
-+    podman start "$CONTAINER_COREDUMP"
- 
--    docker exec -t \
-+    podman exec -t \
-         "$CONTAINER_COREDUMP" \
-         /bin/bash --noprofile --norc -eux \
-             "${IPA_TESTS_REPO_PATH}/${IPA_TESTS_SCRIPTS}/wait-for-systemd.sh"
- 
--    docker exec -t \
-+    podman exec -t \
-         --env IPA_TESTS_REPO_PATH="${IPA_TESTS_REPO_PATH}" \
-         --env IPA_TESTS_SCRIPTS="${IPA_TESTS_REPO_PATH}/${IPA_TESTS_SCRIPTS}" \
-         --env IPA_PLATFORM="${IPA_PLATFORM}" \
-@@ -171,7 +193,7 @@ steps:
-         /bin/bash --noprofile --norc -eux \
-             "${IPA_TESTS_REPO_PATH}/${IPA_TESTS_SCRIPTS}/install-debuginfo.sh"
- 
--    docker exec -t \
-+    podman exec -t \
-         --env IPA_TESTS_REPO_PATH="${IPA_TESTS_REPO_PATH}" \
-         --env COREDUMPS_SUBDIR="$COREDUMPS_SUBDIR" \
-         --env HOST_JOURNAL="$HOST_JOURNAL" \
-diff --git a/ipatests/test_integration/test_sudo.py b/ipatests/test_integration/test_sudo.py
-index 476990f71..c873950fa 100644
---- a/ipatests/test_integration/test_sudo.py
-+++ b/ipatests/test_integration/test_sudo.py
-@@ -24,7 +24,7 @@ from ipaplatform.paths import paths
- from ipatests.test_integration.base import IntegrationTest
- from ipatests.pytest_ipa.integration.tasks import (
-     clear_sssd_cache, get_host_ip_with_hostmask, remote_sssd_config,
--    FileBackup)
-+    FileBackup, install_master, install_client)
- 
- class TestSudo(IntegrationTest):
-     """
-@@ -32,12 +32,15 @@ class TestSudo(IntegrationTest):
-     http://www.freeipa.org/page/V4/Sudo_Integration#Test_Plan
-     """
-     num_clients = 1
--    topology = 'line'
- 
-     @classmethod
-     def install(cls, mh):
-         super(TestSudo, cls).install(mh)
- 
-+        extra_args = ["--idstart=60001", "--idmax=65000"]
-+        install_master(cls.master, setup_dns=True, extra_args=extra_args)
-+        install_client(cls.master, cls.clients[0])
-+
-         cls.client = cls.clients[0]
-         cls.clientname = cls.client.run_command(
-             ['hostname', '-s']).stdout_text.strip()
--- 
-2.49.0
-
-
-From 5f632d9d7813f89d498cfb21c8472ff3cac2538a Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 29 Apr 2025 13:55:23 -0400
-Subject: [PATCH 14/53] ipa-migrate - remove replication state information
-
-Remove replication state information (happens when LDIFs are used).
-State information is written like:
-
-     attribute;adcsn=<CSN>
-
-But we also support ";binary" which should not be removed so special
-handling is needed in that case.
-
-Signed-off-by: Mark Reynolds <mareynol@redhat.com>
-Fixes: https://pagure.io/freeipa/issue/9776
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py | 16 ++++++++++++----
- 1 file changed, 12 insertions(+), 4 deletions(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index 95ef0ac5a..8ef0071f5 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -202,6 +202,14 @@ def decode_attr_vals(entry_attrs):
-     decoded_attrs = {}
-     for attr in entry_attrs:
-         vals = ensure_list_str(entry_attrs[attr])
-+        # Remove replication state data, but don't remove ";binary"
-+        # e.g.  userCertififccate;binary;adcsn=<CSN>
-+        parts = attr.split(";")
-+        if len(parts) > 1 and not attr.endswith(";binary"):
-+            if parts[1] == "binary":
-+                attr = parts[0] + ";binary"
-+            else:
-+                attr = parts[0]
-         decoded_attrs[attr] = vals
-     return decoded_attrs
- 
-@@ -269,19 +277,19 @@ class LDIFParser(ldif.LDIFParser):
-         if self.mc is None:
-             return
- 
-+        entry_attrs = decode_attr_vals(entry)
-         if self.get_realm:
-             # Get the realm from krb container
-             if DN(("cn", "kerberos"), self.mc.remote_suffix) in DN(dn):
-                 # check objectclass krbrealmcontainer
-                 oc_attr = 'objectClass'
--                if 'objectclass' in entry:
-+                if 'objectclass' in entry_attrs:
-                     oc_attr = 'objectclass'
--                if 'krbrealmcontainer' in ensure_list_str(entry[oc_attr]):
--                    self.mc.remote_realm = ensure_str(entry['cn'][0])
-+                if 'krbrealmcontainer' in entry_attrs[oc_attr]:
-+                    self.mc.remote_realm = ensure_str(entry_attrs['cn'][0])
-                     self.mc.log_debug("Found remote realm from ldif: "
-                                       f"{self.mc.remote_realm}")
-         else:
--            entry_attrs = decode_attr_vals(entry)
-             self.mc.process_db_entry(entry_dn=dn, entry_attrs=entry_attrs)
- 
- 
--- 
-2.49.0
-
-
-From 4e23fa92f1a07565618d49ed27b54d33618bba73 Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 29 Apr 2025 14:00:51 -0400
-Subject: [PATCH 15/53] ipa-migrate - do not process AD entgries in staging
- mode
-
-Only migrate AD entries in production mode due to schema conflicts
-created when removing certain AD attributes (e.g.
-ipantsecurityidentifier)
-
-SIgned-off-by: Mark Reynolds <mreynolds@redhat.com>
-relates: https://pagure.io/freeipa/issue/9776
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate_constants.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index 09856f07c..4beaa4f42 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -870,7 +870,7 @@ DB_OBJECTS = {
-         'oc': ['ipantdomainattrs'],
-         'subtree': ',cn=ad,cn=etc,$SUFFIX',
-         'label': 'AD',
--        'mode': 'all',
-+        'mode': 'production',
-         'count': 0,
-     },
- 
--- 
-2.49.0
-
-
-From c052bbbfd2737f88b6496be7d4849cf17d9a126f Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 29 Apr 2025 14:05:15 -0400
-Subject: [PATCH 16/53] ipa-migrate - improve suffix replacement
-
-When values are "normalized/converted" to a new domain the order in
-which the host/release/suffix are converted matters. Replacing the
-suffix first can lead to incorrect results, so convert the host/realm
-before converting the suffix
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-relates: https://pagure.io/freeipa/issue/9776
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py | 7 ++++---
- 1 file changed, 4 insertions(+), 3 deletions(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index 8ef0071f5..a24a2ab7a 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -1084,11 +1084,9 @@ class IPAMigrate():
-         if isinstance(val, bytes) or isinstance(val, DN):
-             return val
- 
--        # Replace base DN
--        val = self.replace_suffix_value(val)
--
-         # For DNS DN we only replace suffix
-         if dns:
-+            val = self.replace_suffix_value(val)
-             return val
- 
-         # Replace host
-@@ -1102,6 +1100,9 @@ class IPAMigrate():
-         # Replace realm
-         val = val.replace(self.remote_realm, self.realm)
- 
-+        # Lastly, replace base DN
-+        val = self.replace_suffix_value(val)
-+
-         return val
- 
-     def convert_values(self, values, dns=False):
--- 
-2.49.0
-
-
-From 5d893c9c3b8d384873f40d2524b1ebf0f34fb452 Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Mon, 28 Apr 2025 18:01:39 +0200
-Subject: [PATCH 17/53] kdb: keep ipadb_get_connection() from succeeding with
- null LDAP context
-
-The final call to ipadb_reinit_mspac() in ipadb_get_connection() is not
-considered essential for the function to succeed, as there might be
-cases where the required pieces of information to generate PACs are not
-yet configured in the database. However, in environments where 389ds is
-overwhelmed, the LDAP connection established at the beginning of
-ipadb_get_connection() might already be lost while executing
-ipadb_reinit_mspac().
-
-Connection errors were not distinguished from configuration errors,
-which could result in ipadb_get_connection() succeeding while the LDAP
-context is set to null, leading to a KDC crash on the next LDAP request.
-
-ipadb_get_connection() now explicitly checks the value of the LDAP
-context before returning.
-
-Fixes: https://pagure.io/freeipa/issue/9777
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb.c | 31 ++++++++++++++++++++++++-------
- 1 file changed, 24 insertions(+), 7 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
-index 903e19e83..531ee223e 100644
---- a/daemons/ipa-kdb/ipa_kdb.c
-+++ b/daemons/ipa-kdb/ipa_kdb.c
-@@ -530,26 +530,43 @@ int ipadb_get_connection(struct ipadb_context *ipactx)
- 
-     /* get adtrust options using default refresh interval */
-     ret = ipadb_reinit_mspac(ipactx, false, &stmsg);
--    if (ret && stmsg)
--        krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
-+    if (ret) {
-+        if (stmsg) {
-+            krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
-+        }
-+        /* Initialization of the MS-PAC generator is an optional dependency.
-+         * Fail only if the connection was lost. */
-+        if (!ipactx->lcontext) {
-+            goto done;
-+        }
-+    }
- 
-     ret = 0;
- 
- done:
-     ldap_msgfree(res);
- 
-+    /* LDAP context should never be null on success, but keep this test out of
-+     * security to make sure we do not return an invalid context. */
-+    if (ret == 0 && !ipactx->lcontext) {
-+        krb5_klog_syslog(LOG_WARNING, "Internal malfunction: LDAP connection "
-+                                      "process resulted in an invalid context "
-+                                      "(please report this incident)");
-+        ret = LDAP_SERVER_DOWN;
-+    }
-+
-     if (ret) {
-+        /* Cleanup LDAP context if connection failed. */
-         if (ipactx->lcontext) {
-             ldap_unbind_ext_s(ipactx->lcontext, NULL, NULL);
-             ipactx->lcontext = NULL;
-         }
--        if (ret == LDAP_SERVER_DOWN) {
--            return ETIMEDOUT;
--        }
--        return EIO;
-+
-+        /* Replace LDAP error code by POSIX error code. */
-+        ret = ret == LDAP_SERVER_DOWN ? ETIMEDOUT : EIO;
-     }
- 
--    return 0;
-+    return ret;
- }
- 
- static krb5_principal ipadb_create_local_tgs(krb5_context kcontext,
--- 
-2.49.0
-
-
-From b394ac6f7ad62d021412d34364a22ea0dc5a6362 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 6 May 2025 09:52:35 -0400
-Subject: [PATCH 18/53] Add --domain option to ipa-client-automount for DNS
- discovery
-
-If the client machine is not in the IPA DNS domain then discovery
-will not find a server. Add a --domain option so that the set of
-servers can be discovered.
-
-Note that --domain is initialized to "" rather than None to match
-the behavior in ipa-client-install.
-
-Fixes: https://pagure.io/freeipa/issue/9780
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
----
- client/man/ipa-client-automount.1         |  3 +++
- ipaclient/install/ipa_client_automount.py |  8 +++++++-
- ipatests/test_integration/test_nfs.py     | 24 +++++++++++++++++++++++
- 3 files changed, 34 insertions(+), 1 deletion(-)
-
-diff --git a/client/man/ipa-client-automount.1 b/client/man/ipa-client-automount.1
-index d55ca994d..1a061e84b 100644
---- a/client/man/ipa-client-automount.1
-+++ b/client/man/ipa-client-automount.1
-@@ -49,6 +49,9 @@ NFSv4 is also configured. The rpc.gssd and rpc.idmapd are started on clients to
- \fB\-\-server\fR=\fISERVER\fR
- Set the FQDN of the IPA server to connect to.
- .TP
-+\fB\-\-domain\fR=\fIDOMAIN\fR
-+Primary DNS domain of the IPA deployment to be used for server discovery.
-+.TP
- \fB\-\-location\fR=\fILOCATION\fR
- Automount location.
- .TP
-diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py
-index 9f49ff9ed..035eefb1c 100644
---- a/ipaclient/install/ipa_client_automount.py
-+++ b/ipaclient/install/ipa_client_automount.py
-@@ -61,6 +61,12 @@ def parse_options():
-     usage = "%prog [options]\n"
-     parser = IPAOptionParser(usage=usage)
-     parser.add_option("--server", dest="server", help="FQDN of IPA server")
-+    parser.add_option(
-+        "--domain",
-+        dest="domain",
-+        default="",
-+        help="Primary DNS domain of the IPA deployment"
-+    )
-     parser.add_option(
-         "--location",
-         dest="location",
-@@ -387,7 +393,7 @@ def configure_automount():
-     ds = discovery.IPADiscovery()
-     if not options.server:
-         print("Searching for IPA server...")
--        ret = ds.search(ca_cert_path=ca_cert_path)
-+        ret = ds.search(domain=options.domain, ca_cert_path=ca_cert_path)
-         logger.debug('Executing DNS discovery')
-         if ret == discovery.NO_LDAP_SERVER:
-             logger.debug('Autodiscovery did not find LDAP server')
-diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
-index 68cba2988..32d107b71 100644
---- a/ipatests/test_integration/test_nfs.py
-+++ b/ipatests/test_integration/test_nfs.py
-@@ -355,3 +355,27 @@ class TestIpaClientAutomountFileRestore(IntegrationTest):
- 
-     def test_nsswitch_backup_restore_sssd(self):
-         self.nsswitch_backup_restore()
-+
-+
-+class TestIpaClientAutomountDiscovery(IntegrationTest):
-+
-+    num_clients = 1
-+    topology = 'line'
-+
-+    def test_automount_invalid_domain(self):
-+        """Validate that the --domain option is passed into
-+           Discovery. This is expected to fail discovery.
-+        """
-+        testdomain = "client.test"
-+        msg1 = f"Search for LDAP SRV record in {testdomain}"
-+        msg2 = f"Search DNS for SRV record of _ldap._tcp.{testdomain}"
-+        msg3 = "Autodiscovery did not find LDAP server"
-+
-+        client = self.clients[0]
-+        result = client.run_command([
-+            'ipa-client-automount', '--domain', 'client.test',
-+            '--debug'
-+        ], stdin_text="n", raiseonerr=False)
-+        assert msg1 in result.stderr_text
-+        assert msg2 in result.stderr_text
-+        assert msg3 in result.stderr_text
--- 
-2.49.0
-
-
-From 3e3af2d153f3fe8e8bfc0805e92cba0f5f649d73 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 6 Nov 2024 09:59:23 +0200
-Subject: [PATCH 19/53] Use OpenSSL provider with BIND for Fedora 42+ and
- RHEL10+
-
-OpenSSL Engine API is deprecated and ability to compile against it is
-removed in RHEL10. OpenSSL provider API is the future.
-
-Fedora 42+ also defaults to OpenSSL provider. With pkcs11-provider, the
-same PKCS#11 modules can be loaded transparently like with OpenSSL
-engines. Thus, we can update configuration to use the provider API.
-
-While Fedora 41 also defaults to OpenSSL provider, we need BIND version
-that supports using OpenSSL provider API. This backport was only done in
-Fedora 42.
-
-Fixes: https://pagure.io/freeipa/issue/9696
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in                               | 12 +++-
- install/share/Makefile.am                     |  2 +
- .../share/bind.openssl.provider.cnf.template  | 19 +++++++
- .../bind.openssl.provider.crp.cnf.template    | 25 +++++++++
- ipaplatform/base/constants.py                 |  1 +
- ipaplatform/fedora/constants.py               |  9 ++-
- ipaplatform/rhel/constants.py                 |  7 ++-
- ipaserver/dnssec/bindmgr.py                   | 27 ++++++---
- ipaserver/install/dnskeysyncinstance.py       | 55 +++++++++++++++----
- ipaserver/install/server/upgrade.py           | 12 ++--
- 10 files changed, 140 insertions(+), 29 deletions(-)
- create mode 100644 install/share/bind.openssl.provider.cnf.template
- create mode 100644 install/share/bind.openssl.provider.crp.cnf.template
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 01193a39e..558b3cfff 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -163,12 +163,20 @@
- 
- # BIND employs 'pkcs11' OpenSSL engine instead of native PKCS11
- # Fedora 31+ uses OpenSSL engine, as well as Fedora ELN (RHEL9)
--%if 0%{?fedora} || 0%{?rhel} >= 9
-+# Howevever, Fedora 40+ and RHEL10+ use OpenSSL provider
-+%if 0%{?fedora} < 40 || 0%{?rhel} == 9
-     %global openssl_pkcs11_version 0.4.10-6
-+    %global openssl_pkcs11_name openssl-pkcs11
-     %global softhsm_version 2.5.0-4
-+%else
-+%if 0%{?fedora} >= 40 || 0%{?rhel} >= 10
-+    %global openssl_pkcs11_version 0.3
-+    %global openssl_pkcs11_name pkcs11-provider
-+    %global softhsm_version 2.6.1
- %else
-     %global with_bind_pkcs11 1
- %endif
-+%endif
- 
- %if 0%{?rhel} == 8
- # Make sure to use PKI versions that work with 389-ds fix for https://github.com/389ds/389-ds-base/issues/4609
-@@ -628,7 +636,7 @@ Requires: bind-dnssec-utils >= %{bind_version}
- Requires: bind-pkcs11 >= %{bind_version}
- %else
- Requires: softhsm >= %{softhsm_version}
--Requires: openssl-pkcs11 >= %{openssl_pkcs11_version}
-+Requires: %{openssl_pkcs11_name} >= %{openssl_pkcs11_version}
- %endif
- # See https://bugzilla.redhat.com/show_bug.cgi?id=1825812
- # RHEL 8.3+ and Fedora 32+ have 2.1
-diff --git a/install/share/Makefile.am b/install/share/Makefile.am
-index d8d270ca9..ae69c7bb8 100644
---- a/install/share/Makefile.am
-+++ b/install/share/Makefile.am
-@@ -50,6 +50,8 @@ dist_app_DATA =				\
- 	bind.named.conf.template	\
- 	bind.openssl.cnf.template	\
- 	bind.openssl.cryptopolicy.cnf.template	\
-+	bind.openssl.provider.cnf.template	\
-+	bind.openssl.provider.crp.cnf.template	\
- 	certmap.conf.template		\
- 	kdc.conf.template		\
- 	kdc_extensions.template		\
-diff --git a/install/share/bind.openssl.provider.cnf.template b/install/share/bind.openssl.provider.cnf.template
-new file mode 100644
-index 000000000..699922d13
---- /dev/null
-+++ b/install/share/bind.openssl.provider.cnf.template
-@@ -0,0 +1,19 @@
-+# OpenSSL configuration file
-+# File generated by IPA instalation
-+openssl_conf = openssl_init
-+
-+[openssl_init]
-+providers = provider_sect
-+
-+[provider_sect]
-+default = default_sect
-+pkcs11 = pkcs11_bind_sect
-+
-+[default_sect]
-+activate = 1
-+
-+[pkcs11_bind_sect]
-+pkcs11-module-path = $SOFTHSM_MODULE
-+pkcs11-module-token-pin = file:$SOFTHSM_PIN
-+activate = 1
-+
-diff --git a/install/share/bind.openssl.provider.crp.cnf.template b/install/share/bind.openssl.provider.crp.cnf.template
-new file mode 100644
-index 000000000..b52175e8f
---- /dev/null
-+++ b/install/share/bind.openssl.provider.crp.cnf.template
-@@ -0,0 +1,25 @@
-+# OpenSSL configuration file
-+# File generated by IPA instalation
-+openssl_conf = openssl_init
-+
-+[openssl_init]
-+ssl_conf = ssl_configuration
-+providers = provider_sect
-+
-+[ssl_configuration]
-+system_default = crypto_policy
-+
-+[crypto_policy]
-+.include $CRYPTO_POLICY_FILE
-+
-+[provider_sect]
-+default = default_sect
-+pkcs11 = pkcs11_bind_sect
-+
-+[default_sect]
-+activate = 1
-+
-+[pkcs11_bind_sect]
-+pkcs11-module-path = $SOFTHSM_MODULE
-+pkcs11-module-token-pin = file:$SOFTHSM_PIN
-+activate = 1
-diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py
-index 4c8038a84..8caded5f9 100644
---- a/ipaplatform/base/constants.py
-+++ b/ipaplatform/base/constants.py
-@@ -123,6 +123,7 @@ class BaseConstantsNamespace:
-     NAMED_DATA_DIR = "data/"
-     NAMED_OPTIONS_VAR = "OPTIONS"
-     NAMED_OPENSSL_ENGINE = None
-+    NAMED_OPENSSL_PROVIDER = None
-     NAMED_ZONE_COMMENT = ""
-     NOBODY_GROUP = Group("nobody")
-     PKI_USER = User("pkiuser")
-diff --git a/ipaplatform/fedora/constants.py b/ipaplatform/fedora/constants.py
-index 896e6f607..1360b0353 100644
---- a/ipaplatform/fedora/constants.py
-+++ b/ipaplatform/fedora/constants.py
-@@ -19,6 +19,10 @@ from ipaplatform.osinfo import osinfo
- # Fedora 29 has both
- HAS_NFS_CONF = osinfo.version_number >= (30,)
- 
-+# Fedora 40 and later deprecated OpenSSL engine and recommend using OpenSSL
-+# provider API. However, only bind 9.18 in F42+ was built with OpenSSL provider.
-+HAS_OPENSSL_PROVIDER = osinfo.version_number >= (42,)
-+
- 
- __all__ = ("constants", "User", "Group")
- 
-@@ -32,6 +36,9 @@ class FedoraConstantsNamespace(RedHatConstantsNamespace):
-     if HAS_NFS_CONF:
-         SECURE_NFS_VAR = None
- 
--    NAMED_OPENSSL_ENGINE = "pkcs11"
-+    if HAS_OPENSSL_PROVIDER:
-+        NAMED_OPENSSL_PROVIDER = True
-+    else:
-+        NAMED_OPENSSL_ENGINE = "pkcs11"
- 
- constants = FedoraConstantsNamespace()
-diff --git a/ipaplatform/rhel/constants.py b/ipaplatform/rhel/constants.py
-index bc8c65a5d..f4b503521 100644
---- a/ipaplatform/rhel/constants.py
-+++ b/ipaplatform/rhel/constants.py
-@@ -18,8 +18,11 @@ from ipaplatform.osinfo import osinfo
- # RHEL 8 uses /etc/nfs.conf
- HAS_NFS_CONF = osinfo.version_number >= (8,)
- # RHEL 9 uses pkcs11 as openssl engine
--HAS_PKCS11_OPENSSL_ENGINE = osinfo.version_number >= (9,)
-+HAS_PKCS11_OPENSSL_ENGINE = osinfo.version_number == (9,)
- 
-+# RHEL 10 and later deprecated OpenSSL engine and recommend using OpenSSL
-+# provider API.
-+HAS_OPENSSL_PROVIDER = osinfo.version_number >= (10,)
- 
- __all__ = ("constants", "User", "Group")
- 
-@@ -31,5 +34,7 @@ class RHELConstantsNamespace(RedHatConstantsNamespace):
-         SECURE_NFS_VAR = None
-     if HAS_PKCS11_OPENSSL_ENGINE:
-         NAMED_OPENSSL_ENGINE = "pkcs11"
-+    if HAS_OPENSSL_PROVIDER:
-+        NAMED_OPENSSL_PROVIDER = True
- 
- constants = RHELConstantsNamespace()
-diff --git a/ipaserver/dnssec/bindmgr.py b/ipaserver/dnssec/bindmgr.py
-index 0c79cc03d..4b0eccefc 100644
---- a/ipaserver/dnssec/bindmgr.py
-+++ b/ipaserver/dnssec/bindmgr.py
-@@ -68,7 +68,7 @@ class BINDMgr:
-             str_val,
-             ipalib.constants.LDAP_GENERALIZED_TIME_FORMAT
-         )
--        return dt.strftime(time_bindfmt).encode('utf-8')
-+        return dt.strftime(time_bindfmt)
- 
-     def dates2params(self, ldap_attrs):
-         """Convert LDAP timestamps to list of parameters suitable
-@@ -117,21 +117,30 @@ class BINDMgr:
-         """Run dnssec-keyfromlabel on given LDAP object.
-         :returns: base file name of output files, e.g. Kaaa.test.+008+19719
-         """
--        logger.info('attrs: %s', attrs)
-         assert attrs.get('idnsseckeyzone', [b'FALSE'])[0] == b'TRUE', \
-             b'object %s is not a DNS zone key' % attrs['dn']
- 
--        uri = b"%s;pin-source=%s" % (
--            attrs['idnsSecKeyRef'][0],
--            paths.DNSSEC_SOFTHSM_PIN.encode('utf-8')
--        )
-+        uri = None
-+        # LDAP object entries are all in binary encoding
-+        keyref = attrs['idnsSecKeyRef'][0].decode('utf-8')
-+        if platformconstants.NAMED_OPENSSL_ENGINE is not None:
-+            uri = "%s;pin-source=%s" % (
-+                keyref,
-+                paths.DNSSEC_SOFTHSM_PIN
-+            )
-+        elif platformconstants.NAMED_OPENSSL_PROVIDER is not None:
-+            uri = "%s;token=%s" % (
-+                keyref,
-+                ipalib.constants.SOFTHSM_DNSSEC_TOKEN_LABEL
-+            )
-+
-+        assert uri is not None
-         cmd = [
-             paths.DNSSEC_KEYFROMLABEL,
--            '-E', 'pkcs11',
-             '-K', workdir,
--            '-a', attrs['idnsSecAlgorithm'][0],
--            '-l', uri
-+            '-a', attrs['idnsSecAlgorithm'][0].decode('utf-8'),
-         ]
-+        cmd.extend(['-l', uri])
-         cmd.extend(self.dates2params(attrs))
-         if attrs.get('idnsSecKeySep', [b'FALSE'])[0].upper() == b'TRUE':
-             cmd.extend(['-f', 'KSK'])
-diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py
-index 365246552..1979a472d 100644
---- a/ipaserver/install/dnskeysyncinstance.py
-+++ b/ipaserver/install/dnskeysyncinstance.py
-@@ -155,21 +155,36 @@ class DNSKeySyncInstance(service.Service):
-         return False
- 
-     def setup_named_openssl_conf(self):
-+        opensslcnf_tmpl = None
-+        conf_file_dict = {
-+            'CRYPTO_POLICY_FILE': paths.CRYPTO_POLICY_OPENSSLCNF_FILE,
-+            'SOFTHSM_MODULE': paths.LIBSOFTHSM2_SO,
-+            'SOFTHSM_PIN': paths.DNSSEC_SOFTHSM_PIN,
-+        }
-         if constants.NAMED_OPENSSL_ENGINE is not None:
--            logger.debug("Setup OpenSSL config for BIND")
--            # setup OpenSSL config for BIND,
--            # this one is needed because FreeIPA installation
--            # disables p11-kit-proxy PKCS11 module
--            conf_file_dict = {
--                'OPENSSL_ENGINE': constants.NAMED_OPENSSL_ENGINE,
--                'SOFTHSM_MODULE': paths.LIBSOFTHSM2_SO,
--                'CRYPTO_POLICY_FILE': paths.CRYPTO_POLICY_OPENSSLCNF_FILE,
--            }
-+            # Traditional configuration using OpenSSL engine API
-+            # requires openssl-pkcs11 engine to load PKCS#11 token
-+            # provided by SoftHSMv2
-+            conf_file_dict['OPENSSL_ENGINE'] = constants.NAMED_OPENSSL_ENGINE
-             if paths.CRYPTO_POLICY_OPENSSLCNF_FILE is None:
-                 opensslcnf_tmpl = "bind.openssl.cnf.template"
-             else:
-                 opensslcnf_tmpl = "bind.openssl.cryptopolicy.cnf.template"
-+        elif constants.NAMED_OPENSSL_PROVIDER is not None:
-+            # OpenSSL provider API is preferred and requires
-+            # pkcs11-provider to load PKCS#11 token provided by SoftHSMv2
-+            if paths.CRYPTO_POLICY_OPENSSLCNF_FILE is None:
-+                opensslcnf_tmpl = "bind.openssl.provider.cnf.template"
-+            else:
-+                opensslcnf_tmpl = "bind.openssl.provider.crp.cnf.template"
-+        else:
-+            conf_file_dict = None
- 
-+        if opensslcnf_tmpl is not None and conf_file_dict is not None:
-+            logger.debug("Setup OpenSSL config for BIND")
-+            # setup OpenSSL config for BIND,
-+            # this one is needed because FreeIPA installation
-+            # disables p11-kit-proxy PKCS11 module
-             named_openssl_txt = ipautil.template_file(
-                 os.path.join(paths.USR_SHARE_IPA_DIR, opensslcnf_tmpl),
-                 conf_file_dict
-@@ -189,7 +204,8 @@ class DNSKeySyncInstance(service.Service):
-             'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF,
-             quotes=False, separator='=')
- 
--        if constants.NAMED_OPENSSL_ENGINE is not None:
-+        if any([constants.NAMED_OPENSSL_ENGINE is not None,
-+                constants.NAMED_OPENSSL_PROVIDER is not None]):
-             directivesetter.set_directive(
-                 sysconfig,
-                 'OPENSSL_CONF', paths.DNSSEC_OPENSSL_CONF,
-@@ -200,9 +216,23 @@ class DNSKeySyncInstance(service.Service):
-                 constants.NAMED_OPTIONS_VAR,
-                 separator="="
-             ) or ''
--            if not self._are_named_options_configured(options):
-+            new_options = None
-+            if all([constants.NAMED_OPENSSL_ENGINE is not None,
-+                    not self._are_named_options_configured(options)]):
-                 engine_cmd = "-E {}".format(constants.NAMED_OPENSSL_ENGINE)
-                 new_options = ' '.join([options, engine_cmd])
-+            # Remove '-E pkcs11' from the options in the OpenSSL provider case
-+            if all([constants.NAMED_OPENSSL_ENGINE is None,
-+                    self._are_named_options_configured(options)]):
-+                lst_options = options.split()
-+                try:
-+                    idx = lst_options.index('-E')
-+                    lst_options.pop(idx)
-+                    lst_options.pop(idx)
-+                    new_options = ' '.join(lst_options)
-+                except ValueError:
-+                    pass
-+            if new_options is not None:
-                 directivesetter.set_directive(
-                     sysconfig,
-                     constants.NAMED_OPTIONS_VAR, new_options,
-@@ -216,7 +246,8 @@ class DNSKeySyncInstance(service.Service):
-             'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF,
-             quotes=False, separator='=')
- 
--        if constants.NAMED_OPENSSL_ENGINE is not None:
-+        if any([constants.NAMED_OPENSSL_ENGINE is not None,
-+                constants.NAMED_OPENSSL_PROVIDER is not None]):
-             directivesetter.set_directive(
-                 sysconfig,
-                 'OPENSSL_CONF', paths.DNSSEC_OPENSSL_CONF,
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index f26a08aef..fb716d4c2 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -549,15 +549,19 @@ def ca_initialize_hsm_state(ca):
- 
- def dnssec_set_openssl_engine(dnskeysyncd):
-     """
--    Setup OpenSSL engine for BIND
-+    Setup OpenSSL engine or provider for BIND
-     """
--    if constants.NAMED_OPENSSL_ENGINE is None:
-+    if all([constants.NAMED_OPENSSL_ENGINE is None,
-+            constants.NAMED_OPENSSL_PROVIDER is None]):
-         return False
- 
--    if sysupgrade.get_upgrade_state('dns', 'openssl_engine'):
-+    # Nothing to do if we are using OpenSSL engine already and not on the OS
-+    # that requires OpenSSL provider instead.
-+    if all([sysupgrade.get_upgrade_state('dns', 'openssl_engine'),
-+            constants.NAMED_OPENSSL_PROVIDER is None]):
-         return False
- 
--    logger.info('[Set OpenSSL engine for BIND]')
-+    logger.info('[Set OpenSSL engine or provider for BIND]')
-     dnskeysyncd.setup_named_openssl_conf()
-     dnskeysyncd.setup_named_sysconfig()
-     dnskeysyncd.setup_ipa_dnskeysyncd_sysconfig()
--- 
-2.49.0
-
-
-From 3094ef83b898bb7b7a3e835084e444fd403c6ee8 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 1 Apr 2025 14:53:24 +0300
-Subject: [PATCH 20/53] DNS: detect when OpenSSL engine should be removed on
- upgrade
-
-For OpenSSL Provider API use we don't need 'named -E engine-name'
-anymore, it has to be removed. The removal process is slightly
-complicated because we need to detect '-E engine-name' and compare it
-with the engine we know about (pkcs11) but if we are upgrading to the
-build that supports OpenSSL Provider API, we don't know the engine name
-anymore.
-
-Fixes: https://pagure.io/freeipa/issue/9696
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/dnskeysyncinstance.py | 14 +++++++++++++-
- 1 file changed, 13 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py
-index 1979a472d..ae8a67a00 100644
---- a/ipaserver/install/dnskeysyncinstance.py
-+++ b/ipaserver/install/dnskeysyncinstance.py
-@@ -149,7 +149,19 @@ class DNSKeySyncInstance(service.Service):
-         if options:
-             pattern = r"[ ]*-[a-zA-Z46]*E[ ]*(.*?)(?: |$)"
-             engines = re.findall(pattern, options)
--            if engines and engines[-1] == constants.NAMED_OPENSSL_ENGINE:
-+
-+            # if no '-E <engine-name>' and we switched to the provider API,
-+            # just exist, no named configuration to adjust
-+            if len(engines) == 0 and constants.NAMED_OPENSSL_ENGINE is None:
-+                return False
-+
-+            # Something is configured in '-E <engine-name>' but we don't have
-+            # an engine name to compare because we already switched to the
-+            # provider API, we only need to ensure old engine ref is removed.
-+            if constants.NAMED_OPENSSL_ENGINE is None:
-+                return True
-+
-+            if engines[-1] == constants.NAMED_OPENSSL_ENGINE:
-                 return True
- 
-         return False
--- 
-2.49.0
-
-
-From efbe63a6ff2cbdab128c6d3c879862dba22ac1cb Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 25 Apr 2025 14:47:02 +0300
-Subject: [PATCH 21/53] ipa-dnskeysyncd: use systemd-tmpfiles to handle tokens
-
-ipa-dnskeysyncd daemon relies on both OpenDNSSEC and BIND accessing the
-same cryptographic token. We use SoftHSMv2 here and store token in
-DNSSEC_TOKENS_DIR, defined by the IPA platform.
-
-Configure ipa-dnskeysyncd service to update permissions of the token
-files using custom systemd-tmpfiles configuration.
-
-Extend SELinux policy to handle access to the token under a separate
-file context. Both token and its pin file need to be accessed by the BIND
-rndc tool.
-
-Fixes: https://pagure.io/freeipa/issue/9696
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- daemons/dnssec/Makefile.am                |  1 +
- daemons/dnssec/ipa-dnskeysyncd.service.in |  1 +
- freeipa.spec.in                           |  1 +
- init/tmpfilesd/Makefile.am                | 11 ++++++++---
- init/tmpfilesd/ipa-dnssec.conf.in         |  4 ++++
- ipaserver/install/dnskeysyncinstance.py   | 10 ++++++++++
- ipaserver/install/server/upgrade.py       | 10 +++++-----
- selinux/ipa.fc                            |  3 +++
- selinux/ipa.te                            |  7 +++++++
- 9 files changed, 40 insertions(+), 8 deletions(-)
- create mode 100644 init/tmpfilesd/ipa-dnssec.conf.in
-
-diff --git a/daemons/dnssec/Makefile.am b/daemons/dnssec/Makefile.am
-index 0edab98be..d270f0f9a 100644
---- a/daemons/dnssec/Makefile.am
-+++ b/daemons/dnssec/Makefile.am
-@@ -33,6 +33,7 @@ CLEANFILES = $(systemdsystemunit_DATA) $(nodist_app_SCRIPTS)
- 		-e 's|@ODS_USER[@]|$(ODS_USER)|g' \
- 		-e 's|@ODS_GROUP[@]|$(ODS_GROUP)|g' \
- 		-e 's|@NAMED_GROUP[@]|$(NAMED_GROUP)|g' \
-+		-e 's|@IPA_DATA_DIR[@]|$(IPA_DATA_DIR)|g' \
- 		'$(srcdir)/$@.in' >$@
- 
- dnssecconfdir = $(IPA_SYSCONF_DIR)/dnssec
-diff --git a/daemons/dnssec/ipa-dnskeysyncd.service.in b/daemons/dnssec/ipa-dnskeysyncd.service.in
-index cd07275ad..6730c9676 100644
---- a/daemons/dnssec/ipa-dnskeysyncd.service.in
-+++ b/daemons/dnssec/ipa-dnskeysyncd.service.in
-@@ -4,6 +4,7 @@ Description=IPA key daemon
- [Service]
- Environment=LC_ALL=C.UTF-8
- EnvironmentFile=@sysconfenvdir@/ipa-dnskeysyncd
-+ExecStartPre=/bin/sh -c '/bin/sed -e "s,@DNSSEC_TOKENS_DIR@,${DNSSEC_TOKENS_DIR},g;s,@DNSSEC_SOFTHSM_PIN@,${DNSSEC_SOFTHSM_PIN},g" @IPA_DATA_DIR@/ipa-dnssec.conf | /usr/bin/systemd-tmpfiles --create -'
- ExecStart=@libexecdir@/ipa/ipa-dnskeysyncd
- User=@ODS_USER@
- Group=@NAMED_GROUP@
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 558b3cfff..78004dc4f 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1771,6 +1771,7 @@ fi
- %{_libexecdir}/ipa/ipa-ods-exporter
- %{_sbindir}/ipa-dns-install
- %{_mandir}/man1/ipa-dns-install.1*
-+%{_usr}/share/ipa/ipa-dnssec.conf
- %attr(644,root,root) %{_unitdir}/ipa-dnskeysyncd.service
- %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket
- %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service
-diff --git a/init/tmpfilesd/Makefile.am b/init/tmpfilesd/Makefile.am
-index 5d6e96f2c..8d264aaab 100644
---- a/init/tmpfilesd/Makefile.am
-+++ b/init/tmpfilesd/Makefile.am
-@@ -1,12 +1,17 @@
- dist_noinst_DATA =     \
--	ipa.conf.in
-+	ipa.conf.in    \
-+	ipa-dnssec.conf.in
- 
- systemdtmpfiles_DATA =         \
- 	ipa.conf
- 
--CLEANFILES = $(systemdtmpfiles_DATA)
-+appdir = $(IPA_DATA_DIR)
-+dist_app_DATA =	\
-+	ipa-dnssec.conf
-+
-+CLEANFILES = $(systemdtmpfiles_DATA) $(app_DATA)
- 
- %: %.in Makefile
- 	sed \
--		-e 's|@HTTPD_GROUP[@]|$(HTTPD_GROUP)|g' \
-+		-e 's|@HTTPD_GROUP[@]|$(HTTPD_GROUP)|g;s|@ODS_USER[@]|$(ODS_USER)|g;s|@NAMED_GROUP[@]|$(NAMED_GROUP)|g' \
- 		 '$(srcdir)/$@.in' >$@
-diff --git a/init/tmpfilesd/ipa-dnssec.conf.in b/init/tmpfilesd/ipa-dnssec.conf.in
-new file mode 100644
-index 000000000..1dd2b6170
---- /dev/null
-+++ b/init/tmpfilesd/ipa-dnssec.conf.in
-@@ -0,0 +1,4 @@
-+d	@DNSSEC_TOKENS_DIR@	2770	@ODS_USER@	@NAMED_GROUP@
-+A+	@DNSSEC_TOKENS_DIR@	-	-	-	-	group:@NAMED_GROUP@:rw,user:@ODS_USER@:rw
-+Z	@DNSSEC_TOKENS_DIR@	-	-	-	-	-
-+z	@DNSSEC_SOFTHSM_PIN@	-	@ODS_USER@	@NAMED_GROUP@	-	-
-diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py
-index ae8a67a00..9c2bba11c 100644
---- a/ipaserver/install/dnskeysyncinstance.py
-+++ b/ipaserver/install/dnskeysyncinstance.py
-@@ -258,6 +258,16 @@ class DNSKeySyncInstance(service.Service):
-             'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF,
-             quotes=False, separator='=')
- 
-+        directivesetter.set_directive(
-+            sysconfig,
-+            'DNSSEC_TOKENS_DIR', paths.DNSSEC_TOKENS_DIR,
-+            quotes=False, separator='=')
-+
-+        directivesetter.set_directive(
-+            sysconfig,
-+            'DNSSEC_SOFTHSM_PIN', paths.DNSSEC_SOFTHSM_PIN,
-+            quotes=False, separator='=')
-+
-         if any([constants.NAMED_OPENSSL_ENGINE is not None,
-                 constants.NAMED_OPENSSL_PROVIDER is not None]):
-             directivesetter.set_directive(
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index fb716d4c2..58896e330 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -547,7 +547,7 @@ def ca_initialize_hsm_state(ca):
-         ca.set_hsm_state(config)
- 
- 
--def dnssec_set_openssl_engine(dnskeysyncd):
-+def dnssec_set_openssl_provider(dnskeysyncd):
-     """
-     Setup OpenSSL engine or provider for BIND
-     """
-@@ -555,9 +555,9 @@ def dnssec_set_openssl_engine(dnskeysyncd):
-             constants.NAMED_OPENSSL_PROVIDER is None]):
-         return False
- 
--    # Nothing to do if we are using OpenSSL engine already and not on the OS
-+    # Nothing to do if we are using OpenSSL provider already and not on the OS
-     # that requires OpenSSL provider instead.
--    if all([sysupgrade.get_upgrade_state('dns', 'openssl_engine'),
-+    if all([sysupgrade.get_upgrade_state('dns', 'openssl_provider'),
-             constants.NAMED_OPENSSL_PROVIDER is None]):
-         return False
- 
-@@ -565,7 +565,7 @@ def dnssec_set_openssl_engine(dnskeysyncd):
-     dnskeysyncd.setup_named_openssl_conf()
-     dnskeysyncd.setup_named_sysconfig()
-     dnskeysyncd.setup_ipa_dnskeysyncd_sysconfig()
--    sysupgrade.set_upgrade_state('dns', 'openssl_engine', True)
-+    sysupgrade.set_upgrade_state('dns', 'openssl_provider', True)
- 
-     return True
- 
-@@ -1892,7 +1892,7 @@ def upgrade_configuration():
-                 dnskeysyncd.create_instance(fqdn, api.env.realm)
-                 dnskeysyncd.start_dnskeysyncd()
-             else:
--                if dnssec_set_openssl_engine(dnskeysyncd):
-+                if dnssec_set_openssl_provider(dnskeysyncd):
-                     dnskeysyncd.start_dnskeysyncd()
-             dnskeysyncd.set_dyndb_ldap_workdir_permissions()
- 
-diff --git a/selinux/ipa.fc b/selinux/ipa.fc
-index 15e8e41aa..ffab59933 100644
---- a/selinux/ipa.fc
-+++ b/selinux/ipa.fc
-@@ -24,6 +24,9 @@
- 
- /var/lib/ipa/gssproxy/http.keytab -- gen_context(system_u:object_r:ipa_http_keytab_t,s0)
- 
-+/var/lib/ipa/dnssec/tokens -- gen_context(system_u:object_r:ipa_dnskey_t,s0)
-+/var/lib/ipa/dnssec/softhsm_pin -- gen_context(system_u:object_r:ipa_dnskey_t,s0)
-+
- /var/log/ipa(/.*)?              gen_context(system_u:object_r:ipa_log_t,s0)
- 
- /var/log/ipabackup.log	--	gen_context(system_u:object_r:ipa_log_t,s0)
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index e4ce66687..c6d40b148 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -265,6 +265,13 @@ corenet_tcp_bind_generic_node(ipa_dnskey_t)
- corenet_tcp_connect_kerberos_port(ipa_dnskey_t)
- corenet_tcp_connect_rndc_port(ipa_dnskey_t)
- 
-+# Allow rndc to access SoftHSM token in IPA directory
-+gen_require(`
-+    type ndc_t;
-+')
-+allow ndc_t ipa_dnskey_t:file { getattr open read };
-+
-+
- dev_read_rand(ipa_dnskey_t)
- dev_read_sysfs(ipa_dnskey_t)
- 
--- 
-2.49.0
-
-
-From a66adf2618d8d92b80c79537c7bcaaedea2bd9a4 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 29 Apr 2025 09:37:44 +0300
-Subject: [PATCH 22/53] freeipa.spec.in: update BIND-related dependencies
-
-BIND in Fedora 42+ includes a custom backport for DNSSEC support when
-using OpenSSL provider API. Make sure we have that support included.
-
-For RHEL 10 we should be using a similar build but it is not yet
-available, so make sure we include the version that is up to date prior
-to enabling DNSSEC with OpenSSL provider API. Once new BIND build is
-available, we can enable OpenSSL provider API usage in ipaplatform.rhel.
-
-Fixes: https://pagure.io/freeipa/issue/9696
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in | 21 ++++++++++++++++-----
- 1 file changed, 16 insertions(+), 5 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 78004dc4f..78b044b02 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -90,7 +90,13 @@
- 
- # Fix for TLS 1.3 PHA, RHBZ#1775158
- %global httpd_version 2.4.37-21
-+
-+# DNSSEC support with OpenSSL provider API in RHEL 10
-+%if 0%{?rhel} < 10
- %global bind_version 9.11.20-6
-+%else
-+%global bind_version 9.18.33-3
-+%endif
- 
- # support for passkey
- %global sssd_version 2.9.0
-@@ -139,11 +145,11 @@
- # Fix for TLS 1.3 PHA, RHBZ#1775146
- %global httpd_version 2.4.41-9
- 
--# Fix for RHBZ#2117342
--%if 0%{?fedora} < 37
--%global bind_version 9.11.24-1
-+%if 0%{?fedora} < 42
-+%global bind_version 32:9.18.33-1
- %else
--%global bind_version 32:9.18.7-1
-+# BIND version with backport of DNSSEC support over OpenSSL provider API
-+%global bind_version 32:9.18.35-2
- %endif
- # Don't use Fedora's Python dependency generator on Fedora 30/rawhide yet.
- # Some packages don't provide new dist aliases.
-@@ -626,7 +632,12 @@ If you are installing an IPA server, you need to install this package.
- Summary: IPA integrated DNS server with support for automatic DNSSEC signing
- BuildArch: noarch
- Requires: %{name}-server = %{version}-%{release}
--Requires: bind-dyndb-ldap >= 11.2-2
-+# Both Fedora 42+ and RHEL support newer bind-dyndb-ldap 11.11
-+%if 0%{?fedora} < 42
-+Requires: bind-dyndb-ldap >= 11.10-33
-+%else
-+Requires: bind-dyndb-ldap >= 11.11
-+%endif
- Requires: bind >= %{bind_version}
- Requires: bind-utils >= %{bind_version}
- # bind-dnssec-utils is required by the OpenDNSSec integration
--- 
-2.49.0
-
-
-From 13332be5931b2492b19121c083ab0e37aa1ae88f Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 5 May 2025 11:18:59 +0300
-Subject: [PATCH 23/53] freeipa.spec.in: do not recommend encrypted DNS on
- pre-F42 systems
-
-Fedora 41 or earlier do not have infrastructure to run encrypted DNS
-server side.
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 78b044b02..ccb37ff0a 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -652,7 +652,9 @@ Requires: %{openssl_pkcs11_name} >= %{openssl_pkcs11_version}
- # See https://bugzilla.redhat.com/show_bug.cgi?id=1825812
- # RHEL 8.3+ and Fedora 32+ have 2.1
- Requires: opendnssec >= 2.1.6-5
-+%if 0%{?fedora} >= 42 || 0%{?rhel} > 9
- Recommends: %{name}-server-encrypted-dns
-+%endif
- %{?systemd_requires}
- 
- Provides: %{alt_name}-server-dns = %{version}
-@@ -670,6 +672,8 @@ Integrated DNS server is BIND 9. OpenDNSSEC provides key management.
- %package server-encrypted-dns
- Summary: support for encrypted DNS in IPA integrated DNS server
- Requires: %{name}-client-encrypted-dns
-+# Will need newer bind-dyndb-ldap to allow use of OpenSSL provider API
-+Requires: bind-dyndb-ldap >= 11.11
- 
- %description server-encrypted-dns
- Provides support for enabling DNS over TLS in the IPA integrated DNS
--- 
-2.49.0
-
-
-From eeec57942a38dff637aec8f30099e6df4373b936 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 14 Nov 2024 10:36:41 -0500
-Subject: [PATCH 24/53] Azure CI: Use F42
-
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/azure/Dockerfiles/Dockerfile.build.fedora  | 3 ++-
- ipatests/azure/Dockerfiles/Dockerfile.build.rawhide | 1 +
- ipatests/azure/templates/variables-fedora.yml       | 2 +-
- 3 files changed, 4 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-index 3d2926140..6372e806e 100644
---- a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-+++ b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-@@ -1,4 +1,4 @@
--FROM registry.fedoraproject.org/fedora-toolbox:40
-+FROM registry.fedoraproject.org/fedora-toolbox:42
- MAINTAINER [FreeIPA Developers freeipa-devel@lists.fedorahosted.org]
- ENV container=docker LANG=en_US.utf8 LANGUAGE=en_US.utf8 LC_ALL=en_US.utf8
- 
-@@ -22,6 +22,7 @@ RUN echo 'deltarpm = false' >> /etc/dnf/dnf.conf \
-     && dnf clean all && rm -rf /root/rpms /root/srpms \
-     && sed -i 's/.*PermitRootLogin .*/#&/g' /etc/ssh/sshd_config \
-     && echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config \
-+    && sed -i -e 's@^\(session.*required.*pam_loginuid\)@#\1@' /etc/pam.d/sshd \
-     && systemctl enable sshd \
-     && for i in /usr/lib/systemd/system/*-domainname.service; \
-     do sed -i 's#^ExecStart=/#ExecStart=-/#' $i ; done \
-diff --git a/ipatests/azure/Dockerfiles/Dockerfile.build.rawhide b/ipatests/azure/Dockerfiles/Dockerfile.build.rawhide
-index 575ad10b2..f39cb85cb 100644
---- a/ipatests/azure/Dockerfiles/Dockerfile.build.rawhide
-+++ b/ipatests/azure/Dockerfiles/Dockerfile.build.rawhide
-@@ -23,6 +23,7 @@ RUN echo 'deltarpm = false' >> /etc/dnf/dnf.conf \
-     && dnf clean all && rm -rf /root/rpms /root/srpms \
-     && sed -i 's/.*PermitRootLogin .*/#&/g' /etc/ssh/sshd_config \
-     && echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config \
-+    && sed -i -e 's@^\(session.*required.*pam_loginuid\)@#\1@' /etc/pam.d/sshd \
-     && systemctl enable sshd \
-     && for i in /usr/lib/systemd/system/*-domainname.service; \
-     do sed -i 's#^ExecStart=/#ExecStart=-/#' $i ; done \
-diff --git a/ipatests/azure/templates/variables-fedora.yml b/ipatests/azure/templates/variables-fedora.yml
-index fafa9a073..f4f96438b 100644
---- a/ipatests/azure/templates/variables-fedora.yml
-+++ b/ipatests/azure/templates/variables-fedora.yml
-@@ -1,7 +1,7 @@
- variables:
-   IPA_PLATFORM: fedora
-   # the Docker public image to build IPA packages (rpms)
--  DOCKER_BUILD_IMAGE: 'registry.fedoraproject.org/fedora-toolbox:40'
-+  DOCKER_BUILD_IMAGE: 'registry.fedoraproject.org/fedora-toolbox:42'
- 
-   # the Dockerfile to build Docker image for running IPA tests
-   DOCKER_DOCKERFILE: ${{ format('Dockerfile.build.{0}', variables.IPA_PLATFORM) }}
--- 
-2.49.0
-
-
-From 69156cbeb4f096b5f63674c3f20b8054ff4cf993 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 14 Nov 2024 11:05:29 -0500
-Subject: [PATCH 25/53] Make the Azure template work with both dnf4 and dnf5
-
-Add a version test to call dnf builddep differently based on
-release.
-
-Remove --skip-broken when calling dnf builddep. This was an
-old workaround from 2019.  The option is no longer supported
-with dnf5. First seen in F41. Drop in all calls.
-
-dnf5 doesn't take a --spec option. It has to be passed a spec file.
-So in that case copy freeipa.spec.in to freeipa.spec and call.
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/azure/templates/prepare-build-fedora.yml | 10 +++++++++-
- 1 file changed, 9 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/azure/templates/prepare-build-fedora.yml b/ipatests/azure/templates/prepare-build-fedora.yml
-index 3a74d7f2a..66a47266b 100644
---- a/ipatests/azure/templates/prepare-build-fedora.yml
-+++ b/ipatests/azure/templates/prepare-build-fedora.yml
-@@ -26,5 +26,13 @@ steps:
- 
-     echo "Installing FreeIPA development dependencies"
-     sudo dnf builddep -y freeipa
--    sudo dnf builddep -y --skip-broken -D "with_wheels 1" -D "with_lint 1" -D "with_doc 1" --spec freeipa.spec.in --best --allowerasing --setopt=install_weak_deps=False
-+    version=$(grep VERSION_ID /etc/os-release |cut -d= -f2)
-+    if [ $version -ge 41 ]; then
-+        cp freeipa.spec.in freeipa.spec
-+        sudo dnf builddep -y -D "with_wheels 1" -D "with_lint 1" -D "with_doc 1" --best --allowerasing --setopt=install_weak_deps=False freeipa.spec
-+        rm -f freeipa.spec
-+    else
-+        sudo dnf builddep -y -D "with_wheels 1" -D "with_lint 1" -D "with_doc 1" --spec freeipa.spec.in --best --allowerasing --setopt=install_weak_deps=False
-+    fi
-+
-   displayName: Prepare build environment
--- 
-2.49.0
-
-
-From 6a99d75f2557f4d5ad5693b3c63d57a22af2bf1f Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 14 Nov 2024 12:12:00 -0500
-Subject: [PATCH 26/53] Test: dnf5 handles updating itself differently than
- dnf4
-
-If there are no updates then dnf update dnf will fail with a return
-code of 1.
-
-https://github.com/rpm-software-management/dnf5/issues/1862
-
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/azure/Dockerfiles/Dockerfile.build.fedora | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-index 6372e806e..7da36b1e1 100644
---- a/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-+++ b/ipatests/azure/Dockerfiles/Dockerfile.build.fedora
-@@ -4,7 +4,6 @@ ENV container=docker LANG=en_US.utf8 LANGUAGE=en_US.utf8 LC_ALL=en_US.utf8
- 
- ADD dist /root
- RUN echo 'deltarpm = false' >> /etc/dnf/dnf.conf \
--    && dnf update -y dnf \
-     && dnf update -y python3 \
-     && (sed -i 's/%_install_langs \(.*\)/\0:fr/g' /etc/rpm/macros.image-language-conf ||:) \
-     && dnf install -y systemd \
--- 
-2.49.0
-
-
-From 9d91d38b14a5b24e4d141c0bb925e9708b000dbe Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 15 Nov 2024 08:45:14 +0200
-Subject: [PATCH 27/53] Move wheel constraints to F41+
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .wheelconstraints.in                             | 4 ++--
- ipatests/azure/templates/prepare-lint-fedora.yml | 8 +++++++-
- pylintrc                                         | 5 ++++-
- 3 files changed, 13 insertions(+), 4 deletions(-)
-
-diff --git a/.wheelconstraints.in b/.wheelconstraints.in
-index cd95619d4..3e75a6715 100644
---- a/.wheelconstraints.in
-+++ b/.wheelconstraints.in
-@@ -10,5 +10,5 @@ ipaserver == @VERSION@
- ipatests == @VERSION@
- 
- # keep pylint version in sync with current Fedora release
--# F39 has 3.0.25
--pylint ~= 3.0.2
-+# F41 has 3.3.1
-+pylint ~= 3.3.1
-diff --git a/ipatests/azure/templates/prepare-lint-fedora.yml b/ipatests/azure/templates/prepare-lint-fedora.yml
-index c35733156..9b76b1177 100644
---- a/ipatests/azure/templates/prepare-lint-fedora.yml
-+++ b/ipatests/azure/templates/prepare-lint-fedora.yml
-@@ -1,6 +1,12 @@
- steps:
- - script: |
-     set -e
-+    IPA_VERSION=$(echo -e "include(VERSION.m4)\nIPA_VERSION" | m4 - | tail -1)
-+    export IPA_VERSION
-+    echo "IPA version: ${IPA_VERSION}"
-+    cp .wheelconstraints.in .wheelconstraints
-+    sed -i -e "s,@VERSION@,${IPA_VERSION},g" .wheelconstraints
-     sudo dnf -y install python3-pip
--    python3 -m pip install --user --constraint .wheelconstraints.in --ignore-installed pylint
-+    python3 -m pip install --user --constraint .wheelconstraints --ignore-installed pylint
-+    rm -f .wheelconstraints
-   displayName: Install Lint dependencies
-diff --git a/pylintrc b/pylintrc
-index 8fadeffbd..9006c3869 100644
---- a/pylintrc
-+++ b/pylintrc
-@@ -50,6 +50,7 @@ disable=
-     too-many-instance-attributes,
-     too-many-locals,
-     too-many-nested-blocks,
-+    too-many-positional-arguments,
-     too-many-public-methods,
-     too-many-return-statements,
-     too-many-statements,
-@@ -123,6 +124,9 @@ disable=
-     use-implicit-booleaness-not-comparison-to-string,
-     use-implicit-booleaness-not-comparison-to-zero,
-     broad-exception-raised,
-+    possibly-used-before-assignment,
-+    use-yield-from
-+    
- 
- [REPORTS]
- 
-@@ -142,7 +146,6 @@ msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg})'
- [VARIABLES]
- dummy-variables-rgx=(_.+|unused)
- 
--
- [IPA]
- forbidden-imports=
-     client/:ipaserver,
--- 
-2.49.0
-
-
-From 66df1812d2d393b123f662e2a726624bd52d2e39 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 15 Nov 2024 10:58:40 +0200
-Subject: [PATCH 28/53] fix used-before-assignment errors where pylint cannot
- infer logic
-
-install/tools/ipa-csreplica-manage:471: [E0601(used-before-assignment), main] Using variable 'replica1' before assignment)
-install/tools/ipa-csreplica-manage:471: [E0601(used-before-assignment), main] Using variable 'replica2' before assignment)
-
-install/tools/ipa-replica-manage:1598: [E0601(used-before-assignment), main] Using variable 'replica1' before assignment)
-install/tools/ipa-replica-manage:1598: [E0601(used-before-assignment), main] Using variable 'replica2' before assignment)
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/tools/ipa-csreplica-manage.in | 1 +
- install/tools/ipa-replica-manage.in   | 1 +
- 2 files changed, 2 insertions(+)
-
-diff --git a/install/tools/ipa-csreplica-manage.in b/install/tools/ipa-csreplica-manage.in
-index 2fab27a94..8f4daec88 100644
---- a/install/tools/ipa-csreplica-manage.in
-+++ b/install/tools/ipa-csreplica-manage.in
-@@ -402,6 +402,7 @@ def exit_on_managed_topology(what, hint="topologysegment"):
- 
- 
- def main():
-+    replica = replica1 = replica2 = None
-     installutils.check_server_configuration()
-     options, args = parse_options()
- 
-diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in
-index c8ab74a80..d56526bc0 100644
---- a/install/tools/ipa-replica-manage.in
-+++ b/install/tools/ipa-replica-manage.in
-@@ -1533,6 +1533,7 @@ def exit_on_managed_topology(what):
-              "the topology.".format(what))
- 
- def main(options, args):
-+    replica = replica1 = replica2 = None
-     if os.getegid() == 0:
-         installutils.check_server_configuration()
-     elif not os.path.exists(paths.IPA_DEFAULT_CONF):
--- 
-2.49.0
-
-
-From 7a8f3b82602fda716ce7510212d6ab876c1b789a Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 15 Nov 2024 14:01:01 +0200
-Subject: [PATCH 29/53] pylint: do not use return at the end of flow
-
-pylint 3.3+ considers the last 'return' in the flow 'useless':
-
-contrib/perflog:89: [R1711(useless-return), parselog.display_times] Useless return at end of function or method)
-ipatests/pytest_ipa/integration/tasks.py:1342: [R1711(useless-return), two_connected_topo] Useless return at end of function or method)
-ipaserver/plugins/baseldap.py:575: [R1711(useless-return), host_is_master] Useless return at end of function or method)
-ipaserver/install/replication.py:1425: [R1711(useless-return), ReplicationManager.initialize_replication] Useless return at end of function or method)
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- contrib/perflog                          | 1 -
- ipaserver/install/replication.py         | 2 +-
- ipaserver/plugins/baseldap.py            | 2 +-
- ipatests/pytest_ipa/integration/tasks.py | 2 +-
- 4 files changed, 3 insertions(+), 4 deletions(-)
-
-diff --git a/contrib/perflog b/contrib/perflog
-index 837d7feb4..c4ea095ef 100644
---- a/contrib/perflog
-+++ b/contrib/perflog
-@@ -108,7 +108,6 @@ class parselog(admintool.AdminTool):
-             output = True
-         if not output:
-             print(f' No {empty} found')
--            return
- 
-     def run(self):
-         super(parselog, self).run()
-diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
-index 9d9aa1c4b..6d07edf8e 100644
---- a/ipaserver/install/replication.py
-+++ b/ipaserver/install/replication.py
-@@ -1428,7 +1428,7 @@ class ReplicationManager:
-         try:
-             conn.modify_s(dn, mod)
-         except ldap.ALREADY_EXISTS:
--            return
-+            pass
- 
-     def force_sync(self, conn, hostname):
- 
-diff --git a/ipaserver/plugins/baseldap.py b/ipaserver/plugins/baseldap.py
-index e5fc77318..c991d84ea 100644
---- a/ipaserver/plugins/baseldap.py
-+++ b/ipaserver/plugins/baseldap.py
-@@ -584,7 +584,7 @@ def host_is_master(ldap, fqdn):
-         raise errors.ValidationError(name='hostname', error=_('An IPA master host cannot be deleted or disabled'))
-     except errors.NotFound:
-         # Good, not a master
--        return
-+        pass
- 
- 
- def add_missing_object_class(ldap, objectclass, dn, entry_attrs=None, update=True):
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index dccfaf30e..c6e0cf393 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -1396,7 +1396,7 @@ def two_connected_topo(master, replicas):
-             i += 1
- 
-     except IndexError:
--        return
-+        pass
- 
- 
- @_topo('double-circle')
--- 
-2.49.0
-
-
-From 7df021d3d2472ff91d03efc1142e5dc8295d82cb Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 15 Nov 2024 16:28:23 +0200
-Subject: [PATCH 30/53] test_console: rework matching to adjust to Python 3.13
-
-Different formatting causes the old test assert to always fail.
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_cmdline/test_console.py | 11 ++++++++++-
- 1 file changed, 10 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_cmdline/test_console.py b/ipatests/test_cmdline/test_console.py
-index 4e2403df1..1c0c728fa 100644
---- a/ipatests/test_cmdline/test_console.py
-+++ b/ipatests/test_cmdline/test_console.py
-@@ -41,4 +41,13 @@ class TestIPAConsole:
-     def test_help(self, api_obj):
-         s = self.run_pydoc(api_obj.Command.group_add)
-         # check for __signature__ in help()
--        assert "group_add(cn: str, *, description: str = None," in s
-+        # Since Python 3.13 the format did change and cannot be influenced
-+        # check both variants of the possible output
-+        snippets = [
-+            " |  group_add(\n |      cn: str,\n |      *,\n"
-+            " |      description: str = None,",
-+            "group_add(cn: str, *, description: str = None,"
-+        ]
-+
-+        present = [snippet in s for snippet in snippets]
-+        assert any(present)
--- 
-2.49.0
-
-
-From 6e43b2afef2360cfa5fb4f3034a0bd69846e3ae9 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 29 Apr 2025 10:32:45 +0300
-Subject: [PATCH 31/53] Make IPAAbstractVersion available to all platforms
-
-They still need to implement own comparison function
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaplatform/base/tasks.py               | 31 +++++++++++++++++++++-
- ipaplatform/redhat/tasks.py             | 35 +++++--------------------
- ipatests/test_ipaplatform/test_tasks.py |  1 -
- 3 files changed, 37 insertions(+), 30 deletions(-)
-
-diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
-index cd17e41d3..53ef34882 100644
---- a/ipaplatform/base/tasks.py
-+++ b/ipaplatform/base/tasks.py
-@@ -23,6 +23,7 @@ This module contains default platform-specific implementations of system tasks.
- '''
- 
- from __future__ import absolute_import
-+from functools import total_ordering
- 
- import os
- import logging
-@@ -48,6 +49,34 @@ RESOLVE1_IPA_CONF = textwrap.dedent("""
- """)
- 
- 
-+@total_ordering
-+class IPAAbstractVersion:
-+
-+    def __init__(self, version):
-+        self._version = version
-+        self._bytes = version.encode('utf-8')
-+
-+    @property
-+    def version(self):
-+        return self._version
-+
-+    def __eq__(self, other):
-+        if not isinstance(other, IPAAbstractVersion):
-+            return NotImplemented
-+        return self._vercmp(self._bytes, other._bytes) == 0
-+
-+    def __lt__(self, other):
-+        if not isinstance(other, IPAAbstractVersion):
-+            return NotImplemented
-+        return self._vercmp(self._bytes, other._bytes) < 0
-+
-+    def __hash__(self):
-+        return hash(self._version)
-+
-+    def _vercmp(self, _a, _b):
-+        return NotImplemented
-+
-+
- class BaseTaskNamespace:
- 
-     def restore_context(self, filepath, force=False):
-@@ -274,7 +303,7 @@ class BaseTaskNamespace:
-         :param version: textual version
-         :return: object implementing proper __cmp__ method for version compare
-         """
--        return parse_version(version)
-+        return IPAAbstractVersion(parse_version(version))
- 
-     def set_hostname(self, hostname):
-         """
-diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
-index d3eda0172..18203bf0e 100644
---- a/ipaplatform/redhat/tasks.py
-+++ b/ipaplatform/redhat/tasks.py
-@@ -50,7 +50,7 @@ import ipapython.errors
- from ipaplatform.constants import constants
- from ipaplatform.paths import paths
- from ipaplatform.redhat.authconfig import get_auth_tool
--from ipaplatform.base.tasks import BaseTaskNamespace
-+from ipaplatform.base.tasks import BaseTaskNamespace, IPAAbstractVersion
- 
- logger = logging.getLogger(__name__)
- 
-@@ -77,44 +77,22 @@ NM_IPA_CONF = textwrap.dedent("""
- 
- 
- @total_ordering
--class IPAVersion:
-+class IPAVersion(IPAAbstractVersion):
-     _rpmvercmp_func = None
- 
--    @classmethod
--    def _rpmvercmp(cls, a, b):
-+    def _vercmp(self, a, b):
-         """Lazy load and call librpm's rpmvercmp
-         """
--        rpmvercmp_func = cls._rpmvercmp_func
-+        rpmvercmp_func = self._rpmvercmp_func
-         if rpmvercmp_func is None:
-             librpm = ctypes.CDLL(find_library('rpm'))
-             rpmvercmp_func = librpm.rpmvercmp
-             # int rpmvercmp(const char *a, const char *b)
-             rpmvercmp_func.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
-             rpmvercmp_func.restype = ctypes.c_int
--            cls._rpmvercmp_func = rpmvercmp_func
-+            self._rpmvercmp_func = rpmvercmp_func
-         return rpmvercmp_func(a, b)
- 
--    def __init__(self, version):
--        self._version = version
--        self._bytes = version.encode('utf-8')
--
--    @property
--    def version(self):
--        return self._version
--
--    def __eq__(self, other):
--        if not isinstance(other, IPAVersion):
--            return NotImplemented
--        return self._rpmvercmp(self._bytes, other._bytes) == 0
--
--    def __lt__(self, other):
--        if not isinstance(other, IPAVersion):
--            return NotImplemented
--        return self._rpmvercmp(self._bytes, other._bytes) < 0
--
--    def __hash__(self):
--        return hash(self._version)
--
- 
- class RedHatTaskNamespace(BaseTaskNamespace):
- 
-@@ -482,7 +460,8 @@ class RedHatTaskNamespace(BaseTaskNamespace):
- 
-         return True
- 
--    def parse_ipa_version(self, version):
-+    @staticmethod
-+    def parse_ipa_version(version):
-         """
-         :param version: textual version
-         :return: object implementing proper __cmp__ method for version compare
-diff --git a/ipatests/test_ipaplatform/test_tasks.py b/ipatests/test_ipaplatform/test_tasks.py
-index 592a864b3..68fd7b5ce 100644
---- a/ipatests/test_ipaplatform/test_tasks.py
-+++ b/ipatests/test_ipaplatform/test_tasks.py
-@@ -31,7 +31,6 @@ def test_ipa_version():
-     assert v3 <= v3
-     assert v3 != v4
-     assert v3 == v3
--    assert not v3 == v4
-     assert v4 > v3
-     assert v4 >= v3
- 
--- 
-2.49.0
-
-
-From 1fed74f110ed9c8bd70a49ec85ef9a895fffdb9b Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
-Date: Wed, 2 Apr 2025 15:00:08 +0200
-Subject: [PATCH 32/53] Stop using deprecated pkg_resources
-
-Use packaging.version and importlib.metadata instead.
-
-Note that packaging.version only parses versions compatible with Python packaging
-(originally defined in PEP 440).
-https://packaging.python.org/en/latest/specifications/version-specifiers/
-
-Fixes https://pagure.io/freeipa/issue/9676
-
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in                                     |  6 +-----
- ipaplatform/base/tasks.py                           |  2 +-
- ipapython/version.py.in                             |  2 +-
- ipaserver/custodia/server/__init__.py               | 13 ++++---------
- ipaserver/install/cainstance.py                     |  2 +-
- ipaserver/install/krbinstance.py                    |  2 +-
- ipaserver/install/server/replicainstall.py          |  2 +-
- ipaserver/install/server/upgrade.py                 |  2 +-
- .../pytest_ipa/integration/create_caless_pki.py     |  2 +-
- ipatests/pytest_ipa/integration/tasks.py            |  2 +-
- ipatests/test_custodia/test_plugins.py              | 13 +++++--------
- ipatests/test_integration/test_adtrust_install.py   |  2 +-
- ipatests/test_integration/test_cert.py              |  2 +-
- ipatests/test_integration/test_commands.py          |  2 +-
- ipatests/test_integration/test_idp.py               |  2 +-
- ipatests/test_integration/test_ipahealthcheck.py    |  2 +-
- ipatests/test_webui/ui_driver.py                    |  2 +-
- ipatests/test_xmlrpc/test_automember_plugin.py      |  2 +-
- pypi/test_placeholder.py                            |  5 ++---
- 19 files changed, 27 insertions(+), 40 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index ccb37ff0a..2b97b1176 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -936,6 +936,7 @@ Requires: python3-jwcrypto >= 0.4.2
- Requires: python3-libipa_hbac
- Requires: python3-netaddr >= %{python_netaddr_version}
- Requires: python3-ifaddr
-+Requires: python3-packaging
- Requires: python3-pyasn1 >= 0.3.2-2
- Requires: python3-pyasn1-modules >= 0.3.2-2
- Requires: python3-pyusb
-@@ -944,11 +945,6 @@ Requires: python3-requests
- Requires: python3-six
- Requires: python3-sss-murmur
- Requires: python3-yubico >= 1.3.2-7
--%if 0%{?rhel} && 0%{?rhel} == 8
--Requires: platform-python-setuptools
--%else
--Requires: python3-setuptools
--%endif
- %if 0%{?rhel}
- Requires: python3-urllib3 >= 1.24.2-3
- %else
-diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
-index 53ef34882..4108a7ced 100644
---- a/ipaplatform/base/tasks.py
-+++ b/ipaplatform/base/tasks.py
-@@ -29,7 +29,7 @@ import os
- import logging
- import textwrap
- 
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- from ipaplatform.paths import paths
- from ipapython import ipautil
-diff --git a/ipapython/version.py.in b/ipapython/version.py.in
-index a8f4218a7..eee8900be 100644
---- a/ipapython/version.py.in
-+++ b/ipapython/version.py.in
-@@ -17,7 +17,7 @@
- # along with this program.  If not, see <http://www.gnu.org/licenses/>.
- #
- 
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- # The full version including strings
- VERSION = "@VERSION@"
-diff --git a/ipaserver/custodia/server/__init__.py b/ipaserver/custodia/server/__init__.py
-index e713de20d..26ca0a481 100644
---- a/ipaserver/custodia/server/__init__.py
-+++ b/ipaserver/custodia/server/__init__.py
-@@ -2,10 +2,9 @@
- from __future__ import absolute_import
- 
- import importlib
-+import importlib.metadata
- import os
- 
--import pkg_resources
--
- import six
- 
- from ipaserver.custodia import log
-@@ -37,17 +36,13 @@ def _load_plugin_class(menu, name):
-     Entry points are preferred over dotted import path.
-     """
-     group = 'custodia.{}'.format(menu)
--    eps = list(pkg_resources.iter_entry_points(group, name))
-+    eps = importlib.metadata.entry_points(group=group, name=name)
-     if len(eps) > 1:
-         raise ValueError(
-             "Multiple entry points for {} {}: {}".format(menu, name, eps))
-     elif len(eps) == 1:
--        # backwards compatibility with old setuptools
--        ep = eps[0]
--        if hasattr(ep, 'resolve'):
--            return ep.resolve()
--        else:
--            return ep.load(require=False)
-+        ep, *_ = eps
-+        return ep.load(require=False)
-     elif '.' in name:
-         # fall back to old style dotted name
-         module, classname = name.rsplit('.', 1)
-diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
-index 2c9fbe04f..b3cc0b262 100644
---- a/ipaserver/install/cainstance.py
-+++ b/ipaserver/install/cainstance.py
-@@ -35,7 +35,7 @@ import syslog
- import time
- import tempfile
- from configparser import RawConfigParser
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- from ipalib import api
- from ipalib import x509
-diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
-index a98875538..9f0eea563 100644
---- a/ipaserver/install/krbinstance.py
-+++ b/ipaserver/install/krbinstance.py
-@@ -26,7 +26,7 @@ import socket
- import dbus
- 
- import dns.name
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- from ipalib import x509
- from ipalib.install import certstore
-diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
-index 1f2c81f85..1e743d7b3 100644
---- a/ipaserver/install/server/replicainstall.py
-+++ b/ipaserver/install/server/replicainstall.py
-@@ -18,7 +18,7 @@ import tempfile
- import textwrap
- import traceback
- 
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- import six
- 
- from ipaclient.install.client import check_ldap_conf, sssd_enable_ifp
-diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
-index 58896e330..eb12f0907 100644
---- a/ipaserver/install/server/upgrade.py
-+++ b/ipaserver/install/server/upgrade.py
-@@ -17,7 +17,7 @@ import sys
- import tempfile
- from contextlib import contextmanager
- from augeas import Augeas
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- from ipalib import api, x509
- from ipalib.constants import RENEWAL_CA_NAME, RA_AGENT_PROFILE, IPA_CA_RECORD
-diff --git a/ipatests/pytest_ipa/integration/create_caless_pki.py b/ipatests/pytest_ipa/integration/create_caless_pki.py
-index e0984ccc9..71d804c33 100644
---- a/ipatests/pytest_ipa/integration/create_caless_pki.py
-+++ b/ipatests/pytest_ipa/integration/create_caless_pki.py
-@@ -26,7 +26,7 @@ from cryptography.hazmat.backends import default_backend
- from cryptography.hazmat.primitives import hashes, serialization
- from cryptography.hazmat.primitives.asymmetric import rsa
- from cryptography.x509.oid import NameOID
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- from pyasn1.type import univ, char, namedtype, tag
- from pyasn1.codec.der import encoder as der_encoder
- from pyasn1.codec.native import decoder as native_decoder
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index c6e0cf393..3ef361807 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -36,7 +36,7 @@ import time
- from shlex import quote
- import configparser
- from contextlib import contextmanager
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- import uuid
- 
- import dns
-diff --git a/ipatests/test_custodia/test_plugins.py b/ipatests/test_custodia/test_plugins.py
-index be8aa936d..9d3f6c24f 100644
---- a/ipatests/test_custodia/test_plugins.py
-+++ b/ipatests/test_custodia/test_plugins.py
-@@ -1,5 +1,6 @@
- # Copyright (C) 2016  Custodia Project Contributors - see LICENSE file
--import pkg_resources
-+import importlib.metadata
-+
- import pytest
- 
- from ipaserver.custodia.plugin import (
-@@ -12,8 +13,8 @@ class TestCustodiaPlugins:
- 
-     def get_entry_points(self, group):
-         eps = []
--        for e in pkg_resources.iter_entry_points(group):
--            if e.dist.project_name != self.project_name:
-+        for e in importlib.metadata.entry_points(group=group):
-+            if e.dist.name != self.project_name:
-                 # only interested in our own entry points
-                 continue
-             eps.append(e)
-@@ -21,11 +22,7 @@ class TestCustodiaPlugins:
- 
-     def assert_ep(self, ep, basecls):
-         try:
--            # backwards compatibility with old setuptools
--            if hasattr(ep, "resolve"):
--                cls = ep.resolve()
--            else:
--                cls = ep.load(require=False)
-+            cls = ep.load(require=False)
-         except Exception as e:  # pylint: disable=broad-except
-             pytest.fail("Failed to load %r: %r" % (ep, e))
-         if not issubclass(cls, basecls):
-diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
-index 79a91dfaa..99d302944 100644
---- a/ipatests/test_integration/test_adtrust_install.py
-+++ b/ipatests/test_integration/test_adtrust_install.py
-@@ -13,7 +13,7 @@ from ipaplatform.paths import paths
- from ipapython.dn import DN
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- import pytest
- 
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index c642caaf0..88859e67f 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -21,7 +21,7 @@ from ipapython.dn import DN
- from cryptography import x509
- from cryptography.x509.oid import ExtensionOID
- from cryptography.hazmat.backends import default_backend
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index fa6da78e9..f64152908 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -42,7 +42,7 @@ from ipatests.test_ipalib.test_x509 import good_pkcs7, badcert
- from ipapython.ipautil import realm_to_suffix, ipa_generate_password
- from ipatests.test_integration.test_topology import find_segment
- from ipaserver.install.installutils import realm_to_serverid
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- logger = logging.getLogger(__name__)
- 
-diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py
-index a29333ef2..9983b3c9d 100644
---- a/ipatests/test_integration/test_idp.py
-+++ b/ipatests/test_integration/test_idp.py
-@@ -12,7 +12,7 @@ from ipatests.pytest_ipa.integration import tasks, create_keycloak
- user_code_script = textwrap.dedent("""
- from selenium import webdriver
- from datetime import datetime
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- from selenium.webdriver.firefox.options import Options
- from selenium.webdriver.common.by import By
- from selenium.webdriver.support.ui import WebDriverWait
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 8d1b9fce3..b8ee2884d 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -27,7 +27,7 @@ from ipaplatform.paths import paths
- from ipaplatform.osinfo import osinfo
- from ipaserver.install.installutils import resolve_ip_addresses_nss
- from ipatests.test_integration.base import IntegrationTest
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- from ipatests.test_integration.test_cert import get_certmonger_fs_id
- from ipatests.test_integration.test_external_ca import (
-     install_server_external_ca_step1,
-diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
-index 5dcea8979..356c4a099 100644
---- a/ipatests/test_webui/ui_driver.py
-+++ b/ipatests/test_webui/ui_driver.py
-@@ -29,7 +29,7 @@ import re
- import time
- from datetime import datetime
- from functools import wraps
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- from urllib.error import URLError
- 
- import pytest
-diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py
-index b3c2138aa..7fd6f6ca6 100644
---- a/ipatests/test_xmlrpc/test_automember_plugin.py
-+++ b/ipatests/test_xmlrpc/test_automember_plugin.py
-@@ -36,7 +36,7 @@ from ipaserver.plugins.automember import REBUILD_TASK_CONTAINER
- import time
- import pytest
- import re
--from pkg_resources import parse_version
-+from packaging.version import parse as parse_version
- 
- try:
-     from ipaserver.plugins.ldap2 import ldap2
-diff --git a/pypi/test_placeholder.py b/pypi/test_placeholder.py
-index 73f4faa61..4b81715c8 100644
---- a/pypi/test_placeholder.py
-+++ b/pypi/test_placeholder.py
-@@ -1,8 +1,7 @@
- # Copyright (C) 2017  FreeIPA Contributors see COPYING for license
- 
- import importlib
--
--import pkg_resources
-+import importlib.metadata
- 
- import pytest
- 
-@@ -45,4 +44,4 @@ def test_import(modname):
-     'ipatests',
- ])
- def test_package_installed(pkgname):
--    pkg_resources.require(pkgname)
-+    importlib.metadata.distribution(pkgname)
--- 
-2.49.0
-
-
-From b5ce6722527cab8602598939964ada0288707a70 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Wed, 30 Apr 2025 11:31:16 +0300
-Subject: [PATCH 33/53] Azure CI: do not run test_ipaserver/test_migratepw
-
-Unfortunately, password migration initializes PyCA several times within
-the same process. PyCA uses PyO3 which explicitly disables such behavior
-and breaks the execution. As a result, the test fails with the following
-error message in the logs:
-
--------------------------------------
- from cryptography.hazmat.bindings._rust import x509 as rust_x509, referer: https://master1.ipa.test/ipa/migration/migration.py
- ImportError: PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process, referer: https://master1.ipa.test/ipa/migration/migration.py
--------------------------------------
-
-This was introduced in in 2022 with the following commit:
-https://github.com/PyO3/pyo3/commit/f17e70316751285340508d0009103570af7e0873
-and means the test always fails to detect the recent Python 3.9+ (Fedora
-42 runs on Python 3.13).
-
-Fedora's PyO3 build allows to re-enable unsafe sub-interpreters but it has
-to be enabled within the package using pyo3 rust crate, e.g. PyCA. PyCA
-is built without this support.
-
-Disable the test until we find out how to fix this mess.
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/azure/azure_definitions/base-fedora.yml | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/ipatests/azure/azure_definitions/base-fedora.yml b/ipatests/azure/azure_definitions/base-fedora.yml
-index 76f015961..fe97928e5 100644
---- a/ipatests/azure/azure_definitions/base-fedora.yml
-+++ b/ipatests/azure/azure_definitions/base-fedora.yml
-@@ -24,6 +24,8 @@ vms:
-     - test_ipatests_plugins
-     - test_xmlrpc/test_dns_plugin.py
-     args: "-k 'not test_dns_soa'"
-+    ignore:
-+    - test_ipaserver/test_migratepw.py
-     type: base
-     isolated: "true"
- 
--- 
-2.49.0
-
-
-From 6b027440dd2a85b98eef85752967a8d34dae96e3 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 5 May 2025 13:38:32 +0300
-Subject: [PATCH 34/53] test_schema: do not fool pytest with a non-test class
- name
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_cmdline/test_schema.py | 12 ++++++------
- 1 file changed, 6 insertions(+), 6 deletions(-)
-
-diff --git a/ipatests/test_cmdline/test_schema.py b/ipatests/test_cmdline/test_schema.py
-index 26caa8aa1..454265a93 100644
---- a/ipatests/test_cmdline/test_schema.py
-+++ b/ipatests/test_cmdline/test_schema.py
-@@ -7,7 +7,7 @@ import time
- from ipaclient.remote_plugins import ServerInfo
- 
- 
--class TestServerInfo(ServerInfo):
-+class AServerInfo(ServerInfo):
-     """Simplified ServerInfo class with hardcoded values"""
-     def __init__(self, fingerprint='deadbeef', hostname='ipa.example.test',
-                  force_check=False, language='en_US',
-@@ -34,22 +34,22 @@ class TestIPAServerInfo:
-     """Test that ServerInfo detects changes in remote configuration"""
- 
-     def test_valid(self):
--        server_info = TestServerInfo()
-+        server_info = AServerInfo()
-         assert server_info.is_valid() is True
- 
-     def test_force_check(self):
--        server_info = TestServerInfo(force_check=True)
-+        server_info = AServerInfo(force_check=True)
-         assert server_info.is_valid() is False
- 
-     def test_language_change(self):
--        server_info = TestServerInfo()
-+        server_info = AServerInfo()
-         assert server_info.is_valid() is True
-         server_info._language = 'fr_FR'
-         assert server_info.is_valid() is False
-         server_info._language = 'en_US'
- 
-     def test_expired(self):
--        server_info = TestServerInfo(expiration=time.time() + 2)
-+        server_info = AServerInfo(expiration=time.time() + 2)
-         assert server_info.is_valid() is True
- 
-         # skip past the expiration time
-@@ -65,7 +65,7 @@ class TestIPAServerInfo:
-         assert server_info.is_valid() is False
- 
-     def test_update_validity(self):
--        server_info = TestServerInfo(expiration=time.time() + 1)
-+        server_info = AServerInfo(expiration=time.time() + 1)
- 
-         # Expiration and time are one second off so the cache is ok
-         assert server_info.is_valid() is True
--- 
-2.49.0
-
-
-From 0aff65d9453d456c7a99c1294dde8c2e2ab57ca8 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 12 May 2025 16:22:46 +0200
-Subject: [PATCH 35/53] dns install: fix selinux avc relabelto
-
-During the DNS server installation in SELinux enforcing mode,
-ipa-dnskeysyncd.service fails to restart because of the AVC:
-avc:  denied  { relabelto } for  pid=29955 comm="systemd-tmpfile" name="softhsm_pin" dev="vda4" ino=38440 scontext=system_u:system_r:systemd_tmpfiles_t:s0 tcontext=system_u:object_r:ipa_dnskey_t:s0 tclass=file permissive=0
-
-Add the missing policies
-allow systemd_tmpfiles_t ipa_dnskey_t:file relabelto;
-allow ipa_dnskey_t fs_t:filesystem associate;
-allow ipa_ods_exporter_t ipa_dnskey_t:file { getattr ioctl open read };
-allow named_t ipa_dnskey_t:file { getattr open read };
-
-Fixes: https://pagure.io/freeipa/issue/9782
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- selinux/ipa.te | 14 ++++++++++++++
- 1 file changed, 14 insertions(+)
-
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index c6d40b148..b53540518 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -271,6 +271,19 @@ gen_require(`
- ')
- allow ndc_t ipa_dnskey_t:file { getattr open read };
- 
-+# Allow relabel from systemd_tmpfiles_t
-+gen_require(`
-+    type systemd_tmpfiles_t;
-+')
-+allow systemd_tmpfiles_t ipa_dnskey_t:file { getattr relabelfrom relabelto };
-+gen_require(`
-+    type fs_t;
-+')
-+allow ipa_dnskey_t fs_t:filesystem associate;
-+gen_require(`
-+    type named_t;
-+')
-+allow named_t ipa_dnskey_t:file { getattr open read };
- 
- dev_read_rand(ipa_dnskey_t)
- dev_read_sysfs(ipa_dnskey_t)
-@@ -320,6 +333,7 @@ optional_policy(`
- allow ipa_ods_exporter_t self:netlink_route_socket { bind create getattr nlmsg_read };
- allow ipa_ods_exporter_t self:udp_socket { connect create getattr };
- allow ipa_ods_exporter_t self:unix_dgram_socket { create getopt setopt };
-+allow ipa_ods_exporter_t ipa_dnskey_t:file { getattr ioctl open read };
- 
- manage_files_pattern(ipa_ods_exporter_t, ipa_var_lib_t, ipa_var_lib_t)
- list_dirs_pattern(ipa_ods_exporter_t, ipa_var_lib_t, ipa_var_lib_t)
--- 
-2.49.0
-
-
-From f18628e5922a7db84285531cb9e9e64082cf0941 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 19 May 2025 18:02:27 +0200
-Subject: [PATCH 36/53] azure pipeline: disable InstallDNSSECFirst
-
-The test is unstable and prevents green CI.
-Moreover it is already executed in PRCI gating tests.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- .../azure/azure_definitions/gating-fedora.yml | 20 +++++++++----------
- 1 file changed, 10 insertions(+), 10 deletions(-)
-
-diff --git a/ipatests/azure/azure_definitions/gating-fedora.yml b/ipatests/azure/azure_definitions/gating-fedora.yml
-index 23a6393f6..0a532edb3 100644
---- a/ipatests/azure/azure_definitions/gating-fedora.yml
-+++ b/ipatests/azure/azure_definitions/gating-fedora.yml
-@@ -45,16 +45,16 @@ vms:
-     - test_integration/test_external_ca.py::TestExternalCAInstall
- 
- - vm_jobs:
--  - container_job: InstallDNSSECFirst
--    containers:
--      replicas: 1
--      resources:
--        replica:
--          mem_limit: "2400m"
--          memswap_limit: "3500m"
--    tests:
--    - test_integration/test_dnssec.py::TestInstallDNSSECFirst
--
-+#  - container_job: InstallDNSSECFirst
-+#    containers:
-+#      replicas: 1
-+#      resources:
-+#        replica:
-+#          mem_limit: "2400m"
-+#          memswap_limit: "3500m"
-+#    tests:
-+#    - test_integration/test_dnssec.py::TestInstallDNSSECFirst
-+#
-   - container_job: simple_replication
-     containers:
-       replicas: 1
--- 
-2.49.0
-
-
-From 8cfafeffd1ad5266b35a7eb796976e873278f500 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 14 May 2025 10:44:26 +0200
-Subject: [PATCH 37/53] ipatests: add extensions to server certificates for
- CAless mode
-
-When installing the server in CA less mode, the tests generate
-server certificates but some extensions were missing.
-
-Generate server cert with an Authority Key Identifier extension
-using the CA's subject key identifier.
-
-Without this extension, replica installation fails with
-    certificate verify failed: Missing Authority Key Identifier
-in the step fetching the DM password from the server.
-
-Add KeyUsage and Extended Key Usage.
-
-Fixes: https://pagure.io/freeipa/issue/9787
-Related: https://github.com/dogtagpki/pki/issues/5051
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../integration/create_caless_pki.py          | 34 +++++++++++++++++++
- 1 file changed, 34 insertions(+)
-
-diff --git a/ipatests/pytest_ipa/integration/create_caless_pki.py b/ipatests/pytest_ipa/integration/create_caless_pki.py
-index 71d804c33..d06f1dd8c 100644
---- a/ipatests/pytest_ipa/integration/create_caless_pki.py
-+++ b/ipatests/pytest_ipa/integration/create_caless_pki.py
-@@ -199,6 +199,20 @@ def profile_server(builder, ca_nick, ca,
-             critical=False,
-         )
- 
-+    if ca:
-+        try:
-+            ski_ext = ca.cert.extensions.get_extension_for_class(
-+                x509.SubjectKeyIdentifier)
-+            builder = builder.add_extension(
-+                x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
-+                    ski_ext.value
-+                ),
-+                critical=False,
-+            )
-+        except x509.ExtensionNotFound:
-+            # if the CA doesn't have a SKI, just ignore
-+            pass
-+
-     if badusage:
-         builder = builder.add_extension(
-             x509.KeyUsage(
-@@ -214,6 +228,26 @@ def profile_server(builder, ca_nick, ca,
-             ),
-             critical=False
-         )
-+    else:
-+        builder = builder.add_extension(
-+            x509.KeyUsage(
-+                digital_signature=True,
-+                content_commitment=False,
-+                key_encipherment=True,
-+                data_encipherment=True,
-+                key_agreement=False,
-+                key_cert_sign=False,
-+                crl_sign=False,
-+                encipher_only=False,
-+                decipher_only=False
-+            ),
-+            critical=False
-+        )
-+
-+    builder = builder.add_extension(
-+        x509.ExtendedKeyUsage([x509.ObjectIdentifier('1.3.6.1.5.5.7.3.1')]),
-+        critical=False,
-+    )
- 
-     if wildcard:
-         names = [x509.DNSName(u'*.' + domain)]
--- 
-2.49.0
-
-
-From 17fdff8f2f1664a387147e13a851bc1248abc29c Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 19 May 2025 09:56:36 +0200
-Subject: [PATCH 38/53] ipatests: test_manual_renewal_master_transfer must wait
- for replication
-
-The test is transferring the CA renewal role from master to replica.
-It calls ipa config-mod on the replica then checks with ipa config-show
-on the master.
-Wait for replication to complete between the 2 steps.
-
-Fixes: https://pagure.io/freeipa/issue/9790
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_replica_promotion.py | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py
-index c754cef88..3c67833d3 100644
---- a/ipatests/test_integration/test_replica_promotion.py
-+++ b/ipatests/test_integration/test_replica_promotion.py
-@@ -417,6 +417,9 @@ class TestRenewalMaster(IntegrationTest):
-         replica = self.replicas[0]
-         replica.run_command(['ipa', 'config-mod',
-                              '--ca-renewal-master-server', replica.hostname])
-+        # wait for replication to complete before checking on the master
-+        tasks.wait_for_replication(replica.ldap_connect())
-+
-         result = self.master.run_command(["ipa", "config-show"]).stdout_text
-         assert("IPA CA renewal master: %s" % replica.hostname in result), (
-             "Replica hostname not found among CA renewal masters"
--- 
-2.49.0
-
-
-From 6f1b9a4228e400ef23f0f411ebf8a98c30cd2f9f Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Mon, 5 May 2025 17:31:18 +0200
-Subject: [PATCH 39/53] Require baserid and secondarybaserid
-
-This has been already required for some time, just not really enforced.
-Also adds few new tests, and removes test without providing rid.
-
-Fixes: https://pagure.io/freeipa/issue/9779
-
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaclient/plugins/idrange.py              | 31 +++------------
- ipaserver/plugins/idrange.py              | 35 +++++++----------
- ipatests/test_cmdline/test_cli.py         | 13 -------
- ipatests/test_xmlrpc/test_range_plugin.py | 46 +++++++++++++++++++++++
- 4 files changed, 66 insertions(+), 59 deletions(-)
-
-diff --git a/ipaclient/plugins/idrange.py b/ipaclient/plugins/idrange.py
-index 1a8d68ed7..b62cb1e35 100644
---- a/ipaclient/plugins/idrange.py
-+++ b/ipaclient/plugins/idrange.py
-@@ -19,7 +19,6 @@
- 
- from ipaclient.frontend import MethodOverride
- from ipalib.plugable import Registry
--from ipalib import api
- 
- register = Registry()
- 
-@@ -33,8 +32,7 @@ class idrange_add(MethodOverride):
-         Also ensure that secondary-rid-base is prompted for when rid-base is
-         specified and vice versa, in case that dom-sid was not specified.
- 
--        Also ensure that rid-base and secondary-rid-base is prompted for
--        if ipa-adtrust-install has been run on the system.
-+        Also ensure that rid-base and secondary-rid-base is prompted for.
-         """
- 
-         # dom-sid can be specified using dom-sid or dom-name options
-@@ -63,27 +61,10 @@ class idrange_add(MethodOverride):
- 
-         else:
-             # This is a local range
--            # Find out whether ipa-adtrust-install has been ran
--            adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
- 
--            if adtrust_is_enabled:
--                # If ipa-adtrust-install has been ran, all local ranges
--                # require both RID base and secondary RID base
--
--                if rid_base is None:
--                    set_from_prompt('ipabaserid')
--
--                if secondary_rid_base is None:
--                    set_from_prompt('ipasecondarybaserid')
--
--            else:
--                # This is a local range on a server with no adtrust support
--
--                # Prompt for secondary RID base only if RID base was given
--                if rid_base is not None and secondary_rid_base is None:
--                    set_from_prompt('ipasecondarybaserid')
-+            # All local ranges require both RID base and secondary RID base
-+            if rid_base is None:
-+                set_from_prompt('ipabaserid')
- 
--                # Symetrically, prompt for RID base if secondary RID base was
--                # given
--                if rid_base is None and secondary_rid_base is not None:
--                    set_from_prompt('ipabaserid')
-+            if secondary_rid_base is None:
-+                set_from_prompt('ipasecondarybaserid')
-diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
-index d155fb46d..1c8b5c689 100644
---- a/ipaserver/plugins/idrange.py
-+++ b/ipaserver/plugins/idrange.py
-@@ -73,10 +73,14 @@ Both types have the following attributes in common:
- With those two attributes a range object can reserve the Posix IDs starting
- with base-id up to but not including base-id+range-size exclusively.
- 
--Additionally an ID range of the local domain may set
-+Additionally an ID range of the local domain must set
-  - rid-base: the first RID(*) of the corresponding RID range
-  - secondary-rid-base: first RID of the secondary RID range
- 
-+If the server is updated from a previous version and defines local ID ranges
-+missing the rid-base and secondary-rid-base, it is recommended to use
-+`ipa-idrange-fix` command to identify the missing values and fix the ID ranges.
-+
- and an ID range of a trusted domain must set
-  - rid-base: the first RID of the corresponding RID range
-  - sid: domain SID of the trusted domain
-@@ -519,11 +523,15 @@ class idrange_add(LDAPCreate):
-                             'or ipa-ad-trust-posix when '
-                             'auto-private-groups is specified'))
- 
--            # secondary base rid must be set if and only if base rid is set
--            if is_set('ipasecondarybaserid') != is_set('ipabaserid'):
--                raise errors.ValidationError(name='ID Range setup',
--                    error=_('Options secondary-rid-base and rid-base must '
--                            'be used together'))
-+            # base rid and secondary base rid must be set for sidgen
-+            if not (is_set('ipabaserid') and is_set('ipasecondarybaserid')):
-+                raise errors.ValidationError(
-+                    name='ID Range setup',
-+                    error=_(
-+                        'You must specify both rid-base and '
-+                        'secondary-rid-base options.'
-+                    )
-+                )
- 
-             # and they must not overlap
-             if is_set('ipabaserid') and is_set('ipasecondarybaserid'):
-@@ -534,21 +542,6 @@ class idrange_add(LDAPCreate):
-                         raise errors.ValidationError(name='ID Range setup',
-                             error=_("Primary RID range and secondary RID range"
-                                     " cannot overlap"))
--
--            # rid-base and secondary-rid-base must be set if
--            # ipa-adtrust-install has been run on the system
--            adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
--
--            if adtrust_is_enabled and not (
--                    is_set('ipabaserid') and is_set('ipasecondarybaserid')):
--                raise errors.ValidationError(
--                    name='ID Range setup',
--                    error=_(
--                        'You must specify both rid-base and '
--                        'secondary-rid-base options, because '
--                        'ipa-adtrust-install has already been run.'
--                    )
--                )
-         return dn
- 
-     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-diff --git a/ipatests/test_cmdline/test_cli.py b/ipatests/test_cmdline/test_cli.py
-index 718798d68..6c86bbb65 100644
---- a/ipatests/test_cmdline/test_cli.py
-+++ b/ipatests/test_cmdline/test_cli.py
-@@ -276,25 +276,12 @@ class TestCLIParsing:
-                 ipasecondarybaserid=u'500000',
-             )
- 
--        def test_without_options():
--            self.check_command(
--                'idrange_add range1 --base-id=1 --range-size=1',
--                'idrange_add',
--                cn=u'range1',
--                ipabaseid=u'1',
--                ipaidrangesize=u'1',
--            )
--
-         adtrust_dn = 'cn=ADTRUST,cn=%s,cn=masters,cn=ipa,cn=etc,%s' % \
-                      (api.env.host, api.env.basedn)
-         adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
-         mockldap = None
- 
-         if not adtrust_is_enabled:
--            # ipa-adtrust-install not run - no need to pass rid-base
--            # and secondary-rid-base
--            test_without_options()
--
-             # Create a mock service object to test against
-             adtrust_add = dict(
-                 ipaconfigstring=b'enabledService',
-diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py
-index 36469525b..ffc89c028 100644
---- a/ipatests/test_xmlrpc/test_range_plugin.py
-+++ b/ipatests/test_xmlrpc/test_range_plugin.py
-@@ -1086,4 +1086,50 @@ class test_range(Declarative):
-             ),
-         ),
- 
-+        # Fail without baserid and secondarybaserid
-+
-+        dict(
-+            desc='Try creating ID range %r without both rid' % (testrange9),
-+            command=('idrange_add', [testrange9],
-+                     dict(ipabaseid=testrange9_base_id,
-+                          ipaidrangesize=testrange9_size)),
-+            expected=errors.ValidationError(
-+                name='ID Range setup',
-+                error=(
-+                    'You must specify both rid-base and '
-+                    'secondary-rid-base options.'
-+                )
-+            )
-+        ),
-+
-+        dict(
-+            desc='Try creating ID range %r without'
-+            'secondarybaserid' % (testrange9),
-+            command=('idrange_add', [testrange9],
-+                     dict(ipabaseid=testrange9_base_id,
-+                          ipaidrangesize=testrange9_size,
-+                          ipabaserid=testrange9_base_rid)),
-+            expected=errors.ValidationError(
-+                name='ID Range setup',
-+                error=(
-+                    'You must specify both rid-base and '
-+                    'secondary-rid-base options.'
-+                )
-+            )
-+        ),
-+
-+        dict(
-+            desc='Try creating ID range %r without baserid' % (testrange9),
-+            command=('idrange_add', [testrange9],
-+                     dict(ipabaseid=testrange9_base_id,
-+                          ipaidrangesize=testrange9_size,
-+                          ipasecondarybaserid=testrange9_secondary_base_rid)),
-+            expected=errors.ValidationError(
-+                name='ID Range setup',
-+                error=(
-+                    'You must specify both rid-base and '
-+                    'secondary-rid-base options.'
-+                )
-+            )
-+        ),
-     ]
--- 
-2.49.0
-
-
-From a55e835339ff6ae7a7c0775a1df8b78ff523a346 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 29 Apr 2025 11:36:48 +0200
-Subject: [PATCH 40/53] PRCI tests: update vagrant image with latest bind
- package
-
-Fedora 42 is now officially available. Update the bind and bind-dyndb-ldap
-packages to the latest versions
-(bind-9.18.35-2.fc42, bind-dyndb-ldap-11.11-3.fc42)
-by updating the vragrant image to 0.0.2
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipatests/prci_definitions/gating.yaml                          | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml         | 2 +-
- ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml | 2 +-
- ipatests/prci_definitions/temp_commit.yaml                     | 2 +-
- 4 files changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index fcb477e34..f1e6afd1e 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -39,7 +39,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f42
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 50540f360..635a5a663 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -55,7 +55,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f42
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 05167f6d7..7cb044404 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -55,7 +55,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f42
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
-diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
-index 62f6f0c07..24b7b4c48 100644
---- a/ipatests/prci_definitions/temp_commit.yaml
-+++ b/ipatests/prci_definitions/temp_commit.yaml
-@@ -61,7 +61,7 @@ jobs:
-         git_refspec: '{git_refspec}'
-         template: &ci-ipa-4-12-latest
-           name: freeipa/ci-ipa-4-12-f42
--          version: 0.0.1
-+          version: 0.0.2
-         timeout: 1800
-         topology: *build
- 
--- 
-2.49.0
-
-
-From 1c069653806ce8224132a35d6d3bd01ac53098b6 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 26 May 2025 18:24:12 +0200
-Subject: [PATCH 41/53] ipa config-mod: fix internalerror when setting an empty
- ipaconfigstring
-
-When ipa config-mod is called with --ipaconfigstring="", the command
-fails with an InternalError.
-This happens because the code added for 32bits uid did not properly
-handle this case.
-
-Same issue if ipa subid-stats is called with a null ipaconfigstring.
-
-This commit now handles when ipaconfigstring is empty or None, and adds
-a test.
-
-Fixes: https://pagure.io/freeipa/issue/9794
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
----
- ipaserver/plugins/config.py                |  4 +--
- ipatests/test_integration/test_commands.py | 30 ++++++++++++++++++++++
- 2 files changed, 32 insertions(+), 2 deletions(-)
-
-diff --git a/ipaserver/plugins/config.py b/ipaserver/plugins/config.py
-index c509c2c13..d9769ab1f 100644
---- a/ipaserver/plugins/config.py
-+++ b/ipaserver/plugins/config.py
-@@ -524,7 +524,7 @@ class config(LDAPObject):
-     def is_config_option_present(self, option):
-         dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'), self.api.env.basedn)
-         configentry = self.api.Backend.ldap2.get_entry(dn, ['ipaconfigstring'])
--        configstring = configentry['ipaconfigstring']
-+        configstring = configentry.get('ipaconfigstring') or []
-         return (option.lower() in map(str.lower, configstring))
- 
- 
-@@ -702,7 +702,7 @@ class config_mod(LDAPUpdate):
-                     error=_('SELinux user map default user not in order list'))
- 
-         if 'ipaconfigstring' in entry_attrs:
--            configstring = entry_attrs['ipaconfigstring']
-+            configstring = entry_attrs['ipaconfigstring'] or []
-             if 'SubID:Disable'.lower() in map(str.lower, configstring):
-                 # Check if SubIDs already allocated
-                 try:
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index f64152908..9cad57721 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -2123,6 +2123,36 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         assert old_err_msg not in dirsrv_error_log
-         assert re.search(new_err_msg, dirsrv_error_log)
- 
-+    @pytest.fixture
-+    def update_ipaconfigstring(self):
-+        """
-+        This fixture stores the value of ipaconfigstring parameter
-+        and reverts to the initial value
-+        """
-+        ldap = self.master.ldap_connect()
-+        dn = DN(
-+            ("cn", "ipaconfig"), ('cn', 'etc'),
-+            self.master.domain.basedn
-+        )
-+        entry = ldap.get_entry(dn)
-+        val = entry.get("ipaconfigstring")
-+        yield
-+
-+        # re-read the entry as the value may have been changed by the test
-+        entry = ldap.get_entry(dn)
-+        entry["ipaconfigstring"] = val
-+        ldap.update_entry(entry)
-+
-+    def test_empty_ipaconfigstring(self, update_ipaconfigstring):
-+        """
-+        Test for https://pagure.io/freeipa/issue/9794
-+
-+        Test that setting an empty ipaconfigstring does not fail.
-+        Subsequent calls to ipa subid-stats should also succeed.
-+        """
-+        self.master.run_command(['ipa', 'config-mod', "--ipaconfigstring="])
-+        self.master.run_command(['ipa', 'subid-stats'])
-+
-     def test_ipa_cacert_manage_prune(self):
-         """Test for ipa-cacert-manage prune
- 
--- 
-2.49.0
-
-
-From 383574be4e645155fb58a79612138e51c3bdc4eb Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Tue, 13 May 2025 15:58:56 +0530
-Subject: [PATCH 42/53] ipatests: Test to check dot forwarders are added to
- unbound.
-
-This test checks that dns forwarder is listed in
-dnsserver-show command and also the dot forwarder is
-added to unbound and included in /etc/unbound/conf.d/zzz-ipa.conf
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Antonio Torres <antorres@redhat.com>
----
- ipatests/test_integration/test_edns.py | 18 ++++++++++++++++++
- 1 file changed, 18 insertions(+)
-
-diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
-index b42570ffa..dd046f226 100644
---- a/ipatests/test_integration/test_edns.py
-+++ b/ipatests/test_integration/test_edns.py
-@@ -247,6 +247,7 @@ class TestDNSOverTLS(IntegrationTest):
- 
- 
- class TestDNS_DoT(TestDNS):
-+
-     @classmethod
-     def install(cls, mh):
-         tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
-@@ -255,3 +256,20 @@ class TestDNS_DoT(TestDNS):
-             "--dot-forwarder", "1.1.1.1#cloudflare-dns.com"
-         ]
-         tasks.install_master(cls.master, extra_args=args)
-+
-+    def test_check_dot_forwarder_added_in_ipa_conf(self):
-+        """
-+        This test checks that forwarders is listed in
-+        dnsserver-show command and also the dot forwarder is
-+        added to unbound and included in
-+        /etc/unbound/conf.d/zzz-ipa.conf
-+        """
-+        msg = 'Forwarders: 127.0.0.55'
-+        cmd1 = self.master.run_command(
-+            ["ipa", "dnsserver-show", self.master.hostname]
-+        )
-+        assert msg in cmd1.stdout_text
-+        contents = self.master.get_file_contents(
-+            paths.UNBOUND_CONF, encoding='utf-8'
-+        )
-+        assert 'forward-addr: 1.1.1.1#cloudflare-dns.com' in contents
--- 
-2.49.0
-
-
-From 777f4c0ed631f70b64f6a972e7e6cb140155ef1f Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 8 May 2025 13:55:34 -0400
-Subject: [PATCH 43/53] Fix some issues identified by a static analyzer
-
-Remove resource leak when reading the IPA config in ipa-getkeytab
-Free popt in ipa-getkeytab
-
-Initialize ret in ipa-otpd/passkey.c
-
-Use the correct free function in util/ipa_krb5.c
-
-Related: https://pagure.io/freeipa/issue/9468
-Fixes: https://pagure.io/freeipa/issue/9365
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
----
- client/ipa-getkeytab.c     | 13 ++++++++++++-
- daemons/ipa-otpd/passkey.c |  2 +-
- util/ipa_krb5.c            |  2 +-
- 3 files changed, 14 insertions(+), 3 deletions(-)
-
-diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
-index 228b981c2..8ca4b8130 100644
---- a/client/ipa-getkeytab.c
-+++ b/client/ipa-getkeytab.c
-@@ -866,6 +866,7 @@ static int read_ipa_config(struct ipa_config **ipacfg)
-         (*ipacfg)->domain = ini_get_string_config_value(obj, &ret);
-     }
- 
-+    ini_config_destroy(cfgctx);
-     return 0;
- }
- 
-@@ -984,7 +985,7 @@ int main(int argc, const char *argv[])
- 	krb5_context krbctx;
- 	krb5_ccache ccache;
- 	krb5_principal uprinc = NULL;
--	krb5_principal sprinc;
-+	krb5_principal sprinc = NULL;
- 	krb5_error_code krberr;
- 	struct keys_container keys = { 0 };
- 	krb5_keytab kt;
-@@ -1026,6 +1027,7 @@ int main(int argc, const char *argv[])
- 			fprintf(stdout, "%s\n", enc);
- 		}
- 		ipa_krb5_free_ktypes(krbctx, ktypes);
-+		poptFreeContext(pc);
- 		exit (0);
- 	}
- 
-@@ -1033,6 +1035,7 @@ int main(int argc, const char *argv[])
- 		if (!quiet) {
- 			poptPrintUsage(pc, stderr, 0);
- 		}
-+		poptFreeContext(pc);
- 		exit(2);
- 	}
- 
-@@ -1041,12 +1044,14 @@ int main(int argc, const char *argv[])
- 		if (!quiet) {
- 			poptPrintUsage(pc, stderr, 0);
- 		}
-+		poptFreeContext(pc);
- 		exit(2);
-     }
- 
-     if (askbindpw) {
- 		bindpw = ask_password(krbctx, _("Enter LDAP password"), NULL, false);
- 		if (!bindpw) {
-+			poptFreeContext(pc);
- 			exit(2);
- 		}
-     }
-@@ -1056,6 +1061,7 @@ int main(int argc, const char *argv[])
-                         _("Bind password required when using a bind DN (-w or -W).\n"));
- 		if (!quiet)
- 			poptPrintUsage(pc, stderr, 0);
-+		poptFreeContext(pc);
- 		exit(10);
- 	}
- 
-@@ -1064,6 +1070,7 @@ int main(int argc, const char *argv[])
-                           "and bind DN simultaneously.\n"));
-         if (!quiet)
-             poptPrintUsage(pc, stderr, 0);
-+        poptFreeContext(pc);
-         exit(2);
-     }
- 
-@@ -1071,6 +1078,7 @@ int main(int argc, const char *argv[])
-         fprintf(stderr, _("Invalid SASL bind mechanism\n"));
-         if (!quiet)
-             poptPrintUsage(pc, stderr, 0);
-+        poptFreeContext(pc);
-         exit(2);
-     }
- 
-@@ -1083,8 +1091,10 @@ int main(int argc, const char *argv[])
-                           "simultaneously.\n"));
-         if (!quiet)
-             poptPrintUsage(pc, stderr, 0);
-+        poptFreeContext(pc);
-         exit(2);
-     }
-+    poptFreeContext(pc);
- 
-     if (server && (strcasecmp(server, "_srv_") == 0)) {
-         struct srvrec *srvrecs, *srv;
-@@ -1119,6 +1129,7 @@ int main(int argc, const char *argv[])
-             /* Discovery failed, fall through to option methods */
-             server = NULL;
-         }
-+        free(ipacfg);
-     }
- 
-     if (!server && !ldap_uri) {
-diff --git a/daemons/ipa-otpd/passkey.c b/daemons/ipa-otpd/passkey.c
-index 8351f0fcf..ad3c45467 100644
---- a/daemons/ipa-otpd/passkey.c
-+++ b/daemons/ipa-otpd/passkey.c
-@@ -307,7 +307,7 @@ bool is_passkey(struct otpd_queue_item *item)
- 
- static json_t *ipa_passkey_to_json_array(char **ipa_passkey)
- {
--    int ret;
-+    int ret = 0;
-     const char *sep;
-     char *start;
-     size_t c;
-diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c
-index bb98ab897..0087e53e6 100644
---- a/util/ipa_krb5.c
-+++ b/util/ipa_krb5.c
-@@ -80,7 +80,7 @@ static krb5_error_code ipa_get_random_salt(krb5_context krbctx,
- void
- ipa_krb5_free_ktypes(krb5_context context, krb5_enctype *val)
- {
--    free(val);
-+    krb5_free_enctypes(context, val);
- }
- 
- /*
--- 
-2.49.0
-
-
-From a31654e5c4ba61177928abede5885a247365d067 Mon Sep 17 00:00:00 2001
-From: PRANAV THUBE <pthube@redhat.com>
-Date: Mon, 19 May 2025 14:46:19 +0530
-Subject: [PATCH 44/53] ipatests: Ignore /run/log/journal in
- test_uninstallation.py
-
-Update - Add /run/log/journal to the allowed list for leftover files/directories
-
-Fixes: https://pagure.io/freeipa/issue/9788
-
-Signed-off-by: PRANAV THUBE <pthube@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_uninstallation.py | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/ipatests/test_integration/test_uninstallation.py b/ipatests/test_integration/test_uninstallation.py
-index 049c50db5..f1cc1917d 100644
---- a/ipatests/test_integration/test_uninstallation.py
-+++ b/ipatests/test_integration/test_uninstallation.py
-@@ -178,6 +178,7 @@ class TestUninstallCleanup(IntegrationTest):
-             '/var/log',
-             '/var/tmp/systemd-private',
-             '/run/systemd',
-+            '/run/log/journal',
-             '/var/lib/authselect/backups/pre_ipaclient',
-             '/var/named/data/named.run',
-             paths.DNSSEC_SOFTHSM_PIN_SO,  # See commit eb54814741
--- 
-2.49.0
-
-
-From 3ba0f6a34cb018a36bc548667e2b433d05da6a45 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Tue, 6 May 2025 15:37:54 +0530
-Subject: [PATCH 45/53] ipatests: Tests for krbLastSuccessfulAuth warning
-
-This testcase checks that ipa-healthcheck issues
-warning when ipaconfigstring=AllowNThash
-
-Ref: https://github.com/freeipa/freeipa-healthcheck/issues/315
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipahealthcheck.py   | 43 ++++++++++++++++++-
- 1 file changed, 42 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index b8ee2884d..0ebc7149f 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -1526,6 +1526,48 @@ class TestIpaHealthCheck(IntegrationTest):
-             ]
-         )
- 
-+    @pytest.fixture
-+    def change_pwd_plugin_default(self):
-+        """
-+        Fixture to change the password plugin feature
-+        to AllowNThash and change it to default
-+        """
-+        self.master.run_command(
-+            [
-+                "ipa", "config-mod", "--delattr",
-+                "ipaconfigstring=KDC:Disable Last Success"
-+            ]
-+        )
-+        yield
-+        self.master.run_command(
-+            [
-+                "ipa", "config-mod", "--addattr",
-+                "ipaconfigstring=KDC:Disable Last Success"
-+            ]
-+        )
-+
-+    def test_krbLastSuccessfulAuth_warning(self, change_pwd_plugin_default):
-+        """
-+        This test checks that warning message is displayed
-+        when password plugin feature is modified to
-+        AllowNThash
-+        """
-+        err_msg = (
-+            "Last Successful Auth is enabled. "
-+            "It may cause performance problems."
-+        )
-+        version = tasks.get_healthcheck_version(self.master)
-+        if parse_version(version) < parse_version("0.18"):
-+            pytest.skip("Check does not exist in ipa-healthcheck < 0.18")
-+        returncode, data = run_healthcheck(
-+            self.master, "ipahealthcheck.ipa.config",
-+            "IPAkrbLastSuccessfulAuth",
-+        )
-+        assert returncode == 1
-+        for check in data:
-+            assert check["result"] == "WARNING"
-+            assert check["kw"]["msg"] == err_msg
-+
-     @pytest.fixture
-     def expire_cert_critical(self):
-         """
-@@ -1553,7 +1595,6 @@ class TestIpaHealthCheck(IntegrationTest):
-             assert "Expired Certificate" in check["kw"]["items"]
-             assert check["kw"]["msg"] == msg
- 
--
-     def test_ipa_healthcheck_expiring(self, restart_service):
-         """
-         There are two overlapping tests for expiring certs, check both.
--- 
-2.49.0
-
-
-From cef199631109b91462bf25ae8893ca8980faf5bf Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Wed, 21 May 2025 17:20:04 +0530
-Subject: [PATCH 46/53] ipatests: ipahealthcheck warns for user provided
- certificates about to expire
-
-This patch tests that ipa-healthcheck tools warns when IPA server is
-installed CALess and user provided certificates are about to expire.
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipahealthcheck.py   | 51 +++++++++++++++++++
- 1 file changed, 51 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 0ebc7149f..13fcc3d43 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -26,6 +26,7 @@ from ipatests.pytest_ipa.integration import tasks
- from ipaplatform.paths import paths
- from ipaplatform.osinfo import osinfo
- from ipaserver.install.installutils import resolve_ip_addresses_nss
-+from ipatests.test_integration.test_caless import CALessBase
- from ipatests.test_integration.base import IntegrationTest
- from packaging.version import parse as parse_version
- from ipatests.test_integration.test_cert import get_certmonger_fs_id
-@@ -3135,3 +3136,53 @@ class TestIpaHealthCheckSingleMaster(IntegrationTest):
-         finally:
-             # cleanup
-             tasks.uninstall_master(self.master)
-+
-+
-+class TestIPAHealthcheckWithCALess(CALessBase):
-+    """
-+    Install CALess server with user provided certificate.
-+    """
-+    num_replicas = 0
-+
-+    @classmethod
-+    def install(cls, mh):
-+        super(TestIPAHealthcheckWithCALess, cls).install(mh)
-+        cls.create_pkcs12('ca1/server')
-+        cls.prepare_cacert('ca1')
-+        result = cls.install_server()
-+        assert result.returncode == 0
-+
-+    @pytest.fixture
-+    def expire_cert_warn(self):
-+        """
-+        Fixture to move the cert to about to expire, by moving the
-+        system date using date -s command and revert it back
-+        """
-+        self.master.run_command(['date','-s', '+11Months10Days'])
-+        yield
-+        self.master.run_command(['date','-s', '-11Months10Days'])
-+        self.master.run_command(['ipactl', 'restart'])
-+
-+    def test_ipahealthcheck_warns_on_expired_user_certs(self, expire_cert_warn):
-+        """
-+        This testcase checks that ipa-healthcheck warns
-+        on expiring user-provided certificates.
-+        """
-+        msg = (
-+            'Request id {key} expires in {days} days. '
-+            'You need to manually renew this certificate.'
-+        )
-+        version = tasks.get_healthcheck_version(self.master)
-+        if parse_version(version) < parse_version("0.18"):
-+            pytest.skip("Check does not exist in ipa-healthcheck < 0.18")
-+        returncode, data = run_healthcheck(
-+            self.master, "ipahealthcheck.ipa.certs",
-+            "IPAUserProvidedExpirationCheck",
-+        )
-+        assert returncode == 1
-+        certs = [d["kw"]["key"] for d in data]
-+        assert set(certs) == {'HTTP', 'LDAP', 'KDC'}
-+        for check in data:
-+            assert check["result"] == "WARNING"
-+            assert check["kw"]["key"] in ("LDAP", "HTTP", "KDC")
-+            assert check["kw"]["msg"] == msg
--- 
-2.49.0
-
-
-From 078e913e51b2141ec38ef2b01bf40310619a487a Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Jun 2025 08:15:29 +0200
-Subject: [PATCH 47/53] Nightly tests: add TestIPAHealthcheckWithCALess
-
-Add the test to nightly_ipa-4-12_latest and nightly_ipa-4-12_latest_selinux
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 635a5a663..9e00c55a1 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -1442,6 +1442,18 @@ jobs:
-         timeout: 3600
-         topology: *master_1repl
- 
-+  fedora-latest-ipa-4-12/test_ipahealthcheck_caless:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_ipahealthcheck.py::TestIPAHealthcheckWithCALess
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *master_1repl
-+
-   fedora-latest-ipa-4-12/test_ipahealthcheck_adtrust:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 7cb044404..f76c96267 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -1555,6 +1555,19 @@ jobs:
-         timeout: 3600
-         topology: *master_1repl
- 
-+  fedora-latest-ipa-4-12/test_ipahealthcheck_caless:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_ipahealthcheck.py::TestIPAHealthcheckWithCALess
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *master_1repl
-+
-   fedora-latest-ipa-4-12/test_ipahealthcheck_adtrust:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
--- 
-2.49.0
-
-
-From 5b10d0eebffe0aaec7e7cb7974b8299905d289e9 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Jun 2025 15:03:40 +0200
-Subject: [PATCH 48/53] ipatests: skip encrypted dns tests on fedora 41
-
-The package ipa-server-encrypted-dns is not available on fedora 41
-as it requires a more recent bind version.
-Skip the tests that require this package in f41.
-
-Fixes: https://pagure.io/freeipa/issue/9799
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
----
- ipatests/test_integration/test_edns.py | 8 ++++++++
- 1 file changed, 8 insertions(+)
-
-diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
-index dd046f226..1f843c7bc 100644
---- a/ipatests/test_integration/test_edns.py
-+++ b/ipatests/test_integration/test_edns.py
-@@ -4,15 +4,20 @@
- """This covers tests for DNS over TLS related feature"""
- 
- from __future__ import absolute_import
-+import pytest
- import textwrap
- 
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
- from ipatests.test_integration.test_dns import TestDNS
- from ipatests.pytest_ipa.integration.firewall import Firewall
-+from ipaplatform.osinfo import osinfo
- from ipaplatform.paths import paths
- 
- 
-+@pytest.mark.skipif(
-+    osinfo.id == 'fedora' and osinfo.version_number == (41,),
-+    reason='Encrypted DNS not supported in fedora 41')
- class TestDNSOverTLS(IntegrationTest):
-     """Tests for DNS over TLS feature."""
- 
-@@ -246,6 +251,9 @@ class TestDNSOverTLS(IntegrationTest):
-         assert '''--dns-over-tls      Configure DNS over TLS''' in cmdout.stdout_text  # noqa: E501
- 
- 
-+@pytest.mark.skipif(
-+    osinfo.id == 'fedora' and osinfo.version_number == (41,),
-+    reason='Encrypted DNS not supported in fedora 41')
- class TestDNS_DoT(TestDNS):
- 
-     @classmethod
--- 
-2.49.0
-
-
-From 0c98af9f70c62da3d3dea02b91a9330a5f9f669a Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Thu, 22 May 2025 08:25:07 +0200
-Subject: [PATCH 49/53] Warn when UID is out of local ID ranges
-
-Provides simple warning when creating new user with uid out of
-all local ranges, as this is the main culprit of breaking Kerberos, by
-not generating ipantsecurityidentifier. We don't have to check for
-user-mod, because modification never changes ipantsecurityidentifier.
-We do not have to check groups, as groups are ignored for ipa without
-AD trust. It's reasonable to revisit this in the future for group
-creation and warn against groups out of ranges as well as
-warn for users with groups without SID, in case AD trust is enabled.
-
-Fixes: https://pagure.io/freeipa/issue/9781
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipalib/messages.py                            | 12 +++++
- ipaserver/plugins/baseuser.py                 | 29 +++++++++++-
- ipatests/test_xmlrpc/test_stageuser_plugin.py | 45 ++++++++++++++++++-
- ipatests/test_xmlrpc/test_user_plugin.py      | 43 ++++++++++++++++++
- .../test_xmlrpc/tracker/stageuser_plugin.py   | 22 +++++++++
- 5 files changed, 148 insertions(+), 3 deletions(-)
-
-diff --git a/ipalib/messages.py b/ipalib/messages.py
-index 6a70bbc75..a440ca622 100644
---- a/ipalib/messages.py
-+++ b/ipalib/messages.py
-@@ -519,6 +519,18 @@ class ServerUpgradeRequired(PublicMessage):
-     )
- 
- 
-+class UidNumberOutOfLocalIDRange(PublicMessage):
-+    """
-+    **13034** UID Number is out of all local ID Ranges
-+    """
-+    errno = 13034
-+    type = "warning"
-+    format = _(
-+        "User '%(user)s', with UID Number '%(uidnumber)d' is out of all ID "
-+        "Ranges, 'SID' will not be correctly generated."
-+    )
-+
-+
- def iter_messages(variables, base):
-     """Return a tuple with all subclasses
-     """
-diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
-index 22393b8f6..21e05d4d9 100644
---- a/ipaserver/plugins/baseuser.py
-+++ b/ipaserver/plugins/baseuser.py
-@@ -23,7 +23,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key
- import re
- import six
- 
--from ipalib import api, errors, constants
-+from ipalib import api, errors, constants, messages
- from ipalib import (
-     Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
- from ipalib.parameters import Principal, Certificate, MAX_UINT32
-@@ -198,6 +198,22 @@ def validate_passkey(ugettext, key):
-     return None
- 
- 
-+def is_in_local_idrange(uidnumber):
-+    result = api.Command.idrange_find(
-+        iparangetype='ipa-local',
-+        sizelimit=0,
-+    )
-+
-+    for r in result['result']:
-+        if 'ipabaserid' in r:
-+            ipabaseid = int(r['ipabaseid'][0])
-+            ipaidrangesize = int(r['ipaidrangesize'][0])
-+            if ipabaseid <= uidnumber < ipabaseid + ipaidrangesize:
-+                return True
-+
-+    return False
-+
-+
- class baseuser(LDAPObject):
-     """
-     baseuser object.
-@@ -621,6 +637,17 @@ class baseuser_add(LDAPCreate):
-             add_missing_object_class(ldap, 'ipaidpuser', dn,
-                                      entry_attrs, update=False)
- 
-+        # Check and warn if we're out of local idrange
-+        # Skip dynamically assigned uid, old clients say 999
-+        uidnumber = entry_attrs.get('uidnumber')
-+        if (
-+            uidnumber != -1
-+            and uidnumber != 999
-+            and not is_in_local_idrange(uidnumber)
-+        ):
-+            self.add_message(messages.UidNumberOutOfLocalIDRange(
-+                user=entry_attrs.get('uid'), uidnumber=uidnumber))
-+
-     def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
-         assert isinstance(dn, DN)
-         self.obj.convert_usercertificate_post(entry_attrs, **options)
-diff --git a/ipatests/test_xmlrpc/test_stageuser_plugin.py b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-index 6ed593fbf..dc4940a99 100644
---- a/ipatests/test_xmlrpc/test_stageuser_plugin.py
-+++ b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-@@ -80,9 +80,7 @@ options_def = OrderedDict([
-     ('car license', {u'carlicense': u'abc1234'}),
-     ('SSH key', {u'ipasshpubkey': sshpubkey}),
-     ('manager', {u'manager': u'auser1'}),
--    ('user ID number', {u'uidnumber': uid}),
-     ('group ID number', {u'gidnumber': gid}),
--    ('UID and GID numbers', {u'uidnumber': uid, u'gidnumber': gid}),
-     ('password', {u'userpassword': u'Secret123'}),
-     ('random password', {u'random': True}),
-     ])
-@@ -90,6 +88,13 @@ options_def = OrderedDict([
- options_ok = list(options_def.values())
- options_ids = list(options_def.keys())
- 
-+warn_options_def = OrderedDict([
-+    ('user ID number', {u'uidnumber': uid}),
-+    ('UID and GID numbers', {u'uidnumber': uid, u'gidnumber': gid}),
-+])
-+
-+warn_options_ok = list(warn_options_def.values())
-+warn_options_ids = list(warn_options_def.keys())
- 
- @pytest.fixture(scope='class')
- def stageduser(request, xmlrpc_setup):
-@@ -108,6 +113,12 @@ def stageduser2(request, xmlrpc_setup):
-     return tracker.make_fixture_activate(request)
- 
- 
-+@pytest.fixture(scope='class', params=warn_options_ok, ids=warn_options_ids)
-+def warn_stageduser(request, xmlrpc_setup):
-+    tracker = StageUserTracker(u'warnuser', u'staged', u'user', **request.param)
-+    return tracker.make_fixture_activate(request)
-+
-+
- @pytest.fixture(scope='class')
- def user_activated(request, xmlrpc_setup):
-     tracker = UserTracker(u'suser2', u'staged', u'user')
-@@ -273,6 +284,36 @@ class TestStagedUser(XMLRPC_test):
- 
-         user_activated.delete()
- 
-+    def test_warn_create_with_attr(self, warn_stageduser, user, user_activated):
-+        """ Tests creating a user with various valid attributes that throw
-+        a warning listed in 'warn_options_ok' list"""
-+        # create staged user with specified parameters
-+        user.ensure_exists()  # necessary for manager test
-+        warn_stageduser.ensure_missing()
-+        command = warn_stageduser.make_create_command()
-+        result = command()
-+        warn_stageduser.track_create()
-+        warn_stageduser.check_create_with_warning(result, (13034,))
-+
-+        # activate user, verify that specified values were preserved
-+        # after activation
-+        user_activated.ensure_missing()
-+        user_activated = UserTracker(
-+            warn_stageduser.uid, warn_stageduser.givenname,
-+            warn_stageduser.sn, **warn_stageduser.kwargs)
-+        user_activated.create_from_staged(warn_stageduser)
-+        command = warn_stageduser.make_activate_command()
-+        result = command()
-+        user_activated.check_activate(result)
-+
-+        # verify the staged user does not exist after activation
-+        command = warn_stageduser.make_retrieve_command()
-+        with raises_exact(errors.NotFound(
-+                reason=u'%s: stage user not found' % warn_stageduser.uid)):
-+            command()
-+
-+        user_activated.delete()
-+
-     def test_delete_stageduser(self, stageduser):
-         stageduser.delete()
- 
-diff --git a/ipatests/test_xmlrpc/test_user_plugin.py b/ipatests/test_xmlrpc/test_user_plugin.py
-index c0415cae6..420c80213 100644
---- a/ipatests/test_xmlrpc/test_user_plugin.py
-+++ b/ipatests/test_xmlrpc/test_user_plugin.py
-@@ -826,6 +826,49 @@ class TestCreate(XMLRPC_test):
-         user_idp.check_create(result, ['ipaidpsub'])
-         user_idp.delete()
- 
-+    def test_out_of_idrange(self):
-+        """Test ensuring warning is thrown when uid is out of range"""
-+        uidnumber = 2000
-+        testuser = UserTracker(
-+            name="testwarning", givenname="test",
-+            sn="warning", uidnumber=uidnumber
-+        )
-+        testuser.attrs.update(
-+            uidnumber=[u'2000'],
-+        )
-+        command = testuser.make_create_command()
-+        result = command()
-+        result_messages = result['messages']
-+        assert len(result_messages) == 1
-+        assert result_messages[0]['type'] == 'warning'
-+        assert result_messages[0]['code'] == 13034
-+        testuser.delete()
-+
-+    def test_in_idrange(self):
-+        """Test ensuring no warning is thrown when uid is in range"""
-+        result = api.Command.idrange_find(
-+            iparangetype='ipa-local',
-+            sizelimit=0,
-+        )
-+
-+        assert len(result) >= 1
-+        ipabaseid = int(result['result'][0]['ipabaseid'][0])
-+        ipaidrangesize = int(result['result'][0]['ipaidrangesize'][0])
-+
-+        # Take the last valid id, as we're not sure which has not yet been used
-+        valid_id = ipabaseid + ipaidrangesize - 1
-+        testuser = UserTracker(
-+            name="testnowarning", givenname="test",
-+            sn="nowarning", uidnumber=valid_id
-+        )
-+        testuser.attrs.update(
-+            uidnumber=[str(valid_id)],
-+        )
-+        command = testuser.make_create_command()
-+        result = command()
-+        assert "messages" not in result
-+        testuser.delete()
-+
- 
- @pytest.mark.tier1
- class TestUserWithGroup(XMLRPC_test):
-diff --git a/ipatests/test_xmlrpc/tracker/stageuser_plugin.py b/ipatests/test_xmlrpc/tracker/stageuser_plugin.py
-index 17744a98e..93157ba3a 100644
---- a/ipatests/test_xmlrpc/tracker/stageuser_plugin.py
-+++ b/ipatests/test_xmlrpc/tracker/stageuser_plugin.py
-@@ -3,6 +3,7 @@
- #
- 
- import six
-+import copy
- 
- from ipalib import api, errors
- from ipaplatform.constants import constants as platformconstants
-@@ -187,6 +188,27 @@ class StageUserTracker(PasskeyMixin, KerberosAliasMixin, Tracker):
-             result=self.filter_attrs(expected),
-         ), result)
- 
-+    def check_create_with_warning(self, result,
-+                                  warning_codes=(), extra_keys=()):
-+        """ Check 'stageuser-add' command result """
-+        expected = self.filter_attrs(self.create_keys | set(extra_keys))
-+
-+        result = copy.deepcopy(result)
-+        assert 'messages' in result
-+        assert len(result['messages']) == len(warning_codes)
-+        codes = [message['code'] for message in result['messages']]
-+        for code in warning_codes:
-+            assert code in codes
-+            codes.pop(codes.index(code))
-+
-+        del result['messages']
-+
-+        assert_deepequal(dict(
-+            value=self.uid,
-+            summary=u'Added stage user "%s"' % self.uid,
-+            result=self.filter_attrs(expected),
-+        ), result)
-+
-     def check_delete(self, result):
-         """ Check 'stageuser-del' command result """
-         assert_deepequal(dict(
--- 
-2.49.0
-
-
-From 0155718308fa58f43f2ec8df240c1df1c929195e Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 2 Jun 2025 14:47:48 +0200
-Subject: [PATCH 50/53] ipatests: fix invalid range creation in
- test_ipa_idrange_fix.py
-
-The test is creating a local ID range without rid-base and
-secondary-rid-base in order to test the behavior of ipa-idrange-fix.
-
-Since the patch for ticket #9779 it is not possible any more to call
-ipa idrange-add for local range without these parameters. The test needs
-to create the invalid local range using a direct ldapmodify instead.
-
-Fixes: https://pagure.io/freeipa/issue/9801
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
----
- .../test_integration/test_ipa_idrange_fix.py  | 39 ++++++++++++-------
- 1 file changed, 25 insertions(+), 14 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_idrange_fix.py b/ipatests/test_integration/test_ipa_idrange_fix.py
-index 0c915bd09..6559818d3 100644
---- a/ipatests/test_integration/test_ipa_idrange_fix.py
-+++ b/ipatests/test_integration/test_ipa_idrange_fix.py
-@@ -40,13 +40,18 @@ class TestIpaIdrangeFix(IntegrationTest):
- 
-     def test_idrange_no_rid_bases(self):
-         """Test ipa-idrange-fix command with IDrange with no RID bases."""
--        self.master.run_command([
--            "ipa",
--            "idrange-add",
--            "idrange_no_rid_bases",
--            "--base-id", '10000',
--            "--range-size", '20000',
--        ])
-+        # Use ldapmodify to create the range without rid bases
-+        idrange_ldif = (
-+            "dn: cn=idrange_no_rid_bases,cn=ranges,cn=etc,{suffix}\n"
-+            "changetype: add\n"
-+            "objectclass: top\n"
-+            "objectclass: ipaIDrange\n"
-+            "objectclass: ipadomainidrange\n"
-+            "ipaRangeType: ipa-local\n"
-+            "ipaBaseID: 10000\n"
-+            "ipaIDRangeSize: 20000\n"
-+        ).format(suffix=str(self.master.domain.basedn))
-+        tasks.ldapmodify_dm(self.master, idrange_ldif)
- 
-         result = self.master.run_command(["ipa-idrange-fix", "--unattended"])
-         expected_text = "RID bases updated for range 'idrange_no_rid_bases'"
-@@ -62,13 +67,19 @@ class TestIpaIdrangeFix(IntegrationTest):
-         previously had a range with RID bases reversed - secondary lower than
-         primary. It is a valid configuration, so we should fix no-RID range.
-         """
--        self.master.run_command([
--            "ipa",
--            "idrange-add",
--            "idrange_no_rid_bases",
--            "--base-id", '10000',
--            "--range-size", '20000',
--        ])
-+        # Use ldapmodify to create the range without rid bases
-+        idrange_ldif = (
-+            "dn: cn=idrange_no_rid_bases,cn=ranges,cn=etc,{suffix}\n"
-+            "changetype: add\n"
-+            "objectclass: top\n"
-+            "objectclass: ipaIDrange\n"
-+            "objectclass: ipadomainidrange\n"
-+            "ipaRangeType: ipa-local\n"
-+            "ipaBaseID: 10000\n"
-+            "ipaIDRangeSize: 20000\n"
-+        ).format(suffix=str(self.master.domain.basedn))
-+        tasks.ldapmodify_dm(self.master, idrange_ldif)
-+
-         self.master.run_command([
-             "ipa",
-             "idrange-add",
--- 
-2.49.0
-
-
-From 8653c48bd2550063c404cca80453ef6392e0fddc Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 20 May 2025 13:58:54 +0200
-Subject: [PATCH 51/53] ipatests: fix xfail annotation for
- test_ipa_healthcheck_fips_enabled
-
-The test is expected to fail
-- on rhel 10.0 with ipa-healthcheck < 0.17
-
-On Fedora 41, the command fips-mode-check is still available.
-On Fedora 42, it has been removed but ipa-healthcheck 0.18 has
-been adapted.
-
-Fixes: https://pagure.io/freeipa/issue/9791
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 13fcc3d43..3dcc22411 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -375,11 +375,11 @@ class TestIpaHealthCheck(IntegrationTest):
-         if (
-             parse_version(healthcheck_version) < parse_version("0.17")
-             and osinfo.id == 'rhel'
--            and osinfo.version_number >= (10,0)
-+            and osinfo.version_number == (10,0)
-         ):
-             # Patch: https://github.com/freeipa/freeipa-healthcheck/pull/349
--            pytest.xfail("Patch is unavailable for RHEL 10.0 and above"
--                         "freeipa-healtheck version 0.16 or less")
-+            pytest.skip("Patch is unavailable for RHEL 10.0 "
-+                        "freeipa-healthcheck version 0.16 or less")
- 
-         returncode, check = run_healthcheck(self.master,
-                                             source="ipahealthcheck.meta.core",
--- 
-2.49.0
-
-
-From d0f6979c0250bdf5299404bf711cef74dd458042 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Wed, 4 Jun 2025 09:36:55 +0200
-Subject: [PATCH 52/53] Fix inconsistency in manpage for DoT forwarder option
-
-The example given in manpages for --dot-forwarder option is inconsistent
-to the format that is required.
-
-Fixes: https://pagure.io/freeipa/issue/9804
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- install/tools/man/ipa-dns-install.1     | 2 +-
- install/tools/man/ipa-replica-install.1 | 2 +-
- install/tools/man/ipa-server-install.1  | 2 +-
- 3 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/install/tools/man/ipa-dns-install.1 b/install/tools/man/ipa-dns-install.1
-index 6008d2028..96eee1ef7 100644
---- a/install/tools/man/ipa-dns-install.1
-+++ b/install/tools/man/ipa-dns-install.1
-@@ -74,7 +74,7 @@ An unattended installation that will never prompt for user input
- Configure DNS over TLS.
- .TP
- \fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
--Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: 1.2.3.4#dns.example.com. This option can be used multiple times.
- .TP
- \fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
- Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
-diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1
-index c55d21253..637c5c1b5 100644
---- a/install/tools/man/ipa-replica-install.1
-+++ b/install/tools/man/ipa-replica-install.1
-@@ -228,7 +228,7 @@ Disable DNSSEC validation on this server.
- Configure DNS over TLS.
- .TP
- \fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
--Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: 1.2.3.4#dns.example.com. This option can be used multiple times.
- .TP
- \fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
- Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
-diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
-index 84d82531c..b9367ce11 100644
---- a/install/tools/man/ipa-server-install.1
-+++ b/install/tools/man/ipa-server-install.1
-@@ -257,7 +257,7 @@ Allow creation of (reverse) zone even if the zone is already resolvable. Using t
- Configure DNS over TLS.
- .TP
- \fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
--Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
-+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: 1.2.3.4#dns.example.com. This option can be used multiple times.
- .TP
- \fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
- Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
--- 
-2.49.0
-
-
-From 796ed20092d554ee0c9e23295e346ec1e8a0bf6e Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 28 Apr 2025 13:43:40 -0400
-Subject: [PATCH 53/53] Set krbCanonicalName=admin@REALM on the admin user
-
-The admin must always own this name. If another entry has this
-value set then remove it.
-
-There is a uniqueness plugin for this attribute so the only two
-possibilities are:
-
-- no entry has this value set
-- the admin user has this value set
-- a different entry has the value set
-
-Still, for robustness purposes, the upgrade plugin will handle
-more entries.
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
----
- install/share/bootstrap-template.ldif         |  1 +
- .../updates/90-post_upgrade_plugins.update    |  1 +
- .../plugins/add_admin_krbcanonicalname.py     | 79 +++++++++++++++++++
- ipatests/test_integration/test_commands.py    | 38 +++++++++
- 4 files changed, 119 insertions(+)
- create mode 100644 ipaserver/install/plugins/add_admin_krbcanonicalname.py
-
-diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
-index 325eb8450..94972eb72 100644
---- a/install/share/bootstrap-template.ldif
-+++ b/install/share/bootstrap-template.ldif
-@@ -239,6 +239,7 @@ objectClass: ipasshuser
- uid: admin
- krbPrincipalName: admin@$REALM
- krbPrincipalName: root@$REALM
-+krbCanonicalName: admin@$REALM
- cn: Administrator
- sn: Administrator
- uidNumber: $IDSTART
-diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
-index 7c3bba3e0..3d78c7b5a 100644
---- a/install/updates/90-post_upgrade_plugins.update
-+++ b/install/updates/90-post_upgrade_plugins.update
-@@ -25,6 +25,7 @@ plugin: update_mapping_Guests_to_nobody
- plugin: fix_kra_people_entry
- plugin: update_pwpolicy
- plugin: update_pwpolicy_grace
-+plugin: add_admin_krbcanonicalname
- 
- # last
- # DNS version 1
-diff --git a/ipaserver/install/plugins/add_admin_krbcanonicalname.py b/ipaserver/install/plugins/add_admin_krbcanonicalname.py
-new file mode 100644
-index 000000000..e9ffdf55a
---- /dev/null
-+++ b/ipaserver/install/plugins/add_admin_krbcanonicalname.py
-@@ -0,0 +1,79 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+
-+from __future__ import absolute_import
-+
-+import logging
-+
-+from ipalib import errors
-+from ipalib import Registry
-+from ipalib import Updater
-+from ipapython.dn import DN
-+
-+logger = logging.getLogger(__name__)
-+
-+register = Registry()
-+
-+
-+@register()
-+class add_admin_krbcanonicalname(Updater):
-+    """
-+    Ensures that only the admin user has the krbCanonicalName of
-+    admin@$REALM.
-+    """
-+
-+    def execute(self, **options):
-+        ldap = self.api.Backend.ldap2
-+
-+        search_filter = (
-+            "(krbcanonicalname=admin@{})".format(self.api.env.realm))
-+        try:
-+            (entries, _truncated) = ldap.find_entries(
-+                filter=search_filter, base_dn=self.api.env.basedn,
-+                time_limit=0, size_limit=0)
-+        except errors.EmptyResult:
-+            logger.debug("add_admin_krbcanonicalname: No user set with "
-+                         "admin krbcanonicalname")
-+            entries = []
-+            # fall through
-+        except errors.ExecutionError as e:
-+            logger.error("add_admin_krbcanonicalname: Can not get list "
-+                         "of krbcanonicalname: %s", e)
-+            return False, []
-+
-+        admin_set = False
-+        # admin should be only user with admin@ as krbcanonicalname
-+        # It has a uniquness setting so there can be only one, we
-+        # just didn't automatically set it for admin.
-+        for entry in entries:
-+            if entry.single_value.get('uid') != 'admin':
-+                logger.critical(
-+                    "add_admin_krbcanonicalname: "
-+                    "entry %s has a krbcanonicalname of admin. Removing.",
-+                    entry.dn)
-+                del entry['krbcanonicalname']
-+                ldap.update_entry(entry)
-+            else:
-+                admin_set = True
-+
-+        if not admin_set:
-+            dn = DN(
-+                ('uid', 'admin'),
-+                self.api.env.container_user,
-+                self.api.env.basedn)
-+            entry = ldap.get_entry(dn)
-+            entry['krbcanonicalname'] = 'admin@%s' % self.api.env.realm
-+            try:
-+                ldap.update_entry(entry)
-+            except errors.DuplicateEntry:
-+                logger.critical(
-+                    "add_admin_krbcanonicalname: "
-+                    "Failed to set krbcanonicalname on admin. It is set "
-+                    "on another entry.")
-+            except errors.ExecutionError as e:
-+                logger.critical(
-+                    "add_admin_krbcanonicalname: "
-+                    "Failed to set krbcanonicalname on admin: %s", e)
-+
-+        return False, []
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 9cad57721..ad97affe6 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -2179,6 +2179,44 @@ class TestIPACommandWithoutReplica(IntegrationTest):
- 
-         assert isrgrootx1_nick in result
- 
-+    def test_unique_krbcanonicalname(self):
-+        """Verify that the uniqueness for krbcanonicalname is working"""
-+        master = self.master
-+
-+        base_dn = str(master.domain.basedn)
-+        hostname = master.hostname
-+        realm = master.domain.realm
-+        principal = f'test/{hostname}@{realm}'
-+        entry_ldif = textwrap.dedent("""
-+            dn: krbprincipalname={principal},cn=services,cn=accounts,{base_dn}
-+            changetype: add
-+            ipakrbprincipalalias: test/{hostname}@{realm}
-+            krbprincipalname: {principal}
-+            objectclass: ipakrbprincipal
-+            objectclass: ipaobject
-+            objectclass: ipaservice
-+            objectclass: krbprincipal
-+            objectclass: krbprincipalaux
-+            objectclass: top
-+            krbcanonicalname: admin@{realm}
-+            managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn}
-+        """).format(
-+            base_dn=base_dn,
-+            hostname=hostname,
-+            principal=principal,
-+            realm=realm)
-+        tasks.kdestroy_all(master)
-+        master.run_command(
-+            ['kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}'])
-+        args = [
-+            'ldapmodify',
-+            '-Y',
-+            'GSSAPI'
-+        ]
-+        result = master.run_command(args, stdin_text=entry_ldif,
-+                                    raiseonerr=False)
-+        assert "entry with the same attribute value" in result.stderr_text
-+
- 
- class TestIPAautomount(IntegrationTest):
-     @classmethod
--- 
-2.49.0
-

diff --git a/freeipa-4-12-2-post-updates-3.patch b/freeipa-4-12-2-post-updates-3.patch
deleted file mode 100644
index 3df39c0..0000000
--- a/freeipa-4-12-2-post-updates-3.patch
+++ /dev/null
@@ -1,4621 +0,0 @@
-From 39e92c4033d0ecd702281f3ecbeac3b5f654e973 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 19 Jun 2025 17:17:44 +0200
-Subject: [PATCH 01/47] ipatests: fix test_adtrust_install_with_non_ipa_user
-
-Fix the test scenario:
-create a user with a second krbprincipalname but no
-krbcanonical name.
-kinit -E with the other name
-try ipa-adtrust-install with the other name
-It should fail with the error message 'user not found'
-
-Fixes: https://pagure.io/freeipa/issue/9812
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
----
- .../test_integration/test_adtrust_install.py  | 48 ++++++++++++++-----
- 1 file changed, 36 insertions(+), 12 deletions(-)
-
-diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
-index 99d302944..09e227ec8 100644
---- a/ipatests/test_integration/test_adtrust_install.py
-+++ b/ipatests/test_integration/test_adtrust_install.py
-@@ -360,27 +360,51 @@ class TestIpaAdTrustInstall(IntegrationTest):
-         assert msg in result.stdout_text
-         assert result.returncode == 0
- 
--    def test_adtrust_install_with_non_ipa_user(self):
-+    @pytest.fixture
-+    def create_user(self):
-+        # create a user with 'othername' as 2nd krbprincipalname but
-+        # no krbcanonicalname
-+        basedn = self.master.domain.basedn
-+        self.test_user = 'idmuser'
-+        self.test_alias = 'othername'
-+        tasks.create_active_user(
-+            self.master, self.test_user, self.master.config.admin_password,
-+            first=self.test_user, last=self.test_user)
-+        user_update_ldif = textwrap.dedent("""
-+            dn: uid={user},cn=users,cn=accounts,{base_dn}
-+            changetype: modify
-+            add: krbprincipalname
-+            krbprincipalname: {alias}@{realm}
-+            -
-+            delete: krbcanonicalname
-+            """.format(base_dn=basedn, user=self.test_user,
-+                       alias=self.test_alias, realm=self.master.domain.realm))
-+        tasks.ldapmodify_dm(self.master, user_update_ldif)
-+        yield
-+        tasks.kinit_admin(self.master)
-+        self.master.run_command(["ipa", "user-del", self.test_user])
-+
-+    def test_adtrust_install_with_user_missing_krbcanonical(self, create_user):
-         """
-         Test that ipa-adtrust-install command returns
--        an error when kinit is done as alias
--        i.e root which is not an ipa user.
-+        an error when kinit is done as an alias
-+        for which there is no krbcanonicalname.
-         """
--        msg = (
--            'Unrecognized error during check of admin rights: '
--            'root: user not found'
--        )
--        user = 'root'
-+        self.master.run_command(["kdestroy", "-A"])
-         self.master.run_command(
--            ["kinit", "-E", user],
--            stdin_text=self.master.config.admin_password
--        )
-+            ["kinit", "-E", self.test_alias],
-+            stdin_text=self.master.config.admin_password)
-+
-         result = self.master.run_command(
--            ["ipa-adtrust-install", "-A", user,
-+            ["ipa-adtrust-install", "-A", self.test_alias,
-              "-a", self.master.config.admin_password,
-              "-U"], raiseonerr=False
-         )
-         assert result.returncode != 0
-+        msg = (
-+            'Unrecognized error during check of admin rights: '
-+            '{alias}: user not found'
-+        ).format(alias=self.test_alias)
-         assert msg in result.stderr_text
- 
-     def test_adtrust_install_as_regular_ipa_user(self):
--- 
-2.51.0
-
-
-From fba7aa10c8487116075d56c8dedeebefc40b74eb Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 16 Jun 2025 18:15:22 +0200
-Subject: [PATCH 02/47] ipa-idrange-fix: check that IPA server is installed
-
-If ipa-idrange-fix is called on a system where the server is not configured,
-it crashes with a Traceback when trying to access api.env.basedn.
-
-Check that IPA server is configured before processing further
-
-ipatests: add test launching ipa-idrange-fix on unconfigured server
-
-Fixes: https://pagure.io/freeipa/issue/9809
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: David Hanina <dhanina@redhat.com>
----
- ipaserver/install/ipa_idrange_fix.py                   |  3 +++
- .../test_integration/test_cli_ipa_not_configured.py    | 10 ++++++++++
- 2 files changed, 13 insertions(+)
-
-diff --git a/ipaserver/install/ipa_idrange_fix.py b/ipaserver/install/ipa_idrange_fix.py
-index c6c67ae93..cd21ed428 100644
---- a/ipaserver/install/ipa_idrange_fix.py
-+++ b/ipaserver/install/ipa_idrange_fix.py
-@@ -10,6 +10,7 @@ from ipalib import api, errors
- from ipapython.admintool import AdminTool
- from ipapython.dn import DN
- from ipapython import ipautil
-+from ipaserver.install.installutils import check_server_configuration
- from typing import List, Tuple
- 
- logger = logging.getLogger(__name__)
-@@ -169,6 +170,8 @@ for confirmation",
-         super().validate_options(needs_root)
- 
-     def run(self):
-+        check_server_configuration()
-+
-         api.bootstrap(in_server=True)
-         api.finalize()
- 
-diff --git a/ipatests/test_integration/test_cli_ipa_not_configured.py b/ipatests/test_integration/test_cli_ipa_not_configured.py
-index 1bf36d8ee..7c5601247 100644
---- a/ipatests/test_integration/test_cli_ipa_not_configured.py
-+++ b/ipatests/test_integration/test_cli_ipa_not_configured.py
-@@ -22,3 +22,13 @@ class TestIPANotConfigured(IntegrationTest):
-         assert (exp_str in cmd.stderr_text and
-                 cmd.returncode == SERVER_NOT_CONFIGURED and
-                 unexp_str not in cmd.stderr_text)
-+
-+    def test_ipa_idrange_fix(self):
-+        """
-+        Test for https://pagure.io/freeipa/issue/9809
-+        Launch ipa-idrange-fix command when the server is not configured.
-+        """
-+        exp_str = "IPA is not configured"
-+        cmd = self.master.run_command(["ipa-idrange-fix"], raiseonerr=False)
-+        assert (exp_str in cmd.stderr_text
-+                and cmd.returncode == SERVER_NOT_CONFIGURED)
--- 
-2.51.0
-
-
-From 7301f00b4fcd1159f9c93fc109b95e0f0b71ffc4 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Wed, 28 May 2025 11:23:06 -0400
-Subject: [PATCH 03/47] Add token options to immutables for pki override
-
-Setting the HSM options in a pki-override-file will
-result in a successful installation but it affects the
-storage of the token library path. It is stored as None.
-
-We could also instead save the provided value from the
-override but an override isn't necessary in this case
-since IPA provides command-line options to enable it.
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- doc/designs/hsm.md                            |  3 +-
- ipaserver/install/dogtaginstance.py           |  6 +++
- .../test_pki_config_override.py               | 45 +++++++++++++++++++
- 3 files changed, 53 insertions(+), 1 deletion(-)
-
-diff --git a/doc/designs/hsm.md b/doc/designs/hsm.md
-index 786f93e31..9e7557f47 100644
---- a/doc/designs/hsm.md
-+++ b/doc/designs/hsm.md
-@@ -32,7 +32,8 @@ Only RSA keys will be supported.
- 
- Using an HSM should be largely invisible to users and administrators beyond passing additional options during installation. The options required and any pre-installion work are HSM-specific.
- 
--It will not be possible to mix and match by default. PKI supports specifying the token values so a user can override these using --pki-config-override but it is, and will be, untested.
-+It will not be possible to mix and match by default. IPA allows overriding some, but not all, of the PKI options. The following HSM-specific options are added to the not allowed list: pki_hsm_enable, pki_hsm_libfile, pki_hsm_modulename, pki_token_name, pki_token_password. IPA requires that its HSM command-line options are used.
-+
- 
- There are a few basic rules:
- 
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index d5796c75a..1f18d7fae 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -1042,6 +1042,12 @@ class PKIIniLoader:
-         'pki_clone_replication_clone_port',
-         'pki_clone_replicate_schema',
-         'pki_clone_uri',
-+        # hsm
-+        'pki_hsm_enable',
-+        'pki_hsm_libfile',
-+        'pki_hsm_modulename',
-+        'pki_token_name',
-+        'pki_token_password',
-         # cainstance
-         'pki_ds_secure_connection',
-         'pki_server_database_password',
-diff --git a/ipatests/test_integration/test_pki_config_override.py b/ipatests/test_integration/test_pki_config_override.py
-index d520b00c4..a7fd9296e 100644
---- a/ipatests/test_integration/test_pki_config_override.py
-+++ b/ipatests/test_integration/test_pki_config_override.py
-@@ -20,6 +20,25 @@ ipa_ca_key_algorithm=SHA512withRSA
- ipa_ca_signing_algorithm=SHA512withRSA
- """
- 
-+HSM_OVERRIDE = """
-+[CA]
-+pki_hsm_enable = True
-+pki_hsm_libfile = /usr/lib64/pkcs11/libsofthsm2.so
-+pki_hsm_modulename = libsofthsm2
-+pki_token_name = ipa_token
-+pki_token_password = Secret123
-+"""
-+
-+
-+def server_install_teardown(func):
-+    def wrapped(*args):
-+        master = args[0].master
-+        try:
-+            func(*args)
-+        finally:
-+            tasks.uninstall_master(master)
-+    return wrapped
-+
- 
- class TestPKIConfigOverride(IntegrationTest):
-     @classmethod
-@@ -40,3 +59,29 @@ class TestPKIConfigOverride(IntegrationTest):
-         cert = load_pem_x509_certificate(ca_pem)
-         assert cert.public_key().key_size == 4096
-         assert cert.signature_hash_algorithm.name == hashes.SHA512.name
-+
-+
-+class TestPKIHSMConfigOverride(IntegrationTest):
-+    num_replicas = 0
-+
-+    @classmethod
-+    def install(cls, mh):
-+        pass
-+
-+    @server_install_teardown
-+    def test_immutable_options(self):
-+        """Providing the immunible HSM options should fail"""
-+        pki_ini = tasks.upload_temp_contents(self.master, HSM_OVERRIDE)
-+        extra_args = [
-+            '--pki-config-override', pki_ini,
-+        ]
-+        result = tasks.install_master(
-+            self.master, setup_dns=False, extra_args=extra_args,
-+            raiseonerr=False
-+        )
-+        self.master.run_command(['rm', '-f', pki_ini])
-+        tasks.assert_error(
-+            result,
-+            'immutable options: pki_hsm_enable, pki_hsm_libfile, '
-+            'pki_hsm_modulename, pki_token_name, pki_token_password'
-+        )
--- 
-2.51.0
-
-
-From ceaa1c9a244499534343dc667227e47a923212ee Mon Sep 17 00:00:00 2001
-From: Mark Reynolds <mreynolds@redhat.com>
-Date: Tue, 17 Jun 2025 12:50:36 -0400
-Subject: [PATCH 04/47] ipa-migrate - only remove repl state attribute options
-
-Improve how we process attributes that might include replication state
-data. Previously we only cared about ";binary" but there are other
-attribute options that are used in IPA. Now we completely break down the
-attribute into each option and rebuild it without any repl state options
-
-Fixes: https://pagure.io/freeipa/issue/9784
-
-Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ipa_migrate.py           | 17 +++++++++--------
- ipaserver/install/ipa_migrate_constants.py |  2 ++
- 2 files changed, 11 insertions(+), 8 deletions(-)
-
-diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
-index a24a2ab7a..b26fb6685 100644
---- a/ipaserver/install/ipa_migrate.py
-+++ b/ipaserver/install/ipa_migrate.py
-@@ -33,7 +33,7 @@ from ipaserver.install.ipa_migrate_constants import (
-     DS_CONFIG, DB_OBJECTS, DS_INDEXES, BIND_DN, LOG_FILE_NAME,
-     STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS,
-     DNA_REGEN_VAL, DNA_REGEN_ATTRS, NIS_PLUGIN, IGNORE_ATTRS,
--    DB_EXCLUDE_TREES, POLICY_OP_ATTRS
-+    DB_EXCLUDE_TREES, POLICY_OP_ATTRS, STATE_OPTIONS
- )
- 
- """
-@@ -202,14 +202,15 @@ def decode_attr_vals(entry_attrs):
-     decoded_attrs = {}
-     for attr in entry_attrs:
-         vals = ensure_list_str(entry_attrs[attr])
--        # Remove replication state data, but don't remove ";binary"
--        # e.g.  userCertififccate;binary;adcsn=<CSN>
-+        # Remove "only" replication state data, but don't remove other attr
-+        # options like ";binary"
-+        #    e.g.  userCertificate;binary;adcsn=<CSN>
-         parts = attr.split(";")
--        if len(parts) > 1 and not attr.endswith(";binary"):
--            if parts[1] == "binary":
--                attr = parts[0] + ";binary"
--            else:
--                attr = parts[0]
-+        attr_parts = [
-+            parts[0]] + [p for p in parts[1:]
-+                         if not any(p.startswith(opt)
-+                                    for opt in STATE_OPTIONS)]
-+        attr = (';').join(attr_parts)
-         decoded_attrs[attr] = vals
-     return decoded_attrs
- 
-diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
-index 4beaa4f42..19cd51413 100644
---- a/ipaserver/install/ipa_migrate_constants.py
-+++ b/ipaserver/install/ipa_migrate_constants.py
-@@ -117,6 +117,8 @@ AD_TRUST_ATTRS = [  # ipaNTTrustedDomain objectclass
-     'ipantadditionalsuffixes',
- ]
- 
-+STATE_OPTIONS = ('adcsn-', 'mdcsn-', 'vucsn-', 'vdcsn-')
-+
- DNA_REGEN_VAL = "-1"
- 
- DNA_REGEN_ATTRS = [
--- 
-2.51.0
-
-
-From cafaed1c2119fb2e25209eefac74bc21ccab3dcb Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Thu, 25 Jul 2024 19:35:33 +0200
-Subject: [PATCH 05/47] ipa-kdb: support storing multiple KVNO for the same
- principal
-
-All MIT krb5 keys are encoded with an encryption type identifier and a
-KVNO (key version number). The KVNO is referring to the original
-credentials string (and its associated salt) which was used to generate
-the key using the derivation function of the associated encryption type.
-
-So far, when a set of Kerberos keys was provided to ipa-kdb, only the
-newest KVNO ones were saved in a single "krbPrincipalKey" LDAP
-attribute. All the older ones were deleted in the process.
-
-This commit allows to keep older keys by splitting them in multiple
-"krbPrincipalKey" attribute based on their KVNO.
-
-Fixes: https://pagure.io/freeipa/issue/9370
-Signed-off-by: Julien Rische <jrische@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb_principals.c | 125 +++++++++++++++++++++++----
- util/ipa_krb5.c                      |   7 ++
- 2 files changed, 115 insertions(+), 17 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
-index 114957b88..19998c2a3 100644
---- a/daemons/ipa-kdb/ipa_kdb_principals.c
-+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
-@@ -22,6 +22,7 @@
- 
- #include "ipa_kdb.h"
- #include "ipa_krb5.h"
-+#include <stdlib.h>
- #include <unicase.h>
- 
- /*
-@@ -279,22 +280,64 @@ done:
- static int ipadb_ldap_attr_to_key_data(LDAP *lcontext, LDAPMessage *le,
-                                        char *attrname,
-                                        krb5_key_data **result, int *num,
--                                       krb5_kvno *res_mkvno)
-+                                       krb5_kvno *mkvno)
- {
--    struct berval **vals;
--    int mkvno;
-+    struct berval **vals, **p;
-+    krb5_key_data *cur_res = NULL, *fin_res = NULL, *tmp_res;
-+    int fin_mkvno = 0, cur_mkvno, cur_num, fin_num = 0;
-     int ret;
- 
-     vals = ldap_get_values_len(lcontext, le, attrname);
--    if (!vals) {
-+    if (!vals)
-         return ENOENT;
-+
-+    for (p = vals; *p; ++p) {
-+        ret = ber_decode_krb5_key_data(*p, &cur_mkvno, &cur_num, &cur_res);
-+        if (ret)
-+            goto end;
-+
-+        /* All keys in a principal entry should be encrypted with the same
-+         * master key. */
-+        if (fin_mkvno == 0) {
-+            fin_mkvno = cur_mkvno;
-+        } else if (cur_mkvno != fin_mkvno) {
-+            ret = EINVAL;
-+            goto end;
-+        }
-+
-+        if (!fin_res) {
-+            fin_res = cur_res;
-+        } else {
-+            tmp_res = realloc(fin_res, (fin_num + cur_num) * sizeof(*fin_res));
-+            if (!tmp_res) {
-+                ret = ENOMEM;
-+                goto end;
-+            } else {
-+                fin_res = tmp_res;
-+            }
-+
-+            memcpy(fin_res + fin_num, cur_res, cur_num * sizeof(*fin_res));
-+            free(cur_res);
-+        }
-+
-+        cur_res = NULL;
-+        fin_num += cur_num;
-     }
- 
--    ret = ber_decode_krb5_key_data(vals[0], &mkvno, num, result);
--    ldap_value_free_len(vals);
--    if (ret == 0) {
--        *res_mkvno = mkvno;
-+    if (mkvno)
-+        *mkvno = fin_mkvno;
-+    if (num)
-+        *num = fin_num;
-+    if (result) {
-+        *result = fin_res;
-+    } else {
-+        free(fin_res);
-     }
-+
-+end:
-+    ldap_value_free_len(vals);
-+    if (cur_res && fin_res != cur_res)
-+        free(cur_res);
-     return ret;
- }
- 
-@@ -2532,15 +2575,26 @@ static krb5_error_code ipadb_get_mkvno_from_tl_data(krb5_tl_data *tl_data,
-     return 0;
- }
- 
-+static int desc_key_data(const void *a, const void *b)
-+{
-+    const krb5_key_data *ka = a;
-+    const krb5_key_data *kb = b;
-+
-+    return ka->key_data_kvno != kb->key_data_kvno
-+        ? kb->key_data_kvno - ka->key_data_kvno
-+        : kb->key_data_type[0] - ka->key_data_type[0];
-+}
-+
- static krb5_error_code ipadb_get_ldap_mod_key_data(struct ipadb_mods *imods,
-                                                    krb5_key_data *key_data,
-                                                    int n_key_data, int mkvno,
-                                                    int mod_op)
- {
-     krb5_error_code kerr;
--    struct berval *bval = NULL;
-+    krb5_key_data *kvno_kdata;
-+    struct berval **bvals = NULL;
-     LDAPMod *mod;
--    int ret;
-+    int i, j, begin, n_kvno;
- 
-     /* If the key data is empty, remove all keys. */
-     if (n_key_data == 0 || key_data == NULL) {
-@@ -2559,19 +2613,56 @@ static krb5_error_code ipadb_get_ldap_mod_key_data(struct ipadb_mods *imods,
-         return 0;
-     }
- 
--    ret = ber_encode_krb5_key_data(key_data, n_key_data, mkvno, &bval);
--    if (ret != 0) {
--        kerr = ret;
-+    /* Copy key list. */
-+    kvno_kdata = calloc(n_key_data, sizeof(*kvno_kdata));
-+    if (!kvno_kdata)
-+        return ENOMEM;
-+
-+    memcpy(kvno_kdata, key_data, n_key_data * sizeof(*kvno_kdata));
-+
-+    /* Make sure the key list is sorted by KVNO and enctype. */
-+    qsort(kvno_kdata, n_key_data, sizeof(*kvno_kdata), desc_key_data);
-+
-+    /* Count number of distinct KVNOs. */
-+    for (i = 1, n_kvno = 1; i < n_key_data; ++i) {
-+        if (kvno_kdata[i - 1].key_data_kvno != kvno_kdata[i].key_data_kvno)
-+            ++n_kvno;
-+    }
-+
-+    bvals = calloc(n_kvno, sizeof(*bvals));
-+    if (!bvals) {
-+        kerr = ENOMEM;
-         goto done;
-     }
- 
--    kerr = ipadb_get_ldap_mod_bvalues(imods, "krbPrincipalKey",
--                                      &bval, 1, mod_op);
-+    /* Add a "krbPrincipalKey" attribute for each KVNO. */
-+    for (i = 0, j = 0, begin = 0; i < n_key_data; ++i) {
-+        if (kvno_kdata[begin].key_data_kvno != kvno_kdata[i].key_data_kvno) {
-+            kerr = ber_encode_krb5_key_data(kvno_kdata + begin, i - begin,
-+                                            mkvno, bvals + j);
-+            if (kerr)
-+                goto done;
-+
-+            begin = i;
-+            ++j;
-+        }
-+    }
-+
-+    kerr = ber_encode_krb5_key_data(kvno_kdata + begin, i - begin, mkvno,
-+                                    bvals + j);
-+    if (kerr)
-+        goto done;
-+
-+    kerr = ipadb_get_ldap_mod_bvalues(imods, "krbPrincipalKey", bvals, n_kvno,
-+                                      mod_op);
- 
- done:
--    if (kerr) {
--        ber_bvfree(bval);
-+    if (kerr && bvals) {
-+        for (i = 0; i < n_kvno; ++i)
-+            ber_bvfree(bvals[i]);
-+        free(bvals);
-     }
-+    free(kvno_kdata);
-     return kerr;
- }
- 
-diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c
-index 0087e53e6..fbd4d57de 100644
---- a/util/ipa_krb5.c
-+++ b/util/ipa_krb5.c
-@@ -394,6 +394,13 @@ int ber_encode_krb5_key_data(krb5_key_data *data,
- 
-     for (i = 0; i < numk; i++) {
- 
-+        /* All keys must have the same KVNO, because there is only one attribute
-+         * for all of them. */
-+        if (data[i].key_data_kvno != data[0].key_data_kvno) {
-+            ret = EINVAL;
-+            goto done;
-+        }
-+
-         ret = ber_printf(be, "{");
-         if (ret == -1) {
-             ret = EFAULT;
--- 
-2.51.0
-
-
-From fb90a9492150d668003984345dcac874c4e26e61 Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Wed, 12 Mar 2025 13:49:47 +0100
-Subject: [PATCH 06/47] Use ipaplatform tasks for krb5 enctypes
-
-Provide the master key encryption type and the list of supported
-encryption types as ipaplatform.<platform>.tasks methods. This allows
-to generate the list at runtime based on the environment (e.g. FIPS) and
-override the list depending of the platform.
-
-3DES HMAC-SHA1 encryption type is now removed from supported encryption
-types if it is present.
-
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/share/kerberos.ldif           | 18 +-----------------
- install/updates/50-krbenctypes.update | 11 +----------
- ipaplatform/base/tasks.py             | 25 +++++++++++++++++++++++++
- ipaplatform/redhat/tasks.py           |  8 ++++++++
- ipaserver/install/krbinstance.py      | 27 +++++++++++----------------
- ipaserver/install/ldapupdate.py       | 17 ++++++++++++++++-
- 6 files changed, 62 insertions(+), 44 deletions(-)
-
-diff --git a/install/share/kerberos.ldif b/install/share/kerberos.ldif
-index 3a5c30ec8..78c29923b 100644
---- a/install/share/kerberos.ldif
-+++ b/install/share/kerberos.ldif
-@@ -14,25 +14,9 @@ objectClass: krbrealmcontainer
- objectClass: krbticketpolicyaux
- krbSubTrees: $SUFFIX
- krbSearchScope: 2
--krbSupportedEncSaltTypes: aes256-cts:normal
--krbSupportedEncSaltTypes: aes256-cts:special
--krbSupportedEncSaltTypes: aes128-cts:normal
--krbSupportedEncSaltTypes: aes128-cts:special
--krbSupportedEncSaltTypes: aes128-sha2:normal
--krbSupportedEncSaltTypes: aes128-sha2:special
--krbSupportedEncSaltTypes: aes256-sha2:normal
--krbSupportedEncSaltTypes: aes256-sha2:special
--${FIPS}krbSupportedEncSaltTypes: camellia128-cts-cmac:normal
--${FIPS}krbSupportedEncSaltTypes: camellia128-cts-cmac:special
--${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:normal
--${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:special
- krbMaxTicketLife: 86400
- krbMaxRenewableAge: 604800
--krbDefaultEncSaltTypes: aes256-sha2:special
--krbDefaultEncSaltTypes: aes128-sha2:special
--krbDefaultEncSaltTypes: aes256-cts:special
--krbDefaultEncSaltTypes: aes128-cts:special
--
-+${LDIF_SUPPORTED_ENCTYPES}${LDIF_DEFAULT_ENCTYPES}
- # Default password Policy
- dn: cn=global_policy,cn=$REALM,cn=kerberos,$SUFFIX
- changetype: add
-diff --git a/install/updates/50-krbenctypes.update b/install/updates/50-krbenctypes.update
-index 1bf2bf33a..3061b98c9 100644
---- a/install/updates/50-krbenctypes.update
-+++ b/install/updates/50-krbenctypes.update
-@@ -1,11 +1,2 @@
- dn: cn=$REALM,cn=kerberos,$SUFFIX
--${FIPS}add: krbSupportedEncSaltTypes: camellia128-cts-cmac:normal
--${FIPS}add: krbSupportedEncSaltTypes: camellia128-cts-cmac:special
--${FIPS}add: krbSupportedEncSaltTypes: camellia256-cts-cmac:normal
--${FIPS}add: krbSupportedEncSaltTypes: camellia256-cts-cmac:special
--add: krbSupportedEncSaltTypes: aes128-sha2:normal
--add: krbSupportedEncSaltTypes: aes128-sha2:special
--add: krbSupportedEncSaltTypes: aes256-sha2:normal
--add: krbSupportedEncSaltTypes: aes256-sha2:special
--remove: krbDefaultEncSaltTypes: des3-hmac-sha1:special
--remove: krbDefaultEncSaltTypes: arcfour-hmac:special
-+${ADD_SUPPORTED_ENCTYPES}${ADD_DEFAULT_ENCTYPES}${REMOVE_SUPPORTED_ENCTYPES}${REMOVE_DEFAULT_ENCTYPES}
-diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
-index 4108a7ced..9e221d872 100644
---- a/ipaplatform/base/tasks.py
-+++ b/ipaplatform/base/tasks.py
-@@ -540,4 +540,29 @@ class BaseTaskNamespace:
-                 'ipa-client-automount-nsswitch', 'previous-automount'
-             )
- 
-+    def get_masterkey_enctype(self):
-+        return 'aes256-sha2'
-+
-+    # Encryption types allowed for Kerberos keys
-+    def get_supported_enctypes(self):
-+        return ('aes256-sha2:special', 'aes128-sha2:special',
-+                'aes256-sha2:normal', 'aes128-sha2:normal',
-+                'aes256-cts:special', 'aes128-cts:special',
-+                'aes256-cts:normal', 'aes128-cts:normal',
-+                'camellia256-cts:special', 'camellia128-cts:special',
-+                'camellia256-cts:normal', 'camellia128-cts:normal')
-+
-+    # Encryption types used in the past, not supported anymore
-+    def get_removed_supported_enctypes(self):
-+        return ('des3-hmac-sha1:special')
-+
-+    # Encryption types used by default when generating Kerberos keys
-+    def get_default_enctypes(self):
-+        return ('aes256-sha2:special', 'aes128-sha2:special',
-+                'aes256-cts:special', 'aes128-cts:special')
-+
-+    # Encryption types no longer used by default when generating keys
-+    def get_removed_default_enctypes(self):
-+        return ('des3-hmac-sha1:special', 'arcfour-hmac:special')
-+
- tasks = BaseTaskNamespace()
-diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
-index 18203bf0e..4953dc78a 100644
---- a/ipaplatform/redhat/tasks.py
-+++ b/ipaplatform/redhat/tasks.py
-@@ -751,5 +751,13 @@ class RedHatTaskNamespace(BaseTaskNamespace):
-             logger.info("It may happen if the configuration was done "
-                         "using authconfig instead of authselect")
- 
-+    def get_supported_enctypes(self):
-+        enctypes = super().get_supported_enctypes()
-+
-+        if not self.is_fips_enabled():
-+            return enctypes
-+
-+        return tuple(e for e in enctypes if not e.startswith('camellia'))
-+
- 
- tasks = RedHatTaskNamespace()
-diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
-index 9f0eea563..fb1a28842 100644
---- a/ipaserver/install/krbinstance.py
-+++ b/ipaserver/install/krbinstance.py
-@@ -55,14 +55,6 @@ from ipaplatform.paths import paths
- 
- logger = logging.getLogger(__name__)
- 
--MASTER_KEY_TYPE = 'aes256-sha2'
--SUPPORTED_ENCTYPES = ('aes256-sha2:special', 'aes128-sha2:special',
--                      'aes256-sha2:normal', 'aes128-sha2:normal',
--                      'aes256-cts:special', 'aes128-cts:special',
--                      'aes256-cts:normal', 'aes128-cts:normal',
--                      'camellia256-cts:special', 'camellia128-cts:special',
--                      'camellia256-cts:normal', 'camellia128-cts:normal')
--
- 
- def get_pkinit_request_ca():
-     """
-@@ -299,15 +291,18 @@ class KrbInstance(service.Service):
-                              INCLUDES=includes,
-                              FIPS='#' if fips_enabled else '')
- 
--        if fips_enabled:
--            supported_enctypes = list(
--                filter(lambda e: not e.startswith('camellia'),
--                       SUPPORTED_ENCTYPES))
--        else:
--            supported_enctypes = SUPPORTED_ENCTYPES
--        self.sub_dict['SUPPORTED_ENCTYPES'] = ' '.join(supported_enctypes)
-+        supported_enctypes = tasks.get_supported_enctypes()
-+        str_supported_enctypes = ' '.join(supported_enctypes)
-+        ldif_supported_enctypes = ''.join(f'krbSupportedEncSaltTypes: {e}\n'
-+                                          for e in supported_enctypes)
-+        ldif_default_enctypes = ''.join(f'krbDefaultEncSaltTypes: {e}\n'
-+                                        for e in tasks.get_default_enctypes())
-+
-+        self.sub_dict['SUPPORTED_ENCTYPES'] = str_supported_enctypes
-+        self.sub_dict['LDIF_SUPPORTED_ENCTYPES'] = ldif_supported_enctypes
-+        self.sub_dict['LDIF_DEFAULT_ENCTYPES'] = ldif_default_enctypes
- 
--        self.sub_dict['MASTER_KEY_TYPE'] = MASTER_KEY_TYPE
-+        self.sub_dict['MASTER_KEY_TYPE'] = tasks.get_masterkey_enctype()
- 
-         # IPA server/KDC is not a subdomain of default domain
-         # Proper domain-realm mapping needs to be specified
-diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
-index c3b59732a..9a1a1167e 100644
---- a/ipaserver/install/ldapupdate.py
-+++ b/ipaserver/install/ldapupdate.py
-@@ -54,6 +54,9 @@ UPDATES_DIR=paths.UPDATES_DIR
- UPDATE_SEARCH_TIME_LIMIT = 30  # seconds
- 
- 
-+def ldif_mod(op, attr, values):
-+    return ''.join(f'{op}: {attr}: {v}\n' for v in values)
-+
- def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-     """LDAP template substitution dict for installer and updater
-     """
-@@ -73,6 +76,15 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         named_uid = None
-         named_gid = None
- 
-+    add_supported_enctypes = ldif_mod('add', 'krbSupportedEncSaltTypes',
-+                                      tasks.get_supported_enctypes())
-+    add_default_enctypes = ldif_mod('add', 'krbDefaultEncSaltTypes',
-+                                    tasks.get_default_enctypes())
-+    rm_supported_enctypes = ldif_mod('remove', 'krbSupportedEncSaltTypes',
-+                                     tasks.get_removed_supported_enctypes())
-+    rm_default_enctypes = ldif_mod('remove', 'krbDefaultEncSaltTypes',
-+                                   tasks.get_removed_default_enctypes())
-+
-     return dict(
-         REALM=realm,
-         DOMAIN=domain,
-@@ -82,7 +94,10 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         HOST=fqdn,
-         LIBARCH=paths.LIBARCH,
-         TIME=int(time.time()),
--        FIPS="#" if tasks.is_fips_enabled() else "",
-+        ADD_SUPPORTED_ENCTYPES=add_supported_enctypes,
-+        ADD_DEFAULT_ENCTYPES=add_default_enctypes,
-+        REMOVE_SUPPORTED_ENCTYPES=rm_supported_enctypes,
-+        REMOVE_DEFAULT_ENCTYPES=rm_default_enctypes,
-         # idstart, idmax, and idrange_size may be None
-         IDSTART=idstart,
-         IDMAX=idmax,
--- 
-2.51.0
-
-
-From b69616ed48224b48b5615044314787f69af13c13 Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Wed, 12 Mar 2025 13:50:46 +0100
-Subject: [PATCH 07/47] Add test for master key upgrade
-
-Since commit 4ed7da378940198cf4415f86d4eb013de6ac6455 in MIT krb5,
-kdb5_util sets the required bits on the modification mask of updated
-principal entries to enable ipa-kdb to switch the active master key.
-
-This commit creates a "test_fedora_legacy" ipaplatform where the AES
-HMAC-SHA2 encryption types are not enabled. "test_mkey_upgrade" uses
-this platform to initialize a domain with an aes256-cts-hmac-sha1-96
-master key, and test its upgrade to aes256-cts-hmac-sha384-192.
-
-Some parts of the test infrastructure had to be made aware of this new
-platform (e.g. firewall integration).
-
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaplatform/setup.py                          |   1 +
- ipaplatform/test_fedora_legacy/__init__.py    |   7 +
- ipaplatform/test_fedora_legacy/constants.py   |  16 +++
- ipaplatform/test_fedora_legacy/paths.py       |  13 ++
- ipaplatform/test_fedora_legacy/services.py    |  27 ++++
- ipaplatform/test_fedora_legacy/tasks.py       |  35 +++++
- ipatests/pytest_ipa/integration/firewall.py   |   1 +
- .../test_integration/test_mkey_upgrade.py     | 135 ++++++++++++++++++
- 8 files changed, 235 insertions(+)
- create mode 100644 ipaplatform/test_fedora_legacy/__init__.py
- create mode 100644 ipaplatform/test_fedora_legacy/constants.py
- create mode 100644 ipaplatform/test_fedora_legacy/paths.py
- create mode 100644 ipaplatform/test_fedora_legacy/services.py
- create mode 100644 ipaplatform/test_fedora_legacy/tasks.py
- create mode 100644 ipatests/test_integration/test_mkey_upgrade.py
-
-diff --git a/ipaplatform/setup.py b/ipaplatform/setup.py
-index 086a99d1f..1d6df1eff 100644
---- a/ipaplatform/setup.py
-+++ b/ipaplatform/setup.py
-@@ -42,6 +42,7 @@ if __name__ == '__main__':
-             "ipaplatform.rhel",
-             "ipaplatform.rhel_container",
-             "ipaplatform.suse",
-+            "ipaplatform.test_fedora_legacy",
-             "ipaplatform.opencloudos",
-             "ipaplatform.tencentos"
-         ],
-diff --git a/ipaplatform/test_fedora_legacy/__init__.py b/ipaplatform/test_fedora_legacy/__init__.py
-new file mode 100644
-index 000000000..a4f4cff5a
---- /dev/null
-+++ b/ipaplatform/test_fedora_legacy/__init__.py
-@@ -0,0 +1,7 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+"""
-+This module contains Fedora AES HMAC-SHA1 master key specific platform files.
-+"""
-+NAME = 'test_fedora_legacy'
-diff --git a/ipaplatform/test_fedora_legacy/constants.py b/ipaplatform/test_fedora_legacy/constants.py
-new file mode 100644
-index 000000000..0cebac86a
---- /dev/null
-+++ b/ipaplatform/test_fedora_legacy/constants.py
-@@ -0,0 +1,16 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+"""Fedora AES HMAC-SHA1 master key constants
-+"""
-+from ipaplatform.fedora.constants import FedoraConstantsNamespace, User, Group
-+
-+
-+__all__ = ("constants", "User", "Group")
-+
-+
-+class TestFedoraLegacyConstantsNamespace(FedoraConstantsNamespace):
-+    pass
-+
-+
-+constants = TestFedoraLegacyConstantsNamespace()
-diff --git a/ipaplatform/test_fedora_legacy/paths.py b/ipaplatform/test_fedora_legacy/paths.py
-new file mode 100644
-index 000000000..71fb8acdc
---- /dev/null
-+++ b/ipaplatform/test_fedora_legacy/paths.py
-@@ -0,0 +1,13 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+"""Fedora AES HMAC-SHA1 master key paths
-+"""
-+from ipaplatform.fedora.paths import FedoraPathNamespace
-+
-+
-+class TestFedoraLegacyPathNamespace(FedoraPathNamespace):
-+    pass
-+
-+
-+paths = TestFedoraLegacyPathNamespace()
-diff --git a/ipaplatform/test_fedora_legacy/services.py b/ipaplatform/test_fedora_legacy/services.py
-new file mode 100644
-index 000000000..adbe21f82
---- /dev/null
-+++ b/ipaplatform/test_fedora_legacy/services.py
-@@ -0,0 +1,27 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+"""Fedora AES HMAC-SHA1 master key services
-+"""
-+from ipaplatform.fedora import services as fedora_services
-+
-+
-+test_fedora_legacy_system_units = fedora_services.fedora_system_units.copy()
-+
-+
-+class TestFedoraLegacyService(fedora_services.FedoraService):
-+    system_units = test_fedora_legacy_system_units
-+
-+
-+def test_fedora_legacy_service_class_factory(name, api=None):
-+    return fedora_services.fedora_service_class_factory(name, api)
-+
-+
-+class TestFedoraLegacyServices(fedora_services.FedoraServices):
-+    def service_class_factory(self, name, api=None):
-+        return test_fedora_legacy_service_class_factory(name, api)
-+
-+
-+timedate_services = fedora_services.timedate_services
-+service = test_fedora_legacy_service_class_factory
-+knownservices = TestFedoraLegacyServices()
-diff --git a/ipaplatform/test_fedora_legacy/tasks.py b/ipaplatform/test_fedora_legacy/tasks.py
-new file mode 100644
-index 000000000..e6e72d723
---- /dev/null
-+++ b/ipaplatform/test_fedora_legacy/tasks.py
-@@ -0,0 +1,35 @@
-+#
-+# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
-+#
-+"""Fedora AES HMAC-SHA1 master key tasks
-+"""
-+from ipaplatform.fedora.tasks import FedoraTaskNamespace
-+
-+from re import compile
-+
-+
-+def add_aes_sha1(enctypes):
-+    return tuple({*enctypes,
-+                  'aes256-cts:special', 'aes128-cts:special',
-+                  'aes256-cts:normal', 'aes128-cts:normal'})
-+
-+
-+class TestFedoraLegacyTaskNamespace(FedoraTaskNamespace):
-+
-+    def get_masterkey_enctype(self):
-+        return 'aes256-cts'
-+
-+    def get_supported_enctypes(self):
-+        aes_sha2_pattern = compile('^aes[0-9]+-sha2:')
-+
-+        return tuple(e for e in super().get_supported_enctypes()
-+                     if not aes_sha2_pattern.match(e))
-+
-+    def get_removed_supported_enctypes(self):
-+        return add_aes_sha1(super().get_removed_supported_enctypes())
-+
-+    def get_removed_default_enctypes(self):
-+        return add_aes_sha1(super().get_removed_default_enctypes())
-+
-+
-+tasks = TestFedoraLegacyTaskNamespace()
-diff --git a/ipatests/pytest_ipa/integration/firewall.py b/ipatests/pytest_ipa/integration/firewall.py
-index 1a5af5c5e..01661b6c6 100644
---- a/ipatests/pytest_ipa/integration/firewall.py
-+++ b/ipatests/pytest_ipa/integration/firewall.py
-@@ -239,6 +239,7 @@ class Firewall(FirewallBase):
-         firewalls = {
-             'rhel': FirewallD,
-             'fedora': FirewallD,
-+            'test_fedora_legacy': FirewallD,
-             'debian': FirewallD,
-             'ubuntu': FirewallD,
-             'altlinux': NoOpFirewall,
-diff --git a/ipatests/test_integration/test_mkey_upgrade.py b/ipatests/test_integration/test_mkey_upgrade.py
-new file mode 100644
-index 000000000..933623fbf
---- /dev/null
-+++ b/ipatests/test_integration/test_mkey_upgrade.py
-@@ -0,0 +1,135 @@
-+#
-+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
-+#
-+
-+import re
-+import textwrap
-+
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+
-+
-+class TestMkeyUpgrade(IntegrationTest):
-+
-+    num_replicas = 1
-+    topology = 'line'
-+
-+    @classmethod
-+    def install(cls, mh):
-+        cls.master.put_file_contents(
-+            '/etc/profile.d/ipaplatform.sh',
-+            'export IPAPLATFORM_OVERRIDE=test_fedora_legacy')
-+        cls.master.run_command(['mkdir', '/etc/systemd/system/ipa.service.d'])
-+        cls.master.put_file_contents(
-+            '/etc/systemd/system/ipa.service.d/platform.conf',
-+            '[Service]\n'
-+            'Environment="IPAPLATFORM_OVERRIDE=test_fedora_legacy"')
-+        cls.master.run_command(['systemctl', 'daemon-reload'])
-+        tasks.install_master(cls.master, setup_dns=False)
-+        tasks.install_replica(cls.master, cls.replicas[0], setup_dns=False)
-+
-+    def test_old_active_mkey(self):
-+        p = re.compile('^KVNO: 1, Enctype: aes256-cts-hmac-sha1-96, .+ \\*$',
-+                       flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+
-+    def test_enable_new_entypes(self):
-+        base_dn = "dc=%s" % (",dc=".join(self.master.domain.name.split(".")))
-+        realm = self.master.domain.name.upper()
-+
-+        entry_ldif = textwrap.dedent("""
-+            dn: cn={realm},cn=kerberos,{base_dn}
-+            changetype: modify
-+            replace: krbSupportedEncSaltTypes
-+            krbSupportedEncSaltTypes: aes128-sha2:normal
-+            krbSupportedEncSaltTypes: aes128-sha2:special
-+            krbSupportedEncSaltTypes: aes256-sha2:normal
-+            krbSupportedEncSaltTypes: aes256-sha2:special
-+            krbSupportedEncSaltTypes: aes256-cts:normal
-+            krbSupportedEncSaltTypes: aes256-cts:special
-+            krbSupportedEncSaltTypes: aes128-cts:normal
-+            krbSupportedEncSaltTypes: aes128-cts:special
-+            krbSupportedEncSaltTypes: camellia128-cts-cmac:normal
-+            krbSupportedEncSaltTypes: camellia128-cts-cmac:special
-+            krbSupportedEncSaltTypes: camellia256-cts-cmac:normal
-+            krbSupportedEncSaltTypes: camellia256-cts-cmac:special
-+            -
-+            replace: krbDefaultEncSaltTypes
-+            krbDefaultEncSaltTypes: aes256-sha2:special
-+            krbDefaultEncSaltTypes: aes128-sha2:special
-+            krbDefaultEncSaltTypes: aes256-cts:special
-+            krbDefaultEncSaltTypes: aes128-cts:special""").format(
-+            base_dn=base_dn, realm=realm)
-+        tasks.ldapmodify_dm(self.master, entry_ldif)
-+
-+    def test_add_new_mkey(self):
-+        self.master.run_command('kdb5_util add_mkey -e aes256-sha2 -s',
-+                                stdin_text='Secret123\nSecret123')
-+
-+    def test_new_inactive_mkey(self):
-+        p = re.compile('^KVNO: 2, Enctype: aes256-cts-hmac-sha384-192, ',
-+                       flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+
-+    def test_switch_mkey(self):
-+        self.master.run_command(['kdb5_util', 'use_mkey', '2'])
-+
-+    def test_new_active_mkey(self):
-+        p = re.compile('^KVNO: 2, Enctype: aes256-cts-hmac-sha384-192, .+ \\*$',
-+                       flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys'])
-+        assert p.search(result.stdout_text)
-+
-+    def test_used_old_mkey(self):
-+        p = re.compile('^MKey: vno 1$', flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kadmin.local', 'getprinc',
-+                                          f'ldap/{self.master.hostname}'])
-+        assert p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kadmin.local', 'getprinc',
-+                                               f'ldap/{self.master.hostname}'])
-+        assert p.search(result.stdout_text)
-+
-+    def test_reencrypt_with_new_mkey(self):
-+        self.master.run_command(['kdb5_util', '-x', 'unlockiter',
-+                                 'update_princ_encryption', '-vf'])
-+
-+    def test_used_new_mkey(self):
-+        p = re.compile('^MKey: vno 2$', flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kadmin.local', 'getprinc',
-+                                          f'ldap/{self.master.hostname}'])
-+        assert p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kadmin.local', 'getprinc',
-+                                               f'ldap/{self.master.hostname}'])
-+        assert p.search(result.stdout_text)
-+
-+    def test_purge_old_mkey(self):
-+        self.master.run_command(['kdb5_util', 'purge_mkeys', '-vf'])
-+
-+    def test_only_new_mkey(self):
-+        p = re.compile('^KVNO: 1,', flags=re.MULTILINE)
-+
-+        result = self.master.run_command(['kdb5_util', 'list_mkeys'])
-+        assert not p.search(result.stdout_text)
-+        result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys'])
-+        assert not p.search(result.stdout_text)
-+
-+    @classmethod
-+    def uninstall(cls, mh):
-+        cls.master.run_command([
-+            'rm', '/etc/profile.d/ipaplatform.sh',
-+            '/etc/systemd/system/ipa.service.d/platform.conf'])
-+        cls.master.run_command(['rmdir', '/etc/systemd/system/ipa.service.d'])
-+        super().uninstall(mh)
--- 
-2.51.0
-
-
-From 5db3bfafe6c12222b656f67d5ae3f6745e5f2644 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Thu, 12 Jun 2025 16:23:13 +0200
-Subject: [PATCH 08/47] ipa-client-install: New --no-dnssec-validation option
-
-The new option is needed to be able to deactivate DNSSEC validation
-for unbound.
-
-Unbound is by default configured to do DNSSEC validation with the
-validator module.
-
-The solution is to set module-config to "iterator".
-
-When the server is built with EDNS client subnet support this should be
-changed to "subnetcache iterator" according to the unbound man page.
-
-Fixes: https://pagure.io/freeipa/issue/9805
-Signed-off-by: Thomas Woerner <twoerner@redhat.com>
-Reviewed-By: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
----
- client/man/ipa-client-install.1    |  3 +++
- client/share/unbound.conf.template |  1 +
- ipaclient/install/client.py        | 22 +++++++++++++++++++++-
- 3 files changed, 25 insertions(+), 1 deletion(-)
-
-diff --git a/client/man/ipa-client-install.1 b/client/man/ipa-client-install.1
-index 6833991b8..5432c48a9 100644
---- a/client/man/ipa-client-install.1
-+++ b/client/man/ipa-client-install.1
-@@ -204,6 +204,9 @@ Create DNS A/AAAA record for each IP address on this host.
- .TP
- \fB\-\-dns\-over\-tls\fR
- Configure DNS over TLS.
-+.TP
-+\fB\-\-no\-dnssec\-validation\fR
-+Disable DNSSEC validation for DNS over TLS.
- 
- .SS "SSSD OPTIONS"
- .TP
-diff --git a/client/share/unbound.conf.template b/client/share/unbound.conf.template
-index 166036f65..f611ebb7e 100644
---- a/client/share/unbound.conf.template
-+++ b/client/share/unbound.conf.template
-@@ -3,6 +3,7 @@ server:
-     tls-upstream: yes
-     interface: 127.0.0.55
-     log-servfail: yes
-+    ${MODULE_CONFIG_ITERATOR}module-config: "iterator"
- forward-zone:
-     name: "."
-     forward-tls-upstream: yes
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 43a718283..96e91268f 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -1675,13 +1675,17 @@ def client_dns(server, hostname, options):
-         # setup and enable Unbound as resolver
-         server_ip = str(list(dnsutil.resolve_ip_addresses(server))[0])
-         forward_addr = "forward-addr: %s#%s" % (server_ip, server)
-+        # module_config_iterator is commented out if DNSSEC validation is
-+        # not disabled.
-+        module_config_iterator = '' if options.no_dnssec_validation else '# '
-         ipautil.copy_template_file(
-             paths.UNBOUND_CONF_SRC,
-             paths.UNBOUND_CONF,
-             dict(
-                 TLS_CERT_BUNDLE_PATH=os.path.join(
-                     paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
--                FORWARD_ADDRS=forward_addr
-+                FORWARD_ADDRS=forward_addr,
-+                MODULE_CONFIG_ITERATOR=module_config_iterator
-             )
-         )
-         sr = services.knownservices["systemd-resolved"]
-@@ -2419,6 +2423,16 @@ def install_check(options):
-     if not check_ip_addresses(options):
-         raise ScriptError(rval=CLIENT_INSTALL_ERROR)
- 
-+    if options.dns_over_tls \
-+       and not services.knownservices["unbound"].is_installed():
-+        raise ScriptError(
-+            "To enable DNS over TLS, package ipa-client-encrypted-dns must "
-+            "be installed.")
-+    if options.no_dnssec_validation and not options.dns_over_tls:
-+        raise ScriptError(
-+            "You can not specify --no-dnssec-validation option without the"
-+            "--dns-over-tls option.")
-+
-     # Create the discovery instance
-     ds = discovery.IPADiscovery()
- 
-@@ -4061,6 +4075,12 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
-     )
-     dns_over_tls = enroll_only(dns_over_tls)
- 
-+    no_dnssec_validation = knob(
-+        None,
-+        description="Disable DNSSEC validation for DNS over TLS",
-+    )
-+    no_dnssec_validation = enroll_only(no_dnssec_validation)
-+
-     request_cert = knob(
-         None,
-         deprecated=True,
--- 
-2.51.0
-
-
-From 0412252dd9a27138411e942305cdf54e70c06f27 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Mon, 23 Jun 2025 14:36:51 +0200
-Subject: [PATCH 09/47] ipaserver/install/dns.py: Allow to Turn off DNSSEC
- validation for unbound
-
-Unbound is by default configured to do DNSSEC validation with the validator
-module. The solution is to set module-config to "iterator".
-
-When the server is built with EDNS client subnet support this should be
-changed to "subnetcache iterator" according to the unbound man page.
-
-Fixes: https://pagure.io/freeipa/issue/9805
-Signed-off-by: Thomas Woerner <twoerner@redhat.com>
-Reviewed-By: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
----
- ipaserver/install/dns.py | 6 +++++-
- 1 file changed, 5 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 470e19159..ccec9d801 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -133,13 +133,17 @@ def _setup_dns_over_tls(options):
-     if options.dot_forwarders:
-         forward_addrs = ["forward-addr: %s" % fw
-                          for fw in options.dot_forwarders]
-+    # module_config_iterator is commented out if DNSSEC validation is
-+    # not disabled.
-+    module_config_iterator = '' if options.no_dnssec_validation else '# '
-     ipautil.copy_template_file(
-         paths.UNBOUND_CONF_SRC,
-         paths.UNBOUND_CONF,
-         dict(
-             TLS_CERT_BUNDLE_PATH=os.path.join(
-                 paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
--            FORWARD_ADDRS="\n".join(forward_addrs)
-+            FORWARD_ADDRS="\n".join(forward_addrs),
-+            MODULE_CONFIG_ITERATOR=module_config_iterator
-         )
-     )
- 
--- 
-2.51.0
-
-
-From 15adc2679dabc97fdc4514fc0775be7308bd922a Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Mon, 9 Jun 2025 17:20:32 +0530
-Subject: [PATCH 10/47] ipatests: Tests for 32BitIdranges.
-
-Running 32BitIdrange tests in AD enviornment
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_32bit_idranges.py   | 22 +++++++++++++++++++
- 1 file changed, 22 insertions(+)
-
-diff --git a/ipatests/test_integration/test_32bit_idranges.py b/ipatests/test_integration/test_32bit_idranges.py
-index e76e117e5..a928628d3 100644
---- a/ipatests/test_integration/test_32bit_idranges.py
-+++ b/ipatests/test_integration/test_32bit_idranges.py
-@@ -6,6 +6,7 @@ from __future__ import absolute_import
- 
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
-+from ipatests.test_integration.test_trust import BaseTestTrust
- 
- 
- class Test32BitIdRanges(IntegrationTest):
-@@ -102,3 +103,24 @@ class Test32BitIdRanges(IntegrationTest):
-         )
-         assert result.returncode == 0
-         assert str(uid) in result.stdout_text
-+
-+
-+class Test32BitIdrangeInTrustEnv(Test32BitIdRanges, BaseTestTrust):
-+    """
-+    Tests to check 32BitIdrange functionality
-+    in IPA-AD trust enviornment
-+    """
-+    topology = 'line'
-+    num_ad_domains = 1
-+    num_ad_subdomains = 0
-+    num_ad_treedomains = 0
-+    num_clients = 0
-+
-+    @classmethod
-+    def install(cls, mh):
-+        super(BaseTestTrust, cls).install(mh)
-+        cls.ad = cls.ads[0]
-+        cls.ad_domain = cls.ad.domain.name
-+        tasks.configure_dns_for_trust(cls.master, cls.ad)
-+        tasks.install_adtrust(cls.master)
-+        tasks.establish_trust_with_ad(cls.master, cls.ad.domain.name)
--- 
-2.51.0
-
-
-From 6f2f941d52952debcc638766db33b5aae29bbe81 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Mon, 16 Jun 2025 12:15:53 +0530
-Subject: [PATCH 11/47] ipatests: prci nightly definitions for 32BitIdranges
-
-32BitIdranges testsuite has been included in the
-nightly prci definitions.
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index 9e00c55a1..f0bdc1139 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -2177,3 +2177,15 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 14400
-         topology: *master_2repl_1client
-+
-+  fedora-latest-ipa-4-12/test_32BitIdranges:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunADTests
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_32bit_idranges.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *ad_master
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index f76c96267..851d6440c 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2351,3 +2351,16 @@ jobs:
-         template: *ci-ipa-4-12-latest
-         timeout: 14400
-         topology: *master_2repl_1client
-+
-+  fedora-latest-ipa-4-12/test_32BitIdranges:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunADTests
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_32bit_idranges.py
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *ad_master
--- 
-2.51.0
-
-
-From f4cbea00fde8dada84bfd1262b5271035d3ca7a4 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Thu, 12 Jun 2025 18:34:49 +0200
-Subject: [PATCH 12/47] Replica: Request cert for DoT before setting up bind
-
-Deploying a replica with DNS support using an IPA server DNS with DoT
-fails while setting up DNS over TLS. The request for the certificate for
-DoT using IPA CA is done after the DNS server for the replica is configured.
-
-The nameserver in /etc/resolv.conf has been changed to 127.0.0.1, but
-unbound was not yet configured as a forwarder.
-
-The solution is to move the cert request before the DNS server
-configuration. The unbound config from the client deployment is still
-working at that moment.
-
-Fixes: https://pagure.io/freeipa/issue/9808
-Signed-off-by: Thomas Woerner <twoerner@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/dns.py | 8 +++++++-
- 1 file changed, 7 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index ccec9d801..9740faedd 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -114,7 +114,7 @@ def _disable_dnssec():
-             conn.update_entry(entry)
- 
- 
--def _setup_dns_over_tls(options):
-+def _request_cert_for_dns_over_tls(options):
-     if os.path.isfile(paths.IPA_CA_CRT) and not options.dns_over_tls_cert:
-         # request certificate for DNS over TLS, using IPA CA
-         cert = paths.BIND_DNS_OVER_TLS_CRT
-@@ -128,6 +128,8 @@ def _setup_dns_over_tls(options):
-         constants.NAMED_USER.chown(cert, gid=constants.NAMED_GROUP.gid)
-         constants.NAMED_USER.chown(key, gid=constants.NAMED_GROUP.gid)
- 
-+
-+def _setup_dns_over_tls(options):
-     # setup and enable Unbound as resolver
-     forward_addrs = ["# forward-addr: specify here forwarders"]
-     if options.dot_forwarders:
-@@ -435,6 +437,10 @@ def install(standalone, replica, options, api=api):
-             "and IPA CA is not present."
-         )
- 
-+    if options.dns_over_tls:
-+        print("Request certificate for DNS over TLS, using IPA CA")
-+        _request_cert_for_dns_over_tls(options)
-+
-     bind = bindinstance.BindInstance(fstore, api=api)
-     bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
-                options.forwarders, options.forward_policy,
--- 
-2.51.0
-
-
-From a6ae9f740991888bede82884bd9609db220430e3 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Mon, 23 Jun 2025 10:49:34 +0200
-Subject: [PATCH 13/47] dns: only overwrite resolv.conf during eDNS setup when
- needed
-
-Don't overwrite resolv.conf if it already points to 127.0.0.1. This
-ensures compatibility with read-only containers.
-
-Fixes: https://pagure.io/freeipa/issue/9813
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/dns.py | 16 +++++++++-------
- 1 file changed, 9 insertions(+), 7 deletions(-)
-
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 9740faedd..0f7a3073f 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -33,7 +33,7 @@ from ipapython import ipautil
- from ipapython import dnsutil
- from ipapython.certdb import EXTERNAL_CA_TRUST_FLAGS
- from ipapython.dn import DN
--from ipapython.dnsutil import check_zone_overlap
-+from ipapython.dnsutil import check_zone_overlap, get_ipa_resolver
- from ipapython.install import typing
- from ipapython.install.core import group, knob
- from ipapython.admintool import ScriptError
-@@ -171,17 +171,19 @@ def _setup_dns_over_tls(options):
-             f.write("\n".join(dns_none))
-         nm.reload_or_restart()
- 
--    # Overwrite resolv.conf to point to IPA
-+    # Ensure resolv.conf points to IPA
-     cfg = [
-         "# auto-generated by IPA installer",
-         "search .",
-         "nameserver 127.0.0.1\n"
-     ]
--    fstore = sysrestore.FileStore(paths.SYSRESTORE)
--    fstore.backup_file(paths.RESOLV_CONF)
--    with open(paths.RESOLV_CONF, 'w') as f:
--        f.write('\n'.join(cfg))
--        os.chmod(paths.RESOLV_CONF, 0o644)
-+    nameservers = get_ipa_resolver().nameservers
-+    if not nameservers or nameservers[0] != "127.0.0.1":
-+        fstore = sysrestore.FileStore(paths.SYSRESTORE)
-+        fstore.backup_file(paths.RESOLV_CONF)
-+        with open(paths.RESOLV_CONF, 'w') as f:
-+            f.write('\n'.join(cfg))
-+            os.chmod(paths.RESOLV_CONF, 0o644)
- 
-     services.knownservices.unbound.enable()
-     services.knownservices.unbound.restart()
--- 
-2.51.0
-
-
-From 6bb7ebd40f3fa9c266e62caef961c1078440751d Mon Sep 17 00:00:00 2001
-From: Rafael Guterres Jeffman <rjeffman@redhat.com>
-Date: Tue, 17 Jun 2025 17:15:49 -0300
-Subject: [PATCH 14/47] Use correct capitalization for GitHub and GitLab
-
-The correct third party trademarks are GitHub and GitLab, and this is
-the capitalization that needs to be used for documentation and messages,
-when referring to each service.
-
-Fixes: https://pagure.io/freeipa/issue/9811
-Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- doc/designs/external-idp/idp-api.md      | 22 +++++++++++-----------
- doc/workshop/12-external-idp-support.rst |  4 ++--
- ipaserver/plugins/internal.py            |  2 +-
- ipatests/test_integration/test_cert.py   |  2 +-
- po/ipa.pot                               |  2 +-
- 5 files changed, 16 insertions(+), 16 deletions(-)
-
-diff --git a/doc/designs/external-idp/idp-api.md b/doc/designs/external-idp/idp-api.md
-index fe2ba8d67..59d2ccb57 100644
---- a/doc/designs/external-idp/idp-api.md
-+++ b/doc/designs/external-idp/idp-api.md
-@@ -156,13 +156,13 @@ List of pre-populated IdP types is currently limited by the following provider
- Some IdP providers support parametrized URIs which include organization or a
- realm name, or specific base URL, or both.
- 
--One notable omission in the pre-populated IdP types above is Gitlab.
-+One notable omission in the pre-populated IdP types above is GitLab.
- 
- FreeIPA only supports IdPs that implement OAuth 2.0 Device authorization
- grant flow as defined by the [RFC 8628](https://www.rfc-editor.org/rfc/rfc8628).
- If required IdP cannot be made to support Device authorization grant flow, it
- is recommended to use OAuth 2.0 federation within an IdP that supports this
--method. Gitlab does not support OAuth 2.0 Device authorization grant flow and
-+method. GitLab does not support OAuth 2.0 Device authorization grant flow and
- thus is not supported directly.
- 
- SSSD 2.7.0 implements Kerberos pre-authentication method `idp` (registered as a
-@@ -193,7 +193,7 @@ Choosing `--provider=google` would expand to use the following options:
- | `--scope`=STR         | `openid email`                                     |
- | `--idp-user-id`=STR   | `email`                                            |
- 
--#### Github IdPs
-+#### GitHub IdPs
- 
- Choosing `--provider=github` would expand to use the following options:
- 
-@@ -207,17 +207,17 @@ Choosing `--provider=github` would expand to use the following options:
- | `--scope`=STR         | `user`                                             |
- | `--idp-user-id`=STR   | `login`                                            |
- 
--Please note that Github explicitly states that a user login is not unique and
-+Please note that GitHub explicitly states that a user login is not unique and
- can be reused after a user account was deleted. The configuration above aims
--for an easy setup for testing. If production deployment with Github IdP would
-+for an easy setup for testing. If production deployment with GitHub IdP would
- be required, it is recommended to change `--idp-user-id` to a more unique subject
--like `id`. Unfortunately, Github UI does not give an easy way to discover a
-+like `id`. Unfortunately, GitHub UI does not give an easy way to discover a
- user ID. Other IdPs also lack an easy way to resolve these internal identifiers
- when not authorized by the user themselves.
- 
--For Github, user's ID can be looked up without authentication through the Users
-+For GitHub, user's ID can be looked up without authentication through the Users
- API. Assuming we have `curl` and `jq` utilities available, a request to
--discover an ID of a Github user named `test` would look like:
-+discover an ID of a GitHub user named `test` would look like:
- 
- ```
- $ curl --silent \
-@@ -386,10 +386,10 @@ scope is used, this typically maps to `sub` value. Since there are no ways to
- pull this value for all users in advance, pre-populated IdP templates set OAuth
- 2.0 scopes to include `email` and then use `email` to map IdP subject where possible.
- There are some well-known IdPs which allow reuse of user accounts and emails, this
--applies to both Github and Gitlab. Since Gitlab does not support OAuth 2.0
-+applies to both GitHub and GitLab. Since GitLab does not support OAuth 2.0
- Device authorization grant flow, it is not an issue in itself for this project. However,
--for Github it is known that user accounts can be recycled after their removal. In
--this case we would recommend to use internal Github identifier instead.
-+for GitHub it is known that user accounts can be recycled after their removal. In
-+this case we would recommend to use internal GitHub identifier instead.
- 
- ## Upgrade and backward compatibility
- 
-diff --git a/doc/workshop/12-external-idp-support.rst b/doc/workshop/12-external-idp-support.rst
-index 022c26483..66c714c25 100644
---- a/doc/workshop/12-external-idp-support.rst
-+++ b/doc/workshop/12-external-idp-support.rst
-@@ -94,7 +94,7 @@ authorization grant flow:
- 
- * Microsoft Identity Platform, including Azure AD
- * Google
--* Github
-+* GitHub
- * Keycloak, including Red Hat SSO
- * Okta
- 
-@@ -389,7 +389,7 @@ IPA. Option ``--provider keycloak`` allows us to fill-in pre-defined template
- for Keycloak or Red Hat SSO IdPs. The template expects both Keycloak's realm
- (``--org`` option) and a base URL (``--base-url`` option) because Keycloak is
- typically deployed as a part of a larger solution. These options may not be
--needed for other pre-defined templates like Google or Github.
-+needed for other pre-defined templates like Google or GitHub.
- 
- The `openid` scope is mandatory since
- [Keycloak 19.0.2](https://www.keycloak.org/docs/latest/upgrading/index.html#userinfo-endpoint-changes).
-diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
-index e8642b6f0..283b43077 100644
---- a/ipaserver/plugins/internal.py
-+++ b/ipaserver/plugins/internal.py
-@@ -1100,7 +1100,7 @@ class i18n_messages(Command):
-             "idp": {
-                 "template_keycloak": _("Keycloak or Red Hat SSO"),
-                 "template_google": _("Google"),
--                "template_github": _("Github"),
-+                "template_github": _("GitHub"),
-                 "template_microsoft": _("Microsoft or Azure"),
-                 "template_okta": _("Okta"),
-                 "label_idpclient": _("OAuth 2.0 client details"),
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 88859e67f..05b20b910 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -540,7 +540,7 @@ class TestCAShowErrorHandling(IntegrationTest):
-         4. Verify LWCA is recognized on the server
-         5. Run `ipa ca-show <LWCA>`
- 
--        PKI Github Link: https://github.com/dogtagpki/pki/pull/3605/
-+        PKI GitHub Link: https://github.com/dogtagpki/pki/pull/3605/
-         """
-         self.replicas[0].run_command(['systemctl', 'stop', 'ipa-custodia'])
-         lwca = 'lwca1'
-diff --git a/po/ipa.pot b/po/ipa.pot
-index 41ee14059..07413d546 100644
---- a/po/ipa.pot
-+++ b/po/ipa.pot
-@@ -23944,7 +23944,7 @@ msgid "Google"
- msgstr ""
- 
- #: ipaserver/plugins/internal.py:1103
--msgid "Github"
-+msgid "GitHub"
- msgstr ""
- 
- #: ipaserver/plugins/internal.py:1104
--- 
-2.51.0
-
-
-From 43f1653c41312fa831db30f0aa4e1699816b7cac Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 11 Jul 2025 10:52:58 +0300
-Subject: [PATCH 15/47] freeipa.spec.in: protect scriptlets in environment
- where dbus or systemd do not run
-
-For quite some time, even as early as with Fedora 37, we had
-
-  Running scriptlet: freeipa-server-trust-ad-4.10.1-1.fc37.x86_64
-                                            338/343
-System has not been booted with systemd as init system (PID 1). Can't operate.
-Failed to connect to bus: Host is down
-System has not been booted with systemd as init system (PID 1). Can't operate.
-Failed to connect to bus: Host is down
-warning: %post(freeipa-server-trust-ad-4.10.1-1.fc37.x86_64) scriptlet failed,
-exit status 1
-
-Error in POSTIN scriptlet in rpm package freeipa-server-trust-ad
-
-when installing freeipa-server-trust-ad, but the whole rpm transaction passed
-and the dnf command returned with exit 0. So we did not care about that failure
-because, well, it did not get reported in any way it could be caught.
-
-This has changed with recent registry.fedoraproject.org/fedora:rawhide image
-which seem so have rpm-5.99.91-1.fc43.x86_64. Now instead of exiting with 0 as
-with previous registry.fedoraproject.org/fedora:rawhide images, the dnf exits
-with 1, even if the version freeipa-server-trust-ad-4.12.2-14.fc43.x86_64 is
-the same in both cases.
-
-Fixes: https://pagure.io/freeipa/issue/9826
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 2b97b1176..8b7f72a70 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1326,8 +1326,8 @@ fi
- %post server-trust-ad
- %{_sbindir}/update-alternatives --install %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \
-         winbind_krb5_locator.so /dev/null 90
--/bin/systemctl reload-or-try-restart dbus
--/bin/systemctl reload-or-try-restart oddjobd
-+/bin/systemctl reload-or-try-restart dbus >/dev/null 2>&1 || :
-+/bin/systemctl reload-or-try-restart oddjobd >/dev/null 2>&1 || :
- 
- 
- %posttrans server-trust-ad
-@@ -1344,8 +1344,8 @@ if [ $1 -eq 0 ]; then
-     %{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
-     # Skip systemctl calls when leapp upgrade is in progress
-     if [ -z "$LEAPP_IPU_IN_PROGRESS" ] ; then
--        /bin/systemctl reload-or-try-restart dbus
--        /bin/systemctl reload-or-try-restart oddjobd
-+        /bin/systemctl reload-or-try-restart dbus >/dev/null 2>&1 || :
-+        /bin/systemctl reload-or-try-restart oddjobd >/dev/null 2>&1 || :
-     fi
- fi
- 
--- 
-2.51.0
-
-
-From 3b1d0e1c774b4edeb0844018f50a00ea30e55ea0 Mon Sep 17 00:00:00 2001
-From: David Hanina <dhanina@redhat.com>
-Date: Wed, 16 Jul 2025 07:08:04 +0200
-Subject: [PATCH 16/47] Fix terminal height for Rawhide
-
-Replaces old ioctl function with shutil.get_terminal_size.
-This should be more universal.
-
-Fixes: https://pagure.io/freeipa/issue/9824
-Signed-off-by: David Hanina <dhanina@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipalib/util.py | 11 ++---------
- 1 file changed, 2 insertions(+), 9 deletions(-)
-
-diff --git a/ipalib/util.py b/ipalib/util.py
-index 128420181..0036d143b 100644
---- a/ipalib/util.py
-+++ b/ipalib/util.py
-@@ -35,10 +35,7 @@ import dns
- import encodings
- import sys
- import ssl
--import termios
--import fcntl
- import shutil
--import struct
- import subprocess
- 
- import netaddr
-@@ -1335,7 +1332,7 @@ def no_matching_interface_for_ip_address_warning(addr_list):
-             )
- 
- 
--def get_terminal_height(fd=1):
-+def get_terminal_height():
-     """
-     Get current terminal height
- 
-@@ -1345,11 +1342,7 @@ def get_terminal_height(fd=1):
-     Returns:
-         int: Terminal height
-     """
--    try:
--        return struct.unpack(
--            'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, b'1234'))[0]
--    except (IOError, OSError, struct.error):
--        return os.environ.get("LINES", 25)
-+    return shutil.get_terminal_size().lines
- 
- 
- def get_pager():
--- 
-2.51.0
-
-
-From 45cce31e2596de2c9b6048674510572c248e2ec9 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Tue, 15 Jul 2025 10:52:01 +0300
-Subject: [PATCH 17/47] kdb: prevent double crash in RBCD ACL free
-
-acl_list was set to prev->tl_data_contents and its value is freed but
-then is is freed again outside of the if(). Just reset acl_list pointer
-as prev->tl_data_contents is removed unconditionally outside of the RBCD
-ACL removal.
-
-Related: https://pagure.io/freeipa/issue/9367
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb_principals.c | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
-index 19998c2a3..a7e77e940 100644
---- a/daemons/ipa-kdb/ipa_kdb_principals.c
-+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
-@@ -2160,7 +2160,8 @@ void ipadb_free_principal(krb5_context kcontext, krb5_db_entry *entry)
-                 for (i = 0; (acl_list != NULL) && (acl_list[i] != NULL); i++) {
-                     free(acl_list[i]);
-                 }
--                free(acl_list);
-+                /* prev->tl_data_contents will be removed below */
-+                acl_list = NULL;
-             }
-             free(prev->tl_data_contents);
-             free(prev);
--- 
-2.51.0
-
-
-From 0c9ba2a0075f02315810521357cf2e5b52fc7d41 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Wed, 9 Apr 2025 13:10:58 +0530
-Subject: [PATCH 18/47] ipatests: Tests for ipa-migrate tool with ldif file
-
-This test checks that when ipa-migrate tool
-uses ldif file it works without any error.
-
-Related: https://pagure.io/freeipa/issue/9776
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../test_ipa_ipa_migration.py                 | 80 +++++++++++++++++++
- 1 file changed, 80 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
-index 95c29234f..c6247e772 100644
---- a/ipatests/test_integration/test_ipa_ipa_migration.py
-+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
-@@ -1265,3 +1265,83 @@ class TestIPAMigrationWithADtrust(IntegrationTest):
-             ["ipa", "idrange-show", ad_domain_name + "_id_range"]
-         )
-         assert cmd1.stdout_text == cmd2.stdout_text
-+
-+
-+class TestIPAMigratewithBackupRestore(IntegrationTest):
-+    """
-+    Test for ipa-migrate tool with backup files.
-+    The master and replicas[1] are used to create the data source.
-+    The replicas[0] is used as new server, retrieving data from the source.
-+    replicas[1] is needed to make sure that the source LDIF
-+    file contains replication attributes with
-+    options (for instance objectClass;vucsn-67f7b3de000300030000).
-+    """
-+    num_replicas = 2
-+    topology = "line"
-+
-+    @classmethod
-+    def install(cls, mh):
-+        tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
-+        prepare_ipa_server(cls.master)
-+        tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True)
-+        tasks.install_replica(cls.master, cls.replicas[1],
-+                              setup_dns=True, setup_kra=True)
-+
-+    @pytest.fixture
-+    def create_delete_user(self):
-+        """
-+        This fixtures creates a ldapuser using the
-+        ldif file and then delete the users
-+        """
-+        self.master.run_command(['ipa', 'user-add', 'testuser',
-+                                 '--first', 'test',
-+                                 '--last', 'user'])
-+        self.master.run_command(['ipa', 'user-del', 'testuser'])
-+        yield
-+
-+    def test_ipa_migrate_stage_mode(self, create_delete_user):
-+        """
-+        This test checks ipa-migrate with LDIF file
-+        from backup of remote server is successful.
-+        """
-+        ERR_MSG = (
-+            "error: change collided with another change"
-+        )
-+        dashed_domain_name = self.master.domain.realm.replace(
-+            ".", '-'
-+        )
-+        DB_LDIF_FILE = '{}-userRoot.ldif'.format(
-+            dashed_domain_name
-+        )
-+        SCHEMA_LDIF_FILE = '{}''/config_files/schema/99user.ldif'.format(
-+            dashed_domain_name)
-+        CONFIG_LDIF_FILE = '{}''/config_files/dse.ldif'.format(
-+            dashed_domain_name)
-+        param = [
-+            '-n', '-g', CONFIG_LDIF_FILE, '-m', SCHEMA_LDIF_FILE,
-+            '-f', DB_LDIF_FILE
-+        ]
-+        tasks.kinit_admin(self.master)
-+        tasks.kinit_admin(self.replicas[0])
-+        backup_path = tasks.get_backup_dir(self.master)
-+        remote_ipa_tar_file = backup_path + '/ipa-full.tar'
-+        ipa_tar_file = self.master.get_file_contents(
-+            remote_ipa_tar_file
-+        )
-+        replica_file_name = "/tmp/ipa-full.tar"
-+        self.replicas[0].put_file_contents(
-+            replica_file_name, ipa_tar_file
-+        )
-+        self.replicas[0].run_command(
-+            ['/usr/bin/tar', '-xvf', replica_file_name]
-+        )
-+        result = run_migrate(
-+            self.replicas[0],
-+            "stage-mode",
-+            self.master.hostname,
-+            "cn=Directory Manager",
-+            self.master.config.admin_password,
-+            extra_args=param,
-+        )
-+        assert result.returncode == 0
-+        assert ERR_MSG not in result.stderr_text
--- 
-2.51.0
-
-
-From 6ee21100aaa7ffb85fcebd29033c27f3f259f773 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 28 Jul 2025 08:53:06 +0200
-Subject: [PATCH 19/47] ipatests: Nightly definitions for
- TestIPAMigratewithBackupRestore
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../prci_definitions/nightly_ipa-4-12_latest.yaml   | 12 ++++++++++++
- .../nightly_ipa-4-12_latest_selinux.yaml            | 13 +++++++++++++
- 2 files changed, 25 insertions(+)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index f0bdc1139..bc2a10de4 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -2154,6 +2154,18 @@ jobs:
-         timeout: 7200
-         topology: *ad_master_1repl_1client
- 
-+  fedora-latest-ipa-4-12/test_IPAMigrateBackupRestore:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigratewithBackupRestore
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *master_2repl_1client
-+
-   fedora-latest-ipa-4-12/test_cockpit:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index 851d6440c..fc31186df 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -2326,6 +2326,19 @@ jobs:
-         timeout: 7200
-         topology: *ad_master_1repl_1client
- 
-+  fedora-latest-ipa-4-12/test_IPAMigrateBackupRestore:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_ipa_ipa_migration.py::TestIPAMigratewithBackupRestore
-+        template: *ci-ipa-4-12-latest
-+        timeout: 7200
-+        topology: *master_2repl_1client
-+
-   fedora-latest-ipa-4-12/test_cockpit:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
--- 
-2.51.0
-
-
-From 7887b0211cdcd6fbd9a3d86882fabd26e50bcadd Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Thu, 17 Jul 2025 17:25:59 +0300
-Subject: [PATCH 20/47] install: make use of shared temp directory for hsm
- validation
-
-During hsm validation temporary directory and files are prepared
-by root in base temporary directory which can be private and
-thereby not readable by anybody but root. The validation in its
-turn, run some checks as pkiuser and fail to read not accessible
-resources in such a case.
-
-Hence, to make them available some shared place need to be used,
-e.g. /tmp.
-
-Fixes: https://pagure.io/freeipa/issue/9831
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ca.py | 12 ++++++++++--
- 1 file changed, 10 insertions(+), 2 deletions(-)
-
-diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
-index 2959aceed..d0d9887e5 100644
---- a/ipaserver/install/ca.py
-+++ b/ipaserver/install/ca.py
-@@ -11,6 +11,8 @@ from __future__ import print_function, absolute_import
- import enum
- import logging
- import os.path
-+import tempfile
-+
- import pki.util
- 
- import six
-@@ -212,7 +214,11 @@ def hsm_validator(token_name, token_library, token_password):
-     pkiuser = constants.PKI_USER
-     pkigroup = constants.PKI_GROUP
-     group_list = os.getgrouplist(pkiuser, pkigroup.gid)
--    with certdb.NSSDatabase() as tempnssdb:
-+    with (
-+        tempfile.TemporaryDirectory(dir=paths.TMP) as tempnssdir,
-+        tempfile.NamedTemporaryFile(mode="w+", dir=paths.TMP) as pwdfile,
-+    ):
-+        tempnssdb = certdb.NSSDatabase(nssdir=tempnssdir)
-         tempnssdb.create_db(user=str(pkiuser), group=str(pkigroup))
-         # Try adding the token library to the temporary database in
-         # case it isn't already available. Ignore all errors.
-@@ -250,7 +256,9 @@ def hsm_validator(token_name, token_library, token_password):
-                 "Token named '%s' was not found. Check permissions"
-                 % token_name
-             )
--        pwdfile = ipautil.write_tmp_file(token_password)
-+
-+        pwdfile.write(token_password)
-+        pwdfile.flush()
-         os.fchown(pwdfile.fileno(), pkiuser.uid, pkigroup.gid)
-         args = [
-             paths.CERTUTIL,
--- 
-2.51.0
-
-
-From bae780843ef26da1d0876086205cda9f590e9c01 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Tue, 24 Jun 2025 13:41:17 +0200
-Subject: [PATCH 21/47] dns: disable all previous Unbound configuration before
- deploying ours
-
-Previous configuration from another packages might break our Unbound
-setup. Rename the config files to disable them before deploying our
-configuration.
-
-Fixes: https://pagure.io/freeipa/issue/9814
-Signed-off-by: Antonio Torres <antorres@redhat.com>
-Reviewed-By: Thomas Woerner <twoerner@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaclient/install/client.py       | 29 ++++++++++++++++++++++++++---
- ipaplatform/base/paths.py         |  1 +
- ipaserver/install/bindinstance.py | 17 +++++++++++++++--
- ipaserver/install/dns.py          | 11 ++++++++++-
- 4 files changed, 52 insertions(+), 6 deletions(-)
-
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 96e91268f..1885e4a8d 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -1656,7 +1656,7 @@ def get_server_connection_interface(server):
-     raise RuntimeError(msg)
- 
- 
--def client_dns(server, hostname, options):
-+def client_dns(server, hostname, options, statestore):
- 
-     try:
-         verify_host_resolvable(hostname)
-@@ -1672,12 +1672,22 @@ def client_dns(server, hostname, options):
- 
-     # Setup DNS over TLS
-     if options.dns_over_tls:
-+        fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
-+        statestore.backup_state("dns_over_tls", "enabled", True)
-+        save_state(services.knownservices["unbound"], statestore)
-         # setup and enable Unbound as resolver
-         server_ip = str(list(dnsutil.resolve_ip_addresses(server))[0])
-         forward_addr = "forward-addr: %s#%s" % (server_ip, server)
-         # module_config_iterator is commented out if DNSSEC validation is
-         # not disabled.
-         module_config_iterator = '' if options.no_dnssec_validation else '# '
-+        # backup and remove all previous Unbound configuration
-+        for filename in os.listdir(paths.UNBOUND_CONFIG_DIR):
-+            filepath = os.path.join(paths.UNBOUND_CONFIG_DIR, filename)
-+            if filepath == paths.UNBOUND_CONF:
-+                continue
-+            fstore.backup_file(filepath)
-+            remove_file(filepath)
-         ipautil.copy_template_file(
-             paths.UNBOUND_CONF_SRC,
-             paths.UNBOUND_CONF,
-@@ -1710,7 +1720,6 @@ def client_dns(server, hostname, options):
-             "search .",
-             "nameserver 127.0.0.55\n"
-         ]
--        fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
-         fstore.backup_file(paths.RESOLV_CONF)
-         with open(paths.RESOLV_CONF, 'w') as f:
-             f.write('\n'.join(cfg))
-@@ -3242,7 +3251,7 @@ def _install(options, tdict):
-     tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
- 
-     if not options.on_master:
--        client_dns(cli_server[0], hostname, options)
-+        client_dns(cli_server[0], hostname, options, statestore)
- 
-     update_ssh_keys(hostname, paths.SSH_CONFIG_DIR, options, cli_server[0])
- 
-@@ -3632,6 +3641,20 @@ def uninstall(options):
-         except Exception:
-             pass
- 
-+    # Restore unbound to its original status
-+    if statestore.restore_state("dns_over_tls", "enabled"):
-+        unbound = services.knownservices['unbound']
-+        if not statestore.restore_state('unbound', 'running'):
-+            unbound.stop()
-+        if not statestore.restore_state('unbound', 'enabled'):
-+            unbound.disable()
-+        # restore unbound config files that were removed during IPA install
-+        remove_file(paths.UNBOUND_CONF)
-+        for filename, fileinfo in fstore.files.items():
-+            if paths.UNBOUND_CONFIG_DIR in fileinfo:
-+                fstore.restore_file(
-+                    os.path.join(paths.UNBOUND_CONFIG_DIR, filename))
-+
-     logger.info("Disabling client Kerberos and LDAP configurations")
-     was_sssd_installed = False
-     was_sshd_configured = False
-diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
-index a5bca789b..8b62971f9 100644
---- a/ipaplatform/base/paths.py
-+++ b/ipaplatform/base/paths.py
-@@ -102,6 +102,7 @@ class BasePathNamespace:
-     NAMED_MANAGED_KEYS_DIR = "/var/named/dynamic"
-     NAMED_CRYPTO_POLICY_FILE = None
-     UNBOUND_CONF_SRC = '/usr/share/ipa/client/unbound.conf.template'
-+    UNBOUND_CONFIG_DIR = "/etc/unbound/conf.d/"
-     UNBOUND_CONF = "/etc/unbound/conf.d/zzz-ipa.conf"
-     NSLCD_CONF = "/etc/nslcd.conf"
-     NSS_LDAP_CONF = "/etc/nss_ldap.conf"
-diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
-index 0cc1f1325..ea4d4bf0e 100644
---- a/ipaserver/install/bindinstance.py
-+++ b/ipaserver/install/bindinstance.py
-@@ -690,6 +690,10 @@ class BindInstance(service.Service):
-         self.reverse_zones = reverse_zones
- 
-         self.sstore.backup_state("dns_over_tls", "enabled", dns_over_tls)
-+        self.sstore.backup_state("unbound", "running",
-+                                 services.knownservices["unbound"].is_running())
-+        self.sstore.backup_state("unbound", "enabled",
-+                                 services.knownservices["unbound"].is_enabled())
- 
-         if not zonemgr:
-             self.zonemgr = 'hostmaster.%s' % normalize_zone(self.domain)
-@@ -1382,8 +1386,17 @@ class BindInstance(service.Service):
-         if self.sstore.restore_state("dns_over_tls", "enabled"):
-             if not self.sstore.restore_state("dns_over_tls", "external_crt"):
-                 certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_CRT)
--            services.knownservices["unbound"].disable()
--            services.knownservices["unbound"].stop()
-+            # only disable unbound if it was before IPA was deployed
-+            if not self.sstore.restore_state("unbound", "enabled"):
-+                services.knownservices["unbound"].disable()
-+            if not self.sstore.restore_state("unbound", "running"):
-+                services.knownservices["unbound"].stop()
-+            # restore unbound config files that were removed during IPA install
-+            ipautil.remove_file(paths.UNBOUND_CONF)
-+            for filename, fileinfo in self.fstore.files.items():
-+                if paths.UNBOUND_CONFIG_DIR in fileinfo:
-+                    self.fstore.restore_file(
-+                        os.path.join(paths.UNBOUND_CONFIG_DIR, filename))
- 
-         ipautil.remove_file(paths.NAMED_CONF_BAK)
-         ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 0f7a3073f..39c2f677b 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -138,6 +138,16 @@ def _setup_dns_over_tls(options):
-     # module_config_iterator is commented out if DNSSEC validation is
-     # not disabled.
-     module_config_iterator = '' if options.no_dnssec_validation else '# '
-+
-+    # backup and remove all previous Unbound configuration
-+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
-+    for filename in os.listdir(paths.UNBOUND_CONFIG_DIR):
-+        filepath = os.path.join(paths.UNBOUND_CONFIG_DIR, filename)
-+        if filepath == paths.UNBOUND_CONF:
-+            continue
-+        fstore.backup_file(filepath)
-+        ipautil.remove_file(filepath)
-+
-     ipautil.copy_template_file(
-         paths.UNBOUND_CONF_SRC,
-         paths.UNBOUND_CONF,
-@@ -179,7 +189,6 @@ def _setup_dns_over_tls(options):
-     ]
-     nameservers = get_ipa_resolver().nameservers
-     if not nameservers or nameservers[0] != "127.0.0.1":
--        fstore = sysrestore.FileStore(paths.SYSRESTORE)
-         fstore.backup_file(paths.RESOLV_CONF)
-         with open(paths.RESOLV_CONF, 'w') as f:
-             f.write('\n'.join(cfg))
--- 
-2.51.0
-
-
-From 2863b585d2750cc71d6409da1848124583cabc13 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 30 Jul 2025 13:18:11 +0200
-Subject: [PATCH 22/47] Spec file: use nodejs22 on fedora 41+
-
-Do not use the newest nodesjs24 as it does not provide the /usr/bin/node
-command, only /usr/bin/node-24.
-Force the use of nodejs22.
-
-Fixes: https://pagure.io/freeipa/issue/9836
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 8b7f72a70..1cb3d0233 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -328,8 +328,9 @@ BuildRequires:  libsss_idmap-devel
- BuildRequires:  libsss_certmap-devel
- BuildRequires:  libsss_nss_idmap-devel >= %{sssd_version}
- %if 0%{?fedora} >= 41 || 0%{?rhel} >= 10
-+# on fedora 41+, stick to nodejs22
- # Do not use nodejs22 on fedora < 41, https://pagure.io/freeipa/issue/9643
--BuildRequires: nodejs(abi)
-+BuildRequires: nodejs(abi) == 127
- %elif 0%{?fedora} >= 39
- # Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374
- BuildRequires:  nodejs(abi) < 127
--- 
-2.51.0
-
-
-From b69363940b7ad61e80649389eda80f53f722fd7c Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Thu, 24 Jul 2025 16:57:33 +0530
-Subject: [PATCH 23/47] ipatests: Remove xfail from
- test_installation::test_number_of_zones
-
-Removed xfail for test_number_of_zones
-This test is xpass when executed in IdM-CI
-but still failing in PRCI. There is a different configuration
-in the 2 CIs and with this we will get a green test
-in IdM-CI nightly tests.
-
-Related : https://pagure.io/freeipa/issue/9135
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_installation.py | 9 ++++++---
- 1 file changed, 6 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index 1fae47267..e8ca76300 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -587,13 +587,16 @@ class TestInstallWithCA_DNS3(CALessBase):
-     ticket 7239
-     """
- 
--    @pytest.mark.xfail(
--        osinfo.id == 'fedora' and osinfo.version_number >= (36,),
--        reason='freeipa ticket 9135', strict=True)
-     @server_install_setup
-     def test_number_of_zones(self):
-         """There should be two zones: one forward, one reverse"""
- 
-+        # Removed xfail for test_number_of_zones
-+        # This test is xpass when executed in IdM-CI
-+        # but still failing in PRCI. There is a different configuration
-+        # in the 2 CIs and with this we will get a green test
-+        # in IdM-CI nightly tests.
-+
-         self.create_pkcs12('ca1/server')
-         self.prepare_cacert('ca1')
- 
--- 
-2.51.0
-
-
-From 1f915d4b2a1793a0a3e536604643f80aa76b6b0c Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Fri, 23 Aug 2024 15:08:57 +0530
-Subject: [PATCH 24/47] ipatests: Update ipatests to test topology with
- multiple domain.
-
-Added changes in ipatests so that ipa server-replica-client
-can be installed with two domain - ipa.test and trustedipa.test
-
-Related: https://pagure.io/freeipa/issue/9657
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/pytest_ipa/integration/__init__.py   | 34 ++++++++-
- ipatests/pytest_ipa/integration/config.py     | 30 +++++++-
- ipatests/test_integration/base.py             | 71 +++++++++++++++++++
- .../test_integration/test_multidomain_ipa.py  | 54 ++++++++++++++
- pylint_plugins.py                             | 12 ++++
- 5 files changed, 198 insertions(+), 3 deletions(-)
- create mode 100644 ipatests/test_integration/test_multidomain_ipa.py
-
-diff --git a/ipatests/pytest_ipa/integration/__init__.py b/ipatests/pytest_ipa/integration/__init__.py
-index eb032cd72..31e909160 100644
---- a/ipatests/pytest_ipa/integration/__init__.py
-+++ b/ipatests/pytest_ipa/integration/__init__.py
-@@ -415,6 +415,16 @@ def mh(request, class_integration_logs):
-             'type': 'AD_TREEDOMAIN',
-             'hosts': {'ad_treedomain': 1}
-         })
-+    for _i in range(cls.num_trusted_domains):
-+        domain_descriptions.append({
-+            'type': 'TRUSTED_IPA',
-+            'hosts':
-+            {
-+                'master': 1,
-+                'replica': cls.num_trusted_replicas,
-+                'client': cls.num_trusted_clients,
-+            }
-+        })
- 
-     mh = make_multihost_fixture(
-         request,
-@@ -423,10 +433,20 @@ def mh(request, class_integration_logs):
-         _config=get_global_config(),
-     )
- 
--    mh.domain = mh.config.domains[0]
-+    for domain in mh.config.domains:
-+        if domain.type == 'IPA':
-+            mh.domain = domain
-+        elif domain.type == 'TRUSTED_IPA':
-+            mh.trusted_domain = domain
-+
-     [mh.master] = mh.domain.hosts_by_role('master')
-     mh.replicas = mh.domain.hosts_by_role('replica')
-     mh.clients = mh.domain.hosts_by_role('client')
-+    if mh.config.trusted_domains:
-+        [mh.trusted_master] = mh.trusted_domain.hosts_by_role('master')
-+        mh.trusted_replicas = mh.trusted_domain.hosts_by_role('replica')
-+        mh.trusted_clients = mh.trusted_domain.hosts_by_role('client')
-+
-     ad_domains = mh.config.ad_domains
-     if ad_domains:
-         mh.ads = []
-@@ -489,6 +509,12 @@ def add_compat_attrs(cls, mh):
-         cls.ad_subdomains = mh.ad_subdomains
-         cls.ad_treedomains = mh.ad_treedomains
- 
-+    cls.trusted_domains = mh.config.trusted_domains
-+    if cls.trusted_domains:
-+        cls.trusted_master = mh.trusted_master
-+        cls.trusted_replicas = mh.trusted_replicas
-+        cls.trusted_clients = mh.trusted_clients
-+
- 
- def del_compat_attrs(cls):
-     """Remove convenience attributes from the test class
-@@ -506,6 +532,12 @@ def del_compat_attrs(cls):
-         del cls.ad_treedomains
-     del cls.ad_domains
- 
-+    if cls.trusted_domains:
-+        del cls.trusted_master
-+        del cls.trusted_replicas
-+        del cls.trusted_clients
-+    del cls.trusted_domains
-+
- 
- def skip_if_fips(reason='Not supported in FIPS mode', host='master'):
-     if callable(reason):
-diff --git a/ipatests/pytest_ipa/integration/config.py b/ipatests/pytest_ipa/integration/config.py
-index 1f4dff7f6..0fb2313db 100644
---- a/ipatests/pytest_ipa/integration/config.py
-+++ b/ipatests/pytest_ipa/integration/config.py
-@@ -88,13 +88,19 @@ class Config(pytest_multihost.config.Config):
-     def ad_domains(self):
-         return [d for d in self.domains if d.is_ad_type]
- 
-+    @property
-+    def trusted_domains(self):
-+        return [d for d in self.domains if d.is_trusted_ipa_type]
-+
-     def get_all_hosts(self):
-         for domain in self.domains:
-             for host in domain.hosts:
-                 yield host
- 
-     def get_all_ipa_hosts(self):
--        for ipa_domain in (d for d in self.domains if d.is_ipa_type):
-+        for ipa_domain in (d for d in self.domains
-+                           if d.is_ipa_type or d.is_trusted_ipa_type
-+                           ):
-             for ipa_host in ipa_domain.hosts:
-                 yield ipa_host
- 
-@@ -135,7 +141,7 @@ class Domain(pytest_multihost.config.Domain):
-         self.name = str(name)
-         self.hosts = []
- 
--        assert self.is_ipa_type or self.is_ad_type
-+        assert self.is_ipa_type or self.is_ad_type or self.is_trusted_ipa_type
-         self.realm = self.name.upper()
-         self.basedn = DN(*(('dc', p) for p in name.split('.')))
- 
-@@ -143,6 +149,10 @@ class Domain(pytest_multihost.config.Domain):
-     def is_ipa_type(self):
-         return self.type == 'IPA'
- 
-+    @property
-+    def is_trusted_ipa_type(self):
-+        return self.type == 'TRUSTED_IPA'
-+
-     @property
-     def is_ad_type(self):
-         return self.type == 'AD' or self.type.startswith('AD_')
-@@ -158,6 +168,8 @@ class Domain(pytest_multihost.config.Domain):
-             return ('ad_subdomain',)
-         elif self.type == 'AD_TREEDOMAIN':
-             return ('ad_treedomain',)
-+        elif self.type == 'TRUSTED_IPA':
-+            return ('trusted_master', 'trusted_replica', 'trusted_client')
-         else:
-             raise LookupError(self.type)
- 
-@@ -168,6 +180,8 @@ class Domain(pytest_multihost.config.Domain):
-             return Host
-         elif self.is_ad_type:
-             return WinHost
-+        elif self.is_trusted_ipa_type:
-+            return Host
-         else:
-             raise LookupError(self.type)
- 
-@@ -175,6 +189,10 @@ class Domain(pytest_multihost.config.Domain):
-     def master(self):
-         return self.host_by_role('master')
- 
-+    @property
-+    def trusted_master(self):
-+        return self.host_by_role('trusted_master')
-+
-     @property
-     def masters(self):
-         return self.hosts_by_role('master')
-@@ -183,10 +201,18 @@ class Domain(pytest_multihost.config.Domain):
-     def replicas(self):
-         return self.hosts_by_role('replica')
- 
-+    @property
-+    def trusted_replicas(self):
-+        return self.hosts_by_role('replica')
-+
-     @property
-     def clients(self):
-         return self.hosts_by_role('client')
- 
-+    @property
-+    def trusted_clients(self):
-+        return self.hosts_by_role('client')
-+
-     @property
-     def ads(self):
-         return self.hosts_by_role('ad')
-diff --git a/ipatests/test_integration/base.py b/ipatests/test_integration/base.py
-index 4717667cb..cb34067d4 100644
---- a/ipatests/test_integration/base.py
-+++ b/ipatests/test_integration/base.py
-@@ -33,6 +33,7 @@ class IntegrationTest:
-     num_replicas = 0
-     num_clients = 0
-     num_ad_domains = 0
-+    num_trusted_domains = 0
-     num_ad_subdomains = 0
-     num_ad_treedomains = 0
-     required_extra_roles = []
-@@ -95,6 +96,7 @@ class IntegrationTest:
-                                cls.clients, domain_level,
-                                random_serial=cls.random_serial,
-                                extra_args=extra_args,)
-+
-     @classmethod
-     def uninstall(cls, mh):
-         for replica in cls.replicas:
-@@ -112,3 +114,72 @@ class IntegrationTest:
-             tasks.uninstall_client(client)
-         if cls.fips_mode:
-             cls.disable_fips_mode()
-+
-+
-+@ordered
-+@pytest.mark.usefixtures('mh')
-+@pytest.mark.usefixtures('integration_logs')
-+class MultiDomainIntegrationTest(IntegrationTest):
-+    num_trusted_domains = 1
-+    num_trusted_replicas = 0
-+    num_trusted_clients = 0
-+
-+    @classmethod
-+    def get_domains(cls):
-+        return super(MultiDomainIntegrationTest, cls
-+                     ).get_domains() + cls.trusted_domains
-+
-+    @classmethod
-+    def install(cls, mh):
-+        super(MultiDomainIntegrationTest, cls).install(mh)
-+        extra_args = []
-+        if cls.topology is None:
-+            return
-+        else:
-+            if cls.token_password:
-+                extra_args.extend(('--token-password', cls.token_password,))
-+            tasks.install_topo(cls.topology,
-+                               cls.trusted_master, cls.trusted_replicas,
-+                               cls.trusted_clients, 1,
-+                               random_serial=cls.random_serial,
-+                               extra_args=extra_args,)
-+        tasks.kinit_admin(cls.master)
-+        tasks.kinit_admin(cls.trusted_master)
-+        # Now enable dnssec on the zones
-+        cls.master.run_command([
-+            "ipa-dns-install",
-+            "--dnssec-master",
-+            "--forwarder", cls.master.config.dns_forwarder,
-+            "-U",
-+        ])
-+        cls.master.run_command([
-+            "ipa", "dnszone-mod", cls.master.domain.name,
-+            "--dnssec=True"
-+        ])
-+        cls.trusted_master.run_command([
-+            "ipa-dns-install",
-+            "--dnssec-master",
-+            "--forwarder", cls.trusted_master.config.dns_forwarder,
-+            "-U",
-+        ])
-+        cls.trusted_master.run_command([
-+            "ipa", "dnszone-mod", cls.trusted_master.domain.name,
-+            "--dnssec=True"
-+        ])
-+
-+    @classmethod
-+    def uninstall(cls, mh):
-+        super(MultiDomainIntegrationTest, cls).uninstall(mh)
-+        for trustedreplica in cls.trusted_replicas:
-+            try:
-+                tasks.run_server_del(
-+                    cls.trusted_master, trustedreplica.hostname, force=True,
-+                    ignore_topology_disconnect=True, ignore_last_of_role=True)
-+            except subprocess.CalledProcessError:
-+                # If the master has already been uninstalled,
-+                # this call may fail
-+                pass
-+            tasks.uninstall_master(trustedreplica)
-+        tasks.uninstall_master(cls.trusted_master)
-+        for client in cls.trusted_clients:
-+            tasks.uninstall_client(client)
-diff --git a/ipatests/test_integration/test_multidomain_ipa.py b/ipatests/test_integration/test_multidomain_ipa.py
-new file mode 100644
-index 000000000..b1a39a072
---- /dev/null
-+++ b/ipatests/test_integration/test_multidomain_ipa.py
-@@ -0,0 +1,54 @@
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import MultiDomainIntegrationTest
-+
-+
-+class TestMultidomain(MultiDomainIntegrationTest):
-+    num_clients = 1
-+    num_replicas = 1
-+    num_trusted_clients = 1
-+    num_trusted_replicas = 1
-+    topology = 'line'
-+
-+    def test_multidomain_trust(self):
-+        """
-+        Test services on multidomain topology.
-+        """
-+
-+        for host in (self.master, self.replicas[0],
-+                     self.trusted_master, self.trusted_replicas[0]
-+                     ):
-+            tasks.start_ipa_server(host)
-+
-+        for host in (self.master, self.trusted_master):
-+            tasks.disable_dnssec_validation(host)
-+            tasks.restart_named(host)
-+
-+        for host in (self.master, self.replicas[0],
-+                     self.trusted_master, self.trusted_replicas[0],
-+                     self.clients[0], self.trusted_clients[0]
-+                     ):
-+            tasks.kinit_admin(host)
-+
-+        # Add DNS forwarder to trusted domain on ipa domain
-+        self.master.run_command([
-+            "ipa", "dnsforwardzone-add", self.trusted_master.domain.name,
-+            "--forwarder", self.trusted_master.ip,
-+            "--forward-policy=only"
-+        ])
-+        self.trusted_master.run_command([
-+            "ipa", "dnsforwardzone-add", self.master.domain.name,
-+            "--forwarder", self.master.ip,
-+            "--forward-policy=only"
-+        ])
-+
-+        tasks.install_adtrust(self.master)
-+        tasks.install_adtrust(self.trusted_master)
-+
-+        #  Establish trust
-+        # self.master.run_command([
-+        #     "ipa", "trust-add", "--type=ipa",
-+        #     "--admin", "admin@{}".format(self.trusted_master.domain.realm),
-+        #     "--range-type=ipa-ad-trust-posix",
-+        #     "--password", "--two-way=true",
-+        #     self.trusted_master.domain.name
-+        # ], stdin_text=self.trusted_master.config.admin_password)
-diff --git a/pylint_plugins.py b/pylint_plugins.py
-index d75da3f4a..75d65f016 100644
---- a/pylint_plugins.py
-+++ b/pylint_plugins.py
-@@ -566,6 +566,7 @@ AstroidBuilder(MANAGER).string_build(
-     textwrap.dedent(
-         """\
-     from ipatests.test_integration.base import IntegrationTest
-+    from ipatests.test_integration.base import MultiDomainIntegrationTest
-     from ipatests.pytest_ipa.integration.host import Host, WinHost
-     from ipatests.pytest_ipa.integration.config import Config, Domain
- 
-@@ -584,6 +585,9 @@ AstroidBuilder(MANAGER).string_build(
-         def __getitem__(self, key):
-             return Domain()
- 
-+    class PylintTrustedDomains:
-+        def __getitem__(self, key):
-+            return Domain()
- 
-     Host.config = Config()
-     Host.domain = Domain()
-@@ -596,6 +600,14 @@ AstroidBuilder(MANAGER).string_build(
-     IntegrationTest.ad_treedomains = PylintWinHosts()
-     IntegrationTest.ad_subdomains = PylintWinHosts()
-     IntegrationTest.ad_domains = PylintADDomains()
-+    MultiDomainIntegrationTest.domain = Domain()
-+    MultiDomainIntegrationTest.master = Host()
-+    MultiDomainIntegrationTest.replicas = PylintIPAHosts()
-+    MultiDomainIntegrationTest.clients = PylintIPAHosts()
-+    MultiDomainIntegrationTest.trusted_master = Host()
-+    MultiDomainIntegrationTest.trusted_replicas = PylintIPAHosts()
-+    MultiDomainIntegrationTest.trusted_clients = PylintIPAHosts()
-+    MultiDomainIntegrationTest.trusted_domains = PylintTrustedDomains()
-     """
-     )
- )
--- 
-2.51.0
-
-
-From 902dbeb67e0574dca4c761d058b43af3ac2cef6a Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Mon, 26 May 2025 20:27:02 +0530
-Subject: [PATCH 25/47] ipatests: Add comprehensive tests for
- ipa-client-automount --domain option
-
-- Add parametrized test for domain validation covering valid/invalid formats
-- Add cross-domain discovery test showing --domain enables discovery when
-  client is in different domain than IPA domain
-- Validate configuration in sssd.conf after successful automount setup
-
-The new tests ensure --domain option works correctly and provides proper
-hints for DNS discovery in cross-domain scenarios, reducing user friction
-compared to requiring --server specification.
-
-Related: https://pagure.io/freeipa/issue/9780
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../nightly_ipa-4-12_latest.yaml              |  17 ++
- .../nightly_ipa-4-12_latest_selinux.yaml      |  18 ++
- ipatests/prci_definitions/temp_commit.yaml    |   4 +
- ipatests/test_integration/test_nfs.py         | 215 ++++++++++++++++--
- ipatests/test_ipalib/test_util.py             |  21 +-
- 5 files changed, 255 insertions(+), 20 deletions(-)
-
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-index bc2a10de4..198c6acec 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
-@@ -43,6 +43,10 @@ topologies:
-     name: ad_master_1repl_1client
-     cpu: 6
-     memory: 12096
-+  ipa_ipa_trust: &ipa_ipa_trust
-+    name: ipa_ipa_trust
-+    cpu: 7
-+    memory: 14750
- 
- jobs:
-   fedora-latest-ipa-4-12/build:
-@@ -59,6 +63,19 @@ jobs:
-         timeout: 1800
-         topology: *build
- 
-+  fedora-latest-ipa-4-12/nfs_automountdiscovery:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        test_suite: test_integration/test_nfs.py::TestIpaClientAutomountDiscovery
-+        trusted_domain: True
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *ipa_ipa_trust
-+
-   fedora-latest-ipa-4-12/simple_replication:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-index fc31186df..8f01bb841 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
-@@ -43,6 +43,10 @@ topologies:
-     name: ad_master_1repl_1client
-     cpu: 6
-     memory: 12096
-+  ipa_ipa_trust: &ipa_ipa_trust
-+    name: ipa_ipa_trust
-+    cpu: 7
-+    memory: 14750
- 
- jobs:
-   fedora-latest-ipa-4-12/build:
-@@ -59,6 +63,20 @@ jobs:
-         timeout: 1800
-         topology: *build
- 
-+  fedora-latest-ipa-4-12/nfs_automountdiscovery:
-+    requires: [fedora-latest-ipa-4-12/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-12/build_url}'
-+        selinux_enforcing: True
-+        test_suite: test_integration/test_nfs.py::TestIpaClientAutomountDiscovery
-+        trusted_domain: True
-+        template: *ci-ipa-4-12-latest
-+        timeout: 3600
-+        topology: *ipa_ipa_trust
-+
-   fedora-latest-ipa-4-12/simple_replication:
-     requires: [fedora-latest-ipa-4-12/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
-index 24b7b4c48..036a1f495 100644
---- a/ipatests/prci_definitions/temp_commit.yaml
-+++ b/ipatests/prci_definitions/temp_commit.yaml
-@@ -49,6 +49,10 @@ topologies:
-     name: ad_master_1repl_1client
-     cpu: 6
-     memory: 12096
-+  ipa_ipa_trust: &ipa_ipa_trust
-+    name: ipa_ipa_trust
-+    cpu: 7
-+    memory: 14750
- 
- jobs:
-   fedora-latest-ipa-4-12/build:
-diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
-index 32d107b71..49a86fc8e 100644
---- a/ipatests/test_integration/test_nfs.py
-+++ b/ipatests/test_integration/test_nfs.py
-@@ -21,15 +21,35 @@ import time
- 
- import pytest
- 
--from ipatests.test_integration.base import IntegrationTest
-+from ipaplatform.paths import paths
-+from ipatests.test_integration.base import (
-+    IntegrationTest, MultiDomainIntegrationTest)
- from ipatests.pytest_ipa.integration import tasks
--
- # give some time for units to stabilize
- # otherwise we get transient errors
- WAIT_AFTER_INSTALL = 5
- WAIT_AFTER_UNINSTALL = WAIT_AFTER_INSTALL
- 
- 
-+def remove_automount(host):
-+    time.sleep(WAIT_AFTER_INSTALL)
-+    host.run_command([
-+        'ipa-client-automount', '--uninstall', '-U'
-+    ], raiseonerr=False)
-+    time.sleep(WAIT_AFTER_UNINSTALL)
-+
-+
-+def add_automount(host, extra_args, raiseonerr=False):
-+    time.sleep(WAIT_AFTER_UNINSTALL)
-+    args = [
-+        "ipa-client-automount",
-+    ]
-+    args.extend(extra_args)
-+    ret = host.run_command(args, raiseonerr=raiseonerr)
-+    time.sleep(WAIT_AFTER_INSTALL)
-+    return ret
-+
-+
- class TestNFS(IntegrationTest):
- 
-     num_clients = 3
-@@ -357,25 +377,184 @@ class TestIpaClientAutomountFileRestore(IntegrationTest):
-         self.nsswitch_backup_restore()
- 
- 
--class TestIpaClientAutomountDiscovery(IntegrationTest):
-+class TestIpaClientAutomountDiscovery(MultiDomainIntegrationTest):
- 
-+    num_replicas = 0
-+    num_trusted_replicas = 0
-     num_clients = 1
--    topology = 'line'
-+    num_trusted_clients = 1
-+    topology = "line"
-+
-+    def test_automount_valid_domain(self):
-+        """Test that --domain option controls which domain is used
-+        for DNS SRV record lookup.
- 
--    def test_automount_invalid_domain(self):
--        """Validate that the --domain option is passed into
--           Discovery. This is expected to fail discovery.
-+        Without --domain: client searches in its local domain (fails if no
-+        IPA records) With --domain: client searches in the specified
-+        domain (succeeds for IPA domain)
-         """
--        testdomain = "client.test"
--        msg1 = f"Search for LDAP SRV record in {testdomain}"
--        msg2 = f"Search DNS for SRV record of _ldap._tcp.{testdomain}"
--        msg3 = "Autodiscovery did not find LDAP server"
-+        testdomain1 = self.master.domain.name
-+        client2 = self.trusted_clients[0]
-+        tasks.uninstall_client(client2)
-+        client2.run_command(["ipa-client-install", "--domain", testdomain1,
-+                             "--realm", self.master.domain.realm,
-+                             "--server", self.master.hostname,
-+                             "-p", client2.config.admin_name, "-w",
-+                             client2.config.admin_password, "-U"]
-+                            )
-+        result = add_automount(
-+            client2, extra_args=['--debug', '-U']
-+        )
-+        msg = "Search DNS for SRV record of _ldap._tcp.{0}"
-+        assert msg.format(client2.domain.name) in result.stderr_text
-+        remove_automount(client2)
-+        result2 = add_automount(
-+            client2, extra_args=['--debug', '--domain', testdomain1, '-U']
-+        )
-+        assert msg.format(testdomain1) in result2.stderr_text
- 
-+    @pytest.mark.parametrize(
-+        "domain_input,expected_success,test_description", [
-+            ("{ipa_domain}", True, "valid IPA domain should succeed"),
-+            ("client.test", False, "non-IPA domain should fail discovery"),
-+            ("  example.com  ", True, "whitespace should be trimmed"),
-+            ("EXAMPLE.COM", True, "uppercase should work"),
-+
-+        ])
-+    def test_automount_domain_option_integration(
-+            self, domain_input, expected_success, test_description):
-+        """Test for --domain affects DNS discovery and system integration.
-+
-+        This test verifies that the --domain option actually changes the DNS
-+        discovery behavior and system configuration, not just input validation.
-+
-+        Test cases:
-+        - Valid IPA domain: Should successfully discover IPA services and
-+          configure automount correctly
-+        - Non-IPA domain: Should fail discovery since no IPA DNS records
-+          exist in that domain
-+        """
-+        client = self.clients[0]
-+        ipa_domain = self.master.domain.name
-+
-+        # Replace placeholders in domain_input
-+        domain_to_test = domain_input.format(ipa_domain=ipa_domain)
-+
-+        if expected_success:
-+            # Should succeed
-+            add_automount(
-+                client, extra_args=['--domain', domain_to_test,
-+                                    '--debug', '-U']
-+            )
-+            # Verify configuration if successful
-+            sssd_conf = client.get_file_contents(
-+                paths.SSSD_CONF).decode()
-+            assert "autofs_provider = ipa" in sssd_conf, \
-+                "Autofs provider should be set to ipa"
-+            # Clean up
-+            remove_automount(client)
-+        else:
-+            # Should fail
-+            result = client.run_command([
-+                'ipa-client-automount', '--domain', domain_to_test,
-+                '--debug'
-+            ], stdin_text="n", raiseonerr=False)
-+            assert (
-+                result.returncode != 0
-+                or "Autodiscovery did not find LDAP server" in
-+                result.stderr_text
-+                or "Invalid domain" in result.stderr_text
-+            ), f"Should have failed: {test_description}"
-+
-+    def test_automount_domain_option_overrides_discovery(self):
-+        """Test that explicit --domain option overrides automatic discovery."""
-         client = self.clients[0]
--        result = client.run_command([
--            'ipa-client-automount', '--domain', 'client.test',
--            '--debug'
--        ], stdin_text="n", raiseonerr=False)
--        assert msg1 in result.stderr_text
--        assert msg2 in result.stderr_text
--        assert msg3 in result.stderr_text
-+        ipa_domain = self.master.domain.name
-+
-+        # First install without domain to establish baseline
-+        result_auto = add_automount(client, extra_args=['--debug', '-U'])
-+        assert "Search DNS for SRV record" in result_auto.stderr_text
-+        remove_automount(client)
-+
-+        # Now with explicit domain
-+        result_explicit = add_automount(
-+            client, extra_args=['--domain', ipa_domain, '--debug', '-U']
-+        )
-+        explicit_domain_msg = (
-+            f"Using domain '{ipa_domain}'" in result_explicit.stderr_text
-+            or f"Search DNS for SRV record of _ldap._tcp.{ipa_domain}"
-+            in result_explicit.stderr_text
-+        )
-+
-+        assert explicit_domain_msg, \
-+            "Explicit domain should override automatic discovery"
-+
-+        # Final cleanup
-+        remove_automount(client)
-+
-+    def test_automount_domain_hint_for_cross_domain_discovery(self):
-+        """Test that --domain option enables discovery when client is in
-+           a different domain than the IPA domain.
-+        """
-+        client = self.clients[0]
-+        other_domain = self.trusted_master.domain.name
-+        tasks.uninstall_client(client)
-+        # Add DNS forwarder
-+        self.master.run_command([
-+            "ipa", "dnsforwardzone-add", self.trusted_master.domain.name,
-+            "--forwarder", self.trusted_master.ip,
-+            "--forward-policy=only"
-+        ])
-+        self.trusted_master.run_command([
-+            "ipa", "dnsforwardzone-add", self.master.domain.name,
-+            "--forwarder", self.master.ip,
-+            "--forward-policy=only"
-+        ])
-+        # Backup original resolv.conf
-+        tasks.backup_file(client, paths.RESOLV_CONF)
-+
-+        try:
-+            # Install client in a domain other than the IPA domain
-+            non_ipa_resolv_conf = f"""search {other_domain}
-+nameserver {self.trusted_master.ip}
-+"""
-+            client.put_file_contents(paths.RESOLV_CONF, non_ipa_resolv_conf)
-+
-+            # Ensure client is installed for the test
-+            tasks.uninstall_client(client)
-+            client.run_command(["ipa-client-install", "--domain", other_domain,
-+                                "--realm", self.trusted_master.domain.realm,
-+                                "--server", self.trusted_master.hostname,
-+                                "-p", client.config.admin_name, "-w",
-+                                client.config.admin_password, "-U"]
-+                               )
-+            # Verify DNS discovery will fail when client is in Non-IPA domain
-+            # Attempt automount with --domain hint (should succeed)
-+            nodomain = add_automount(
-+                client, extra_args=['--debug', '-U']
-+            )
-+            # Verify discovery fails.
-+            assert "DNS record not found" in nodomain.stderr_text
-+            remove_automount(client)
-+
-+            # Attempt automount with --domain hint (should succeed)
-+            withdomain = add_automount(
-+                client, extra_args=['--domain', other_domain,
-+                                    '--debug', '-U']
-+            )
-+            # Verify discovery finds a IPA server.
-+            assert "DNS record found" in withdomain.stderr_text
-+            ipa_discovery = (
-+                f"Validated servers: {self.trusted_master.hostname}"
-+                in withdomain.stderr_text
-+            )
-+            assert ipa_discovery, \
-+                "Autodiscovery success"
-+            # Verify configuration was applied correctly
-+            sssd_conf = client.get_file_contents(paths.SSSD_CONF).decode()
-+            assert "autofs_provider = ipa" in sssd_conf, \
-+                "Autofs provider should be configured"
-+        finally:
-+            # Cleanup: restore original resolv.conf and uninstall
-+            tasks.restore_files(client)
-+            remove_automount(client)
-diff --git a/ipatests/test_ipalib/test_util.py b/ipatests/test_ipalib/test_util.py
-index 74a32b72c..ba1b9f120 100644
---- a/ipatests/test_ipalib/test_util.py
-+++ b/ipatests/test_ipalib/test_util.py
-@@ -11,8 +11,10 @@ from unittest import mock
- import pytest
- 
- from ipalib.util import (
--    get_pager, create_https_connection, get_proper_tls_version_span
-+    get_pager, create_https_connection, get_proper_tls_version_span,
-+    validate_domain_name
- )
-+
- from ipaplatform.constants import constants
- 
- 
-@@ -27,7 +29,7 @@ from ipaplatform.constants import constants
- def test_get_pager(pager, expected_result):
-     with mock.patch.dict(os.environ, {'PAGER': pager}):
-         pager = get_pager()
--        assert(pager == expected_result or pager.endswith(expected_result))
-+        assert (pager == expected_result or pager.endswith(expected_result))
- 
- 
- BASE_CTX = ssl.SSLContext(ssl.PROTOCOL_TLS)
-@@ -75,3 +77,18 @@ def test_tls_version_span(minver, maxver, opt, expected):
-     ctx = getattr(conn, "_context")
-     assert ctx.options == BASE_OPT | opt
-     assert ctx.get_ciphers() == BASE_CTX.get_ciphers()
-+
-+
-+@pytest.mark.parametrize("domain_input,expected_valid,description", [
-+    ("invalid..domain", False, "double dots should be rejected"),
-+    (".invalid.domain", False, "leading dot should be rejected"),
-+    ("invalid domain with spaces", False, "spaces should be rejected"),
-+    ("toolong" + "x" * 250 + ".domain", False, "overly long domain rejected"),
-+    ("", False, "empty string should be rejected"),
-+    ("single", False, "single label should be rejected"),
-+])
-+def test_validate_domain_name(domain_input, expected_valid, description):
-+    """Test domain name validation logic in ipalib.util.validate_domain_name"""
-+
-+    with pytest.raises((ValueError, TypeError)):
-+        validate_domain_name(domain_input)
--- 
-2.51.0
-
-
-From 09acc861692d265b72d058192f9dd6f07581839c Mon Sep 17 00:00:00 2001
-From: Sam Morris <sam@robots.org.uk>
-Date: Thu, 31 Jul 2025 15:52:37 +0100
-Subject: [PATCH 26/47] Fix ipa-client-install failure when a trusted CA's
- distinguished name contains slash characters
-
-Fixes: https://pagure.io/freeipa/issue/8924
-Signed-off-by: Sam Morris <sam@robots.org.uk>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaplatform/debian/tasks.py | 12 +++++++++---
- 1 file changed, 9 insertions(+), 3 deletions(-)
-
-diff --git a/ipaplatform/debian/tasks.py b/ipaplatform/debian/tasks.py
-index 8a50c66bc..b56b5a26b 100644
---- a/ipaplatform/debian/tasks.py
-+++ b/ipaplatform/debian/tasks.py
-@@ -146,9 +146,15 @@ used by ca-certificates and is provided for information only.\
-             # CAs who used the same serial number?)
-             filename = f'{subject.ldap_text()} {cert.serial_number}.crt'
- 
--            # pylint: disable=old-division
--            cert_path = path / filename
--            # pylint: enable=old-division
-+            # Some CAs have DNs with a / or NUL character, which are not legal
-+            # in paths. Also escape some other annoying characters for good
-+            # measure.
-+            bad_chars = {'\0', '/', ':'}
-+            safe_filename = ''.join(
-+                ('-' if c in bad_chars else c for c in filename)
-+            )
-+
-+            cert_path = os.path.join(path, safe_filename)
-             try:
-                 f = open(cert_path, 'w')
-             except Exception:
--- 
-2.51.0
-
-
-From 72e1a1db6eb762811e37e5b4d929b47b4a69f006 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 25 Aug 2025 15:32:38 +0200
-Subject: [PATCH 27/47] Spec file: bump samba version to 4.23.0 in f43 and
- above
-
-Fixes: https://pagure.io/freeipa/issue/9843
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- freeipa.spec.in | 10 ++++++++++
- 1 file changed, 10 insertions(+)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 1cb3d0233..5d5d75a49 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -109,7 +109,17 @@
- %global python_netaddr_version 0.7.16
- # Require 4.7.0 which brings Python 3 bindings
- # Require 4.12 which has DsRGetForestTrustInformation access rights fixes
-+%if 0%{?fedora} <= 40
- %global samba_version 2:4.12.10
-+%elif 0%{?fedora} == 41
-+# Require 4.20.0 or later for libndr4, latest F41 is 4.21.7 already
-+%global samba_version 2:4.21.7
-+%elif 0%{?fedora} == 42
-+%global samba_version 2:4.22.0
-+%else
-+# Require 4.23 for passdb ABI bump
-+%global samba_version 2:4.23.0
-+%endif
- 
- # 3.14.5-45 or later includes a number of interfaces fixes for IPA interface
- # 36.16-1 fixes BZ#2115691
--- 
-2.51.0
-
-
-From bc3c095d74c2d56a20346db878e8c724a2cfd227 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 27 Aug 2025 17:18:57 +0200
-Subject: [PATCH 28/47] DNS over TLS: use system trust store
-
-When a client or server is configured with DoT, the installer
-creates /etc/unbound/conf.d/zzz-ipa.conf with
-    tls-cert-bundle: /etc/pki/tls/certs/ca-bundle.crt
-
-This certificate bundle does not exist any more in fedora 43+
-(https://fedoraproject.org/wiki/Changes/droppingOfCertPemFile)
-and the missing file prevents unbound service from starting.
-
-The configuration should rather set
-    tls-system-cert: yes
-in order to rely on the systemwide trust store.
-
-The upgrade is also handled by this change.
-
-Fixes: https://pagure.io/freeipa/issue/9838
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- client/share/unbound.conf.template |  2 +-
- freeipa.spec.in                    | 11 +++++++++++
- ipaclient/install/client.py        |  2 --
- ipaserver/install/dns.py           |  2 --
- 4 files changed, 12 insertions(+), 5 deletions(-)
-
-diff --git a/client/share/unbound.conf.template b/client/share/unbound.conf.template
-index f611ebb7e..9b1c983e5 100644
---- a/client/share/unbound.conf.template
-+++ b/client/share/unbound.conf.template
-@@ -1,5 +1,5 @@
- server:
--    tls-cert-bundle: $TLS_CERT_BUNDLE_PATH
-+    tls-system-cert: yes
-     tls-upstream: yes
-     interface: 127.0.0.55
-     log-servfail: yes
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 5d5d75a49..c8dc67f4f 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1413,6 +1413,17 @@ if [ $1 -gt 1 ] ; then
-             fi
-         fi
-     fi
-+
-+    UNBOUND_CFG=/etc/unbound/conf.d/zzz-ipa.conf
-+    if [ -f "$UNBOUND_CFG" -a $restore -ge 2 ]; then
-+        # The client has been configured for Dot
-+        # replace the line tls-cert-bundle: /etc/pki/tls/certs/ca-bundle.crt
-+        # with tls-system-cert: yes
-+        # See https://fedoraproject.org/wiki/Changes/droppingOfCertPemFile
-+        if grep -E -q 'tls-cert-bundle: \/etc\/pki\/tls\/certs\/ca-bundle.crt'  $UNBOUND_CFG 2>/dev/null; then
-+            sed -E --in-place=.orig 's/tls-cert-bundle: \/etc\/pki\/tls\/certs\/ca-bundle.crt/tls-system-cert: yes/' $UNBOUND_CFG
-+        fi
-+    fi
- fi
- 
- 
-diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
-index 1885e4a8d..2a7f4b956 100644
---- a/ipaclient/install/client.py
-+++ b/ipaclient/install/client.py
-@@ -1692,8 +1692,6 @@ def client_dns(server, hostname, options, statestore):
-             paths.UNBOUND_CONF_SRC,
-             paths.UNBOUND_CONF,
-             dict(
--                TLS_CERT_BUNDLE_PATH=os.path.join(
--                    paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
-                 FORWARD_ADDRS=forward_addr,
-                 MODULE_CONFIG_ITERATOR=module_config_iterator
-             )
-diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
-index 39c2f677b..72f9de07c 100644
---- a/ipaserver/install/dns.py
-+++ b/ipaserver/install/dns.py
-@@ -152,8 +152,6 @@ def _setup_dns_over_tls(options):
-         paths.UNBOUND_CONF_SRC,
-         paths.UNBOUND_CONF,
-         dict(
--            TLS_CERT_BUNDLE_PATH=os.path.join(
--                paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
-             FORWARD_ADDRS="\n".join(forward_addrs),
-             MODULE_CONFIG_ITERATOR=module_config_iterator
-         )
--- 
-2.51.0
-
-
-From 03f48bffdd82cc48b5952c112db2b024f085d5d4 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 4 Sep 2025 16:08:22 +0200
-Subject: [PATCH 29/47] xmlrpc test: fix test_find_orphan_automember_rules
-
-The test was written with a complex logic depending on the
-installed version of 389-ds. The command was expected to fail if
-version < 1.4.0.22.
-
-Fedora has been shipping the fixed version for a long time already,
-and the test can be simplified. Moreover, the code responsible
-for retrieving 389 ds version does not work for ds 3.1.3 which
-provides a vendorVersion attribute not compliant with python
-versioning schema.
-
-Fixes: https://pagure.io/freeipa/issue/9850
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- .../test_xmlrpc/test_automember_plugin.py     | 29 ++-----------------
- 1 file changed, 2 insertions(+), 27 deletions(-)
-
-diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py
-index 7fd6f6ca6..aa7c1d65a 100644
---- a/ipatests/test_xmlrpc/test_automember_plugin.py
-+++ b/ipatests/test_xmlrpc/test_automember_plugin.py
-@@ -35,8 +35,6 @@ from ipaserver.plugins.automember import REBUILD_TASK_CONTAINER
- 
- import time
- import pytest
--import re
--from packaging.version import parse as parse_version
- 
- try:
-     from ipaserver.plugins.ldap2 import ldap2
-@@ -1492,31 +1490,8 @@ class TestAutomemberFindOrphans(XMLRPC_test):
- 
-         hostgroup1.ensure_missing()
- 
--        # Test rebuild (is failing)
--        # rebuild fails if 389-ds is older than 1.4.0.22 where unmembering
--        # feature was implemented: https://pagure.io/389-ds-base/issue/50077
--        if not have_ldap2:
--            pytest.skip('server plugin not available')
--        ldap = ldap2(api)
--        ldap.connect()
--        rootdse = ldap.get_entry(DN(''), ['vendorVersion'])
--        version = rootdse.single_value.get('vendorVersion')
--        # The format of vendorVersion is the following:
--        # 389-Directory/1.3.8.4 B2019.037.1535
--        # Extract everything between 389-Directory/ and ' B'
--        mo = re.search(r'389-Directory/(.*) B', version)
--        vendor_version = parse_version(mo.groups()[0])
--        expected_failure = vendor_version < parse_version('1.4.0.22')
--
--        try:
--            api.Command['automember_rebuild'](type=u'hostgroup')
--        except errors.DatabaseError:
--            rebuild_failure = True
--        else:
--            rebuild_failure = False
--        if expected_failure != rebuild_failure:
--            pytest.fail("unexpected result for automember_rebuild with "
--                        "an orphan automember rule")
-+        # Rebuild with a missing hostgroup
-+        api.Command['automember_rebuild'](type=u'hostgroup')
- 
-         # Find obsolete automember rules
-         result = api.Command['automember_find_orphans'](type=u'hostgroup')
--- 
-2.51.0
-
-
-From 537e032aae5896b12f6d531291954839b790c712 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 9 Sep 2025 12:55:28 +0200
-Subject: [PATCH 30/47] azure webui tests: force chromium version
-
-azure webui tests are failing with chromium-140.0.7339.80-1.fc42
-Force to install an older version
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/azure/templates/prepare-webui-fedora.yml | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/azure/templates/prepare-webui-fedora.yml b/ipatests/azure/templates/prepare-webui-fedora.yml
-index 9e2d9361e..9590a7d62 100644
---- a/ipatests/azure/templates/prepare-webui-fedora.yml
-+++ b/ipatests/azure/templates/prepare-webui-fedora.yml
-@@ -1,5 +1,6 @@
- steps:
- - script: |
-     set -e
--    sudo dnf -y install npm fontconfig gtk3 atk at-spi2-atk chromium
-+    sudo dnf install -y chromium-134.0.6998.165-1.fc42
-+    sudo dnf -y install npm fontconfig gtk3 atk at-spi2-atk
-   displayName: Install WebUI Unit tests prerequisites
--- 
-2.51.0
-
-
-From b923355ff04dd88b1530d0bb2e032280afc5d315 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 26 Aug 2025 09:00:48 +0200
-Subject: [PATCH 31/47] ipatests: remove xfail for PKI 11.7
-
-The test test_ca_show_error_handling is green with PKI 11.7
-because the PKI regression has been fixed.
-Update the xfail condition to 11.5 <= version < 11.7.
-
-Fixes: https://pagure.io/freeipa/issue/9606
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_cert.py | 6 ++++--
- 1 file changed, 4 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 05b20b910..84adf2cea 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -558,8 +558,10 @@ class TestCAShowErrorHandling(IntegrationTest):
-         )
-         error_msg = 'ipa: ERROR: The certificate for ' \
-                     '{} is not available on this server.'.format(lwca)
--        bad_version = (tasks.get_pki_version(self.master)
--                       >= tasks.parse_version('11.5.0'))
-+        pki_version = tasks.get_pki_version(self.master)
-+        # The regression was introduced in 11.5 and fixed in 11.7
-+        bad_version = (tasks.parse_version('11.5.0') <= pki_version
-+                       < tasks.parse_version('11.7.0'))
-         with xfail_context(bad_version,
-                            reason="https://pagure.io/freeipa/issue/9606"):
-             assert error_msg in result.stderr_text
--- 
-2.51.0
-
-
-From 4a2e912d2386dfeb9765e32dd244b32b03cbf9a5 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 2 Sep 2025 10:05:31 +0200
-Subject: [PATCH 32/47] ipatests: fix test_certmonger_ipa_responder_jsonrpc
-
-Test scenario:
-- install IPA server and client
-- store the start date
-- request a certificate on the client using ipa-getcert
-- check in the journal after start date that the request was done using the
-https://.../ipa/json URI
-
-The test obtains the start date on the runner. As a consequence, if the runner
-is late compared to the client, it may miss the message in the journal.
-The date should rather be obtained on the client.
-
-Fixes: https://pagure.io/freeipa/issue/9848
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_cert.py | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 84adf2cea..ddc4e089a 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -13,7 +13,6 @@ import pytest
- import random
- import re
- import string
--import time
- import textwrap
- 
- from ipaplatform.paths import paths
-@@ -70,9 +69,10 @@ class TestInstallMasterClient(IntegrationTest):
-     def install(cls, mh):
-         super().install(mh)
- 
--        # time to look into journal logs in
-+        # store the start time to look into journal logs in
-         # test_certmonger_ipa_responder_jsonrpc
--        cls.since = time.strftime('%Y-%m-%d %H:%M:%S')
-+        result = cls.clients[0].run_command(['date', '+%Y-%m-%d %H:%M:%S'])
-+        cls.since = result.stdout_text.strip()
- 
-     def test_cacert_file_appear_with_option_F(self):
-         """Test if getcert creates cacert file with -F option
--- 
-2.51.0
-
-
-From 9b631f80720fe1f2492d1a30bb1c2410af5eb587 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 3 Sep 2025 14:57:52 +0200
-Subject: [PATCH 33/47] ipatests: fix test_otp
-
-The test is performing ssh from the runner to the master
-but is using the external_hostname and randomly fails.
-
-Make sure to use the configured hostname instead.
-
-Fixes: https://pagure.io/freeipa/issue/9849
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_otp.py | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
-index 0babb4589..a4adeee69 100644
---- a/ipatests/test_integration/test_otp.py
-+++ b/ipatests/test_integration/test_otp.py
-@@ -504,7 +504,7 @@ class TestOTPToken(IntegrationTest):
-             )
-             with xfail_context(rhel_fail or fedora_fail, reason=github_ticket):
-                 result = ssh_2fa_with_cmd(master,
--                                          self.master.external_hostname,
-+                                          self.master.hostname,
-                                           USER3, PASSWORD, otpvalue=otpvalue,
-                                           command="klist")
-                 print(result.stdout_text)
-@@ -552,7 +552,7 @@ class TestOTPToken(IntegrationTest):
-             otpvalue = totp.generate(int(time.time())).decode('ascii')
-             tasks.clear_sssd_cache(self.master)
-             result = ssh_2fa_with_cmd(master,
--                                      self.master.external_hostname,
-+                                      self.master.hostname,
-                                       USER4, PASSWORD, otpvalue=otpvalue,
-                                       command="klist")
-             print(result.stdout_text)
--- 
-2.51.0
-
-
-From 7d4b5541b2de30a91cd1f14b790bfa33dca8ea5f Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 8 Sep 2025 18:57:09 +0300
-Subject: [PATCH 34/47] dcerpc: make sure forest trust info structure version
- is 1
-
-[MS-DRSR] 5.64 FOREST_TRUST_INFORMATION defines version of the
-ForestTrustInfo structure as 1. We didn't set it so it was defaulting to
-zero. Samba validates the version number and rejects ForestTrustInfo
-structures from FreeIPA.
-
-Make sure new structures are always set to version 1 and old strutures
-fixed up to have version 1.
-
-Fixes: https://pagure.io/freeipa/issue/9852
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 38 +++++++++++++++++++++++++++++++++++++-
- ipaserver/dcerpc.py       |  1 +
- 2 files changed, 38 insertions(+), 1 deletion(-)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index 26f8e89ec..3b8fc90fb 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -30,6 +30,8 @@ char *smb_xstrdup(const char *s);
- #include <smbldap.h>
- 
- #include <gen_ndr/samr.h>
-+#include <gen_ndr/drsblobs.h>
-+#include <gen_ndr/ndr_drsblobs.h>
- 
- #include <passdb.h>
- 
-@@ -101,7 +103,6 @@ struct unixid {
- 	enum id_type type;
- }/* [public] */;
- 
--enum ndr_err_code ndr_pull_trustAuthInOutBlob(struct ndr_pull *ndr, int ndr_flags, struct trustAuthInOutBlob *r); /*available in libndr-samba.so */
- bool sid_check_is_builtin(const struct dom_sid *sid); /* available in libpdb.so */
- /* available in libpdb.so, renamed from sid_check_is_domain() in c43505b621725c9a754f0ee98318d451b093f2ed */
- bool sid_linearize(char *outbuf, size_t len, const struct dom_sid *sid); /* available in libsmbconf.so */
-@@ -2422,6 +2423,36 @@ static bool get_uint32_t_from_ldap_msg(struct ipasam_private *ipasam_state,
- 	return true;
- }
- 
-+static bool repack_pdb_forest_trust_info(struct pdb_trusted_domain *td)
-+{
-+	struct ForestTrustInfo *fti = NULL;
-+	enum ndr_err_code ndr_err = 0;
-+	/*
-+	 * Fix-up the version field as Samba expects it.
-+	 * We need to unpack the blob, change, and pack it again
-+	 */
-+	fti = talloc(td, struct ForestTrustInfo);
-+	if (fti == NULL) {
-+	    return false;
-+	}
-+	ndr_err = ndr_pull_struct_blob_all(&td->trust_forest_trust_info, td, fti,
-+					   (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo);
-+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-+	    TALLOC_FREE(fti);
-+	    return false;
-+	}
-+
-+	fti->version = 1;
-+
-+	talloc_free(td->trust_forest_trust_info.data);
-+	td->trust_forest_trust_info = data_blob_null;
-+
-+	ndr_err = ndr_push_struct_blob(&td->trust_forest_trust_info, td, fti,
-+				       (ndr_push_flags_fn_t)ndr_push_ForestTrustInfo);
-+	TALLOC_FREE(fti);
-+	return NDR_ERR_CODE_IS_SUCCESS(ndr_err);
-+}
-+
- static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 				    struct ipasam_private *ipasam_state,
- 				    LDAPMessage *entry,
-@@ -2614,6 +2645,11 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 					LDAP_ATTRIBUTE_TRUST_FOREST_TRUST_INFO,
- 					&td->trust_forest_trust_info)) {
- 		DEBUG(9, ("Failed to set forest trust info.\n"));
-+	} else {
-+		res = repack_pdb_forest_trust_info(td);
-+		if (!res) {
-+			return false;
-+		}
- 	}
- 
- 	*_td = td;
-diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
-index 3344ea226..b75c4fccf 100644
---- a/ipaserver/dcerpc.py
-+++ b/ipaserver/dcerpc.py
-@@ -1658,6 +1658,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
- 
-     ftinfo.count = len(ftinfo_records)
-     ftinfo.records = ftinfo_records
-+    ftinfo.version = 1
-     result['ftinfo_data'] = ndr_pack(ftinfo)
-     return result
- 
--- 
-2.51.0
-
-
-From 486558c63e4b0db6673e863a6bd89e885f8106dd Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 8 Sep 2025 18:59:50 +0300
-Subject: [PATCH 35/47] dcerpc: Support Samba 4.23
-
-Samba 4.23 ignores trust to PIM and 'within the forest' domains. It
-didn't know what to do with them in past as well but starting with 4.23
-the domain trust entries which have trust attributes with
-LSA_TRUST_ATTRIBUTE_PIM_TRUST and LSA_TRUST_ATTRIBUTE_WITHIN_FOREST bits
-set will be ignored.
-
-FreeIPA did default to LSA_TRUST_ATTRIBUTE_WITHIN_FOREST for domains
-stored with trusted attributes set to 0 or missing. This was behavior in
-past for Samba as well. Since FreeIPA only supports forest trust and an
-external trust, in both cases we should set explicit LSA trust attribute
-bits:
- - LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE for the forest trust
- - LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE for the external trust
-
-Fixes: https://pagure.io/freeipa/issue/9852
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 8 ++++----
- ipaserver/dcerpc.py       | 2 ++
- 2 files changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index 3b8fc90fb..9fb4db4c5 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -2594,10 +2594,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 		TALLOC_FREE(td);
- 		return false;
- 	}
--	if (td->trust_attributes == 0) {
--		/* attribute wasn't present, this is a subdomain within the
--		 * parent forest */
--		td->trust_attributes = LSA_TRUST_ATTRIBUTE_WITHIN_FOREST;
-+	if (td->trust_attributes == 0 && (td->domain_name != dns_domain)) {
-+                /* attribute wasn't present and this is not a subdomain within
-+                 * the parent forest */
-+		td->trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
- 	}
- 
- 	res = get_uint32_t_from_ldap_msg(ipasam_state, entry,
-diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
-index b75c4fccf..1182f128b 100644
---- a/ipaserver/dcerpc.py
-+++ b/ipaserver/dcerpc.py
-@@ -1362,6 +1362,8 @@ class TrustDomainInstance:
-         info.trust_attributes = 0
-         if trust_external:
-             info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
-+        else:
-+            info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
- 
-         try:
-             dname = lsa.String()
--- 
-2.51.0
-
-
-From fcd0f0c6e07200e90628f108c7566b0ede7879b8 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 11 Sep 2025 13:52:41 +0300
-Subject: [PATCH 36/47] ipasam: simplify error handling in
- fill_pdb_trusted_domain
-
-Related: https://pagure.io/freeipa/issue/9852
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 44 ++++++++++++++++++---------------------
- 1 file changed, 20 insertions(+), 24 deletions(-)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index 9fb4db4c5..ea85a7f43 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -2507,7 +2507,8 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 	rc = ldap_str2dn(strdn, &dn, LDAP_DN_FORMAT_LDAPV3);
- 	if (rc) {
- 		free(strdn);
--		return false;
-+		res = false;
-+		goto done;
- 	}
- 
- 	for (count = 0; dn[count] != NULL; count++);
-@@ -2519,8 +2520,8 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 			  strdn, ipasam_state->trust_dn));
- 		ldap_dnfree(dn);
- 		free(strdn);
--		TALLOC_FREE(td);
--		return false;
-+		res = false;
-+		goto done;
- 
- 	}
- 
-@@ -2543,7 +2544,8 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 					       dummy, &sid);
- 		TALLOC_FREE(dummy);
- 		if (err != IDMAP_SUCCESS) {
--			return false;
-+			res = false;
-+			goto done;
- 		}
- 		sid_copy(&td->security_identifier, sid);
- 		talloc_free(sid);
-@@ -2583,16 +2585,14 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 					 LDAP_ATTRIBUTE_TRUST_DIRECTION,
- 					 &td->trust_direction);
- 	if (!res) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 
- 	res = get_uint32_t_from_ldap_msg(ipasam_state, entry,
- 					 LDAP_ATTRIBUTE_TRUST_ATTRIBUTES,
- 					 &td->trust_attributes);
- 	if (!res) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 	if (td->trust_attributes == 0 && (td->domain_name != dns_domain)) {
-                 /* attribute wasn't present and this is not a subdomain within
-@@ -2604,8 +2604,7 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 					 LDAP_ATTRIBUTE_TRUST_TYPE,
- 					 &td->trust_type);
- 	if (!res) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 	if (td->trust_type == 0) {
- 		/* attribute wasn't present, set default value */
-@@ -2614,28 +2613,24 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 
- 	td->trust_posix_offset = talloc_zero(td, uint32_t);
- 	if (td->trust_posix_offset == NULL) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 	res = get_uint32_t_from_ldap_msg(ipasam_state, entry,
- 					 LDAP_ATTRIBUTE_TRUST_POSIX_OFFSET,
- 					 td->trust_posix_offset);
- 	if (!res) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 
- 	td->supported_enc_type = talloc_zero(td, uint32_t);
- 	if (td->supported_enc_type == NULL) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 	res = get_uint32_t_from_ldap_msg(ipasam_state, entry,
- 					 LDAP_ATTRIBUTE_SUPPORTED_ENC_TYPE,
- 					 td->supported_enc_type);
- 	if (!res) {
--		TALLOC_FREE(td);
--		return false;
-+		goto done;
- 	}
- 	if (*td->supported_enc_type == 0) {
- 		*td->supported_enc_type = ipasam_state->supported_enctypes;
-@@ -2647,14 +2642,15 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
- 		DEBUG(9, ("Failed to set forest trust info.\n"));
- 	} else {
- 		res = repack_pdb_forest_trust_info(td);
--		if (!res) {
--			return false;
--		}
- 	}
- 
--	*_td = td;
--
--	return true;
-+done:
-+	if (res) {
-+		*_td = td;
-+	} else {
-+		TALLOC_FREE(td);
-+	}
-+	return res;
- }
- 
- static NTSTATUS ipasam_get_trusted_domain(struct pdb_methods *methods,
--- 
-2.51.0
-
-
-From cb36c3d9969ce0c0beacb43c7d4ea29da652c499 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 11 Sep 2025 13:57:46 +0300
-Subject: [PATCH 37/47] ipasam: address signedness warnings
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 7 ++-----
- 1 file changed, 2 insertions(+), 5 deletions(-)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index ea85a7f43..55a3a03a5 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -236,15 +236,13 @@ static void idmap_talloc_free(void *ptr, void *pvt)
- 
- static void sid_copy(struct dom_sid *dst, const struct dom_sid *src)
- {
--	size_t c;
--
- 	memset(dst, 0, sizeof(*dst));
- 
- 	dst->sid_rev_num = src->sid_rev_num;
- 	dst->num_auths = src->num_auths;
- 	memcpy(&dst->id_auth[0], &src->id_auth[0], sizeof(src->id_auth));
- 
--	for (c = 0; c < src->num_auths; c++) {
-+	for (int8_t c = 0; c < src->num_auths; c++) {
- 		dst->sub_auths[c] = src->sub_auths[c];
- 	}
- }
-@@ -3249,7 +3247,6 @@ static NTSTATUS ipasam_enum_trusteddoms(struct pdb_methods *methods,
- {
- 	NTSTATUS status;
- 	struct pdb_trusted_domain **td;
--	int i;
- 
- 	status = ipasam_enum_trusted_domains(methods, mem_ctx,
- 					     num_domains, &td);
-@@ -3267,7 +3264,7 @@ static NTSTATUS ipasam_enum_trusteddoms(struct pdb_methods *methods,
- 		goto fail;
- 	}
- 
--	for (i = 0; i < *num_domains; i++) {
-+	for (uint32_t i = 0; i < *num_domains; i++) {
- 		struct trustdom_info *dom_info;
- 
- 		dom_info = talloc(*domains, struct trustdom_info);
--- 
-2.51.0
-
-
-From fc40c7cdcfa3e59d5122248f3ed9faa135de22a6 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 11 Sep 2025 13:58:03 +0300
-Subject: [PATCH 38/47] ipasam: define prototypes
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index 55a3a03a5..839a621ef 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -5380,12 +5380,14 @@ static NTSTATUS pdb_init_ipasam(struct pdb_methods **pdb_method,
- 	return NT_STATUS_OK;
- }
- 
-+NTSTATUS samba_module_init(void);
- NTSTATUS samba_module_init(void)
- {
- 	return smb_register_passdb(PASSDB_INTERFACE_VERSION, "ipasam",
- 				   pdb_init_ipasam);
- }
- 
-+NTSTATUS samba_init_module(void);
- NTSTATUS samba_init_module(void)
- {
- 	return smb_register_passdb(PASSDB_INTERFACE_VERSION, "ipasam",
--- 
-2.51.0
-
-
-From 94493640e10547cd4aff82b017391916149822e5 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 17 Sep 2025 10:13:44 +0200
-Subject: [PATCH 39/47] ipatests: update the Let's Encrypt cert chain
-
-The test TestIPACommand::test_cacert_manage is using
-Let's Encrypt chain to check the ipa-cacert-manage install
-command.
-The chain isrgrootx1 > r3 must be replaced with
-isrgrootx1 > r12 because r3 expired Sep 15.
-
-Fixes: https://pagure.io/freeipa/issue/9857
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_commands.py | 63 +++++++++++-----------
- 1 file changed, 31 insertions(+), 32 deletions(-)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index ad97affe6..fcf347ee0 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -88,41 +88,40 @@ isrgrootx1 = (
- )
- isrgrootx1_nick = 'CN=ISRG Root X1,O=Internet Security Research Group,C=US'
- 
--# This sub-CA expires on Sep 15, 2025 and will need to be replaced
-+# This sub-CA expires on March 12, 2027 and will need to be replaced
- # after this date. Otherwise TestIPACommand::test_cacert_manage fails.
--letsencryptauthorityr3 = (
-+letsencryptauthorityr12 = (
-     b'-----BEGIN CERTIFICATE-----\n'
--    b'MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw\n'
-+    b'MIIFBjCCAu6gAwIBAgIRAMISMktwqbSRcdxA9+KFJjwwDQYJKoZIhvcNAQELBQAw\n'
-     b'TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n'
--    b'cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw\n'
--    b'WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n'
--    b'RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n'
--    b'AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP\n'
--    b'R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx\n'
--    b'sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm\n'
--    b'NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg\n'
--    b'Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG\n'
--    b'/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC\n'
--    b'AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB\n'
--    b'Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA\n'
--    b'FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw\n'
--    b'AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw\n'
--    b'Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB\n'
--    b'gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W\n'
--    b'PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl\n'
--    b'ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz\n'
--    b'CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm\n'
--    b'lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4\n'
--    b'avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2\n'
--    b'yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O\n'
--    b'yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids\n'
--    b'hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+\n'
--    b'HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv\n'
--    b'MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX\n'
--    b'nLRbwHOoq7hHwg==\n'
-+    b'cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\n'
-+    b'WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n'
-+    b'RW5jcnlwdDEMMAoGA1UEAxMDUjEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n'
-+    b'CgKCAQEA2pgodK2+lP474B7i5Ut1qywSf+2nAzJ+Npfs6DGPpRONC5kuHs0BUT1M\n'
-+    b'5ShuCVUxqqUiXXL0LQfCTUA83wEjuXg39RplMjTmhnGdBO+ECFu9AhqZ66YBAJpz\n'
-+    b'kG2Pogeg0JfT2kVhgTU9FPnEwF9q3AuWGrCf4yrqvSrWmMebcas7dA8827JgvlpL\n'
-+    b'Thjp2ypzXIlhZZ7+7Tymy05v5J75AEaz/xlNKmOzjmbGGIVwx1Blbzt05UiDDwhY\n'
-+    b'XS0jnV6j/ujbAKHS9OMZTfLuevYnnuXNnC2i8n+cF63vEzc50bTILEHWhsDp7CH4\n'
-+    b'WRt/uTp8n1wBnWIEwii9Cq08yhDsGwIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB\n'
-+    b'hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB\n'
-+    b'/wIBADAdBgNVHQ4EFgQUALUp8i2ObzHom0yteD763OkM0dIwHwYDVR0jBBgwFoAU\n'
-+    b'ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC\n'
-+    b'hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG\n'
-+    b'A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN\n'
-+    b'AQELBQADggIBAI910AnPanZIZTKS3rVEyIV29BWEjAK/duuz8eL5boSoVpHhkkv3\n'
-+    b'4eoAeEiPdZLj5EZ7G2ArIK+gzhTlRQ1q4FKGpPPaFBSpqV/xbUb5UlAXQOnkHn3m\n'
-+    b'FVj+qYv87/WeY+Bm4sN3Ox8BhyaU7UAQ3LeZ7N1X01xxQe4wIAAE3JVLUCiHmZL+\n'
-+    b'qoCUtgYIFPgcg350QMUIWgxPXNGEncT921ne7nluI02V8pLUmClqXOsCwULw+PVO\n'
-+    b'ZCB7qOMxxMBoCUeL2Ll4oMpOSr5pJCpLN3tRA2s6P1KLs9TSrVhOk+7LX28NMUlI\n'
-+    b'usQ/nxLJID0RhAeFtPjyOCOscQBA53+NRjSCak7P4A5jX7ppmkcJECL+S0i3kXVU\n'
-+    b'y5Me5BbrU8973jZNv/ax6+ZK6TM8jWmimL6of6OrX7ZU6E2WqazzsFrLG3o2kySb\n'
-+    b'zlhSgJ81Cl4tv3SbYiYXnJExKQvzf83DYotox3f0fwv7xln1A2ZLplCb0O+l/AK0\n'
-+    b'YE0DS2FPxSAHi0iwMfW2nNHJrXcY3LLHD77gRgje4Eveubi2xxa+Nmk/hmhLdIET\n'
-+    b'iVDFanoCrMVIpQ59XWHkzdFmoHXHBV7oibVjGSO7ULSQ7MJ1Nz51phuDJSgAIU7A\n'
-+    b'0zrLnOrAj/dfrlEWRhCvAgbuwLZX1A2sjNjXoPOHbsPiy+lO1KF8/XY7\n'
-     b'-----END CERTIFICATE-----\n'
- )
--le_r3_nick = "CN=R3,O=Let's Encrypt,C=US"
-+le_r12_nick = "CN=R12,O=Let's Encrypt,C=US"
- 
- # Certificates for reproducing duplicate ipaCertSubject values.
- # The trick to creating the second intermediate is for the validity
-@@ -1230,7 +1229,7 @@ class TestIPACommand(IntegrationTest):
-                result.stderr_text
- 
-         # Install 3rd party CA's, Let's Encrypt in this case
--        for cert in (isrgrootx1, letsencryptauthorityr3):
-+        for cert in (isrgrootx1, letsencryptauthorityr12):
-             certfile = os.path.join(self.master.config.test_dir, 'cert.pem')
-             self.master.put_file_contents(certfile, cert)
-             result = self.master.run_command(
-@@ -1257,7 +1256,7 @@ class TestIPACommand(IntegrationTest):
- 
-         # deletion of a subca
-         result = self.master.run_command(
--            ['ipa-cacert-manage', 'delete', le_r3_nick],
-+            ['ipa-cacert-manage', 'delete', le_r12_nick],
-             raiseonerr=False
-         )
-         assert result.returncode == 0
--- 
-2.51.0
-
-
-From 0a5509665485baa5190f2e3c6fdd765c966a4405 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Mon, 15 Sep 2025 09:41:31 +0300
-Subject: [PATCH 40/47] GetEntryFromLDIF: handle DNs case-insensitive
-
-LDAP expects case-insensitive DNs, so modify LDIF parser to
-compare DNs as case-insensitive strings and use case-preserving but
-case-insensitive dictionary.
-
-Fixes: https://pagure.io/freeipa/issue/9854
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/install/upgradeinstance.py | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/ipaserver/install/upgradeinstance.py b/ipaserver/install/upgradeinstance.py
-index b84f50b05..ecd0a08ea 100644
---- a/ipaserver/install/upgradeinstance.py
-+++ b/ipaserver/install/upgradeinstance.py
-@@ -29,7 +29,7 @@ import traceback
- from ipalib import api
- from ipaplatform.paths import paths
- from ipaplatform import services
--from ipapython import ipaldap
-+from ipapython import ipaldap, ipautil
- 
- from ipaserver.install import installutils
- from ipaserver.install import schemaupdate
-@@ -57,8 +57,8 @@ class GetEntryFromLDIF(ldif.LDIFParser):
-          returned if list is empty.
-         """
-         ldif.LDIFParser.__init__(self, input_file)
--        self.entries_dn = entries_dn
--        self.results = {}
-+        self.entries_dn = [e.lower() for e in entries_dn]
-+        self.results = ipautil.CIDict()
- 
-     def get_results(self):
-         """
-@@ -67,7 +67,7 @@ class GetEntryFromLDIF(ldif.LDIFParser):
-         return self.results
- 
-     def handle(self, dn, entry):
--        if self.entries_dn and dn not in self.entries_dn:
-+        if self.entries_dn and dn.lower() not in self.entries_dn:
-             return
- 
-         self.results[dn] = entry
--- 
-2.51.0
-
-
-From aae73dfc3b549f1dc21a8bd3a2bbd7d05d8edce1 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 15 Sep 2025 16:59:52 +0200
-Subject: [PATCH 41/47] ipatests: exclude TomcatFileCheck when RSN are enabled
-
-Because of PKI issue #4906, the permissions of
-/var/lib/pki/pki-tomcat/conf/ca/CS.cfg
-are too permissive when RSN is enabled and the check TomcatFileCheck
-from ipahealthcheck.ipa.files fails.
-
-Exclude this check when RSN are enabled.
-
-Related: https://github.com/dogtagpki/pki/issues/4906
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 3dcc22411..6d04d3b10 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -2521,6 +2521,12 @@ class TestIpaHealthCLI(IntegrationTest):
-         )
-         tasks.install_packages(cls.master, HEALTHCHECK_PKG)
-         set_excludes(cls.master, "key", "DSCLE0004")
-+        # Because of issue PKI#4906, skip the check ipahealthcheck.ipa.files
-+        # TomcatFileCheck if random serial numbers are enabled
-+        cs_cfg = cls.master.get_file_contents(paths.CA_CS_CFG_PATH,
-+                                              encoding='utf-8')
-+        if "dbs.cert.id.generator=random" in cs_cfg:
-+            set_excludes(cls.master, "check", "TomcatFileCheck")
- 
-     def test_indent(self):
-         """
--- 
-2.51.0
-
-
-From a23e08286de966aa35071378f9ce27e4fd53993a Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 15 Sep 2025 15:09:28 +0200
-Subject: [PATCH 42/47] ipatests: add xfail for TestKRAinstallAfterCertRenew
-
-With PKI 11.6, pkispawn checks that the certificate provided in
-pki_admin_cert_file = /root/.dogtag/pki-tomcat/ca_admin.cert is still
-valid.
-In this scenario the date is moved past the validy date and IPA
-does not refresh the certificate, hence the test fails.
-Mark as known issue (ticket https://pagure.io/freeipa/issue/9763)
-
-Related: https://pagure.io/freeipa/issue/9763
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_installation.py | 15 +++++++++++++--
- 1 file changed, 13 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index e8ca76300..34ccc317f 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -36,6 +36,7 @@ from ipatests.test_integration.base import IntegrationTest
- from ipatests.test_integration.test_caless import CALessBase, ipa_certs_cleanup
- from ipatests.test_integration.test_cert import get_certmonger_fs_id
- from ipatests.pytest_ipa.integration import skip_if_fips
-+from ipatests.util import xfail_context
- from ipaplatform import services
- 
- 
-@@ -1627,8 +1628,18 @@ class TestKRAinstallAfterCertRenew(IntegrationTest):
- 
-         passwd = "{passwd}\n{passwd}\n{passwd}".format(passwd=admin_pass)
-         self.master.run_command(['kinit', 'admin'], stdin_text=passwd)
--        cmd = self.master.run_command(['ipa-kra-install', '-p', dm_pass, '-U'])
--        self.master.run_command(['systemctl', 'start', 'chronyd'])
-+        try:
-+            # With PKI 11.6 the validity of the cert in
-+            # /root/.dogtag/pki-tomcat/ca_admin.cert is checked
-+            # and KRA install fails. Known IPA issue
-+            pki_version = tasks.get_pki_version(self.master)
-+            with xfail_context(pki_version >= tasks.parse_version('11.6.0'),
-+                               'https://pagure.io/freeipa/issue/9763'):
-+                cmd = self.master.run_command([
-+                    'ipa-kra-install', '-p', dm_pass, '-U'
-+                ])
-+        finally:
-+            self.master.run_command(['systemctl', 'start', 'chronyd'])
- 
- 
- class TestKRAinstallOnReplicaWithCAHost(IntegrationTest):
--- 
-2.51.0
-
-
-From ec92f726c3e14db135e11268983e9e842fcaf485 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 14 Aug 2025 13:51:45 -0400
-Subject: [PATCH 43/47] Test that certificates beyond 2038 can be parsed
-
-The old 32-bit time_t will overflow in 2038. Test that
-the IPA certificate loader will be able to parse
-certificates far into the future.
-
-Related: https://pagure.io/freeipa/issue/2496
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_ipalib/test_x509.py | 35 +++++++++++++++++++++++++++++++
- 1 file changed, 35 insertions(+)
-
-diff --git a/ipatests/test_ipalib/test_x509.py b/ipatests/test_ipalib/test_x509.py
-index 74761296e..59273029a 100644
---- a/ipatests/test_ipalib/test_x509.py
-+++ b/ipatests/test_ipalib/test_x509.py
-@@ -185,6 +185,32 @@ bswNlxWfohaW0FzTRfTMbIrwoUCWil/Uw2kBYnld15gwzuLDNQ==
- -----END CERTIFICATE-----
- '''
- 
-+# Generated by moving the date forward 20 years and running
-+# certutil -S -d . -x -n cacert -s 'CN=Authority,O=EXAMPLE', \
-+# -t CT,CT,CT -m 1 -v 1200 -z /etc/group --keyUsage certSigning -2
-+#
-+# The answers are Y, 0, Y for CA, 0 pathlen, Y critical
-+future_crt = b'''\
-+-----BEGIN CERTIFICATE-----
-+MIIC7DCCAdSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMRAwDgYDVQQKEwdFWEFN
-+UExFMRIwEAYDVQQDEwlBdXRob3JpdHkwIBcNNDUwODEzMjAzODM1WhgPMjE0NTA4
-+MTMyMDM4MzVaMCYxEDAOBgNVBAoTB0VYQU1QTEUxEjAQBgNVBAMTCUF1dGhvcml0
-+eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEoViGF+AZPbyFuPVcv
-+IbORShWy0iIPCTw+mdxTp+w3g3wzCQNQP1d0y8kI4xHqm3PTmVf0LdGwAhgKpeTu
-+CdR97scvjGDNCa8pluQ1kmal2rkSeKYkVEw4BwO0RfBPYj3WIkamLIHcFapD97C3
-+M2UptAUj6wCpRzRmFG6H2vgujcZZ/J4P5C97sxPa9H8BRYjEPnIXRsmKOof9EM/Q
-+u/haBJnq27ajSLO+Sz0J0dKG1+aZj0tJw4dWsjdyHQ1S5JBI6xdvOFGmws6XRkgp
-+9Mr7MS4j9xuNk5tMucP2rsi++wju7hrhaWfqfcfQFf+gSCT3l0FnoT5n0tPEjEiP
-+1uMCAwEAAaMjMCEwEgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAgQwDQYJ
-+KoZIhvcNAQELBQADggEBAC/UO/COTCIdA+A7m6YGCcPcdtgvMOQVfKtq5UYAaG/i
-+zE09mGQqMgiGJh7PWTKT7R6Q9caFFHBnTy2mUkvV0O7xHtKSVyMlkmQ9+ga0jq+D
-+5uKAKZLrefC+pbjIIgunHTOaSFfggT9Z+i3IP2M1JyA0zMrcRQIM2OyDdn8F4/k5
-+VegTAUP/W/WgMC6WrvLhKCQqQORPt6zbvuK0GvQXSBTRgG+ctX4N1LaLhv8lF6kL
-+dyVGK1EGi9Geu5HHfrZTHx6qLgXKD1KbfZVdMtX90vTO/ISXOhnmRzC7EqLi9YEJ
-+noLqHv5DUjSOIO3gMKTBImvUNg88hPp7zE/3//uSvXc=
-+-----END CERTIFICATE-----
-+'''
-+
- 
- class test_x509:
-     """
-@@ -332,6 +358,15 @@ class test_x509:
-         with pytest.raises(ValueError):
-             x509.load_pem_x509_certificate(v1_cert)
- 
-+    def test_future_certificate_dates(self):
-+        """
-+        Issue a certificate that is valid after 2038 that will be valid
-+        after 2016 and validate that it is readable by ipalib.x509.py.
-+        """
-+        cert = x509.load_pem_x509_certificate(future_crt)
-+        assert cert.not_valid_before.year > 2038
-+        assert cert.not_valid_after.year > 2106
-+
- 
- class test_ExternalCAProfile:
-     def test_MSCSTemplateV1_good(self):
--- 
-2.51.0
-
-
-From be7255ea6025df423348ad58abcea6c23df1d634 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 14 Aug 2025 16:20:48 -0400
-Subject: [PATCH 44/47] Test that password expiration date past 2038 works
-
-There was a time we capped the expiration date at 2038
-because otherwise time would overflow. That was fixed.
-Add a test to ensure it stays that way.
-
-Fixes: https://pagure.io/freeipa/issue/2496
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_commands.py | 53 ++++++++++++++++++++++
- 1 file changed, 53 insertions(+)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index fcf347ee0..6aaf4f299 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -200,6 +200,28 @@ duplicatesubject = (
- duplicate_serial = "4097"
- 
- 
-+@pytest.fixture()
-+def expire_password():
-+    """
-+    Fixture to expire a user's password far into the future past
-+    2038, then revert time back.
-+    """
-+    hosts = dict()
-+
-+    def _expire_password(host):
-+        hosts['host'] = host
-+        tasks.move_date(host, 'stop', '+20Years')
-+        host.run_command(
-+            ['ipactl', 'restart', '--ignore-service-failures']
-+        )
-+
-+    yield _expire_password
-+
-+    host = hosts.pop('host')
-+    tasks.uninstall_master(host)
-+    tasks.move_date(host, 'start', '-20Years')
-+
-+
- class TestIPACommand(IntegrationTest):
-     """
-     A lot of commands can be executed against a single IPA installation
-@@ -1888,6 +1910,37 @@ class TestIPACommand(IntegrationTest):
-         assert f"{interm_nick}  {intermediate_serial}" not in certs
-         assert f"{interm_nick}  {duplicate_serial}" in certs
- 
-+    def test_expiration_date_post_2038(self, expire_password):
-+        """Test that expiration dates after 2038 function without
-+           overflow.
-+        """
-+        testuser = 'testuser2038'
-+        password = 'Secret@123'
-+
-+        tasks.kinit_admin(self.master)
-+        tasks.user_add(self.master, testuser, password=password)
-+        self.master.run_command([
-+            'ipa', 'user-mod', testuser, '--password-expiration',
-+            '20381112175322Z',
-+        ])
-+
-+        tasks.kdestroy_all(self.master)
-+        expire_password(self.master)
-+
-+        new_password = "%s\n%s\n%s\n" % (password,
-+                                         password,
-+                                         password)
-+
-+        # kinit_user will pass in the "password" value as stdin. We
-+        # should see a prompt about an expired password and set a new
-+        # one (to the same thing). If this kinit succeeds then the
-+        # expiration date was honored and a new one can be set. Time will
-+        # be restored on return from this function.
-+        tasks.kinit_user(self.master, testuser, new_password)
-+
-+        # This must be the last test in this class because it moves
-+        # time and uninstalls the server when it is finished.
-+
- 
- class TestIPACommandWithoutReplica(IntegrationTest):
-     """
--- 
-2.51.0
-
-
-From 1db66d23c79f9d8f53ad4ad6febd25e54f9f6da0 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 19 Nov 2024 16:36:32 -0500
-Subject: [PATCH 45/47] Catch decoding errors in CertificateSigningRequest
- parameters
-
-A try/except is needed around base64.b64decode in case an invalid
-CSR is passed in to a cert function.
-
-Related: https://pagure.io/freeipa/issue/9738
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipalib/parameters.py                    |  9 +++++++--
- ipatests/test_ipalib/test_parameters.py | 25 +++++++++++++++++--------
- 2 files changed, 24 insertions(+), 10 deletions(-)
-
-diff --git a/ipalib/parameters.py b/ipalib/parameters.py
-index d0c8a84a9..355ba3eec 100644
---- a/ipalib/parameters.py
-+++ b/ipalib/parameters.py
-@@ -102,6 +102,7 @@ a more detailed description for clarity.
- import re
- import decimal
- import base64
-+import binascii
- import datetime
- import inspect
- import typing
-@@ -1523,7 +1524,11 @@ class CertificateSigningRequest(Param):
-             return value
- 
-         value = strip_csr_header(value)
--        return base64.b64decode(value)
-+        try:
-+            return base64.b64decode(value)
-+        except binascii.Error as e:
-+            raise CertificateOperationError(
-+                error=_('not a valid CSR: %s') % e)
- 
-     def _convert_scalar(self, value, index=None):
-         """
-@@ -1537,7 +1542,7 @@ class CertificateSigningRequest(Param):
-             try:
-                 value = value.encode('ascii')
-             except UnicodeDecodeError:
--                raise CertificateOperationError('not a valid CSR')
-+                raise CertificateOperationError(error=_('not a valid CSR'))
- 
-         if isinstance(value, bytes):
-             # try to extract DER from whatever we got
-diff --git a/ipatests/test_ipalib/test_parameters.py b/ipatests/test_ipalib/test_parameters.py
-index 9feb613b5..60abeb95e 100644
---- a/ipatests/test_ipalib/test_parameters.py
-+++ b/ipatests/test_ipalib/test_parameters.py
-@@ -1720,11 +1720,16 @@ class test_CertificateSigningRequest(ClassChecker):
-         b'\xed\r\x96\xfb\x1b\x8dN\xac\x89gz9\x98\xd9\xd6\x9e\x7fW}\xf4\x97;'
-         b'\xad\xfe\xad\xc276\x80qmE\xc7|\x0b\xb1^R],'
-     )
--    malformed_csr = (
-+    malformed_csr = (  # value not a CSR
-         b'-----BEGIN CERTIFICATE REQUEST-----\n'
-         b'VGhpcyBpcyBhbiBpbnZhbGlkIENTUg==\n'
-         b'-----END CERTIFICATE REQUEST-----\n'
-     )
-+    malformed_csr_2 = (  # invalid input
-+        b'-----BEGIN CERTIFICATE REQUEST-----\n'
-+        b'123\n'
-+        b'-----END CERTIFICATE REQUEST-----\n'
-+    )
- 
-     def test_init(self):
-         # create the parameter
-@@ -1752,14 +1757,18 @@ class test_CertificateSigningRequest(ClassChecker):
-                     self.sample_csr)
- 
-         # test that we fail the same with malformed CSR as bytes or str
--        for prep_input in (
--            lambda x: x,
--            lambda x: x.decode('utf-8'),
-+        for input in (
-+            self.malformed_csr,
-+            self.malformed_csr_2
-         ):
--            # test that malformed CSRs won't be accepted
--            raises(errors.CertificateOperationError,
--                   o.convert,
--                   prep_input(self.malformed_csr))
-+            for prep_input in (
-+                lambda x: x,
-+                lambda x: x.decode('utf-8'),
-+            ):
-+                # test that malformed CSRs won't be accepted
-+                raises(errors.CertificateOperationError,
-+                       o.convert,
-+                       prep_input(input))
- 
-         # test DER as an input to the convert method
-         csr_object = o.convert(self.sample_der_csr)
--- 
-2.51.0
-
-
-From 272ed4d9f54cc38eeece4314c76775fd95a7e667 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Tue, 22 Apr 2025 15:39:16 +0000
-Subject: [PATCH 46/47] Don't let lack of subca in PKI prevent LDAP deletion
-
-If a subCA only exists in LDAP then it couldn't be removed.
-
-Allow the process to continue. It will fail in the same way if
-it doesn't exist on both Dogtag and IPA.
-
-Related: https://pagure.io/freeipa/issue/9738
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipaserver/plugins/ca.py                |  5 ++++-
- ipaserver/plugins/dogtag.py            |  2 ++
- ipatests/test_integration/test_cert.py | 27 ++++++++++++++++++++++++++
- 3 files changed, 33 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py
-index 44dd29941..d35275cc6 100644
---- a/ipaserver/plugins/ca.py
-+++ b/ipaserver/plugins/ca.py
-@@ -353,7 +353,10 @@ class ca_del(LDAPDelete):
-                 key=keys[0],
-                 reason=_("IPA CA cannot be deleted"))
- 
--        ca_id = self.api.Command.ca_show(keys[0])['result']['ipacaid'][0]
-+        try:
-+            ca_id = self.api.Command.ca_show(keys[0])['result']['ipacaid'][0]
-+        except errors.NotFound:
-+            return dn
-         with self.api.Backend.ra_lightweight_ca as ca_api:
-             data = ca_api.read_ca(ca_id)
-             if data['enabled']:
-diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
-index 00fe1ad64..03c06eaa4 100644
---- a/ipaserver/plugins/dogtag.py
-+++ b/ipaserver/plugins/dogtag.py
-@@ -667,6 +667,8 @@ class RestClient(Backend):
-         )
-         if status < 200 or status >= 300:
-             explanation = self._parse_dogtag_error(resp_body) or ''
-+            if status == 404:
-+                raise errors.NotFound(reason=explanation)
-             raise errors.HTTPRequestError(
-                 status=status,
-                 reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index ddc4e089a..3e1e8fd1f 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -299,6 +299,33 @@ class TestInstallMasterClient(IntegrationTest):
-         ).stdout_text
-         assert 'issued:' in result
- 
-+    def test_remove_missing_lwca(self):
-+        """Test removing an IPA LWCA if the PKI copy is missing."""
-+        lwca_regex = r'  Authority ID: (.*)$'
-+        lwca = 'lwca'
-+        subject = 'CN=LWCA'
-+        result = self.master.run_command([
-+            'ipa', 'ca-add', lwca, '--subject', subject
-+        ])
-+        assert 'Created CA "{}"'.format(lwca) in result.stdout_text
-+
-+        result = self.master.run_command(['ipa', 'ca-show', lwca, '--all'])
-+        m = None
-+        for line in result.stdout_text.split('\n'):
-+            m = re.match(lwca_regex, line)
-+            if m:
-+                break
-+        assert m
-+        ca_id = m.groups(0)[0]
-+
-+        remove_ca_ldif = textwrap.dedent("""
-+             dn: cn={ca_id},ou=authorities,ou=ca,o=ipaca
-+             changetype: delete
-+             """.format(ca_id=ca_id))
-+        tasks.ldapmodify_dm(self.master, remove_ca_ldif)
-+
-+        self.master.run_command(['ipa', 'ca-del', lwca])
-+
- 
- class TestCertmongerRekey(IntegrationTest):
- 
--- 
-2.51.0
-
-
-From c1c0c0d8784fdec78dcd590b5cdfab6f3a2b4b0d Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 18 Sep 2025 18:20:11 +0300
-Subject: [PATCH 47/47] ipasam: remove definitions which included from
- ndr_drsblobs.h
-
-Do not need to have duplicates as we include them. They used to be not
-exported by Samba but not anymore.
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- daemons/ipa-sam/ipa_sam.c | 47 ---------------------------------------
- 1 file changed, 47 deletions(-)
-
-diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
-index 839a621ef..c43ffddbb 100644
---- a/daemons/ipa-sam/ipa_sam.c
-+++ b/daemons/ipa-sam/ipa_sam.c
-@@ -43,53 +43,6 @@ char *smb_xstrdup(const char *s);
- #include "ipa_pwd.h"
- #include "ipa_mspac.h"
- 
--/* from drsblobs.h */
--struct AuthInfoNone {
--	uint32_t size;/* [value(0)] */
--};
--
--struct AuthInfoNT4Owf {
--	uint32_t size;/* [value(16)] */
--	struct samr_Password password;
--};
--
--struct AuthInfoClear {
--	uint32_t size;
--	uint8_t *password;
--};
--
--struct AuthInfoVersion {
--	uint32_t size;/* [value(4)] */
--	uint32_t version;
--};
--
--union AuthInfo {
--	struct AuthInfoNone none;/* [case(TRUST_AUTH_TYPE_NONE)] */
--	struct AuthInfoNT4Owf nt4owf;/* [case(TRUST_AUTH_TYPE_NT4OWF)] */
--	struct AuthInfoClear clear;/* [case(TRUST_AUTH_TYPE_CLEAR)] */
--	struct AuthInfoVersion version;/* [case(TRUST_AUTH_TYPE_VERSION)] */
--}/* [nodiscriminant] */;
--
--struct AuthenticationInformation {
--	NTTIME LastUpdateTime;
--	enum lsa_TrustAuthType AuthType;
--	union AuthInfo AuthInfo;/* [switch_is(AuthType)] */
--	DATA_BLOB _pad;/* [flag(LIBNDR_FLAG_ALIGN4)] */
--}/* [public] */;
--
--struct AuthenticationInformationArray {
--	uint32_t count;
--	struct AuthenticationInformation *array;
--}/* [gensize,nopush,public,nopull] */;
--
--struct trustAuthInOutBlob {
--	uint32_t count;
--	uint32_t current_offset;/* [value((count>0)?12:0)] */
--	uint32_t previous_offset;/* [value((count>0)?12+ndr_size_AuthenticationInformationArray(&current,ndr->flags):0)] */
--	struct AuthenticationInformationArray current;/* [subcontext_size((previous_offset)-(current_offset)),subcontext(0)] */
--	struct AuthenticationInformationArray previous;/* [subcontext(0),flag(LIBNDR_FLAG_REMAINING)] */
--}/* [gensize,public,nopush] */;
--
- /* from generated idmap.h - hopefully OK */
- enum id_type {
- 	ID_TYPE_NOT_SPECIFIED,
--- 
-2.51.0
-

diff --git a/freeipa-4-12-2-post-updates-edns.patch b/freeipa-4-12-2-post-updates-edns.patch
deleted file mode 100644
index e8b6c3b..0000000
--- a/freeipa-4-12-2-post-updates-edns.patch
+++ /dev/null
@@ -1,242 +0,0 @@
-From 603792b86a25d2f78a2458fa4cc93ebe1bbabb6a Mon Sep 17 00:00:00 2001
-From: Francisco Trivino <ftrivino@redhat.com>
-Date: Wed, 12 Jun 2024 15:28:28 +0200
-Subject: [PATCH] doc/designs: add encrypted DNS design documents
-
-Add design page for Encrypted DNS traffic support.
-
-Related: https://pagure.io/freeipa/issue/9605
-Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- doc/designs/edns.md                        | 199 +++++++++++++++++++++
- doc/designs/edns/FreeIPA-eDNS-version3.jpg | Bin 0 -> 238094 bytes
- doc/designs/index.rst                      |   1 +
- 3 files changed, 200 insertions(+)
- create mode 100644 doc/designs/edns.md
- create mode 100644 doc/designs/edns/FreeIPA-eDNS-version3.jpg
-
-diff --git a/doc/designs/edns.md b/doc/designs/edns.md
-new file mode 100644
-index 000000000..97a57f26c
---- /dev/null
-+++ b/doc/designs/edns.md
-@@ -0,0 +1,199 @@
-+# FreeIPA Integrated DNS Service with DoT Support
-+
-+Encrypted DNS, also known as DNS over HTTPS (DoH) or DNS over TLS (DoT), is a protocol that encrypts DNS queries and responses exchanged between DNS clients (such as web browsers or applications) and DNS servers. Traditional DNS queries are sent over plaintext connections, which can be intercepted and monitored by malicious actors, posing privacy and security risks.
-+
-+In modern deployments, the internal network can no longer be trusted, it means that all traffic must be authenticated, authorized and encrypted. Encrypted DNS ensures secure communication by enforcing the use of DoT or DoH methods, encrypting all DNS queries and responses. This feature integrates encrypted DNS seamlessly into the FreeIPA management framework, allowing administrators to decide whether the DNS traffic must be encrypted or not.
-+
-+FreeIPA now includes support for deploying its integrated DNS service with DoT enabled. This enhancement aligns with modern security standards, providing encrypted communication between DNS clients and the FreeIPA DNS server while retaining the same features and benefits of FreeIPA's DNS integration.
-+
-+## Goals
-+
-+The primary goal is to provide a way to deploy an integrated DNS server with the enforcement of using DNS over encrypted channels instead of using standard UDP port 53 with unencrypted communication. This means that DNS clients must talk to DNS servers using a DoT method that is fully encrypted. The scope for the traffic encryption is for all DNS queries that are leaving the host, while the local communication within the host does not require encryption.
-+
-+
-+## Design Choices
-+
-+This feature builds upon the existing DNS component in FreeIPA, maintaining its core assumptions and goals while introducing secure communication through DoT. It is designed to enhance FreeIPA's DNS deployment options and address the growing need for encrypted DNS communication. This first version of the feature is limited and restricted as we are aiming for a simple implementation that covers basic use cases. These restrictions will be addressed in future iterations of the feature development.
-+
-+The initial version of DoT is supported only for new installations, upgrade scenarios to a DoT-enabled setup have not been tested. However, administrators can enable DoT on an existing deployment by reconfiguring the integrated DNS service using `ipa-dns-install` with the new DoT options.
-+
-+The deployment of the DNS service will depend on the infrastructure configuration. During server/replica/client deployment or client/replica enrollment, both unencrypted and encrypted communications will be allowed by default. However, for more restrictive scenarios where only encrypted communications are allowed, a new option is needed to enforce this behavior. The new option `--dns-policy` will have two choices: `relaxed` will allow unencrypted DNS queries, while `enforced` will restrict unencrypted queries on port 53.
-+
-+The implementation of DoT in FreeIPA's DNS service leverages the following design choices:
-+
-+- Current FreeIPA works with `Bind` 9.18. This version of Bind doesn't support DoT-forwarding. Therefore, for server/replica deployment, we rely on an additional component, `unbound`, which redirects encrypted DNS queries to the external DNS server. This includes enabling TLS connections on TCP port 853 and managing TLS certificates for both components `Bind` and `Unbound`. The upgrade to the latest LTS `Bind` version (9.20) is already in progress but not completed. Once this upgrade is complete, the design will be simplified by removing `unbound` as Bind will fully support DoT-forwarding. 
-+
-+- Configuration Management: New FreeIPA CLI options are introduced to enable and configure DoT settings.
-+
-+- Certificate Integration: FreeIPA's certificate management framework can automatically be used to generate and manage TLS certificates giving the administrators the choice to either provide a certificate or rely on custodia for certificate auto enrollment if no certificate is provided. These options are handled by the proper CLI options (please check section [CLI](#cli)).
-+
-+- Service Discovery: Clients and replicas can detect DoT-capable DNS servers directly during installation. However, before using the enforced policy for replica or client enrollment `--dns-policy=enforced`, the administrator must configure the system's DNS settings in NetworkManager to ensure secure discovery of IPA servers. Specifically, the system must be set up to use a DoT-capable resolver. Currently, this manual configuration is required, but future improvements will automate the process. The plan is to enhance `ipa-client-install` to configure Unbound as a DoT forwarder before performing domain discovery, ensuring that SRV/TXT records for IPA server discovery are resolved correctly. This is purely to overcome an existing limitation with Bind 9.18. Note that once Bind 9.20 is fully integrated with IPA, Unbound will no longer be necessary, as Bind will support DoT forwarding.
-+
-+- The current implementation introduces two new subpackages: `freeipa-client-encrypted-dns` and `freeipa-server-encrypted-dns`. The `freeipa-server-encrypted-dns` package must be manually installed on FreeIPA servers and replicas, while the `freeipa-client-encrypted-dns` package should be installed on client machines. This design ensures that the encrypted DNS feature, along with its configuration templates and dependencies, remains isolated from normal FreeIPA deployments. By structuring it this way, environments that do not require encrypted DNS support are unaffected. Enabling DoT functionality requires explicitly installing these subpackages, providing a clear and modular approach to integrating encrypted DNS into FreeIPA deployments.
-+
-+
-+## Design Diagram
-+
-+The following diagram represents a topology involving FreeIPA Server, Replica and Client:
-+
-+![edns-diagram](edns/FreeIPA-eDNS-version3.jpg)
-+
-+Note from the diagram that encryption is needed when queries are leaving the machines. When communication happens inside the host only, encryption is not necessary.
-+
-+FreeIPA currently relies on systemd-resolved as a local cache resolver, which is enabled by default. The design involves disabling the systemd-resolved service and replacing it with the Unbound service. The client configuration relies exclusively on Unbound, with a DoT forwarder pointing to the DNS server. The FreeIPA server configuration consists of two main components: `Bind (named)` as an integrated DNS server, accepting both incoming unencrypted queries from localhost and incoming encrypted queries from external traffic, while relying on `Unbound` for handling outgoing external encrypted traffic. We initially opted for Unbound over systemd-resolved because features such as DoT and DoH are more robust and mature in Unbound. Additionally, proposed changes to Fedora to enhance systemd-resolved were never accepted ([Changes/DNS Over TLS](https://www.fedoraproject.org/wiki/Changes/DNS_Over_TLS), [systemd issue #20801](https://github.com/systemd/systemd/issues/20801), [BZ#1889901](https://bugzilla.redhat.com/show_bug.cgi?id=1889901)).
-+
-+The FreeIPA replica deployment depends on whether the DNS integrated service is deployed, distinguishing between two use cases: with and without DNS integrated service. A replica with DNS Integrated Service will mimic a client configuration: it will use Unbound with a DoT forwarder pointing to the DNS server. A replica without DNS Integrated Service will mimic a server configuration: it will use `Bind` for handling incoming unencrypted queries from localhost and encrypted queries from external sources, along with `Unbound` for outgoing encrypted traffic.
-+
-+Another important aspect is the client's ability to perform DNS updates (`nsupdate`) whenever its IP address changes, ensuring that its DNS record remains up-to-date. This communication must also be secured using DoT/DoH. Currently, the client doesn't support nsupdate with GSS-TSIG (which relies on GSS-API to obtain the secret TSIG key) and unauthenticated updates. However, to address this, we integrated `nsupdate` DoT functionality from Bind 9.20 into Bind 9.18, the version currently supported by FreeIPA. As a result, `nsupdate` is now enhanced with new DoT options, allowing it to function correctly in secure environments.
-+
-+## High-level workflow
-+
-+Enabling DoT: Administrators enable DoT during initial setup or by updating existing DNS configurations through the CLI.
-+
-+Certificate Management: FreeIPA generates and assigns TLS certificates to the DNS service. Administrators can also provide custom certificates if needed.
-+
-+DoT Operations: The DNS components listen for DoT traffic on TCP port 853, providing secure communication to clients.
-+
-+## How to Use
-+
-+During the deployment of a FreeIPA server, replica, or client, new options are available to enable DoT support. These options allow administrators to enhance the security of DNS traffic. Here’s how to use the new options:
-+
-+- Enable DoT: use the `--dns-over-tls` option to enable DoT support during the deployment of clients, servers, or replicas. This option deploys Unbound as a local cache resolver (with /etc/resolv.conf pointing to 127.0.0.1 on servers and replicas) and configures Bind on the server to receive DoT requests. On the client side, only Unbound will be deployed (with /etc/resolv.conf pointing to 127.0.0.53). Replica deployment configuration depends on whether the Integrated DNS service is deployed on the new replica. If it is, the server configuration will apply. If the Integrated DNS server is not deployed on the replica, the client configuration will apply.
-+
-+- Specify an Upstream DNS Server with DoT enabled: use the `--dot-forwarder` option to specify the upstream DNS server that supports DoT. The format must be 1.2.3.4#dns.server.test. You still need to specify at least one of `--forwarder`, `--auto-forwarders`, or `--no-forwarders` options for the non-encrypted communication as well as discovery process.
-+
-+- DoT Certificates. If you prefer to use certificates for DoT in Bind/Unbound, use the `--dns-over-tls-key` and `--dns-over-tls-cert` options. These options primarily works with PEM-formatted certificate files. If these options are not specified, the IPA CA will be used to request a new certificate.
-+
-+- DNS Policy: use `--dns-policy` to define the DNS security policy for FreeIPA deployments. It accepts two values: `relaxed` and `enforced`. When set to `relaxed`, the system will attempt to use DoT but will fall back to unencrypted DNS if DoT is unavailable. This mode ensures compatibility with environments where encrypted DNS is not fully supported. When set to `enforced`, the system strictly requires DoT, and any DNS resolution that does not support encryption will be rejected, including discovery from clients.
-+
-+
-+### CLI
-+
-+Overview of the CLI commands for the FreeIPA installers and FreeIPA DNS installers:
-+
-+Configuring DNS services using ipa-dns-install follows the same principles as installing DNS with the ipa-server-install utility.
-+
-+
-+| Option | Description                                       |
-+|:------------------------ | :------------------------------ |
-+| --dns-over-tls  | enable DNS over TLS support. This option is present on both client and server. It deploys Unbound and configures Bind on the server to receive DoT requests.|
-+| --dot-forwarder | the upstream DNS server with DoT support. It must be specified in the format 1.2.3.4#dns.server.test|
-+| --dns-over-tls-key and --dns-over-tls-cert | in case user prefers to have the DoT certificate in Bind generated by themselves. If these are empty, IPA CA is used instead to request a new certificate. |
-+| --dns-policy | Defines the DNS security policy. Accepts `relaxed` (attempts DoT but falls back to unencrypted DNS if unavailable) or `enforced` (strictly requires DoT and rejects unencrypted DNS resolution). |
-+
-+
-+## Troubleshooting and debugging
-+
-+### Testing and Debugging Unbound
-+
-+If you have DNS resolution issues, confirm that the Unbound service is running with:
-+
-+`# systemctl status unbound`
-+
-+Other checks that can be performed:
-+- Verify that Unbound is listening on the correct IP addresses and ports.
-+- Ensure firewall settings allow traffic on port 53 (DNS) and port 853 (DNS over TLS).
-+- Review the Unbound configuration file `/etc/unbound/unbound.conf` to ensure proper setup.
-+- Check for errors in the Unbound logs: `journalctl -u unbound`
-+
-+
-+You can also increase the logging output for Unbound edit `/etc/unbound/unbound.conf` or create a specific logging configuration file (e.g. `/etc/unbound/conf.d/logging.conf`):
-+
-+```
-+server:
-+    verbosity: 3
-+```
-+
-+Restart Unbound to apply the changes:
-+`# systemctl restart unbound`
-+
-+and monitor the Unbound logs for detailed output:
-+
-+`# tail -f /var/log/unbound/unbound.log`
-+
-+To test the resolution from Unbound using the unbound-host standalone utility you want to use:
-+
-+`# unbound-host -C /etc/unbound/conf.d/tls-client.conf example.org`
-+
-+
-+If you have issues with encryption not working:
-+- Ensure that forward-tls-upstream is enabled in the Unbound configuration (`/etc/unbound/unbound.conf`).
-+- Verify that tls-cert-bundle or tls-system-cert is correctly configured and that the specified certificate files are accessible and have the correct permissions.
-+- Check if the system’s certificate bundle is located at the expected path (e.g. `/etc/pki/ca-trust/extracted/pem/`).
-+
-+
-+Alternatively you can also use bind-utils to verify that resolution works through Unbound:
-+
-+`# dig @localhost example.org`
-+
-+For a very basic test of Unbound using OpenSSL
-+
-+`# openssl s_client -connect [::1]:853 -verify_hostname unbound < /dev/null`
-+
-+Output example:
-+
-+```
-+CONNECTED(00000003)
-+Can't use SSL_get_servername
-+depth=0 CN = unbound
-+verify error:num=18:self-signed certificate
-+verify return:1
-+depth=0 CN = unbound
-+verify return:1
-+---
-+Certificate chain
-+ 0 s:CN = unbound
-+   i:CN = unbound
-+   a:PKEY: rsaEncryption, 3072 (bit); sigalg: RSA-SHA256
-+   v:NotBefore: Jul 13 18:09:50 2023 GMT; NotAfter: Mar 30 18:09:50 2043 GMT
-+---
-+```
-+
-+### Testing and Debugging Bind
-+
-+Verify that the Bind service is running correctly:
-+
-+`# systemctl status named`
-+
-+You can run check tool to verify the Bind configuration:
-+
-+`# named-checkconf /etc/named.conf`
-+
-+Check for errors related to DNS resolution in the system logs `/var/named/data/named.run` or run `journalctl -u named`
-+
-+Restart the Bind service to apply changes `systemctl restart named`
-+
-+You can always increase the Bind logging verbosity to debug issues. Edit the Bind configuration file to increase logging levels:
-+
-+```
-+logging {
-+    channel default_debug {
-+        file "data/named.run";
-+        severity dynamic;
-+    };
-+    category default { default_debug; };
-+};
-+```
-+Restart Bind to apply the changes:
-+`# systemctl restart named`
-+
-+and monitor the logs for detailed output:
-+
-+`# tail -f /var/named/data/named.run`
-+
-+
-+
-+### Monitoring Traffic with tcpdump and Wireshark
-+
-+To watch DNS requests and ensure they are encrypted you can easily use tcpdump to capture traffic on port 53 and port 853:
-+`# tcpdump -n port 53 or port 853`
-+
-+Alternatively you can rely on Wireshark for more detailed analysis where:
-+- Port 53 queries should be visible and decodable.
-+- Port 853 queries or answers should be encrypted and not decodable. A private key can be loaded into wireshark to decrypt the traffic (https://wiki.wireshark.org/TLS#tls-decryption)
-+
-+### Debugging Client-Side or nsupdate Issues
-+
-+To test nsupdate and ensure that DNS updates are functioning correctly you can increase the verbosity of nsupdate logs for detailed debugging when running an update, you want to provide DoT options:
-+
-+`# nsupdate -A tlscafile -E tlscertfile -H tlshostname -K tlskeyfile -d -v`
-diff --git a/doc/designs/edns/FreeIPA-eDNS-version3.jpg b/doc/designs/edns/FreeIPA-eDNS-version3.jpg
-new file mode 100644
-index 000000000..6268624ec
-Binary files /dev/null and b/doc/designs/edns/FreeIPA-eDNS-version3.jpg differ
-diff --git a/doc/designs/index.rst b/doc/designs/index.rst
-index ae1460220..8804a33a1 100644
---- a/doc/designs/index.rst
-+++ b/doc/designs/index.rst
-@@ -11,6 +11,7 @@ FreeIPA design documentation
-    adtrust/sudorules-with-ad-objects.md
-    adtrust/auto-private-groups.md
-    adtrust/sidconfig.md
-+   edns.md
-    hsm.md
-    krb-ticket-policy.md
-    extdom-plugin-protocol.md
--- 
-2.49.0
-

diff --git a/freeipa-4-12-5-cve-2025-7493-patchset-1.patch b/freeipa-4-12-5-cve-2025-7493-patchset-1.patch
deleted file mode 100644
index cdfac2e..0000000
--- a/freeipa-4-12-5-cve-2025-7493-patchset-1.patch
+++ /dev/null
@@ -1,106 +0,0 @@
-From a37de8c22976a75caf969e232229ff6521ff4936 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 10 Jul 2025 11:44:36 -0400
-Subject: [PATCH] Enforce uniqueness across krbprincipalname and
- krbcanonicalname
-
-This relies on a fix in 389-ds that extends the uniqueness plugin
-to be able to compare attributes with different matching syntax.
-
-This will prevent privilege escalation attacks if one of the
-attributes is not set on an entry if it is set elsewhere.
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
----
- install/share/unique-attributes.ldif | 28 +++++-----------------------
- install/updates/10-uniqueness.update | 27 +++++++++++++++++++++++----
- 2 files changed, 28 insertions(+), 27 deletions(-)
-
-diff --git a/install/share/unique-attributes.ldif b/install/share/unique-attributes.ldif
-index 60f2c3470..b28d981b5 100644
---- a/install/share/unique-attributes.ldif
-+++ b/install/share/unique-attributes.ldif
-@@ -1,34 +1,16 @@
--dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config
-+dn: cn=kerberos name uniqueness,cn=plugins,cn=config
- changetype: add
- objectClass: top
- objectClass: nsSlapdPlugin
- objectClass: extensibleObject
--cn: krbPrincipalName uniqueness
-+cn: kerberos name uniqueness
- nsslapd-pluginPath: libattr-unique-plugin
- nsslapd-pluginInitfunc: NSUniqueAttr_Init
- nsslapd-pluginType: preoperation
- nsslapd-pluginEnabled: on
--uniqueness-attribute-name: krbPrincipalName
--nsslapd-plugin-depends-on-type: database
--nsslapd-pluginId: NSUniqueAttr
--nsslapd-pluginVersion: 1.1.0
--nsslapd-pluginVendor: Fedora Project
--nsslapd-pluginDescription: Enforce unique attribute values
--uniqueness-subtrees: $SUFFIX
--uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
--uniqueness-across-all-subtrees: on
--
--dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config
--changetype: add
--objectClass: top
--objectClass: nsSlapdPlugin
--objectClass: extensibleObject
--cn: krbCanonicalName uniqueness
--nsslapd-pluginPath: libattr-unique-plugin
--nsslapd-pluginInitfunc: NSUniqueAttr_Init
--nsslapd-pluginType: preoperation
--nsslapd-pluginEnabled: on
--uniqueness-attribute-name: krbCanonicalName
-+uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch:
-+uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch:
-+uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch:
- nsslapd-plugin-depends-on-type: database
- nsslapd-pluginId: NSUniqueAttr
- nsslapd-pluginVersion: 1.1.0
-diff --git a/install/updates/10-uniqueness.update b/install/updates/10-uniqueness.update
-index fa17911f2..5c5bfd3e0 100644
---- a/install/updates/10-uniqueness.update
-+++ b/install/updates/10-uniqueness.update
-@@ -63,13 +63,32 @@ add:uniqueness-subtree-entries-oc: posixAccount
- 
- # krbPrincipalName uniqueness scopes Active/Delete containers
- dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config
--add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
--add:uniqueness-across-all-subtrees: on
-+deleteentry: cn=krbPrincipalName uniqueness,cn=plugins,cn=config
- 
- # krbCanonicalName uniqueness scopes Active/Delete containers
- dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config
--add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
--add:uniqueness-across-all-subtrees: on
-+deleteentry: dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config
-+
-+dn: cn=kerberos name uniqueness,cn=plugins,cn=config
-+default:objectClass: top
-+default:objectClass: nsSlapdPlugin
-+default:objectClass: extensibleObject
-+default:cn: kerberos name uniqueness
-+default:nsslapd-pluginPath: libattr-unique-plugin
-+default:nsslapd-pluginInitfunc: NSUniqueAttr_Init
-+default:nsslapd-pluginType: preoperation
-+default:nsslapd-pluginEnabled: on
-+default:uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch:
-+default:uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch:
-+default:uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch:
-+default:nsslapd-plugin-depends-on-type: database
-+default:nsslapd-pluginId: NSUniqueAttr
-+default:nsslapd-pluginVersion: 1.1.0
-+default:nsslapd-pluginVendor: Fedora Project
-+default:nsslapd-pluginDescription: Enforce unique attribute values
-+default:uniqueness-subtrees: $SUFFIX
-+default:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
-+default:uniqueness-across-all-subtrees: on
- 
- # ipaUniqueID uniqueness scopes Active/Delete containers
- dn: cn=ipaUniqueID uniqueness,cn=plugins,cn=config
--- 
-2.50.1
-

diff --git a/freeipa-4-12-5-cve-2025-7493-patchset-2.patch b/freeipa-4-12-5-cve-2025-7493-patchset-2.patch
deleted file mode 100644
index 414100c..0000000
--- a/freeipa-4-12-5-cve-2025-7493-patchset-2.patch
+++ /dev/null
@@ -1,227 +0,0 @@
-From 54a9d18c74a887c1949230cc23600317af9a862d Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Fri, 18 Jul 2025 10:26:37 +0200
-Subject: [PATCH] ipa-kdb: enforce PAC presence on TGT for TGS-REQ
-
-MS-KILE's PA-PAC-REQUEST sequence allows the Kerberos client to request
-a TGT without a PAC. At the moment, there is no way to configure the MIT
-KDC to reject such request.
-
-This commit enforces the presence of the PAC when processing TGTs
-provided by TGS-REQ. It ensures the server principal of the TGT is the
-same as the one in PAC_CLIENT_INFO (i.e. enforces client principal
-canonicalization) with integrity check.
-
-Only one exception is applied: this check is skipped for local TGTs on
-domain where the MS-PAC generator is not initialized (i.e. domains where
-SID generation was not executed yet).
-
-Signed-off-by: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb.h            |  9 +++
- daemons/ipa-kdb/ipa_kdb_common.c     | 18 ++++++
- daemons/ipa-kdb/ipa_kdb_kdcpolicy.c  |  2 +-
- daemons/ipa-kdb/ipa_kdb_mspac.c      | 87 ++++++++++++++++++++++++++++
- daemons/ipa-kdb/ipa_kdb_principals.c | 21 +------
- 5 files changed, 116 insertions(+), 21 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
-index 58a0339fc..8fa050995 100644
---- a/daemons/ipa-kdb/ipa_kdb.h
-+++ b/daemons/ipa-kdb/ipa_kdb.h
-@@ -431,6 +431,14 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
-                                                   const char *test_realm, size_t size,
-                                                   char **trusted_realm);
- 
-+/* Check the ticket provided in a TGS-REQ. In some situations, the ticket is
-+ * expected to contain a PAC. If it is not the case, or if the function is
-+ * enable to decode an authorization-data element, it fails.
-+ * Any failure should result in the TGS-REQ to be rejected. */
-+krb5_error_code ipadb_enforce_pac(krb5_context kcontext,
-+                                  const krb5_ticket *ticket,
-+                                  const char **status);
-+
- /* DELEGATION CHECKS */
- 
- krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext,
-@@ -475,3 +483,4 @@ int ipadb_string_to_sid(const char *str, struct dom_sid *sid);
- void alloc_sid(struct dom_sid **sid);
- void free_sid(struct dom_sid **sid);
- bool dom_sid_check(const struct dom_sid *sid1, const struct dom_sid *sid2, bool exact_check);
-+bool ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ);
-diff --git a/daemons/ipa-kdb/ipa_kdb_common.c b/daemons/ipa-kdb/ipa_kdb_common.c
-index ae7742a32..fc603cbe2 100644
---- a/daemons/ipa-kdb/ipa_kdb_common.c
-+++ b/daemons/ipa-kdb/ipa_kdb_common.c
-@@ -767,3 +767,21 @@ krb5_error_code ipadb_multibase_search(struct ipadb_context *ipactx,
-     return ipadb_simple_ldap_to_kerr(ret);
- }
- 
-+bool
-+ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
-+{
-+    krb5_data *primary;
-+    size_t l_tgs_name;
-+
-+    if (2 != krb5_princ_size(kcontext, princ))
-+        return false;
-+
-+    primary = krb5_princ_component(kcontext, princ, 0);
-+
-+    l_tgs_name = strlen(KRB5_TGS_NAME);
-+
-+    if (l_tgs_name != primary->length)
-+        return false;
-+
-+    return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
-+}
-diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
-index 2802221c7..aa8de3eda 100644
---- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
-+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
-@@ -195,7 +195,7 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
-     *lifetime_out = 0;
-     *renew_lifetime_out = 0;
- 
--    return 0;
-+    return ipadb_enforce_pac(context, ticket, status);
- }
- 
- krb5_error_code kdcpolicy_ipakdb_initvt(krb5_context context,
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
-index 9723103d8..9a7aedd84 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac.c
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
-@@ -3346,3 +3346,90 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
- 
- 	return KRB5_KDB_NOENTRY;
- }
-+
-+static krb5_error_code
-+check_for_pac(krb5_context kcontext, krb5_authdata **authdata, bool *pac_present)
-+{
-+    krb5_error_code kerr = ENOENT;
-+    size_t i, j;
-+    krb5_authdata **ifrel = NULL;
-+
-+    for (i = 0; authdata && authdata[i]; ++i) {
-+        if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT) {
-+            continue;
-+        }
-+
-+        kerr = krb5_decode_authdata_container(kcontext,
-+                                              KRB5_AUTHDATA_IF_RELEVANT,
-+                                              authdata[i], &ifrel);
-+        if (kerr) {
-+            goto end;
-+        }
-+
-+        for (j = 0; ifrel[j]; ++j) {
-+            if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC) {
-+                break;
-+            }
-+        }
-+        if (ifrel[j]) {
-+            break;
-+        }
-+
-+        krb5_free_authdata(kcontext, ifrel);
-+        ifrel = NULL;
-+    }
-+
-+    *pac_present = ifrel;
-+    kerr = 0;
-+
-+end:
-+    krb5_free_authdata(kcontext, ifrel);
-+    return kerr;
-+}
-+
-+krb5_error_code
-+ipadb_enforce_pac(krb5_context kcontext, const krb5_ticket *ticket,
-+                  const char **status)
-+{
-+    struct ipadb_context *ipactx;
-+    bool pac_present;
-+    krb5_error_code kerr;
-+
-+    /* Filter TGTs only */
-+    if (!ipadb_is_tgs_princ(kcontext, ticket->server)) {
-+        kerr = 0;
-+        goto end;
-+    }
-+
-+    /* Get IPA context */
-+    ipactx = ipadb_get_context(kcontext);
-+    if (!ipactx) {
-+        kerr = KRB5_KDB_DBNOTINITED;
-+        goto end;
-+    }
-+
-+    /* If local TGT but PAC generator not initialized, skip PAC enforcement */
-+    if (krb5_realm_compare(kcontext, ipactx->local_tgs, ticket->server) &&
-+        !ipactx->mspac)
-+    {
-+        krb5_klog_syslog(LOG_WARNING, "MS-PAC not available. This makes "
-+                         "FreeIPA vulnerable to privilege escalation exploit "
-+                         "(CVE-2025-7493). Please generate SIDs to enable PAC "
-+                         "support.");
-+        kerr = 0;
-+        goto end;
-+    }
-+
-+    /* Search for the PAC, fail if it cannot be found */
-+    kerr = check_for_pac(kcontext, ticket->enc_part2->authorization_data,
-+                         &pac_present);
-+    if (kerr) {
-+        *status = "PAC_ENFORCEMENT_CANNOT_DECODE_TGT_AUTHDATA";
-+    } else if (!pac_present) {
-+        kerr = ENOENT;
-+        *status = "PAC_ENFORCEMENT_TGT_WITHOUT_PAC";
-+    }
-+
-+end:
-+    return kerr;
-+}
-diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
-index 19998c2a3..a238bde75 100644
---- a/daemons/ipa-kdb/ipa_kdb_principals.c
-+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
-@@ -188,25 +188,6 @@ done:
-     return ret;
- }
- 
--static bool
--is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
--{
--    krb5_data *primary;
--    size_t l_tgs_name;
--
--    if (2 != krb5_princ_size(kcontext, princ))
--        return false;
--
--    primary = krb5_princ_component(kcontext, princ, 0);
--
--    l_tgs_name = strlen(KRB5_TGS_NAME);
--
--    if (l_tgs_name != primary->length)
--        return false;
--
--    return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
--}
--
- static krb5_error_code
- cmp_local_tgs_princ(krb5_context kcontext, const char *local_realm,
-                    krb5_const_principal princ, bool *result)
-@@ -2080,7 +2061,7 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext,
-         return kerr;
- 
-     /* If TGS principal, some virtual attributes may be added */
--    if (is_tgs_princ(kcontext, (*entry)->princ)) {
-+    if (ipadb_is_tgs_princ(kcontext, (*entry)->princ)) {
-         kerr = cmp_local_tgs_princ(kcontext, ipactx->realm, (*entry)->princ,
-                                    &is_local_tgs_princ);
-         if (kerr)
--- 
-2.51.0
-

diff --git a/freeipa-4-12-5-cve-2025-7493-patchset-3.patch b/freeipa-4-12-5-cve-2025-7493-patchset-3.patch
deleted file mode 100644
index e351259..0000000
--- a/freeipa-4-12-5-cve-2025-7493-patchset-3.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From d57d11974e05f84c0964ca941a6b507419b02211 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 28 Aug 2025 15:31:39 +0200
-Subject: [PATCH] ipatests: extend test for unique krbcanonicalname
-
-Add a test ensuring that root@REALM cannot be added as
-krbcanonicalname
-
-Add a test for PAC enforcement:
-try to access a service using a TGT obtained without PAC.
-Should fail as PAC is now enforced.
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_commands.py | 44 ++++++++++++++++++++--
- 1 file changed, 40 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 38202c9a3fbc5e91c03a5953a5d9bec3c07117f4..c982c678aae047d5cb505889729bcb5bccbc3c20 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -2090,7 +2090,7 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         hostname = master.hostname
-         realm = master.domain.realm
-         principal = f'test/{hostname}@{realm}'
--        entry_ldif = textwrap.dedent("""
-+        entry_ldif_template = textwrap.dedent("""
-             dn: krbprincipalname={principal},cn=services,cn=accounts,{base_dn}
-             changetype: add
-             ipakrbprincipalalias: test/{hostname}@{realm}
-@@ -2101,13 +2101,15 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-             objectclass: krbprincipal
-             objectclass: krbprincipalaux
-             objectclass: top
--            krbcanonicalname: admin@{realm}
-+            krbcanonicalname: {user}@{realm}
-             managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn}
--        """).format(
-+        """)
-+        entry_ldif = entry_ldif_template.format(
-             base_dn=base_dn,
-             hostname=hostname,
-             principal=principal,
--            realm=realm)
-+            realm=realm,
-+            user='admin')
-         tasks.kdestroy_all(master)
-         master.run_command(
-             ['kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}'])
-@@ -2120,6 +2122,40 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-                                     raiseonerr=False)
-         assert "entry with the same attribute value" in result.stderr_text
- 
-+        # Now try with root@realm instead of admin@realm
-+        entry_ldif = entry_ldif_template.format(
-+            base_dn=base_dn,
-+            hostname=hostname,
-+            principal=principal,
-+            realm=realm,
-+            user='root')
-+        args = [
-+            'ldapmodify',
-+            '-Y',
-+            'GSSAPI'
-+        ]
-+        result = master.run_command(args, stdin_text=entry_ldif,
-+                                    raiseonerr=False)
-+        assert "entry with the same attribute value" in result.stderr_text
-+        tasks.kdestroy_all(master)
-+
-+    def test_no_request_pac(self):
-+        # Try to use a TGT obtained without PAC
-+        # Should fail as the presence of the PAC when processing TGTs
-+        # provided by TGS-REQ is now enforced.
-+        hostname = self.master.hostname
-+        realm = self.master.domain.realm
-+        self.master.run_command([
-+            'kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}',
-+            '--no-request-pac'
-+        ])
-+        result = self.master.run_command(
-+            ['kvno', f'ldap/{hostname}@{realm}'],
-+            raiseonerr=False
-+        )
-+        assert result.returncode == 1
-+        assert "PAC_ENFORCEMENT_TGT_WITHOUT_PAC" in result.stderr_text
-+
- 
- class TestIPAautomount(IntegrationTest):
-     @classmethod
--- 
-2.51.0
-

diff --git a/freeipa-4.13-mspac-warnings.patch b/freeipa-4.13-mspac-warnings.patch
deleted file mode 100644
index 4a3bb06..0000000
--- a/freeipa-4.13-mspac-warnings.patch
+++ /dev/null
@@ -1,211 +0,0 @@
-From ae6c4415e754dd6c50d171e5f921c7c0075147c4 Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Wed, 18 Mar 2026 16:47:30 +0100
-Subject: [PATCH 1/2] ipa-kdb: fix PAC creation failure in DAL v9 when PAC
- generator not initialized
-
-When the MS-PAC generator is not initialized:
-- DAL v6: Both PAC creation and verification proceed without PAC
-- DAL v9: PAC verification works, but PAC creation fails with
-  KRB5_PLUGIN_OP_NOTSUPP
-
-This caused AS-REQ and S4U2Self requests to fail on DAL v9 when the
-MS-PAC generator wasn't initialized, while the same requests succeeded
-on DAL v6.
-
-The issue was a redundant check in ipadb_v9_issue_pac() that returned
-KRB5_PLUGIN_OP_NOTSUPP before ipadb_get_pac() could return ENOENT.
-Remove this redundant check and add ENOENT handling after calling
-ipadb_get_pac() and ipadb_common_verify_pac(), matching DAL v6
-behavior.
-
-DAL v6 and v9 now have consistent behavior: all operations proceed
-without PAC when the MS-PAC generator is not initialized.
-
-Related: https://pagure.io/freeipa/issue/9962
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-Signed-off-by: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb_mspac_v9.c | 12 +++++-------
- 1 file changed, 5 insertions(+), 7 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-index 60db048e1..2d52da700 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-@@ -120,10 +120,6 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
-             /* Continue even if initilization of PAC generator failed.
-              * It may caused by the trust objects part only. */
- 
--            /* At least the core part of the PAC generator is required. */
--            if (!ipactx->mspac)
--                return KRB5_PLUGIN_OP_NOTSUPP;
--
-             kerr = ipadb_get_pac(context, flags,
-                                  client, server, replaced_reply_key,
-                                  authtime, &new_pac);
-@@ -135,9 +131,11 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
-                                         NULL,
-                                         authtime,
-                                         old_pac, &new_pac);
--        if (kerr == ENOENT) {
--            kerr = 0;
--        }
-+    }
-+
-+    if (kerr == ENOENT) {
-+        /* MS-PAC generator not initialized; proceed without PAC. */
-+        kerr = 0;
-     }
- 
-     /* in krb5 1.20 no need to sign tickets anymore, KDC does it for us */
--- 
-2.53.0
-
-
-From 12b1971f4a059ccae0564d1cfe52716ce16c69bb Mon Sep 17 00:00:00 2001
-From: Julien Rische <jrische@redhat.com>
-Date: Wed, 18 Mar 2026 18:03:28 +0100
-Subject: [PATCH 2/2] ipa-kdb: warn when tickets are issued without PAC
-
-When the MS-PAC generator is not initialized and a ticket is created
-or verified without a PAC, log a warning message. This helps identify
-deployments vulnerable to CVE-2025-7493.
-
-A new warn_mspac_unavailable() helper function is introduced to avoid
-code duplication. It takes a boolean parameter to distinguish between
-ticket creation and verification contexts.
-
-The warning is now consistently logged in all scenarios where tickets
-proceed without PAC:
-- DAL v6 and v9: PAC creation failures (AS-REQ, S4U2Self, S4U2Proxy)
-- DAL v6 and v9: PAC verification failures
-- KDC policy plugin: PAC enforcement check for local TGTs
-
-Related: https://pagure.io/freeipa/issue/9962
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-Signed-off-by: Julien Rische <jrische@redhat.com>
----
- daemons/ipa-kdb/ipa_kdb_mspac.c         | 15 +++++++++++----
- daemons/ipa-kdb/ipa_kdb_mspac_private.h |  2 ++
- daemons/ipa-kdb/ipa_kdb_mspac_v6.c      | 16 +++++++++++++---
- daemons/ipa-kdb/ipa_kdb_mspac_v9.c      |  3 +++
- 4 files changed, 29 insertions(+), 7 deletions(-)
-
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
-index 9a7aedd84..1ed62ae82 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac.c
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
-@@ -93,6 +93,16 @@ void free_sid(struct dom_sid **sid)
-     }
- }
- 
-+void warn_mspac_unavailable(bool is_creation)
-+{
-+    const char *operation = is_creation ? "creation" : "verification";
-+    krb5_klog_syslog(LOG_WARNING,
-+                     "MS-PAC not available during ticket %s. This makes "
-+                     "FreeIPA vulnerable to privilege escalation exploit "
-+                     "(CVE-2025-7493). Please generate SIDs to enable PAC "
-+                     "support.", operation);
-+}
-+
- int ipadb_string_to_sid(const char *str, struct dom_sid *sid)
- {
-     unsigned long val;
-@@ -3412,10 +3422,7 @@ ipadb_enforce_pac(krb5_context kcontext, const krb5_ticket *ticket,
-     if (krb5_realm_compare(kcontext, ipactx->local_tgs, ticket->server) &&
-         !ipactx->mspac)
-     {
--        krb5_klog_syslog(LOG_WARNING, "MS-PAC not available. This makes "
--                         "FreeIPA vulnerable to privilege escalation exploit "
--                         "(CVE-2025-7493). Please generate SIDs to enable PAC "
--                         "support.");
-+        warn_mspac_unavailable(false);
-         kerr = 0;
-         goto end;
-     }
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_private.h b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
-index e650cfa73..eb8ae2240 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac_private.h
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
-@@ -74,3 +74,5 @@ krb5_error_code ipadb_common_verify_pac(krb5_context context,
-                                         krb5_timestamp authtime,
-                                         krb5_pac old_pac,
-                                         krb5_pac *pac);
-+
-+void warn_mspac_unavailable(bool is_creation);
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_v6.c b/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
-index 96cd50e4c..89221530d 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
-@@ -315,7 +315,10 @@ krb5_error_code ipadb_sign_authdata(krb5_context context,
-             krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
- 
-         kerr = ipadb_get_pac(context, flags, client, server, NULL, authtime, &pac);
--        if (kerr != 0 && kerr != ENOENT) {
-+        if (kerr == ENOENT) {
-+            /* MS-PAC generator not initialized; proceed without PAC. */
-+            warn_mspac_unavailable(true);
-+        } else if (kerr != 0) {
-             goto done;
-         }
-     } else if (with_pac && !is_as_req) {
-@@ -329,7 +332,10 @@ krb5_error_code ipadb_sign_authdata(krb5_context context,
-         if ((pac_auth_data == NULL) || (pac_auth_data[0] == NULL)) {
-             if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
-                 kerr = ipadb_get_pac(context, flags, client_entry, server, NULL, authtime, &pac);
--                if (kerr != 0 && kerr != ENOENT) {
-+                if (kerr == ENOENT) {
-+                    /* MS-PAC generator not initialized; proceed without PAC. */
-+                    warn_mspac_unavailable(true);
-+                } else if (kerr != 0) {
-                     goto done;
-                 }
-             }
-@@ -342,7 +348,11 @@ krb5_error_code ipadb_sign_authdata(krb5_context context,
-             kerr = ipadb_verify_pac(context, flags, ks_client_princ, client,
-                                     server, krbtgt, server_key, krbtgt_key,
-                                     authtime, pac_auth_data, &pac);
--            if (kerr != 0) {
-+            if (kerr == ENOENT) {
-+                /* MS-PAC not initialized; proceed without PAC verification. */
-+                warn_mspac_unavailable(false);
-+                kerr = 0;
-+            } else if (kerr != 0) {
-                 goto done;
-             }
-         }
-diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-index 2d52da700..6fe94e3b8 100644
---- a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-+++ b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
-@@ -46,6 +46,7 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
-     bool with_pad;
-     krb5_error_code kerr = 0;
-     bool is_as_req = flags & CLIENT_REFERRALS_FLAGS;
-+    bool is_pac_creation = false;
-     const char *stmsg = NULL;
- 
-     if (is_as_req) {
-@@ -85,6 +86,7 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
-     if (old_pac == NULL ||
-         (client != NULL && (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION))) {
-         /* generate initial PAC */
-+        is_pac_creation = true;
-         if (with_pac) {
-             krb5_boolean force_reinit_mspac = FALSE;
-             struct ipadb_context *ipactx = ipadb_get_context(context);
-@@ -135,6 +137,7 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
- 
-     if (kerr == ENOENT) {
-         /* MS-PAC generator not initialized; proceed without PAC. */
-+        warn_mspac_unavailable(is_pac_creation);
-         kerr = 0;
-     }
- 
--- 
-2.53.0
-

diff --git a/freeipa-fix-passkey-crash.patch b/freeipa-fix-passkey-crash.patch
deleted file mode 100644
index 254d336..0000000
--- a/freeipa-fix-passkey-crash.patch
+++ /dev/null
@@ -1,41 +0,0 @@
-From 5f7bd9612edd5ab6b8cd45148be75b3fd7c86cf8 Mon Sep 17 00:00:00 2001
-From: Sumit Bose <sbose@redhat.com>
-Date: Wed, 14 Jan 2026 13:58:41 +0100
-Subject: [PATCH] ipa-otpd: do not pass reference of data_out->jdata
-
-The 'o' format specifier will pass the reference of data_out->jdata to
-the created object and data_out->jdata will be dereferenced if the new
-object is dereferenced. Doing an explicit dereferenced of
-data_out->jdata later on will cause a double-free crash.
-
-With 'O' the reference counter of data_out->jdata is just incremented
-and so it can be properly dereferenced.
-
-Fixes: https://pagure.io/freeipa/issue/9906
-Signed-off-by: Sumit Bose <sbose@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- daemons/ipa-otpd/passkey.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/daemons/ipa-otpd/passkey.c b/daemons/ipa-otpd/passkey.c
-index ad3c45467..51d5c316c 100644
---- a/daemons/ipa-otpd/passkey.c
-+++ b/daemons/ipa-otpd/passkey.c
-@@ -437,7 +437,9 @@ static int prepare_rad_reply(struct otpd_queue_item *item)
-         goto done;
-     }
- 
--    jtmp = json_pack("{s:i, s:s, s:o}", "phase", item->passkey->data_out->phase,
-+    /* item->passkey->data_out->jdata will be dereferenced later, so we have
-+     * to use 'O' to keep the reference. */
-+    jtmp = json_pack("{s:i, s:s, s:O}", "phase", item->passkey->data_out->phase,
-                                         "state", item->passkey->data_out->state,
-                                         "data", item->passkey->data_out->jdata);
-     if (jtmp == NULL) {
--- 
-2.50.1
-

diff --git a/freeipa-sssd-mfa-selinux-policy-update.patch b/freeipa-sssd-mfa-selinux-policy-update.patch
deleted file mode 100644
index f6fa9c4..0000000
--- a/freeipa-sssd-mfa-selinux-policy-update.patch
+++ /dev/null
@@ -1,180 +0,0 @@
-From d1bd43afdd5f1992a66254bc132bab9e4ca7db0d Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 5 Mar 2026 18:34:17 +0200
-Subject: [PATCH 1/4] SELinux: transition ipa_otpd_t to sssd_mfa_t when
- executing MFA helpers
-
-When ipa_otpd_t executes sssd MFA helper binaries (oidc_child,
-passkey_child) labeled sssd_mfa_exec_t, use domtrans_pattern to
-transition into sssd_mfa_t rather than running them in the ipa_otpd_t
-domain. sssd_mfa_t already carries the correct network and device
-permissions for those helpers.
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- selinux/ipa.te | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
-
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index a91c4efce..f98108ed2 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -16,7 +16,7 @@ init_daemon_domain(ipa_otpd_t, ipa_otpd_exec_t)
- 
- type sssd_mfa_t, ipa_domain;
- type sssd_mfa_exec_t;
--can_exec(sssd_mfa_t,sssd_mfa_exec_t)
-+init_daemon_domain(sssd_mfa_t, sssd_mfa_exec_t)
- 
- kernel_dgram_send(ipa_otpd_t)
- allow ipa_otpd_t self:unix_dgram_socket { create getopt setopt };
-@@ -156,6 +156,9 @@ optional_policy(`
- 
- logging_send_syslog_msg(ipa_otpd_t)
- 
-+domtrans_pattern(ipa_otpd_t, sssd_mfa_exec_t, sssd_mfa_t)
-+allow ipa_otpd_t sssd_mfa_t:process { noatsecure rlimitinh siginh };
-+
- ########################################
- #
- # sssd_mfa local policy
--- 
-2.50.1
-
-
-From a29a4175f3b18e5acf9f05e827db8c40ab869735 Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 5 Mar 2026 18:34:50 +0200
-Subject: [PATCH 2/4] SELinux: wrap bare gen_require blocks in optional_policy
- in dnskey section
-
-Four bare gen_require+allow blocks in the ipa_dnskey_t local policy
-referenced external types (ndc_t, systemd_tmpfiles_t, fs_t, named_t)
-outside of optional_policy, causing hard compilation failures if those
-types are absent from the loaded policy. Wrap each block in
-optional_policy to match the convention used throughout the rest of
-the module.
-
-Related: https://pagure.io/freeipa/issue/9948
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- selinux/ipa.te | 32 ++++++++++++++++++++------------
- 1 file changed, 20 insertions(+), 12 deletions(-)
-
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index f98108ed2..92951a170 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -370,24 +370,32 @@ corenet_tcp_connect_kerberos_port(ipa_dnskey_t)
- corenet_tcp_connect_rndc_port(ipa_dnskey_t)
- 
- # Allow rndc to access SoftHSM token in IPA directory
--gen_require(`
--    type ndc_t;
-+optional_policy(`
-+    gen_require(`
-+        type ndc_t;
-+    ')
-+    allow ndc_t ipa_dnskey_t:file { getattr open read };
- ')
--allow ndc_t ipa_dnskey_t:file { getattr open read };
- 
- # Allow relabel from systemd_tmpfiles_t
--gen_require(`
--    type systemd_tmpfiles_t;
-+optional_policy(`
-+    gen_require(`
-+        type systemd_tmpfiles_t;
-+    ')
-+    allow systemd_tmpfiles_t ipa_dnskey_t:file { getattr relabelfrom relabelto };
- ')
--allow systemd_tmpfiles_t ipa_dnskey_t:file { getattr relabelfrom relabelto };
--gen_require(`
--    type fs_t;
-+optional_policy(`
-+    gen_require(`
-+        type fs_t;
-+    ')
-+    allow ipa_dnskey_t fs_t:filesystem associate;
- ')
--allow ipa_dnskey_t fs_t:filesystem associate;
--gen_require(`
--    type named_t;
-+optional_policy(`
-+    gen_require(`
-+        type named_t;
-+    ')
-+    allow named_t ipa_dnskey_t:file { getattr open read };
- ')
--allow named_t ipa_dnskey_t:file { getattr open read };
- 
- dev_read_rand(ipa_dnskey_t)
- dev_read_sysfs(ipa_dnskey_t)
--- 
-2.50.1
-
-
-From 23bf11795883adb98bcf7a46e64267495b3741fe Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Thu, 5 Mar 2026 22:31:43 +0200
-Subject: [PATCH 3/4] SELinux: expand policy coverage for sssd_mfa_t domain
-
-sssd_mfa_t runs oidc_child and passkey_child. Add permissions needed
-for their runtime operation:
-
-- auth_use_nsswitch: NSS lookups (nsswitch.conf traversal)
-- files_read_usr_files: OpenSSL crypto-policies config in /usr/share
-- miscfiles_read_localization: timezone data
-- kernel_read_crypto_sysctls: /proc/sys/crypto/fips_enabled check
-- selinux_getattr_fs: SELinux status probe (is_selinux_enabled)
-- dontaudit net_admin: suppress capability probe that is not required
-- dontaudit init_t:unix_stream_socket: suppress noise from RADIUS
-  socket leaked to child (should be close-on-exec in ipa-otpd)
-- ps_process_pattern(syslogd_t): allow systemd-journal to read
-  sssd_mfa_t process entries in /proc for structured log entries
-
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
----
- selinux/ipa.te | 25 +++++++++++++++++++++++++
- 1 file changed, 25 insertions(+)
-
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index 92951a170..ca13d5b7c 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -205,6 +205,31 @@ optional_policy(`
- 
- logging_send_syslog_msg(sssd_mfa_t)
- 
-+auth_use_nsswitch(sssd_mfa_t)
-+
-+files_read_usr_files(sssd_mfa_t)
-+miscfiles_read_localization(sssd_mfa_t)
-+
-+kernel_read_crypto_sysctls(sssd_mfa_t)
-+
-+selinux_getattr_fs(sssd_mfa_t)
-+
-+dontaudit sssd_mfa_t self:capability net_admin;
-+
-+optional_policy(`
-+    gen_require(`
-+        type init_t;
-+    ')
-+    dontaudit sssd_mfa_t init_t:unix_stream_socket { read write };
-+')
-+
-+optional_policy(`
-+    gen_require(`
-+        type syslogd_t;
-+    ')
-+    ps_process_pattern(syslogd_t, sssd_mfa_t)
-+')
-+
- optional_policy(`
- 	ipa_read_lib(sssd_mfa_t)
- 	ipa_search_lib(sssd_mfa_t)
--- 
-2.50.1
-

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-19  9:22 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19  9:22 [rpms/freeipa] rawhide: clean up unused patches Alexander Bokovoy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox