diff --git a/.gitignore b/.gitignore
index 34c1a4e..d9c893f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/freeipa-4.9.6.tar.gz
+SOURCES/freeipa-4.9.8.tar.gz
diff --git a/.ipa.metadata b/.ipa.metadata
index ab790ce..15ef586 100644
--- a/.ipa.metadata
+++ b/.ipa.metadata
@@ -1 +1 @@
-b7b91082908db35e4acbcd0221b8df4044913dc1 SOURCES/freeipa-4.9.6.tar.gz
+38641a7f95779ba35089fcc10e25ec82a9b0248e SOURCES/freeipa-4.9.8.tar.gz
diff --git a/README.debrand b/README.debrand
deleted file mode 100644
index 01c46d2..0000000
--- a/README.debrand
+++ /dev/null
@@ -1,2 +0,0 @@
-Warning: This package was configured for automatic debranding, but the changes
-failed to apply.
diff --git a/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch b/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch
deleted file mode 100644
index d27c680..0000000
--- a/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 01f4b9d7935ca41c93b17e28543054f36e5baf46 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 30 Jun 2021 14:57:32 +0200
-Subject: [PATCH] Remove unneeded dependency on python-coverage
-
-The spec file requires python3-coverage although it is not
-used in the project.
-
-Fixes: https://pagure.io/freeipa/issue/8905
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- freeipa.spec.in | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index fdca43a24a6e07f77b9cd8a0feec940a0366f128..fbfe4d09eedc169112dcdc18a953134de67b7731 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -872,7 +872,6 @@ BuildArch: noarch
- Requires: python3-ipaclient = %{version}-%{release}
- Requires: python3-ipaserver = %{version}-%{release}
- Requires: iptables
--Requires: python3-coverage
- Requires: python3-cryptography >= 1.6
- Requires: python3-pexpect
- %if 0%{?fedora}
--- 
-2.26.3
-
diff --git a/SOURCES/0001-ipa-kdb-issue-PAC_REQUESTER_SID-only-for-TGTs.patch b/SOURCES/0001-ipa-kdb-issue-PAC_REQUESTER_SID-only-for-TGTs.patch
new file mode 100644
index 0000000..e303e04
--- /dev/null
+++ b/SOURCES/0001-ipa-kdb-issue-PAC_REQUESTER_SID-only-for-TGTs.patch
@@ -0,0 +1,36 @@
+From 669f3d71161741c676ddd6a08bd08d4a4ccd495b Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Fri, 26 Nov 2021 17:40:54 +0200
+Subject: [PATCH] ipa-kdb: issue PAC_REQUESTER_SID only for TGTs
+
+MS-KILE 3.3.5.6.4.8 in revision after Windows Server November 2021
+security fixes added the following requirement:
+
+- PAC_REQUESTER_SID is only added in TGT case (including referrals and
+  tickets to RODCs)
+
+Fixes: https://pagure.io/freeipa/issue/9031
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ daemons/ipa-kdb/ipa_kdb_mspac.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
+index 6f7d1ac15daf17dfca36ebd3265c866725d24717..538cfbba958068bd2ee0aaae7a2743ae82237898 100644
+--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
++++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
+@@ -1148,7 +1148,8 @@ static krb5_error_code ipadb_get_pac(krb5_context kcontext,
+ #endif
+ 
+ #ifdef HAVE_PAC_REQUESTER_SID
+-    {
++    /* MS-KILE 3.3.5.6.4.8: add PAC_REQUESTER_SID only in TGT case */
++    if ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0) {
+         union PAC_INFO pac_requester_sid;
+         /* == Package PAC_REQUESTER_SID == */
+         memset(&pac_requester_sid, 0, sizeof(pac_requester_sid));
+-- 
+2.31.1
+
diff --git a/SOURCES/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch b/SOURCES/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch
deleted file mode 100644
index 411d510..0000000
--- a/SOURCES/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch
+++ /dev/null
@@ -1,134 +0,0 @@
-From dffccae7193b0616cb84792edec480f5f67e1fc6 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Mon, 8 Mar 2021 18:15:50 +0100
-Subject: [PATCH] Add checks to prevent adding auth indicators to internal IPA
- services
-
-Authentication indicators should not be enforced against internal
-IPA services, since not all users of those services are able to produce
-Kerberos tickets with all the auth indicator options. This includes
-host, ldap, HTTP and cifs in IPA server and cifs in IPA clients.
-If a client that is being promoted to replica has an auth indicator
-in its host principal then the promotion is aborted.
-
-Fixes: https://pagure.io/freeipa/issue/8206
-Signed-off-by: Antonio Torres <antorres@redhat.com>
----
- ipaserver/install/server/replicainstall.py | 13 ++++++++++++
- ipaserver/plugins/host.py                  |  5 ++++-
- ipaserver/plugins/service.py               | 24 ++++++++++++++++++++++
- 3 files changed, 41 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
-index 73967a2249d5c8944d70c5c3ca9a9d3b3bfc6b73..f1fb9103687ce9719ef24c8cb3c41088a4003b25 100644
---- a/ipaserver/install/server/replicainstall.py
-+++ b/ipaserver/install/server/replicainstall.py
-@@ -770,6 +770,15 @@ def promotion_check_ipa_domain(master_ldap_conn, basedn):
-         ))
- 
- 
-+def promotion_check_host_principal_auth_ind(conn, hostdn):
-+    entry = conn.get_entry(hostdn, ['krbprincipalauthind'])
-+    if 'krbprincipalauthind' in entry:
-+        raise RuntimeError(
-+            "Client cannot be promoted to a replica if the host principal "
-+            "has an authentication indicator set."
-+        )
-+
-+
- @common_cleanup
- @preserve_enrollment_state
- def promote_check(installer):
-@@ -956,6 +965,10 @@ def promote_check(installer):
-                                      config.master_host_name, None)
- 
-         promotion_check_ipa_domain(conn, remote_api.env.basedn)
-+        hostdn = DN(('fqdn', api.env.host),
-+                    api.env.container_host,
-+                    api.env.basedn)
-+        promotion_check_host_principal_auth_ind(conn, hostdn)
- 
-         # Make sure that domain fulfills minimal domain level
-         # requirement
-diff --git a/ipaserver/plugins/host.py b/ipaserver/plugins/host.py
-index eb1f8ef042faf4b0deadfd5cef47f7688836506e..41fa933e2422184eafc4eae185a163082b96e045 100644
---- a/ipaserver/plugins/host.py
-+++ b/ipaserver/plugins/host.py
-@@ -38,7 +38,7 @@ from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
-                                      LDAPAddAttributeViaOption,
-                                      LDAPRemoveAttributeViaOption)
- from .service import (
--    validate_realm, normalize_principal,
-+    validate_realm, validate_auth_indicator, normalize_principal,
-     set_certificate_attrs, ticket_flags_params, update_krbticketflags,
-     set_kerberos_attrs, rename_ipaallowedtoperform_from_ldap,
-     rename_ipaallowedtoperform_to_ldap, revoke_certs)
-@@ -735,6 +735,8 @@ class host_add(LDAPCreate):
-         update_krbticketflags(ldap, entry_attrs, attrs_list, options, False)
-         if 'krbticketflags' in entry_attrs:
-             entry_attrs['objectclass'].append('krbticketpolicyaux')
-+        validate_auth_indicator(entry_attrs)
-+
-         return dn
- 
-     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-@@ -993,6 +995,7 @@ class host_mod(LDAPUpdate):
-             if 'krbprincipalaux' not in (item.lower() for item in
-                                          entry_attrs['objectclass']):
-                 entry_attrs['objectclass'].append('krbprincipalaux')
-+            validate_auth_indicator(entry_attrs)
- 
-         add_sshpubkey_to_attrs_pre(self.context, attrs_list)
- 
-diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py
-index 1c93478049f5bdfdaf8503e459bd962dbbee9b44..cfbbff3c69c6a92535df58c51767c3d0952c7b0b 100644
---- a/ipaserver/plugins/service.py
-+++ b/ipaserver/plugins/service.py
-@@ -201,6 +201,28 @@ def validate_realm(ugettext, principal):
-         raise errors.RealmMismatch()
- 
- 
-+def validate_auth_indicator(entry):
-+    new_value = entry.get('krbprincipalauthind', None)
-+    if not new_value:
-+        return
-+    # The following services are considered internal IPA services
-+    # and shouldn't be allowed to have auth indicators.
-+    # https://pagure.io/freeipa/issue/8206
-+    pkey = api.Object['service'].get_primary_key_from_dn(entry.dn)
-+    principal = kerberos.Principal(pkey)
-+    server = api.Command.server_find(principal.hostname)['result']
-+    if server:
-+        prefixes = ("host", "cifs", "ldap", "HTTP")
-+    else:
-+        prefixes = ("cifs",)
-+    if principal.service_name in prefixes:
-+        raise errors.ValidationError(
-+            name='krbprincipalauthind',
-+            error=_('authentication indicators not allowed '
-+                    'in service "%s"' % principal.service_name)
-+        )
-+
-+
- def normalize_principal(value):
-     """
-     Ensure that the name in the principal is lower-case. The realm is
-@@ -652,6 +674,7 @@ class service_add(LDAPCreate):
-                     hostname)
- 
-         self.obj.validate_ipakrbauthzdata(entry_attrs)
-+        validate_auth_indicator(entry_attrs)
- 
-         if not options.get('force', False):
-             # We know the host exists if we've gotten this far but we
-@@ -846,6 +869,7 @@ class service_mod(LDAPUpdate):
-         assert isinstance(dn, DN)
- 
-         self.obj.validate_ipakrbauthzdata(entry_attrs)
-+        validate_auth_indicator(entry_attrs)
- 
-         # verify certificates
-         certs = entry_attrs.get('usercertificate') or []
--- 
-2.26.3
-
diff --git a/SOURCES/0002-ipa-kdb-fix-requester-SID-check-according-to-MS-KILE.patch b/SOURCES/0002-ipa-kdb-fix-requester-SID-check-according-to-MS-KILE.patch
new file mode 100644
index 0000000..b220cc9
--- /dev/null
+++ b/SOURCES/0002-ipa-kdb-fix-requester-SID-check-according-to-MS-KILE.patch
@@ -0,0 +1,122 @@
+From 7d93bda31ce0b4e0e22c6e464c9138800dcf8b1c Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Fri, 26 Nov 2021 11:13:51 +0200
+Subject: [PATCH] ipa-kdb: fix requester SID check according to MS-KILE and
+ MS-SFU updates
+
+New versions of MS-KILE and MS-SFU after Windows Server November 2021
+security updates add PAC_REQUESTER_SID buffer check behavior:
+
+ - PAC_REQUESTER_SID should only be added for TGT requests
+
+ - if PAC_REQUESTER_SID is present, KDC must verify that the cname on
+   the ticket resolves to the account with the same SID as the
+   PAC_REQUESTER_SID. If it doesn't KDC must respond with
+   KDC_ERR_TKT_REVOKED
+
+Change requester SID check to skip exact check for non-local
+PAC_REQUESTER_SID but harden to ensure it comes from the trusted domains
+we know about.
+
+If requester SID is the same as in PAC, we already do cname vs PAC SID
+verification.
+
+With these changes FreeIPA works against Windows Server 2019 with
+November 2021 security fixes in cross-realm S4U2Self operations.
+
+Fixes: https://pagure.io/freeipa/issue/9031
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ daemons/ipa-kdb/ipa_kdb_mspac.c | 47 ++++++++++++++++++++++++---------
+ 1 file changed, 34 insertions(+), 13 deletions(-)
+
+diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
+index 538cfbba958068bd2ee0aaae7a2743ae82237898..1b972c167dd50619c7a6bd78eb5c81b0e05a4832 100644
+--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
++++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
+@@ -1697,7 +1697,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
+                                       "local [%s], PAC [%s]",
+                                       dom ? dom : "<failed to display>",
+                                       sid ? sid : "<failed to display>");
+-            return KRB5KDC_ERR_POLICY;
++            return KRB5KDC_ERR_TGT_REVOKED;
+         }
+     }
+ 
+@@ -1709,7 +1709,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
+     kerr = ipadb_get_principal(context, client_princ, flags, &client_actual);
+     if (kerr != 0) {
+         krb5_klog_syslog(LOG_ERR, "PAC issue: ipadb_get_principal failed.");
+-        return KRB5KDC_ERR_POLICY;
++        return KRB5KDC_ERR_TGT_REVOKED;
+     }
+ 
+     ied = (struct ipadb_e_data *)client_actual->e_data;
+@@ -1743,7 +1743,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
+                                   "local [%s] vs PAC [%s]",
+                                   local_sid ? local_sid : "<failed to display>",
+                                   pac_sid ? pac_sid : "<failed to display>");
+-        kerr = KRB5KDC_ERR_POLICY;
++        kerr = KRB5KDC_ERR_TGT_REVOKED;
+         goto done;
+     }
+ 
+@@ -2005,22 +2005,43 @@ static krb5_error_code ipadb_check_logon_info(krb5_context context,
+     /* Check that requester SID is the same as in the PAC entry */
+     if (requester_sid != NULL) {
+         struct dom_sid client_sid;
++        bool is_from_trusted_domain = false;
+         kerr = ipadb_get_sid_from_pac(tmpctx, info.info, &client_sid);
+         if (kerr) {
+             goto done;
+         }
+         result = dom_sid_check(&client_sid, requester_sid, true);
+         if (!result) {
+-            /* memctx is freed by the caller */
+-            char *pac_sid = dom_sid_string(tmpctx, &client_sid);
+-            char *req_sid = dom_sid_string(tmpctx, requester_sid);
+-            krb5_klog_syslog(LOG_ERR, "PAC issue: PAC has a SID "
+-                                      "different from what PAC requester claims. "
+-                                      "PAC [%s] vs PAC requester [%s]",
+-                                      pac_sid ? pac_sid : "<failed to display>",
+-                                      req_sid ? req_sid : "<failed to display>");
+-            kerr = KRB5KDC_ERR_POLICY;
+-            goto done;
++            struct ipadb_context *ipactx = ipadb_get_context(context);
++            if (!ipactx || !ipactx->mspac) {
++                return KRB5_KDB_DBNOTINITED;
++            }
++            /* In S4U case we might be dealing with the PAC issued by the trusted domain */
++            if (is_s4u && (ipactx->mspac->trusts != NULL)) {
++                /* Iterate through list of trusts and check if this SID belongs to
++                * one of the domains we trust */
++                for(int i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
++                    result = dom_sid_check(&ipactx->mspac->trusts[i].domsid,
++                                           requester_sid, false);
++                    if (result) {
++                        is_from_trusted_domain = true;
++                        break;
++                    }
++                }
++            }
++
++            if (!is_from_trusted_domain) {
++                /* memctx is freed by the caller */
++                char *pac_sid = dom_sid_string(tmpctx, &client_sid);
++                char *req_sid = dom_sid_string(tmpctx, requester_sid);
++                krb5_klog_syslog(LOG_ERR, "PAC issue: PAC has a SID "
++                                        "different from what PAC requester claims. "
++                                        "PAC [%s] vs PAC requester [%s]",
++                                        pac_sid ? pac_sid : "<failed to display>",
++                                        req_sid ? req_sid : "<failed to display>");
++                kerr = KRB5KDC_ERR_TGT_REVOKED;
++                goto done;
++            }
+         }
+     }
+ 
+-- 
+2.31.1
+
diff --git a/SOURCES/0003-ipatests-ensure-auth-indicators-can-t-be-added-to-in.patch b/SOURCES/0003-ipatests-ensure-auth-indicators-can-t-be-added-to-in.patch
deleted file mode 100644
index 681a79d..0000000
--- a/SOURCES/0003-ipatests-ensure-auth-indicators-can-t-be-added-to-in.patch
+++ /dev/null
@@ -1,138 +0,0 @@
-From 538a9992fd1394ed24cbcdf2a2a27694ac28da55 Mon Sep 17 00:00:00 2001
-From: Antonio Torres <antorres@redhat.com>
-Date: Mon, 8 Mar 2021 18:20:35 +0100
-Subject: [PATCH] ipatests: ensure auth indicators can't be added to internal
- IPA services
-
-Authentication indicators should not be added to internal IPA services,
-since this can lead to a broken IPA setup. In case a client with
-an auth indicator set in its host principal, promoting it to a replica
-should fail.
-
-Related: https://pagure.io/freeipa/issue/8206
-Signed-off-by: Antonio Torres <antorres@redhat.com>
----
- .../test_replica_promotion.py                 | 38 +++++++++++++++++++
- ipatests/test_xmlrpc/test_host_plugin.py      | 10 +++++
- ipatests/test_xmlrpc/test_service_plugin.py   | 21 ++++++++++
- 3 files changed, 69 insertions(+)
-
-diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py
-index 0a137dbdcb068811899e7ff7914730f14ea651c1..b9c56f775d08885cb6b1226eeb7bcf105f87cdc1 100644
---- a/ipatests/test_integration/test_replica_promotion.py
-+++ b/ipatests/test_integration/test_replica_promotion.py
-@@ -101,6 +101,44 @@ class TestReplicaPromotionLevel1(ReplicaPromotionBase):
-         assert result.returncode == 1
-         assert expected_err in result.stderr_text
- 
-+    @replicas_cleanup
-+    def test_install_with_host_auth_ind_set(self):
-+        """ A client shouldn't be able to be promoted if it has
-+        any auth indicator set in the host principal.
-+        https://pagure.io/freeipa/issue/8206
-+        """
-+
-+        client = self.replicas[0]
-+        # Configure firewall first
-+        Firewall(client).enable_services(["freeipa-ldap",
-+                                          "freeipa-ldaps"])
-+
-+        client.run_command(['ipa-client-install', '-U',
-+                            '--domain', self.master.domain.name,
-+                            '--realm', self.master.domain.realm,
-+                            '-p', 'admin',
-+                            '-w', self.master.config.admin_password,
-+                            '--server', self.master.hostname,
-+                            '--force-join'])
-+
-+        tasks.kinit_admin(client)
-+
-+        client.run_command(['ipa', 'host-mod', '--auth-ind=otp',
-+                            client.hostname])
-+
-+        res = client.run_command(['ipa-replica-install', '-U', '-w',
-+                                  self.master.config.dirman_password],
-+                                 raiseonerr=False)
-+
-+        client.run_command(['ipa', 'host-mod', '--auth-ind=',
-+                            client.hostname])
-+
-+        expected_err = ("Client cannot be promoted to a replica if the host "
-+                        "principal has an authentication indicator set.")
-+        assert res.returncode == 1
-+        assert expected_err in res.stderr_text
-+
-+
-     @replicas_cleanup
-     def test_one_command_installation(self):
-         """
-diff --git a/ipatests/test_xmlrpc/test_host_plugin.py b/ipatests/test_xmlrpc/test_host_plugin.py
-index c66bbc865cd5e1ee5ee5e1874c177a3ea9b08c93..9cfde3565d48e103a0549e2bfb7579e07668f41b 100644
---- a/ipatests/test_xmlrpc/test_host_plugin.py
-+++ b/ipatests/test_xmlrpc/test_host_plugin.py
-@@ -605,6 +605,16 @@ class TestProtectedMaster(XMLRPC_test):
-                 error=u'An IPA master host cannot be deleted or disabled')):
-             command()
- 
-+    def test_try_add_auth_ind_master(self, this_host):
-+        command = this_host.make_update_command({
-+            u'krbprincipalauthind': u'radius'})
-+        with raises_exact(errors.ValidationError(
-+            name='krbprincipalauthind',
-+            error=u'authentication indicators not allowed '
-+                'in service "host"'
-+        )):
-+            command()
-+
- 
- @pytest.mark.tier1
- class TestValidation(XMLRPC_test):
-diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py
-index 4c845938c33e2eca4235d53c4f4644c2fcdeda9c..ed634a0455a41dce367ed638634d1fc6d9e47553 100644
---- a/ipatests/test_xmlrpc/test_service_plugin.py
-+++ b/ipatests/test_xmlrpc/test_service_plugin.py
-@@ -25,6 +25,7 @@ from ipalib import api, errors
- from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid, fuzzy_hash
- from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_digits, fuzzy_date, fuzzy_issuer
- from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_hex, XMLRPC_test
-+from ipatests.test_xmlrpc.xmlrpc_test import raises_exact
- from ipatests.test_xmlrpc import objectclasses
- from ipatests.test_xmlrpc.testcert import get_testcert, subject_base
- from ipatests.test_xmlrpc.test_user_plugin import get_user_result, get_group_dn
-@@ -1552,6 +1553,15 @@ def indicators_host(request):
-     return tracker.make_fixture(request)
- 
- 
-+@pytest.fixture(scope='function')
-+def this_host(request):
-+    """Fixture for the current master"""
-+    tracker = HostTracker(name=api.env.host.partition('.')[0],
-+                          fqdn=api.env.host)
-+    tracker.exists = True
-+    return tracker
-+
-+
- @pytest.fixture(scope='function')
- def indicators_service(request):
-     tracker = ServiceTracker(
-@@ -1587,6 +1597,17 @@ class TestAuthenticationIndicators(XMLRPC_test):
-             expected_updates={u'krbprincipalauthind': [u'radius']}
-         )
- 
-+    def test_update_indicator_internal_service(self, this_host):
-+        command = this_host.make_command('service_mod',
-+                                         'ldap/' + this_host.fqdn,
-+                                         **dict(krbprincipalauthind='otp'))
-+        with raises_exact(errors.ValidationError(
-+            name='krbprincipalauthind',
-+            error=u'authentication indicators not allowed '
-+                 'in service "ldap"'
-+        )):
-+            command()
-+
- 
- @pytest.fixture(scope='function')
- def managing_host(request):
--- 
-2.26.3
-
diff --git a/SOURCES/0004-stageuser-add-ipauserauthtypeclass-when-required.patch b/SOURCES/0004-stageuser-add-ipauserauthtypeclass-when-required.patch
deleted file mode 100644
index 2d9e34f..0000000
--- a/SOURCES/0004-stageuser-add-ipauserauthtypeclass-when-required.patch
+++ /dev/null
@@ -1,57 +0,0 @@
-From a8d6257b2cf64c3dd2b1c5d7bcf81acc3b766853 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 5 Jul 2021 09:51:41 +0200
-Subject: [PATCH] stageuser: add ipauserauthtypeclass when required
-
-The command
-ipa stageuser-add --user-auth-type=xxx
-is currently failing because the objectclass ipauserauthtypeclass
-is missing from the created entry.
-
-There is code adding the missing objectclass in the
-pre_common_callback method of user_add, and this code should
-be common to user_add and stageuser_add. In order to avoid code
-duplication, it makes more sense to move the existing code to
-pre_common_callback of baseuser_add, that is called by both
-classes.
-
-Fixes: https://pagure.io/freeipa/issue/8909
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/plugins/baseuser.py | 3 +++
- ipaserver/plugins/user.py     | 4 ----
- 2 files changed, 3 insertions(+), 4 deletions(-)
-
-diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
-index ae16a978ab01f9c5c257e9cb5567c918a7fafdc5..6035228f19ef8acaf4992490d5512c126881816d 100644
---- a/ipaserver/plugins/baseuser.py
-+++ b/ipaserver/plugins/baseuser.py
-@@ -539,6 +539,9 @@ class baseuser_add(LDAPCreate):
-         if entry_attrs.get('ipatokenradiususername', None):
-             add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
-                                      entry_attrs, update=False)
-+        if entry_attrs.get('ipauserauthtype', None):
-+            add_missing_object_class(ldap, u'ipauserauthtypeclass', dn,
-+                                     entry_attrs, update=False)
- 
-     def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
-         assert isinstance(dn, DN)
-diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py
-index 6f7facb5380ba56feab39b71cd265776f3ab57d8..e4ee572b236c288fd7dcf1d44c5adf1f836f63aa 100644
---- a/ipaserver/plugins/user.py
-+++ b/ipaserver/plugins/user.py
-@@ -617,10 +617,6 @@ class user_add(baseuser_add):
-            'ipauser' not in entry_attrs['objectclass']:
-             entry_attrs['objectclass'].append('ipauser')
- 
--        if 'ipauserauthtype' in entry_attrs and \
--           'ipauserauthtypeclass' not in entry_attrs['objectclass']:
--            entry_attrs['objectclass'].append('ipauserauthtypeclass')
--
-         rcl = entry_attrs.get('ipatokenradiusconfiglink', None)
-         if rcl:
-             if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
--- 
-2.26.3
-
diff --git a/SOURCES/0005-XMLRPC-test-add-a-test-for-stageuser-add-user-auth-t.patch b/SOURCES/0005-XMLRPC-test-add-a-test-for-stageuser-add-user-auth-t.patch
deleted file mode 100644
index d96d52f..0000000
--- a/SOURCES/0005-XMLRPC-test-add-a-test-for-stageuser-add-user-auth-t.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From 932910456e0269edefe396d4af96447f90ff29b3 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 5 Jul 2021 10:22:31 +0200
-Subject: [PATCH] XMLRPC test: add a test for stageuser-add --user-auth-type
-
-Related: https://pagure.io/freeipa/issue/8909
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipatests/test_xmlrpc/test_stageuser_plugin.py | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/ipatests/test_xmlrpc/test_stageuser_plugin.py b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-index 5586fc607e134938225c1c982fc39d169847f549..bc606b093c98ce204ad4ea17e5c16273144fa2e7 100644
---- a/ipatests/test_xmlrpc/test_stageuser_plugin.py
-+++ b/ipatests/test_xmlrpc/test_stageuser_plugin.py
-@@ -343,6 +343,12 @@ class TestStagedUser(XMLRPC_test):
-         result = command()
-         assert result['count'] == 1
- 
-+    def test_create_withuserauthtype(self, stageduser):
-+        stageduser.ensure_missing()
-+        command = stageduser.make_create_command(
-+            options={u'ipauserauthtype': u'password'})
-+        command()
-+
- 
- @pytest.mark.tier1
- class TestCreateInvalidAttributes(XMLRPC_test):
--- 
-2.26.3
-
diff --git a/SOURCES/0006-augeas-bump-version-for-rhel9.patch b/SOURCES/0006-augeas-bump-version-for-rhel9.patch
deleted file mode 100644
index 35859fe..0000000
--- a/SOURCES/0006-augeas-bump-version-for-rhel9.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From 9144526d2d7e7dcd8503c6c38226e17ebb4ed8b9 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 7 Jul 2021 10:49:25 +0200
-Subject: [PATCH] augeas: bump version for rhel9
-
-augeas 1.12.1-0.1 adds support for the new chony configuration
-settings.
-
-Related: https://pagure.io/freeipa/issue/8676
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Anuja More <amore@redhat.com>
----
- freeipa.spec.in | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index fbfe4d09eedc169112dcdc18a953134de67b7731..ae4af099f39641a9f5163d61cfb37e1c3afb6f4b 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -162,13 +162,16 @@
- 
- # augeas support for new chrony options
- # see https://pagure.io/freeipa/issue/8676
--# Note: will need to be updated for RHEL9 when a fix is available for
- # https://bugzilla.redhat.com/show_bug.cgi?id=1931787
- %if 0%{?fedora} >= 33
- %global augeas_version 1.12.0-6
- %else
-+%if 0%{?rhel} >= 9
-+%global augeas_version 1.12.1-0
-+%else
- %global augeas_version 1.12.0-3
- %endif
-+%endif
- 
- %global plugin_dir %{_libdir}/dirsrv/plugins
- %global etc_systemd_dir %{_sysconfdir}/systemd/system
--- 
-2.26.3
-
diff --git a/SOURCES/0007-man-page-update-ipa-server-upgrade.1.patch b/SOURCES/0007-man-page-update-ipa-server-upgrade.1.patch
deleted file mode 100644
index df25002..0000000
--- a/SOURCES/0007-man-page-update-ipa-server-upgrade.1.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From ecb407864fde4d917dabe0aae95881561ed384ab Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Wed, 7 Jul 2021 14:11:40 +0200
-Subject: [PATCH] man page: update ipa-server-upgrade.1
-
-The man page needs to clarify in which case the command needs
-to be run.
-
-Fixes: https://pagure.io/freeipa/issue/8913
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- install/tools/man/ipa-server-upgrade.1 | 7 ++++++-
- 1 file changed, 6 insertions(+), 1 deletion(-)
-
-diff --git a/install/tools/man/ipa-server-upgrade.1 b/install/tools/man/ipa-server-upgrade.1
-index 3db19b0f13da1f5a36bd6e8df23fc916d0401a6d..f01e21c6b599499c4c6dbbcf120b19a3431fb3ed 100644
---- a/install/tools/man/ipa-server-upgrade.1
-+++ b/install/tools/man/ipa-server-upgrade.1
-@@ -8,7 +8,12 @@ ipa\-server\-upgrade \- upgrade IPA server
- .SH "SYNOPSIS"
- ipa\-server\-upgrade [options]
- .SH "DESCRIPTION"
--ipa\-server\-upgrade is used to upgrade IPA server when the IPA packages are being updated. It is not intended to be executed by end\-users.
-+ipa\-server\-upgrade is executed automatically to upgrade IPA server when
-+the IPA packages are being updated. It is not intended to be executed by
-+end\-users, unless the automatic execution reports an error. In this case,
-+the administrator needs to identify and fix the issue that is causing the
-+upgrade failure (with the help of /var/log/ipaupgrade.log)
-+and manually re\-run ipa\-server\-upgrade.
- 
- ipa\-server\-upgrade will:
- 
--- 
-2.26.3
-
diff --git a/SOURCES/0008-Add-basic-support-for-subordinate-user-group-ids.patch b/SOURCES/0008-Add-basic-support-for-subordinate-user-group-ids.patch
deleted file mode 100644
index 6b52566..0000000
--- a/SOURCES/0008-Add-basic-support-for-subordinate-user-group-ids.patch
+++ /dev/null
@@ -1,2324 +0,0 @@
-From 69cd05bf635d19b9844f65d83dace05136a40326 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Fri, 19 Mar 2021 11:48:38 +0100
-Subject: [PATCH] Add basic support for subordinate user/group ids
-
-New LDAP object class "ipaUserSubordinate" with four new fields:
-- ipasubuidnumber / ipasubuidcount
-- ipasubgidnumber / ipasgbuidcount
-
-New self-service permission to add subids.
-
-New command user-auto-subid to auto-assign subid
-
-The code hard-codes counts to 65536, sets subgid equal to subuid, and
-does not allow removal of subids. There is also a hack that emulates a
-DNA plugin with step interval 65536 for testing.
-
-Work around problem with older SSSD clients that fail with unknown
-idrange type "ipa-local-subid", see: https://github.com/SSSD/sssd/issues/5571
-
-Related: https://pagure.io/freeipa/issue/8361
-Signed-off-by: Christian Heimes <cheimes@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ACI.txt                                   |   2 +-
- API.txt                                   |  47 ++-
- Makefile.am                               |   2 +-
- VERSION.m4                                |   4 +-
- doc/designs/index.rst                     |   1 +
- doc/designs/subordinate-ids.md            | 468 ++++++++++++++++++++++
- freeipa.spec.in                           |   1 +
- install/share/60basev2.ldif               |   1 +
- install/share/60basev4.ldif               |  19 +
- install/share/Makefile.am                 |   1 +
- install/share/bootstrap-template.ldif     |  22 +
- install/share/dna.ldif                    |  20 +
- install/tools/Makefile.am                 |   2 +
- install/tools/ipa-subids.in               |   8 +
- install/ui/src/freeipa/user.js            |  53 ++-
- install/updates/20-indices.update         |  18 +
- install/updates/73-subid.update           | 102 +++++
- install/updates/Makefile.am               |   1 +
- ipalib/constants.py                       |  13 +
- ipaserver/install/adtrustinstance.py      |  29 +-
- ipaserver/install/dsinstance.py           |  43 +-
- ipaserver/install/ipa_subids.py           | 154 +++++++
- ipaserver/install/ldapupdate.py           |  95 +++--
- ipaserver/plugins/baseuser.py             | 274 ++++++++++++-
- ipaserver/plugins/idrange.py              |  10 +-
- ipaserver/plugins/internal.py             |  12 +
- ipaserver/plugins/user.py                 |  17 +-
- ipatests/prci_definitions/gating.yaml     |  12 +
- ipatests/test_integration/test_subids.py  | 201 ++++++++++
- ipatests/test_xmlrpc/test_range_plugin.py |   7 +
- 31 files changed, 1565 insertions(+), 75 deletions(-)
- create mode 100644 doc/designs/subordinate-ids.md
- create mode 100644 install/share/60basev4.ldif
- create mode 100644 install/tools/ipa-subids.in
- create mode 100644 install/updates/73-subid.update
- create mode 100644 ipaserver/install/ipa_subids.py
- create mode 100644 ipatests/test_integration/test_subids.py
-
-diff --git a/ACI.txt b/ACI.txt
-index 05852cf6c0150db7d8de99a5f7a44e538df29e5e..fce02a333b212de9b61f920515eed3e356b1391b 100644
---- a/ACI.txt
-+++ b/ACI.txt
-@@ -375,7 +375,7 @@ aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber
- dn: dc=ipa,dc=example
- aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
--aci: (targetattr = "ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
-+aci: (targetattr = "ipasshpubkey || ipasubgidcount || ipasubgidnumber || ipasubuidcount || ipasubuidnumber || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
- aci: (targetattr = "krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || krbprincipaltype || nsaccountlock")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Kerberos Attributes";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
-diff --git a/API.txt b/API.txt
-index 212ef807c771794dc2f89eb89e03b669eb49295b..262b4d6a72c7d7032a7027116f7a4f65aa620615 100644
---- a/API.txt
-+++ b/API.txt
-@@ -4974,7 +4974,7 @@ output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
- command: stageuser_add/1
--args: 1,45,3
-+args: 1,46,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -4992,6 +4992,7 @@ option: Str('givenname', cli_name='first')
- option: Str('homedirectory?', cli_name='homedir')
- option: Str('initials?', autofill=True)
- option: Str('ipasshpubkey*', cli_name='sshpubkey')
-+option: Int('ipasubuidnumber?', cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', cli_name='radius')
- option: Str('ipatokenradiususername?', cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -5080,7 +5081,7 @@ output: Output('result', type=[<type 'dict'>])
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: ListOfPrimaryKeys('value')
- command: stageuser_find/1
--args: 1,58,4
-+args: 1,60,4
- arg: Str('criteria?')
- option: Flag('all', autofill=True, cli_name='all', default=False)
- option: Str('carlicense*', autofill=False)
-@@ -5104,6 +5105,8 @@ option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
- option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
-+option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
-+option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -5145,7 +5148,7 @@ output: ListOfEntries('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: Output('truncated', type=[<type 'bool'>])
- command: stageuser_mod/1
--args: 1,51,3
-+args: 1,52,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -5167,6 +5170,7 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
- option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
-+option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -6058,7 +6062,7 @@ output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
- command: user_add/1
--args: 1,46,3
-+args: 1,47,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -6075,6 +6079,7 @@ option: Str('givenname', cli_name='first')
- option: Str('homedirectory?', cli_name='homedir')
- option: Str('initials?', autofill=True)
- option: Str('ipasshpubkey*', cli_name='sshpubkey')
-+option: Int('ipasubuidnumber?', cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', cli_name='radius')
- option: Str('ipatokenradiususername?', cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -6156,6 +6161,16 @@ option: Str('version?')
- output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
-+command: user_auto_subid/1
-+args: 1,4,3
-+arg: Str('uid', cli_name='login')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Flag('no_members', autofill=True, default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: PrimaryKey('value')
- command: user_del/1
- args: 1,3,3
- arg: Str('uid+', cli_name='login')
-@@ -6180,7 +6195,7 @@ output: Output('result', type=[<type 'bool'>])
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
- command: user_find/1
--args: 1,61,4
-+args: 1,63,4
- arg: Str('criteria?')
- option: Flag('all', autofill=True, cli_name='all', default=False)
- option: Str('carlicense*', autofill=False)
-@@ -6204,6 +6219,8 @@ option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
- option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
-+option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
-+option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -6247,8 +6264,23 @@ output: Output('count', type=[<type 'int'>])
- output: ListOfEntries('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: Output('truncated', type=[<type 'bool'>])
-+command: user_match_subid/1
-+args: 1,8,4
-+arg: Str('criteria?')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Int('ipasubuidnumber', autofill=False, cli_name='subuid')
-+option: Flag('no_members', autofill=True, default=True)
-+option: Flag('pkey_only?', autofill=True, default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Int('sizelimit?', autofill=False)
-+option: Int('timelimit?', autofill=False)
-+option: Str('version?')
-+output: Output('count', type=[<type 'int'>])
-+output: ListOfEntries('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: Output('truncated', type=[<type 'bool'>])
- command: user_mod/1
--args: 1,52,3
-+args: 1,53,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -6270,6 +6302,7 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
- option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
-+option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -7183,10 +7216,12 @@ default: user_add_cert/1
- default: user_add_certmapdata/1
- default: user_add_manager/1
- default: user_add_principal/1
-+default: user_auto_subid/1
- default: user_del/1
- default: user_disable/1
- default: user_enable/1
- default: user_find/1
-+default: user_match_subid/1
- default: user_mod/1
- default: user_remove_cert/1
- default: user_remove_certmapdata/1
-diff --git a/Makefile.am b/Makefile.am
-index c5a33e67f56b2c6f9efb5b4c6af3f7a44ccbdb3c..321df05a7c44f32929a2c5ec45341a42105a8e2f 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -229,7 +229,7 @@ fasttest: $(GENERATED_PYTHON_FILES) ipasetup.py
- 	    --ignore $(abspath $(top_srcdir))/ipatests/test_integration \
- 	    --ignore $(abspath $(top_srcdir))/ipatests/test_xmlrpc
- 
--fastlint: $(GENERATED_PYTHON_FILES) ipasetup.py
-+fastlint: $(GENERATED_PYTHON_FILES) ipasetup.py acilint apilint
- if ! WITH_PYLINT
- 	@echo "ERROR: pylint not available"; exit 1
- endif
-diff --git a/VERSION.m4 b/VERSION.m4
-index 9f024675f905a1ee771b6ff293c25b2ac46d92df..1c1e0d56c0eb5c15be0887fae9f90e399757acc7 100644
---- a/VERSION.m4
-+++ b/VERSION.m4
-@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
- #                                                      #
- ########################################################
- define(IPA_API_VERSION_MAJOR, 2)
--define(IPA_API_VERSION_MINOR, 242)
--# Last change: add status options for cert-find
-+# Last change: add subordinate id feature
-+define(IPA_API_VERSION_MINOR, 243)
- 
- 
- ########################################################
-diff --git a/doc/designs/index.rst b/doc/designs/index.rst
-index cbec1096c363c9c31656b05f22c50321cd45e073..6dd0edff3004fd0d19208f0c063d4156bde3bf91 100644
---- a/doc/designs/index.rst
-+++ b/doc/designs/index.rst
-@@ -17,3 +17,4 @@ FreeIPA design documentation
-    membermanager.md
-    hidden-replicas.md
-    disable-stale-users.md
-+   subordinate-ids.md
-diff --git a/doc/designs/subordinate-ids.md b/doc/designs/subordinate-ids.md
-new file mode 100644
-index 0000000000000000000000000000000000000000..1b578667a8cfdda223af38a14d142c72a5d5c073
---- /dev/null
-+++ b/doc/designs/subordinate-ids.md
-@@ -0,0 +1,468 @@
-+# Central management of subordinate user and group ids
-+
-+Subordinate ids are a Linux Kernel feature to grant a user additional
-+user and group id ranges. Amongst others the feature can be used
-+by container runtime engies to implement rootless containers.
-+Traditionally subordinate id ranges are configured in ``/etc/subuid``
-+and ``/etc/subgid``.
-+
-+To make rootless containers in a large environment as easy as pie, IPA
-+gains the ability to centrally manage and assign subordinate id ranges.
-+SSSD and shadow-util are extended to read subordinate ids from IPA and
-+provide them to userspace tools.
-+
-+## Overview
-+
-+Feature requests
-+
-+* [FreeIPA feature request #8361](https://pagure.io/freeipa/issue/8361)
-+* [SSSD feature request #5197](https://github.com/SSSD/sssd/issues/5197)
-+* [shadow-util feature request #154](https://github.com/shadow-maint/shadow/issues/154)
-+* [389-DS RFE for DNA plugin rhbz#1938239](https://bugzilla.redhat.com/show_bug.cgi?id=1938239)
-+
-+Man pages
-+
-+* [man subuid(5)](https://man7.org/linux/man-pages/man5/subuid.5.html)
-+* [man subgid(5)](https://man7.org/linux/man-pages/man5/subgid.5.html)
-+* [man user_namespaces(7)](https://man7.org/linux/man-pages/man7/user_namespaces.7.html)
-+* [man newuidmap(1)](https://man7.org/linux/man-pages/man1/newuidmap.1.html)
-+
-+Articles / blog posts
-+* [Basic Setup and Use of Podman in a Rootless environment](https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md)
-+* [How does rootless Podman work](https://opensource.com/article/19/2/how-does-rootless-podman-work)
-+
-+## Design choices
-+
-+Some design choices are owed to the circumstance that uids and gids
-+are limited datatypes. The Linux Kernel and userland defines
-+``uid_t`` and ``gid_t`` as unsigned 32bit integers (``uint32_t``), which
-+limits possible values for numeric user and group ids to
-+``0 .. 2^32-2``. ``(uid_t)-1`` is reserved for error reporting. On the
-+other hand the user ``nobody`` typically has uid 65534 / gid 65534. This
-+means we need to assign 65,536 subordinate ids to every user. The
-+theoretical maximum amount of subordinate ranges is less than 65,536
-+(``65536 * 65536 == 2^32``). [``logins.def``](https://man7.org/linux/man-pages/man5/login.defs.5.html)
-+also uses 65536 as default setting for ``SUB_UID_COUNT``.
-+
-+The practical limit is far smaller. Subordinate ids should not overlap
-+with system accounts, local user accounts, IPA user accounts, and
-+mapped accounts from Active Directory. Therefore IPA uses the upper
-+half of the uid_t range (>= 2^31 == 2,147,483,648) for subordinate ids.
-+The high bit is rarely used. IPA limits general numeric ids
-+(``uidNumber``, ``gidNumber``, ID ranges) to maximum values of signed
-+32bit integer (2^31-1) for backwards compatibility with XML-RPC.
-+``logins.def`` defaults to ``SUB_UID_MAX`` 600,100,000.
-+
-+A default subordinate id count of 65,536 and a total range of approx.
-+2.1 billion limits IPA to slightly more than 32,000 possible ranges. It
-+may sound like a lot of users, but there are much bigger installations
-+of IPA. For comparison Fedora Accounts has over 120,000 users stored in
-+IPA.
-+
-+For that reason we treat subordinate id space as premium real estate
-+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.
-+
-+### Revision 1 limitation
-+
-+The first revision of the feature is deliberately limited and
-+restricted. We are aiming for a simple implementation that covers
-+basic use cases. Some restrictions may be lifted in the future.
-+
-+* subuid and subgids cannot be set independently. They are always set
-+  to the same value.
-+* counts are hard-coded to value 65536
-+* once assigned subids cannot be removed
-+* IPA does not support multiple subordinate id ranges. Contrary to
-+  ``/etc/subuid``, users are limited to one set of subordinate ids.
-+* subids are auto-assigned. Auto-assignment is currently emulated
-+  until 389-DS has been extended to support DNA with step interval.
-+* subids are allocated from hard-coded range
-+  ``[2147483648..4294901767]`` (``2^31`` to ``2^32-1-65536``), which
-+  is the upper 2.1 billion uids of ``uid_t`` (``uint32_t``). The range
-+  can hold little 32,767 subordinate id ranges.
-+* Active Directory support is out of scope and may be provided in the
-+  future.
-+
-+### Subid assignment example
-+
-+```
-+>>> import itertools
-+>>> def subids():
-+...     for n in itertools.count(start=0):
-+...         start = SUBID_RANGE_START + (n * SUBID_COUNT)
-+...         last = start + SUBID_COUNT - 1
-+...         yield (start, last)
-+...
-+>>> gen = subids()
-+>>> next(gen)
-+(2147483648, 2147549183)
-+>>> next(gen)
-+(2147549184, 2147614719)
-+>>> next(gen)
-+(2147614720, 2147680255)
-+```
-+
-+The first user has 65565 subordinate ids from uid/gid ``2147483648``
-+to ``2147549183``, the next user has ``2147549184`` to ``2147614719``,
-+and so on. The range count includes the start value.
-+
-+An installation with multiple servers, 389-DS'
-+[DNA](https://directory.fedoraproject.org/docs/389ds/design/dna-plugin.html)
-+plug-in takes care of delegating and assigning chunks of subid ranges
-+to servers. The DNA plug-in guarantees uniqueness across servers.
-+
-+## LDAP
-+
-+### LDAP schema extension
-+
-+The subordinate id feature introduces a new auxiliar object class
-+``ipaSubordinateId`` with four required attributes ``ipaSubUidNumber``,
-+``ipaSubUidCount``, ``ipaSubGidNumber``, and ``ipaSubGidCount``. The
-+attributes with ``number`` suffix store the start value of the interval.
-+The ``count`` attributes contain the size of the interval including the
-+start value. The maximum subid is
-+``ipaSubUidNumber + ipaSubUidCount - 1``.
-+
-+All four attributes are single-value ``INTEGER`` type with standard
-+integer matching rules. OIDs ``2.16.840.1.113730.3.8.23.8`` and
-+``2.16.840.1.113730.3.8.23.11`` are reserved for future use.
-+
-+```raw
-+attributeTypes: (
-+  2.16.840.1.113730.3.8.23.6
-+  NAME 'ipaSubUidNumber'
-+  DESC 'Numerical subordinate user ID (range start value)'
-+  EQUALITY integerMatch ORDERING integerOrderingMatch
-+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
-+  X-ORIGIN 'IPA v4.9'
-+)
-+attributeTypes: (
-+  2.16.840.1.113730.3.8.23.7
-+  NAME 'ipaSubUidCount'
-+  DESC 'Subordinate user ID count (range size)'
-+  EQUALITY integerMatch ORDERING integerOrderingMatch
-+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
-+  X-ORIGIN 'IPA v4.9'
-+)
-+attributeTypes: (
-+  2.16.840.1.113730.3.8.23.9
-+  NAME 'ipaSubGidNumber'
-+  DESC 'Numerical subordinate group ID (range start value)'
-+  EQUALITY integerMatch ORDERING integerOrderingMatch
-+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
-+  X-ORIGIN 'IPA v4.9'
-+)
-+attributeTypes: (
-+  2.16.840.1.113730.3.8.23.10
-+  NAME 'ipaSubGidCount'
-+  DESC 'Subordinate group ID count (range size)'
-+  EQUALITY integerMatch ORDERING integerOrderingMatch
-+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
-+  X-ORIGIN 'IPA v4.9'
-+)
-+```
-+
-+The ``ipaSubordinateId`` object class is an auxiliar subclass of
-+``top`` and requires all four subordinate id attributes as well as
-+``uidNumber``. It does not subclass ``posixAccount`` to make
-+the class reusable in idview overrides later.
-+
-+```raw
-+objectClasses: (
-+  2.16.840.1.113730.3.8.24.4
-+  NAME 'ipaSubordinateId'
-+  DESC 'Subordinate uid and gid for users'
-+  SUP top AUXILIARY
-+  MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount )
-+  X-ORIGIN 'IPA v4.9'
-+)
-+```
-+
-+The ``ipaSubordinateGid`` and ``ipaSubordinateUid`` are defined for
-+future use. IPA always assumes the presence of ``ipaSubordinateId`` and
-+does not use these object classes.
-+
-+```raw
-+objectClasses: (
-+  2.16.840.1.113730.3.8.24.2
-+  NAME 'ipaSubordinateUid'
-+  DESC 'Subordinate uids for users, see subuid(5)'
-+  SUP top AUXILIARY
-+  MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount )
-+  X-ORIGIN 'IPA v4.9'
-+ )
-+objectClasses: (
-+  2.16.840.1.113730.3.8.24.3
-+  NAME 'ipaSubordinateGid'
-+  DESC 'Subordinate gids for users, see subgid(5)'
-+  SUP top AUXILIARY
-+  MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount )
-+  X-ORIGIN 'IPA v4.9'
-+)
-+```
-+
-+### Index
-+
-+The attributes ``ipaSubUidNumber`` and ``ipaSubGidNumber`` are index
-+for ``pres`` and ``eq`` with ``nsMatchingRule: integerOrderingMatch``
-+to enable efficient ``=``, ``>=``, and ``<=`` searches.
-+
-+### Distributed numeric assignment (DNA) plug-in extension
-+
-+Subordinate id auto-assignment requires an extension of 389-DS'
-+[DNA](https://directory.fedoraproject.org/docs/389ds/design/dna-plugin.html)
-+plug-in. The DNA plug-in is responsible for safely assigning unique
-+numeric ids across all replicas.
-+
-+Currently the DNA plug-in only supports a step size of ``1``. A new
-+option ``dnaStepAttr`` (name is tentative) will tell the DNA plug-in
-+to use the value of entry attributes as step size.
-+
-+
-+## Permissions, Privileges, Roles
-+
-+### Self-servive RBAC
-+
-+The self-service permission enables users to request auto-assignment
-+of subordinate uid and gid ranges for themselves. Subordinate ids cannot
-+be modified or deleted.
-+
-+* ACI: *selfservice: Add subordinate id*
-+* Permission: *Self-service subordinate ID*
-+* Privilege: *Subordinate ID Selfservice User*
-+* Role: *Subordinate ID Selfservice Users*
-+* role default member: n/a
-+
-+### Administrator RBAC
-+
-+The administrator permission allows privileged users to auto-assign
-+subordinate ids to users. Once assigned subordinate ids cannot
-+be modified or deleted.
-+
-+* ACI: *Add subordinate ids to any user*
-+* Permission: *Manage subordinate ID*
-+* Privilege: *Subordinate ID Administrators*
-+* default privilege role: *User Administrator*
-+
-+
-+## Workflows
-+
-+In the default configuration of IPA, neither existing users nor new
-+users will have subordinate ids assigned. There are a couple of ways
-+to assign subordinate ids to users.
-+
-+### User administrator
-+
-+Users with *User Administrator* role and members of the *admins* group
-+have permission to auto-assign new subordinate ids to any user. Auto
-+assignment can be performed with new ``user-auto-subid`` command on the
-+command line or with the *Auto assign subordinate ids* action in the
-+*Actions* drop-down menu in the web UI.
-+
-+```shell
-+$ ipa user-auto-subid someusername
-+```
-+
-+### Self-service for group members
-+
-+Ordinary users cannot self-service subordinate ids by default. Admins
-+can assign the new *Subordinate ID Selfservice User* to users group to
-+enable self-service for members of the group.
-+
-+For example to enable self-service for all members of the default user
-+group ``ipausers``, do:
-+
-+```shell
-+$ ipa role-add-member "Subordinate ID Selfservice User" --groups=ipausers
-+```
-+
-+This allows members of ``ipausers`` to request subordinate ids with
-+the ``user-auto-subid`` command or the *Auto assign subordinate ids*
-+action in the web UI.
-+
-+```shell
-+$ ipa user-auto-subid myusername
-+```
-+
-+### Auto assignment with user default object class
-+
-+Admins can also enable auto-assignment of subordinate ids for all new
-+users by adding ``ipasubordinateid`` as a default user objectclass.
-+This can be accomplished in the web UI under "IPA Server" /
-+"Configuration" / "Default user objectclasses" or on the command line
-+with:
-+
-+```shell
-+$ ipa config-mod --addattr="ipaUserObjectClasses=ipasubordinateid"
-+```
-+
-+**NOTE:** The objectclass must be written all lower case.
-+
-+### ipa-subid tool
-+
-+Finally IPA includes a new tool for mass-assignment of subordinate ids.
-+The command uses automatic LDAPI EXTERNAL bind when it's executed as
-+root user. Other it requires valid Kerberos TGT of an admin or user
-+administrator.
-+
-+```raw
-+
-+# /usr/libexec/ipa/ipa-subids --help
-+Usage: ipa-subids
-+
-+Mass-assign subordinate ids
-+
-+Options:
-+  --version             show program's version number and exit
-+  -h, --help            show this help message and exit
-+  --group=GROUP         Filter by group membership
-+  --filter=USER_FILTER  Raw LDAP filter
-+  --dry-run             Dry run mode.
-+  --all-users           All users
-+
-+  Logging and output options:
-+    -v, --verbose       print debugging information
-+    -d, --debug         alias for --verbose (deprecated)
-+    -q, --quiet         output only errors
-+    --log-file=FILE     log to the given file
-+
-+# # /usr/libexec/ipa/ipa-subids --group ipausers
-+Processing user 'testsubordinated1' (1/15)
-+Processing user 'testsubordinated2' (2/15)
-+Processing user 'testsubordinated3' (3/15)
-+Processing user 'testsubordinated4' (4/15)
-+Processing user 'testsubordinated5' (5/15)
-+Processing user 'testsubordinated6' (6/15)
-+Processing user 'testsubordinated7' (7/15)
-+Processing user 'testsubordinated8' (8/15)
-+Processing user 'testsubordinated9' (9/15)
-+Processing user 'testsubordinated10' (10/15)
-+Processing user 'testsubordinated11' (11/15)
-+Processing user 'testsubordinated12' (12/15)
-+Processing user 'testsubordinated13' (13/15)
-+Processing user 'testsubordinated14' (14/15)
-+Processing user 'testsubordinated15' (15/15)
-+Processed 15 user(s)
-+The ipa-subids command was successful
-+```
-+
-+### Find and match users by any subordinate id
-+
-+The ``user-find`` command search by start value of subordinate uid and
-+gid range. The new command ``user-match-subid`` can be used to find a
-+user by any subordinate id in their range.
-+
-+```raw
-+$ ipa user-match-subid --subuid=2153185287
-+  User login: asmith
-+  First name: Alice
-+  Last name: Smith
-+  ...
-+  SubUID range start: 2153185280
-+  SubUID range size: 65536
-+  SubGID range start: 2153185280
-+  SubGID range size: 65536
-+----------------------------
-+Number of entries returned 1
-+----------------------------
-+$ ipa user-match-subid --subuid=2153185279
-+  User login: bjones
-+  First name: Bob
-+  Last name: Jones
-+  ...
-+  SubUID range start: 2153119744
-+  SubUID range size: 65536
-+  SubGID range start: 2153119744
-+  SubGID range size: 65536
-+----------------------------
-+Number of entries returned 1
-+----------------------------
-+```
-+
-+## SSSD integration
-+
-+* base: ``cn=accounts,$SUFFIX`` / ``cn=users,cn=accounts,$SUFFIX``
-+* scope: ``SCOPE_SUBTREE`` (2) / ``SCOPE_ONELEVEL`` (1)
-+* user filter: should include ``(objectClass=posixAccount)``
-+* attributes: ``uidNumber ipaSubUidNumber ipaSubUidCount ipaSubGidNumber ipaSubGidCount``
-+
-+SSSD can safely assume that only *user accounts* of type ``posixAccount``
-+have subordinate ids. In the first revision there are no other entries
-+with subordinate ids. The ``posixAccount`` object class has ``uid``
-+(user login name) and ``uidNumber`` (numeric user id) as mandatory
-+attributes. The ``uid`` attribute is guaranteed to be unique across
-+all user accounts in an IPA domain.
-+
-+The ``uidNumber`` attribute is commonly unique, too. However it's
-+technically possible that an administrator has assigned the same
-+numeric user id to multiple users. Automatically assigned uid numbers
-+don't conflict. SSSD should treat multiple users with same numeric
-+user id as an error.
-+
-+The attribute ``ipaSubUidNumber`` is always accompanied by
-+``ipaSubUidCount`` and ``ipaSubGidNumber`` is always accompanied
-+by ``ipaSubGidCount``. In revision 1 the presence of
-+``ipaSubUidNumber`` implies presence of the other three attributes.
-+All four subordinate id attributes and ``uidNumber`` are single-value
-+``INTEGER`` types. Any value outside of range of ``uint32_t`` must
-+treated as invalid. SSSD will never see the DNA magic value ``-1``
-+in ``cn=accounts,$SUFFIX`` subtree.
-+
-+IPA recommends that SSSD simply extends its existing query for user
-+accounts and requests the four subordinate attributes additionally to
-+RFC 2307 attributes ``rfc2307_user_map``. SSSD can directly take the
-+values and return them without further processing, e.g.
-+``uidNumber:ipaSubUidNumber:ipaSubUidCount`` for ``/etc/subuid``.
-+
-+Filters for additional cases:
-+
-+* subuid filter (find user with subuid by numeric uid):
-+  ``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=$UID))``,
-+  ``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
-+* subuid enumeration filter:
-+  ``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=*))``,
-+  ``(objectClass=ipaSubordinateId)``, or similar
-+* subgid filter (find user with subgid by numeric uid):
-+  ``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=$UID))``,
-+  ``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
-+* subgid enumeration filter:
-+  ``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=*))``,
-+  ``(objectClass=ipaSubordinateId)``, or similar
-+
-+## Implementation details
-+
-+* The four subid attributes are not included in
-+  ``baseuser.default_attributes`` on purpose. The ``config-mod``
-+  command does not permit removal of a user default objectclasses
-+  when the class is the last provider of an attribute in
-+  ``default_attributes``.
-+* ``ipaSubordinateId`` object class does not subclass the other two
-+  object classes. LDAP supports
-+  ``SUP ( ipaSubordinateGid $ ipaSubordinateUid )`` but 389-DS only
-+  auto-inherits from first object class.
-+* The idrange entry ``$REALM_subid_range`` has preconfigured base RIDs
-+  and SID so idrange plug-in and sidgen task ignore the entry. It's the
-+  simplest approach to ensure backwards compatibility with older IPA
-+  server versions that don't know how to handle the new range.
-+  The SID is ``S-1-5-21-738065-838566-$DOMAIN_HASH``. ``S-1-5-21``
-+  is the well-known SID prefix for domain SIDs.  ``738065-838566`` is
-+  the decimal representation of the string ``IPA-SUB``. ``DOMAIN_HASH``
-+  is the MURMUR-3 hash of the domain name for key ``0xdeadbeef``. SSSD
-+  rejects SIDs unless they are prefixed with ``S-1-5-21`` (see
-+  ``sss_idmap.c:is_domain_sid()``).
-+* The new ``$REALM_subid_range`` entry uses range type ``ipa-ad-trust``
-+  instead of range type ``ipa-local-subid`` for backwards compatibility
-+  with older SSSD clients, see
-+  [SSSD #5571](https://github.com/SSSD/sssd/issues/5571).
-+* Shared DNA configuration entries in ``cn=dna,cn=ipa,cn=etc,$SUFFIX``
-+  are automatically removed by existing code. Server and replication
-+  plug-ins search and delete entries by ``dnaHostname`` attribute.
-+
-+### TODO
-+
-+* enable configuration for ``dnaStepAttr``
-+* remove ``fake_dna_plugin`` hack from ``baseuser`` plug-in.
-+* add custom range type for idranges and teach AD trust, sidgen, and
-+  range overlap check code to deal with new range type.
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index ae4af099f39641a9f5163d61cfb37e1c3afb6f4b..044e3559975c399f6697d4da94b5a059eb5b407c 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -1361,6 +1361,7 @@ fi
- %{_libexecdir}/ipa/ipa-pki-wait-running
- %{_libexecdir}/ipa/ipa-otpd
- %{_libexecdir}/ipa/ipa-print-pac
-+%{_libexecdir}/ipa/ipa-subids
- %dir %{_libexecdir}/ipa/custodia
- %attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-dmldap
- %attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat
-diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif
-index f253f30c91350c1358b24986806efea7768ea9ce..952755309d13d7df1806a52af351df250185b16d 100644
---- a/install/share/60basev2.ldif
-+++ b/install/share/60basev2.ldif
-@@ -3,6 +3,7 @@
- ## Attributes:		2.16.840.1.113730.3.8.3 - V2 base attributres
- ## ObjectClasses:	2.16.840.1.113730.3.8.4 - V2 base objectclasses
- ## Attributes:		2.16.840.1.113730.3.8.23 - V4 base attributes
-+## ObjectClasses:	2.16.840.1.113730.3.8.24 - V4 base objectclasses
- ##
- dn: cn=schema
- attributeTypes: (2.16.840.1.113730.3.8.3.1 NAME 'ipaUniqueID' DESC 'Unique identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-diff --git a/install/share/60basev4.ldif b/install/share/60basev4.ldif
-new file mode 100644
-index 0000000000000000000000000000000000000000..7f5173e593ff68a03d4005957b1dc9b9eb489dc5
---- /dev/null
-+++ b/install/share/60basev4.ldif
-@@ -0,0 +1,19 @@
-+## IPA Base OID:	2.16.840.1.113730.3.8
-+##
-+## Attributes:		2.16.840.1.113730.3.8.23 - V4 base attributes
-+## ObjectClasses:	2.16.840.1.113730.3.8.24 - V4 base objectclasses
-+##
-+dn: cn=schema
-+# subordinate ids
-+# range ceiling OIDs are reserved for future use (operational attribute?)
-+# object class requires uidNumber but does not subclass posixAccount so we
-+# can re-use the object class in idview overrides later.
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.6 NAME 'ipaSubUidNumber' DESC 'Numerical subordinate user ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.7 NAME 'ipaSubUidCount' DESC 'Subordinate user ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+# attributeTypes: ( 2.16.840.1.113730.3.8.23.8 NAME 'ipaSubUidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.9 NAME 'ipaSubGidNumber' DESC 'Numerical subordinate group ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.10 NAME 'ipaSubGidCount' DESC 'Subordinate group ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+# attributeTypes: ( 2.16.840.1.113730.3.8.23.11 NAME 'ipaSubGidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subordinate uids for users, see subuid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount ) X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
-diff --git a/install/share/Makefile.am b/install/share/Makefile.am
-index 0f1a6975fc3394316769295e67ac3c2e05ee9cee..e0fe4b7d1756bd05f060a92ab52f910b4bd3adc8 100644
---- a/install/share/Makefile.am
-+++ b/install/share/Makefile.am
-@@ -16,6 +16,7 @@ dist_app_DATA =				\
- 	60ipaconfig.ldif		\
- 	60basev2.ldif			\
- 	60basev3.ldif			\
-+	60basev4.ldif			\
- 	60ipadns.ldif			\
- 	60ipapk11.ldif			\
- 	60certificate-profiles.ldif	\
-diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
-index 6a689798451e8cc072284065849f9a95635f8069..16f2ef822eaf56dd68d4140b22a607539645b151 100644
---- a/install/share/bootstrap-template.ldif
-+++ b/install/share/bootstrap-template.ldif
-@@ -167,6 +167,12 @@ objectClass: nsContainer
- objectClass: top
- cn: posix-ids
- 
-+dn: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
-+changetype: add
-+objectClass: nsContainer
-+objectClass: top
-+cn: subordinate-ids
-+
- dn: cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX
- changetype: add
- objectClass: nsContainer
-@@ -476,6 +482,22 @@ ipaBaseID: $IDSTART
- ipaIDRangeSize: $IDRANGE_SIZE
- ipaRangeType: ipa-local
- 
-+dn: cn=${REALM}_subid_range,cn=ranges,cn=etc,$SUFFIX
-+changetype: add
-+objectClass: top
-+objectClass: ipaIDrange
-+objectClass: ipaTrustedADDomainRange
-+cn: ${REALM}_subid_range
-+ipaBaseID: eval($SUBID_RANGE_START)
-+ipaIDRangeSize: eval($SUBID_RANGE_SIZE)
-+# HACK: RIDs to work around adtrust sidgen issue
-+ipaBaseRID: eval($SUBID_RANGE_START - $IDRANGE_SIZE)
-+# 738065-838566 = IPA-SUB
-+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
-+ipaRangeType: ipa-ad-trust
-+
- dn: cn=ca,$SUFFIX
- changetype: add
- objectClass: nsContainer
-diff --git a/install/share/dna.ldif b/install/share/dna.ldif
-index f4bff3691570eb1fe028b13b69d2cc175c7df174..649313e72fc58112865e5901125923b3704276b1 100644
---- a/install/share/dna.ldif
-+++ b/install/share/dna.ldif
-@@ -16,6 +16,26 @@ dnaThreshold: 500
- dnaSharedCfgDN: cn=posix-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
- dnaExcludeScope: cn=provisioning,$SUFFIX
- 
-+dn: cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
-+changetype: add
-+objectclass: top
-+objectclass: extensibleObject
-+cn: Subordinate IDs
-+dnaType: ipasubuidnumber
-+dnaType: ipasubgidnumber
-+dnaNextValue: eval($SUBID_RANGE_START)
-+dnaMaxValue: eval($SUBID_RANGE_MAX)
-+dnaMagicRegen: -1
-+dnaFilter: (objectClass=ipaSubordinateId)
-+dnaScope: $SUFFIX
-+dnaThreshold: eval($SUBID_DNA_THRESHOLD)
-+# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
-+# dnaStepAttr: ipaSubUidCount
-+# dnaStepAttr: ipaSubGidCount
-+# dnaStepAllowedValues: eval($SUBID_COUNT)
-+dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
-+dnaExcludeScope: cn=provisioning,$SUFFIX
-+
- # Enable the DNA plugin
- dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
- changetype: modify
-diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
-index d6fbf9e3bc84bc475d7a797ff663df40da0a0efa..5f36742957505f6d695097c8aab6c73f9d59e146 100644
---- a/install/tools/Makefile.am
-+++ b/install/tools/Makefile.am
-@@ -38,6 +38,7 @@ dist_noinst_DATA =		\
- 	ipa-pki-retrieve-key.in	\
- 	ipa-pki-wait-running.in	\
- 	ipa-acme-manage.in	\
-+	ipa-subids.in	\
- 	$(NULL)
- 
- nodist_sbin_SCRIPTS =		\
-@@ -78,6 +79,7 @@ nodist_app_SCRIPTS =		\
- 	ipa-httpd-pwdreader	\
- 	ipa-pki-retrieve-key	\
- 	ipa-pki-wait-running	\
-+	ipa-subids	\
- 	$(NULL)
- 
- PYTHON_SHEBANG = 					\
-diff --git a/install/tools/ipa-subids.in b/install/tools/ipa-subids.in
-new file mode 100644
-index 0000000000000000000000000000000000000000..5c7b9f8f788e3c230253e86151cff8234161909b
---- /dev/null
-+++ b/install/tools/ipa-subids.in
-@@ -0,0 +1,8 @@
-+#!/usr/bin/python3
-+#
-+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
-+#
-+
-+from ipaserver.install.ipa_subids import IPASubids
-+
-+IPASubids.run_cli()
-diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
-index a4eb390b7d9ca0fb8f50245cfedec27ca2607cdd..5b49b0f6edbbbb6c802afb803a6406a0ab796c44 100644
---- a/install/ui/src/freeipa/user.js
-+++ b/install/ui/src/freeipa/user.js
-@@ -259,6 +259,33 @@ return {
-                         }
-                     ]
-                 },
-+                {
-+                    name: 'subordinate',
-+                    label: '@i18n:objects.subordinate.identity',
-+                    fields: [
-+                        {
-+                            name: 'ipasubuidnumber',
-+                            label: '@i18n:objects.subordinate.subuidnumber',
-+                            read_only: true
-+                        },
-+                        {
-+                            name: 'ipasubuidcount',
-+                            label: '@i18n:objects.subordinate.subuidcount',
-+                            read_only: true
-+
-+                        },
-+                        {
-+                            name: 'ipasubgidnumber',
-+                            label: '@i18n:objects.subordinate.subgidnumber',
-+                            read_only: true
-+                        },
-+                        {
-+                            name: 'ipasubgidcount',
-+                            label: '@i18n:objects.subordinate.subgidcount',
-+                            read_only: true
-+                        }
-+                    ]
-+                },
-                 {
-                     name: 'pwpolicy',
-                     label: '@i18n:objects.pwpolicy.identity',
-@@ -451,6 +478,16 @@ return {
-                     enable_cond: ['is-locked'],
-                     confirm_msg: '@i18n:objects.user.unlock_confirm'
-                 },
-+                {
-+                    $factory: IPA.object_action,
-+                    name: 'auto_subid',
-+                    method: 'auto_subid',
-+                    label: '@i18n:objects.user.auto_subid',
-+                    needs_confirm: true,
-+                    hide_cond: ['preserved-user'],
-+                    enable_cond: ['no-subid'],
-+                    confirm_msg: '@i18n:objects.user.auto_subid_confirm'
-+                },
-                 {
-                     $type: 'automember_rebuild',
-                     name: 'automember_rebuild',
-@@ -461,12 +498,22 @@ return {
-                     $type: 'cert_request',
-                     hide_cond: ['preserved-user'],
-                     title: '@i18n:objects.cert.issue_for_user'
-+                },
-+                {
-+                    $factory: IPA.object_action,
-+                    name: 'auto_subid',
-+                    method: 'auto_subid',
-+                    label: '@i18n:objects.user.auto_subid',
-+                    needs_confirm: true,
-+                    hide_cond: ['preserved-user'],
-+                    enable_cond: ['no-subid'],
-+                    confirm_msg: '@i18n:objects.user.auto_subid_confirm'
-                 }
-             ],
-             header_actions: [
-                 'reset_password', 'enable', 'disable', 'stage', 'undel',
-                 'delete_active_user', 'delete', 'unlock', 'add_otptoken',
--                'automember_rebuild', 'request_cert'
-+                'automember_rebuild', 'request_cert', 'auto_subid'
-             ],
-             state: {
-                 evaluators: [
-@@ -1159,6 +1206,10 @@ IPA.user.is_locked_evaluator = function(spec) {
-             }
-         }
- 
-+        if (!user.ipasubuidnumber) {
-+            that.state.push('no-subid');
-+        }
-+
-         that.notify_on_change(old_state);
-     };
- 
-diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
-index 6632f105a98276d0d7e63ce249ade15501c3b673..7f83ab9f04c565a59efdd2f41c3e7ee30f5da9c7 100644
---- a/install/updates/20-indices.update
-+++ b/install/updates/20-indices.update
-@@ -272,6 +272,24 @@ add:nsIndexType: eq
- add:nsIndexType: pres
- add:nsIndexType: sub
- 
-+dn: cn=ipaSubGidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
-+only:cn: ipaSubGidNumber
-+default:objectClass: nsIndex
-+default:objectClass: top
-+default:nsSystemIndex: false
-+add:nsIndexType: eq
-+add:nsIndexType: pres
-+add:nsMatchingRule: integerOrderingMatch
-+
-+dn: cn=ipaSubUidNumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
-+only:cn: ipaSubUidNumber
-+default:objectClass: nsIndex
-+default:objectClass: top
-+default:nsSystemIndex: false
-+add:nsIndexType: eq
-+add:nsIndexType: pres
-+add:nsMatchingRule: integerOrderingMatch
-+
- dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
- only:cn: ipasudorunasgroup
- default:objectClass: nsIndex
-diff --git a/install/updates/73-subid.update b/install/updates/73-subid.update
-new file mode 100644
-index 0000000000000000000000000000000000000000..2aab3d445a33ae1663f81ca2d61b62ebc94aa37d
---- /dev/null
-+++ b/install/updates/73-subid.update
-@@ -0,0 +1,102 @@
-+# subordinate ids
-+
-+# self-service RBAC
-+dn: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
-+default:objectClass: groupofnames
-+default:objectClass: nestedgroup
-+default:objectClass: top
-+default:cn: Subordinate ID Selfservice User
-+default:description: User that can self-request subordiante ids
-+# default: member: cn=ipausers,cn=groups,cn=accounts,$SUFFIX
-+
-+dn: cn=Subordinate ID Selfservice Users,cn=privileges,cn=pbac,$SUFFIX
-+default:objectClass: top
-+default:objectClass: groupofnames
-+default:objectClass: nestedgroup
-+default:cn: Subordinate ID Selfservice Users
-+default:description: Subordinate ID Selfservice User
-+default:member: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
-+
-+dn: cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX
-+default:objectClass: top
-+default:objectClass: groupofnames
-+default:objectClass: ipapermission
-+default:cn: Self-service subordinate ID
-+default:ipapermissiontype: SYSTEM
-+default:member: cn=Subordinate ID Selfservice Users,cn=privileges,cn=pbac,$SUFFIX
-+
-+# Administrator RBAC
-+dn: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
-+default:objectClass: top
-+default:objectClass: groupofnames
-+default:objectClass: nestedgroup
-+default:cn: Subordinate ID Administrators
-+default:description: Subordinate ID Administrators
-+default:member: cn=User Administrator,cn=roles,cn=accounts,$SUFFIX
-+
-+dn: cn=Manage subordinate ID,cn=permissions,cn=pbac,$SUFFIX
-+default:objectClass: top
-+default:objectClass: groupofnames
-+default:objectClass: ipapermission
-+default:cn: Manage subordinate ID
-+default:ipapermissiontype: SYSTEM
-+default:member: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
-+
-+# ACIs (in domain database root so they also apply to staging area)
-+#
-+# - allow users to request new subid with DNA_MAGIC value, subid count=65536,
-+#   and subgid == subuid.
-+# - allow user admins to set subids. count=65536 and subgid == subuid
-+#   properties are enforced as wel.
-+#
-+# The delete-when-empty check is required because IPA uses MOD_REPLACE to
-+# set attributes, see https://github.com/389ds/389-ds-base/issues/4597.
-+#
-+# TODO: remove (ipasubuidnumber>=eval($SUBID_RANGE_START) from
-+# self-service permission when 389-DS' DNA plugin supports dnaStepAttr and
-+# fake_dna_plugin hack has been removed.
-+#
-+dn: $SUFFIX
-+add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(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 (write) userdn = "ldap:///self" and groupdn="ldap:///cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX";)
-+add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(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 (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)
-+# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
-+# default: dnaStepAttr: ipaSubUidCount
-+# default: dnaStepAttr: ipaSubGidCount
-+# default: dnaStepAllowedValues: eval($SUBID_COUNT)
-+default: dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
-+default: dnaExcludeScope: cn=provisioning,$SUFFIX
-+default: 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";)
-+default: 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_RANGE_START - $IDRANGE_SIZE)
-+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/Makefile.am b/install/updates/Makefile.am
-index 5741805a65a09c4c00ea47bf437c8821373d1e80..d4f6acba0dc83e4692edd10b8a7617915bd49e84 100644
---- a/install/updates/Makefile.am
-+++ b/install/updates/Makefile.am
-@@ -61,6 +61,7 @@ app_DATA =				\
- 	71-idviews-sasl-mapping.update  \
- 	72-domainlevels.update		\
- 	73-custodia.update		\
-+	73-subid.update		\
- 	73-winsync.update		\
- 	73-certmap.update		\
- 	75-user-trust-attributes.update	\
-diff --git a/ipalib/constants.py b/ipalib/constants.py
-index 79ea36f08cb0108a7434bc58cf0a764e9e15a7af..bee4c92fb39769d427e315116575f217924915be 100644
---- a/ipalib/constants.py
-+++ b/ipalib/constants.py
-@@ -343,3 +343,16 @@ SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC'
- # Apache's mod_ssl SSLVerifyDepth value (Maximum depth of CA
- # Certificates in Client Certificate verification)
- MOD_SSL_VERIFY_DEPTH = '5'
-+
-+# subuid / subgid counts are hard-coded
-+# An interval of 65536 uids/gids is required to map nobody (65534).
-+SUBID_COUNT = 65536
-+
-+# upper half of uid_t (uint32_t)
-+SUBID_RANGE_START = 2 ** 31
-+# theoretical max limit is UINT32_MAX-1 ((2 ** 32) - 2)
-+# We use a smaller value to keep the topmost subid interval unused.
-+SUBID_RANGE_MAX = (2 ** 32) - (2 * SUBID_COUNT)
-+SUBID_RANGE_SIZE = SUBID_RANGE_MAX - SUBID_RANGE_START
-+# threshold before DNA plugin requests a new range
-+SUBID_DNA_THRESHOLD = 500 * SUBID_COUNT
-diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
-index a7a403f37db13b7cccf74dff1b92b22529170b8a..24e90f3ecf5b4669f162e1bc68a33ef9d6094514 100644
---- a/ipaserver/install/adtrustinstance.py
-+++ b/ipaserver/install/adtrustinstance.py
-@@ -36,6 +36,7 @@ from ipaserver.install import service
- from ipaserver.install import installutils
- from ipaserver.install.replication import wait_for_task
- from ipalib import errors, api
-+from ipalib.constants import SUBID_RANGE_START
- from ipalib.util import normalize_zone
- from ipapython.dn import DN
- from ipapython import ipachangeconf
-@@ -352,12 +353,19 @@ class ADTRUSTInstance(service.Service):
-                 DN(api.env.container_ranges, self.suffix),
-                 ldap.SCOPE_ONELEVEL, "(objectclass=ipaDomainIDRange)")
- 
--            # Filter out ranges where RID base is already set
--            no_rid_base_set = lambda r: not any((
--                                  r.single_value.get('ipaBaseRID'),
--                                  r.single_value.get('ipaSecondaryBaseRID')))
-+            ranges_with_no_rid_base = []
-+            for entry in ranges:
-+                sv = entry.single_value
-+                if sv.get('ipaBaseRID') or sv.get('ipaSecondaryBaseRID'):
-+                    # skip range where RID base is already set
-+                    continue
-+                if sv.get('ipaRangeType') == 'ipa-local-subid':
-+                    # ignore subid ranges
-+                    continue
-+                ranges_with_no_rid_base.append(entry)
- 
--            ranges_with_no_rid_base = [r for r in ranges if no_rid_base_set(r)]
-+            logger.debug(repr(ranges))
-+            logger.debug(repr(ranges_with_no_rid_base))
- 
-             # Return if no range is without RID base
-             if len(ranges_with_no_rid_base) == 0:
-@@ -384,6 +392,17 @@ class ADTRUSTInstance(service.Service):
-                                "They have to differ at least by %d." % size)
-                 raise RuntimeError("RID bases too close.\n")
- 
-+            # values above
-+            if any(
-+                v + size >= SUBID_RANGE_START
-+                for v in (self.rid_base, self.secondary_rid_base)
-+            ):
-+                self.print_msg(
-+                    "Ceiling of primary or secondary base is larger than "
-+                    f"start of subordinate id range {SUBID_RANGE_START}."
-+                )
-+                raise RuntimeError("RID bases overlap with SUBID range.\n")
-+
-             # Modify the range
-             # If the RID bases would cause overlap with some other range,
-             # this will be detected by ipa-range-check DS plugin
-diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
-index 6033c04109f6278cb7b6015becd507b2b4699e02..ac9e131bb1b8c6ff8aff911cb257fbb03406d603 100644
---- a/ipaserver/install/dsinstance.py
-+++ b/ipaserver/install/dsinstance.py
-@@ -23,7 +23,6 @@ from __future__ import print_function, absolute_import
- import logging
- import shutil
- import os
--import time
- import tempfile
- import fnmatch
- 
-@@ -46,6 +45,7 @@ from ipaserver.install import certs
- from ipaserver.install import replication
- from ipaserver.install import sysupgrade
- from ipaserver.install import upgradeinstance
-+from ipaserver.install import ldapupdate
- from ipalib import api
- from ipalib import errors
- from ipalib import constants
-@@ -66,6 +66,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif",
-                     "60ipaconfig.ldif",
-                     "60basev2.ldif",
-                     "60basev3.ldif",
-+                    "60basev4.ldif",
-                     "60ipapk11.ldif",
-                     "60ipadns.ldif",
-                     "60certificate-profiles.ldif",
-@@ -214,6 +215,8 @@ class DsInstance(service.Service):
-         if realm_name:
-             self.suffix = ipautil.realm_to_suffix(self.realm)
-             self.serverid = ipaldap.realm_to_serverid(self.realm)
-+            if self.domain is None:
-+                self.domain = self.realm.lower()
-             self.__setup_sub_dict()
-         else:
-             self.suffix = DN()
-@@ -497,34 +500,22 @@ class DsInstance(service.Service):
- 
-     def __setup_sub_dict(self):
-         server_root = find_server_root()
--        try:
--            idrange_size = self.idmax - self.idstart + 1
--        except TypeError:
--            idrange_size = None
--        self.sub_dict = dict(
--            FQDN=self.fqdn, SERVERID=self.serverid,
-+        self.sub_dict = ldapupdate.get_sub_dict(
-+            realm=self.realm,
-+            domain=self.domain,
-+            suffix=self.suffix,
-+            fqdn=self.fqdn,
-+            idstart=self.idstart,
-+            idmax=self.idmax,
-+        )
-+        self.sub_dict.update(
-+            DOMAIN_LEVEL=self.domainlevel,
-+            SERVERID=self.serverid,
-             PASSWORD=self.dm_password,
-             RANDOM_PASSWORD=ipautil.ipa_generate_password(),
--            SUFFIX=self.suffix,
--            REALM=self.realm, USER=DS_USER,
--            SERVER_ROOT=server_root, DOMAIN=self.domain,
--            TIME=int(time.time()), IDSTART=self.idstart,
--            IDMAX=self.idmax, HOST=self.fqdn,
--            ESCAPED_SUFFIX=str(self.suffix),
-+            USER=DS_USER,
-             GROUP=DS_GROUP,
--            IDRANGE_SIZE=idrange_size,
--            DOMAIN_LEVEL=self.domainlevel,
--            MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
--            MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
--            STRIP_ATTRS=" ".join(replication.STRIP_ATTRS),
--            EXCLUDES='(objectclass=*) $ EXCLUDE ' +
--            ' '.join(replication.EXCLUDES),
--            TOTAL_EXCLUDES='(objectclass=*) $ EXCLUDE ' +
--            ' '.join(replication.TOTAL_EXCLUDES),
--            DEFAULT_SHELL=platformconstants.DEFAULT_SHELL,
--            DEFAULT_ADMIN_SHELL=platformconstants.DEFAULT_ADMIN_SHELL,
--            SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
--            SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
-+            SERVER_ROOT=server_root,
-         )
- 
-     def __create_instance(self):
-diff --git a/ipaserver/install/ipa_subids.py b/ipaserver/install/ipa_subids.py
-new file mode 100644
-index 0000000000000000000000000000000000000000..ac77a4008aec58d92c8b24df5e00b83c6998401f
---- /dev/null
-+++ b/ipaserver/install/ipa_subids.py
-@@ -0,0 +1,154 @@
-+#
-+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
-+#
-+
-+import logging
-+
-+from ipalib import api
-+from ipalib import errors
-+from ipalib.facts import is_ipa_configured
-+from ipaplatform.paths import paths
-+from ipapython.admintool import AdminTool, ScriptError
-+from ipapython.dn import DN
-+from ipaserver.plugins.baseldap import DNA_MAGIC
-+
-+logger = logging.getLogger(__name__)
-+
-+
-+class IPASubids(AdminTool):
-+    command_name = "ipa-subids"
-+    usage = "%prog [--group GROUP|--all-users]"
-+    description = "Mass-assign subordinate ids to users"
-+
-+    @classmethod
-+    def add_options(cls, parser):
-+        super(IPASubids, cls).add_options(parser, debug_option=True)
-+        parser.add_option(
-+            "--group",
-+            dest="group",
-+            action="store",
-+            default=None,
-+            help="Updates members of a user group.",
-+        )
-+        parser.add_option(
-+            "--all-users",
-+            dest="all_users",
-+            action="store_true",
-+            default=False,
-+            help="Update all users.",
-+        )
-+        parser.add_option(
-+            "--filter",
-+            dest="user_filter",
-+            action="store",
-+            default="(!(nsaccountlock=TRUE))",
-+            help="Additional raw LDAP filter (default: active users).",
-+        )
-+        parser.add_option(
-+            "--dry-run",
-+            dest="dry_run",
-+            action="store_true",
-+            default=False,
-+            help="Dry run mode.",
-+        )
-+
-+    def validate_options(self, neends_root=False):
-+        super().validate_options(needs_root=True)
-+        opt = self.safe_options
-+
-+        if opt.all_users and opt.group:
-+            raise ScriptError("--group and --all-users are mutually exclusive")
-+        if not opt.all_users and not opt.group:
-+            raise ScriptError("Either --group or --all-users required")
-+
-+    def get_group_info(self):
-+        assert api.isdone("finalize")
-+        group = self.safe_options.group
-+        if group is None:
-+            return None
-+        try:
-+            result = api.Command.group_show(group, no_members=True)
-+            return result["result"]
-+        except errors.NotFound:
-+            raise ScriptError(f"Unknown users group '{group}'.")
-+
-+    def make_filter(self, groupinfo, user_filter):
-+        filters = [
-+            # only users with posixAccount
-+            "(objectClass=posixAccount)",
-+            # without subordinate ids
-+            "(!(objectClass=ipaSubordinateId))",
-+        ]
-+        if groupinfo is not None:
-+            filters.append(
-+                self.ldap2.make_filter({"memberof": groupinfo["dn"]})
-+            )
-+        if user_filter:
-+            filters.append(user_filter)
-+        return self.ldap2.combine_filters(filters, self.ldap2.MATCH_ALL)
-+
-+    def search_users(self, filters):
-+        users_dn = DN(api.env.container_user, api.env.basedn)
-+        attrs = ["objectclass", "uid", "uidnumber"]
-+
-+        logger.debug("basedn: %s", users_dn)
-+        logger.debug("attrs: %s", attrs)
-+        logger.debug("filter: %s", filters)
-+
-+        try:
-+            entries = self.ldap2.get_entries(
-+                base_dn=users_dn,
-+                filter=filters,
-+                attrs_list=attrs,
-+            )
-+        except errors.NotFound:
-+            logger.debug("No entries found")
-+            return []
-+        else:
-+            return entries
-+
-+    def run(self):
-+        if not is_ipa_configured():
-+            print("IPA is not configured.")
-+            return 2
-+
-+        api.bootstrap(in_server=True, confdir=paths.ETC_IPA)
-+        api.finalize()
-+        api.Backend.ldap2.connect()
-+        self.ldap2 = api.Backend.ldap2
-+        user_obj = api.Object["user"]
-+
-+        dry_run = self.safe_options.dry_run
-+        group_info = self.get_group_info()
-+        filters = self.make_filter(
-+            group_info, self.safe_options.user_filter
-+        )
-+
-+        entries = self.search_users(filters)
-+        total = len(entries)
-+        logger.info("Found %i user(s) without subordinate ids", total)
-+
-+        total = len(entries)
-+        for i, entry in enumerate(entries, start=1):
-+            logger.info(
-+                "  Processing user '%s' (%i/%i)",
-+                entry.single_value["uid"],
-+                i,
-+                total
-+            )
-+            user_obj.set_subordinate_ids(
-+                self.ldap2, entry.dn, entry, DNA_MAGIC
-+            )
-+            if not dry_run:
-+                self.ldap2.update_entry(entry)
-+
-+        if dry_run:
-+            logger.info("Dry run mode, no user was modified")
-+        else:
-+            logger.info("Updated %s user(s)", total)
-+
-+        return 0
-+
-+
-+if __name__ == "__main__":
-+    IPASubids.run_cli()
-diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
-index f21e5a5af465be37541b9fbdddaf800b73f80b71..d0516dc3028366df5d03a960866abe72601aa4b6 100644
---- a/ipaserver/install/ldapupdate.py
-+++ b/ipaserver/install/ldapupdate.py
-@@ -32,9 +32,9 @@ import os
- import fnmatch
- import warnings
- 
-+from pysss_murmur import murmurhash3  # pylint: disable=no-name-in-module
- import six
- 
--from ipaserver.install import installutils
- from ipapython import ipautil, ipaldap
- from ipalib import errors
- from ipalib import api, create_api
-@@ -43,6 +43,7 @@ from ipaplatform.constants import constants as platformconstants
- from ipaplatform.paths import paths
- from ipaplatform.tasks import tasks
- from ipapython.dn import DN
-+from ipaserver.install import installutils, replication
- 
- if six.PY3:
-     unicode = str
-@@ -53,6 +54,54 @@ UPDATES_DIR=paths.UPDATES_DIR
- UPDATE_SEARCH_TIME_LIMIT = 30  # seconds
- 
- 
-+def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-+    """LDAP template substitution dict for installer and updater
-+    """
-+    if idstart is None:
-+        idrange_size = None
-+    else:
-+        idrange_size = idmax - idstart + 1
-+
-+    return dict(
-+        REALM=realm,
-+        DOMAIN=domain,
-+        SUFFIX=suffix,
-+        ESCAPED_SUFFIX=str(suffix),
-+        FQDN=fqdn,
-+        HOST=fqdn,
-+        LIBARCH=paths.LIBARCH,
-+        TIME=int(time.time()),
-+        FIPS="#" if tasks.is_fips_enabled() else "",
-+        # idstart, idmax, and idrange_size may be None
-+        IDSTART=idstart,
-+        IDMAX=idmax,
-+        IDRANGE_SIZE=idrange_size,
-+        SUBID_COUNT=constants.SUBID_COUNT,
-+        SUBID_RANGE_START=constants.SUBID_RANGE_START,
-+        SUBID_RANGE_SIZE=constants.SUBID_RANGE_SIZE,
-+        SUBID_RANGE_MAX=constants.SUBID_RANGE_MAX,
-+        SUBID_DNA_THRESHOLD=constants.SUBID_DNA_THRESHOLD,
-+        DOMAIN_HASH=murmurhash3(domain, len(domain), 0xdeadbeef),
-+        MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
-+        MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
-+        STRIP_ATTRS=" ".join(replication.STRIP_ATTRS),
-+        EXCLUDES=(
-+            '(objectclass=*) $ EXCLUDE ' + ' '.join(replication.EXCLUDES)
-+        ),
-+        TOTAL_EXCLUDES=(
-+            '(objectclass=*) $ EXCLUDE '
-+            + ' '.join(replication.TOTAL_EXCLUDES)
-+        ),
-+        DEFAULT_SHELL=platformconstants.DEFAULT_SHELL,
-+        DEFAULT_ADMIN_SHELL=platformconstants.DEFAULT_ADMIN_SHELL,
-+        SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
-+        SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
-+        # uid / gid for autobind
-+        NAMED_UID=platformconstants.NAMED_USER.uid,
-+        NAMED_GID=platformconstants.NAMED_GROUP.gid,
-+    )
-+
-+
- def connect(ldapi=False, realm=None, fqdn=None):
-     """Create a connection for updates"""
-     if ldapi:
-@@ -284,35 +333,33 @@ class LDAPUpdate:
-             ldap_uri=self.ldapuri
-         )
-         self.api.finalize()
--
-         self.create_connection()
- 
-+        # get ipa-local domain idrange settings
-+        domain_range = f"{self.api.env.realm}_id_range"
-+        try:
-+            result = self.api.Command.idrange_show(domain_range)["result"]
-+        except errors.NotFound:
-+            idstart = None
-+            idmax = None
-+        else:
-+            idstart = int(result['ipabaseid'][0])
-+            idrange_size = int(result['ipaidrangesize'][0])
-+            idmax = idstart + idrange_size - 1
-+
-+        default_sub = get_sub_dict(
-+            realm=api.env.realm,
-+            domain=api.env.domain,
-+            suffix=api.env.basedn,
-+            fqdn=api.env.host,
-+            idstart=idstart,
-+            idmax=idmax,
-+        )
-         replication_plugin = (
-             installutils.get_replication_plugin_name(self.conn.get_entry)
-         )
-+        default_sub["REPLICATION_PLUGIN"] = replication_plugin
- 
--        default_sub = dict(
--            REALM=api.env.realm,
--            DOMAIN=api.env.domain,
--            SUFFIX=api.env.basedn,
--            ESCAPED_SUFFIX=str(api.env.basedn),
--            FQDN=api.env.host,
--            LIBARCH=paths.LIBARCH,
--            TIME=int(time.time()),
--            MIN_DOMAIN_LEVEL=str(constants.MIN_DOMAIN_LEVEL),
--            MAX_DOMAIN_LEVEL=str(constants.MAX_DOMAIN_LEVEL),
--            STRIP_ATTRS=" ".join(constants.REPL_AGMT_STRIP_ATTRS),
--            EXCLUDES="(objectclass=*) $ EXCLUDE %s" % (
--                " ".join(constants.REPL_AGMT_EXCLUDES)
--            ),
--            TOTAL_EXCLUDES="(objectclass=*) $ EXCLUDE %s" % (
--                " ".join(constants.REPL_AGMT_TOTAL_EXCLUDES)
--            ),
--            SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
--            SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
--            FIPS="#" if tasks.is_fips_enabled() else "",
--            REPLICATION_PLUGIN=replication_plugin,
--        )
-         for k, v in default_sub.items():
-             self.sub_dict.setdefault(k, v)
- 
-diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
-index 6035228f19ef8acaf4992490d5512c126881816d..12ff03c2302ff08aabb9369306965e0c125724f8 100644
---- a/ipaserver/plugins/baseuser.py
-+++ b/ipaserver/plugins/baseuser.py
-@@ -17,9 +17,10 @@
- # You should have received a copy of the GNU General Public License
- # along with this program.  If not, see <http://www.gnu.org/licenses/>.
- 
-+import random
- import six
- 
--from ipalib import api, errors
-+from ipalib import api, errors, output, constants
- from ipalib import (
-     Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
- from ipalib.parameters import Principal, Certificate
-@@ -27,13 +28,13 @@ from ipalib.plugable import Registry
- from .baseldap import (
-     DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
-     LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute,
--    LDAPAddMember, LDAPRemoveMember,
-+    LDAPQuery, LDAPAddMember, LDAPRemoveMember,
-     LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
--    add_missing_object_class)
-+    add_missing_object_class, DNA_MAGIC, pkey_to_value, entry_to_dict
-+)
- from ipaserver.plugins.service import (validate_realm, normalize_principal)
- from ipalib.request import context
- from ipalib import _
--from ipalib.constants import PATTERN_GROUPUSER_NAME
- from ipapython import kerberos
- from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
- from ipapython.ipavalidate import Email
-@@ -161,7 +162,7 @@ class baseuser(LDAPObject):
-     possible_objectclasses = [
-         'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
-         'ipatokenradiusproxyuser', 'ipacertmapobject',
--        'ipantuserattrs'
-+        'ipantuserattrs', 'ipasubordinateid',
-     ]
-     disallow_object_classes = ['krbticketpolicyaux']
-     permission_filter_objectclasses = ['posixaccount']
-@@ -175,13 +176,15 @@ class baseuser(LDAPObject):
-         'krbprincipalexpiration', 'usercertificate;binary',
-         'krbprincipalname', 'krbcanonicalname',
-         'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath',
--        'ipanthomedirectory', 'ipanthomedirectorydrive'
-+        'ipanthomedirectory', 'ipanthomedirectorydrive',
-     ]
-     search_display_attributes = [
-         'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname',
-         'krbprincipalname', 'loginshell',
-         'mail', 'telephonenumber', 'title', 'nsaccountlock',
-         'uidnumber', 'gidnumber', 'sshpubkeyfp',
-+        'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
-+        'ipasubgidcount',
-     ]
-     uuid_attribute = 'ipauniqueid'
-     attribute_members = {
-@@ -198,7 +201,7 @@ class baseuser(LDAPObject):
- 
-     takes_params = (
-         Str('uid',
--            pattern=PATTERN_GROUPUSER_NAME,
-+            pattern=constants.PATTERN_GROUPUSER_NAME,
-             pattern_errmsg='may only include letters, numbers, _, -, . and $',
-             maxlength=255,
-             cli_name='login',
-@@ -429,6 +432,41 @@ class baseuser(LDAPObject):
-                     'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
-                     'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'),
-                 ),
-+        Int(
-+            'ipasubuidnumber?',
-+            label=_('SubUID range start'),
-+            cli_name='subuid',
-+            doc=_('Start value for subordinate user ID (subuid) range'),
-+            minvalue=constants.SUBID_RANGE_START,
-+            maxvalue=constants.SUBID_RANGE_MAX,
-+        ),
-+        Int(
-+            'ipasubuidcount?',
-+            label=_('SubUID range size'),
-+            cli_name='subuidcount',
-+            doc=_('Subordinate user ID count'),
-+            flags={'no_create', 'no_update', 'no_search'},
-+            minvalue=constants.SUBID_COUNT,
-+            maxvalue=constants.SUBID_COUNT,
-+        ),
-+        Int(
-+            'ipasubgidnumber?',
-+            label=_('SubGID range start'),
-+            cli_name='subgid',
-+            doc=_('Start value for subordinate group ID (subgid) range'),
-+            flags={'no_create', 'no_update'},
-+            minvalue=constants.SUBID_RANGE_START,
-+            maxvalue=constants.SUBID_RANGE_MAX,
-+        ),
-+        Int(
-+            'ipasubgidcount?',
-+            label=_('SubGID range size'),
-+            cli_name='subgidcount',
-+            doc=_('Subordinate group ID count'),
-+            flags={'no_create', 'no_update', 'no_search'},
-+            minvalue=constants.SUBID_COUNT,
-+            maxvalue=constants.SUBID_COUNT,
-+        ),
-     )
- 
-     def normalize_and_validate_email(self, email, config=None):
-@@ -526,6 +564,131 @@ class baseuser(LDAPObject):
-         except KeyError:
-             pass
- 
-+    def handle_subordinate_ids(self, ldap, dn, entry_attrs):
-+        """Handle ipaSubordinateId object class
-+        """
-+        obj_classes = entry_attrs.get("objectclass")
-+        new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
-+        new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
-+
-+        # entry has object class ipaSubordinateId
-+        # default to auto-assigment of subuids
-+        if (
-+            new_subuid is None
-+            and obj_classes is not None
-+            and self.has_objectclass(obj_classes, "ipasubordinateid")
-+        ):
-+            new_subuid = DNA_MAGIC
-+
-+        # neither auto-assignment nor explicit assignment
-+        if new_subuid is None:
-+            # nothing to do
-+            return False
-+
-+        # enforce subuid == subgid
-+        if new_subgid is not None and new_subgid != new_subuid:
-+            raise errors.ValidationError(
-+                name="ipasubgidnumber",
-+                error=_("subgidnumber must be equal to subuidnumber")
-+            )
-+
-+        self.set_subordinate_ids(ldap, dn, entry_attrs, new_subuid)
-+        return True
-+
-+    def set_subordinate_ids(self, ldap, dn, entry_attrs, subuid):
-+        """Set subuid value of an entry
-+
-+        Takes care of objectclass and sibbling attributes
-+        """
-+        if "objectclass" in entry_attrs:
-+            obj_classes = entry_attrs["objectclass"]
-+        else:
-+            _entry_attrs = ldap.get_entry(dn, ["objectclass"])
-+            entry_attrs["objectclass"] = _entry_attrs["objectclass"]
-+            obj_classes = entry_attrs["objectclass"]
-+
-+        if not self.has_objectclass(obj_classes, "ipasubordinateid"):
-+            # could append ipasubordinategid and ipasubordinateuid, too
-+            obj_classes.append("ipasubordinateid")
-+
-+        # XXX HACK, remove later
-+        if subuid == DNA_MAGIC:
-+            subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
-+
-+        entry_attrs["ipasubuidnumber"] = subuid
-+        # enforice subuid == subgid for now
-+        entry_attrs["ipasubgidnumber"] = subuid
-+        # hard-coded constants
-+        entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
-+        entry_attrs["ipasubgidcount"] = constants.SUBID_COUNT
-+
-+    def get_subid_match_candidate_filter(
-+        self, ldap, *, subuid, subgid, extra_filters=(), offset=None,
-+    ):
-+        """Create LDAP filter to locate matching/overlapping subids
-+        """
-+        if subuid is None and subgid is None:
-+            raise ValueError("subuid and subgid are both None")
-+        if offset is None:
-+            # assumes that no subordinate count is larger than SUBID_COUNT
-+            offset = constants.SUBID_COUNT - 1
-+
-+        class_filters = "(objectclass=ipasubordinateid)"
-+        subid_filters = []
-+        if subuid is not None:
-+            subid_filters.append(
-+                ldap.combine_filters(
-+                    [
-+                        f"(ipasubuidnumber>={subuid - offset})",
-+                        f"(ipasubuidnumber<={subuid + offset})",
-+                    ],
-+                    rules=ldap.MATCH_ALL
-+                )
-+            )
-+        if subgid is not None:
-+            subid_filters.append(
-+                ldap.combine_filters(
-+                    [
-+                        f"(ipasubgidnumber>={subgid - offset})",
-+                        f"(ipasubgidnumber<={subgid + offset})",
-+                    ],
-+                    rules=ldap.MATCH_ALL
-+                )
-+            )
-+
-+        subid_filters = ldap.combine_filters(
-+            subid_filters, rules=ldap.MATCH_ANY
-+        )
-+        filters = [class_filters, subid_filters]
-+        filters.extend(extra_filters)
-+        return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
-+
-+    def _fake_dna_plugin(self, ldap, dn, entry_attrs):
-+        """XXX HACK, remove when 389-DS DNA plugin supports steps"""
-+        uidnumber = entry_attrs.single_value.get("uidnumber")
-+        if uidnumber is None:
-+            entry = ldap.get_entry(dn, ["uidnumber"])
-+            uidnumber = entry.single_value["uidnumber"]
-+        uidnumber = int(uidnumber)
-+
-+        if uidnumber == DNA_MAGIC:
-+            return (
-+                3221225472
-+                + random.randint(1, 16382) * constants.SUBID_COUNT
-+            )
-+
-+        if not hasattr(context, "idrange_ipabaseid"):
-+            range_name = f"{self.api.env.realm}_id_range"
-+            range = self.api.Command.idrange_show(range_name)["result"]
-+            context.idrange_ipabaseid = int(range["ipabaseid"][0])
-+
-+        range_start = context.idrange_ipabaseid
-+
-+        assert uidnumber >= range_start
-+        assert uidnumber < range_start + 2**14
-+
-+        return (uidnumber - range_start) * constants.SUBID_COUNT + 2**31
-+
- 
- class baseuser_add(LDAPCreate):
-     """
-@@ -536,6 +699,7 @@ class baseuser_add(LDAPCreate):
-         assert isinstance(dn, DN)
-         set_krbcanonicalname(entry_attrs)
-         self.obj.convert_usercertificate_pre(entry_attrs)
-+        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
-         if entry_attrs.get('ipatokenradiususername', None):
-             add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
-                                      entry_attrs, update=False)
-@@ -688,6 +852,7 @@ class baseuser_mod(LDAPUpdate):
- 
-         self.check_objectclass(ldap, dn, entry_attrs)
-         self.obj.convert_usercertificate_pre(entry_attrs)
-+        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
-         self.preserve_krbprincipalname_pre(ldap, entry_attrs, *keys, **options)
-         update_samba_attrs(ldap, dn, entry_attrs, **options)
- 
-@@ -968,3 +1133,98 @@ class baseuser_remove_certmapdata(ModCertMapData,
-                                   LDAPRemoveAttribute):
-     __doc__ = _("Remove one or more certificate mappings from the user entry.")
-     msg_summary = _('Removed certificate mappings from user "%(value)s"')
-+
-+
-+class baseuser_auto_subid(LDAPQuery):
-+    __doc__ = _("Auto-assign subuid and subgid range to user entry")
-+
-+    has_output = output.standard_entry
-+
-+    def execute(self, cn, **options):
-+        ldap = self.obj.backend
-+        dn = self.obj.get_dn(cn)
-+
-+        try:
-+            entry_attrs = ldap.get_entry(
-+                dn, ["objectclass", "ipasubuidnumber"]
-+            )
-+        except errors.NotFound:
-+            raise self.obj.handle_not_found(cn)
-+
-+        if "ipasubuidnumber" in entry_attrs:
-+            raise errors.AlreadyContainsValueError(attr="ipasubuidnumber")
-+
-+        self.obj.set_subordinate_ids(ldap, dn, entry_attrs, subuid=DNA_MAGIC)
-+        ldap.update_entry(entry_attrs)
-+
-+        # fetch updated entry (use search display attribute to show subids)
-+        if options.get('all', False):
-+            attrs_list = ['*'] + self.obj.search_display_attributes
-+        else:
-+            attrs_list = set(self.obj.search_display_attributes)
-+            attrs_list.update(entry_attrs.keys())
-+            if options.get('no_members', False):
-+                attrs_list.difference_update(self.obj.attribute_members)
-+            attrs_list = list(attrs_list)
-+
-+        entry = self._exc_wrapper((cn,), options, ldap.get_entry)(
-+            dn, attrs_list
-+        )
-+        entry_attrs = entry_to_dict(entry, **options)
-+        entry_attrs['dn'] = dn
-+
-+        return dict(result=entry_attrs, value=pkey_to_value(cn, options))
-+
-+
-+class baseuser_match_subid(baseuser_find):
-+    __doc__ = _("Match users by any subordinate uid in their range")
-+
-+    _subid_attrs = {
-+        "ipasubuidnumber",
-+        "ipasubuidcount",
-+        "ipasubgidnumber",
-+        "ipasubgidcount"
-+    }
-+
-+    def get_options(self):
-+        base_options = {p.name for p in self.obj.takes_params}
-+        for option in super().get_options():
-+            if option.name == "ipasubuidnumber":
-+                yield option.clone(
-+                    label=_('SubUID match'),
-+                    doc=_('Match value for subordinate user ID'),
-+                    required=True,
-+                )
-+            elif option.name not in base_options:
-+                # raw, version
-+                yield option.clone()
-+
-+    def pre_callback(
-+        self, ldap, filters, attrs_list, base_dn, scope, *args, **options
-+    ):
-+        # search for candidates in range
-+        # Code assumes that no subordinate count is larger than SUBID_COUNT
-+        filters = self.obj.get_subid_match_candidate_filter(
-+            ldap, subuid=options["ipasubuidnumber"], subgid=None,
-+        )
-+        # always include subid attributes
-+        for missing in self._subid_attrs.difference(attrs_list):
-+            attrs_list.append(missing)
-+
-+        return filters, base_dn, scope
-+
-+    def post_callback(self, ldap, entries, truncated, *args, **options):
-+        # filter out mismatches manually
-+        osubuid = options["ipasubuidnumber"]
-+        new_entries = []
-+        for entry in entries:
-+            esubuid = int(entry.single_value["ipasubuidnumber"])
-+            esubcount = int(entry.single_value["ipasubuidcount"])
-+            minsubuid = esubuid
-+            maxsubuid = esubuid + esubcount - 1
-+            if minsubuid <= osubuid <= maxsubuid:
-+                new_entries.append(entry)
-+
-+        entries[:] = new_entries
-+
-+        return truncated
-diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
-index 32b9c0c2d01b616d76505fc06fa9b6e5e209b234..3e486b8e27cfb12f2e4732fc1ee113f25dfbac5b 100644
---- a/ipaserver/plugins/idrange.py
-+++ b/ipaserver/plugins/idrange.py
-@@ -205,6 +205,7 @@ class idrange(LDAPObject):
-     # The commented range types are planned but not yet supported
-     range_types = {
-         u'ipa-local': unicode(_('local domain range')),
-+        # u'ipa-local-subid': unicode(_('local domain subid range')),
-         # u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
-         u'ipa-ad-trust': unicode(_('Active Directory domain range')),
-         u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
-@@ -221,10 +222,14 @@ class idrange(LDAPObject):
-         Int('ipabaseid',
-             cli_name='base_id',
-             label=_("First Posix ID of the range"),
-+            minvalue=1,
-+            maxvalue=Int.MAX_UINT32
-         ),
-         Int('ipaidrangesize',
-             cli_name='range_size',
-             label=_("Number of IDs in the range"),
-+            minvalue=1,
-+            maxvalue=Int.MAX_UINT32
-         ),
-         Int('ipabaserid?',
-             cli_name='rid_base',
-@@ -669,7 +674,10 @@ class idrange_mod(LDAPUpdate):
-         except errors.NotFound:
-             raise self.obj.handle_not_found(*keys)
- 
--        if old_attrs['iparangetype'][0] == 'ipa-local':
-+        if (
-+            old_attrs['iparangetype'][0] in {'ipa-local', 'ipa-local-subid'}
-+            or old_attrs['cn'][0] == f'{self.api.env.realm}_subid_range'
-+        ):
-             raise errors.ExecutionError(
-                 message=_('This command can not be used to change ID '
-                           'allocation for local IPA domain. Run '
-diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
-index 70164eb8654d211523c98722a02b77ee13eb0009..199838b199eb4cdabf597bd34d571d05547fd32e 100644
---- a/ipaserver/plugins/internal.py
-+++ b/ipaserver/plugins/internal.py
-@@ -1547,6 +1547,13 @@ class i18n_messages(Command):
-                     "Drive to mount a home directory"
-                 ),
-             },
-+            "subordinate": {
-+                "identity": _("Subordinate user and group id"),
-+                "subuidnumber": _("Subordinate user id"),
-+                "subuidcount": _("Subordinate user id count"),
-+                "subgidnumber": _("Subordinate group id"),
-+                "subgidcount": _("Subordinate group id count"),
-+            },
-             "trustconfig": {
-                 "options": _("Options"),
-             },
-@@ -1570,6 +1577,11 @@ class i18n_messages(Command):
-                 "add_into_sudo": _(
-                     "Add user '${primary_key}' into sudo rules"
-                 ),
-+                "auto_subid": _("Auto assign subordinate ids"),
-+                "auto_subid_confirm": _(
-+                    "Are you sure you want to auto-assign a subordinate id "
-+                    "to user ${object}?"
-+                ),
-                 "contact": _("Contact Settings"),
-                 "delete_mode": _("Delete mode"),
-                 "employee": _("Employee Information"),
-diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py
-index e4ee572b236c288fd7dcf1d44c5adf1f836f63aa..f89b3ad5d9c994fe1ceb3da560fde7cc5bf5155a 100644
---- a/ipaserver/plugins/user.py
-+++ b/ipaserver/plugins/user.py
-@@ -50,7 +50,10 @@ from .baseuser import (
-     baseuser_add_principal,
-     baseuser_remove_principal,
-     baseuser_add_certmapdata,
--    baseuser_remove_certmapdata)
-+    baseuser_remove_certmapdata,
-+    baseuser_auto_subid,
-+    baseuser_match_subid,
-+)
- from .idviews import remove_ipaobject_overrides
- from ipalib.plugable import Registry
- from .baseldap import (
-@@ -202,6 +205,8 @@ class user(baseuser):
-             'ipapermright': {'read', 'search', 'compare'},
-             'ipapermdefaultattr': {
-                 'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
-+                'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
-+                'ipasubgidcount',
-             },
-             'fixup_function': fix_addressbook_permission_bindrule,
-         },
-@@ -1306,3 +1311,13 @@ class user_add_principal(baseuser_add_principal):
- class user_remove_principal(baseuser_remove_principal):
-     __doc__ = _('Remove principal alias from the user entry')
-     msg_summary = _('Removed aliases from user "%(value)s"')
-+
-+
-+@register()
-+class user_auto_subid(baseuser_auto_subid):
-+    __doc__ = baseuser_auto_subid.__doc__
-+
-+
-+@register()
-+class user_match_subid(baseuser_match_subid):
-+    __doc__ = baseuser_match_subid.__doc__
-diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
-index a66b56ad8f62a458e9cc240440e7d222c32c599f..6ddd155c9967fa248581a59c68dfe547a34be623 100644
---- a/ipatests/prci_definitions/gating.yaml
-+++ b/ipatests/prci_definitions/gating.yaml
-@@ -298,3 +298,15 @@ jobs:
-         template: *ci-ipa-4-9-latest
-         timeout: 3600
-         topology: *master_1repl
-+
-+  fedora-latest-ipa-4-9/test_subids:
-+    requires: [fedora-latest-ipa-4-9/build]
-+    priority: 100
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-9/build_url}'
-+        test_suite: test_integration/test_subids.py
-+        template: *ci-ipa-4-9-latest
-+        timeout: 3600
-+        topology: *master_1repl
-diff --git a/ipatests/test_integration/test_subids.py b/ipatests/test_integration/test_subids.py
-new file mode 100644
-index 0000000000000000000000000000000000000000..b462f22ac067f3e1e97ef3f6d63d4e14e4ae79af
---- /dev/null
-+++ b/ipatests/test_integration/test_subids.py
-@@ -0,0 +1,201 @@
-+#
-+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
-+#
-+
-+"""Tests for subordinate ids
-+"""
-+import os
-+
-+from ipalib.constants import SUBID_COUNT, SUBID_RANGE_START, SUBID_RANGE_MAX
-+from ipaplatform.paths import paths
-+from ipatests.pytest_ipa.integration import tasks
-+from ipatests.test_integration.base import IntegrationTest
-+
-+
-+class TestSubordinateId(IntegrationTest):
-+    num_replicas = 0
-+    topology = "star"
-+
-+    def _parse_result(self, result):
-+        info = {}
-+        for line in result.stdout_text.split("\n"):
-+            line = line.strip()
-+            if line:
-+                if ":" not in line:
-+                    continue
-+                k, v = line.split(":", 1)
-+                k = k.strip()
-+                v = v.strip()
-+                try:
-+                    v = int(v, 10)
-+                except ValueError:
-+                    if v == "FALSE":
-+                        v = False
-+                    elif v == "TRUE":
-+                        v = True
-+                info.setdefault(k.lower(), []).append(v)
-+
-+        for k, v in info.items():
-+            if len(v) == 1:
-+                info[k] = v[0]
-+            else:
-+                info[k] = set(v)
-+        return info
-+
-+    def get_user(self, uid):
-+        cmd = ["ipa", "user-show", "--all", "--raw", uid]
-+        result = self.master.run_command(cmd)
-+        return self._parse_result(result)
-+
-+    def user_auto_subid(self, uid, **kwargs):
-+        cmd = ["ipa", "user-auto-subid", uid]
-+        return self.master.run_command(cmd, **kwargs)
-+
-+    def test_auto_subid(self):
-+        tasks.kinit_admin(self.master)
-+        uid = "testuser_auto1"
-+        tasks.user_add(self.master, uid)
-+        info = self.get_user(uid)
-+        assert "ipasubuidcount" not in info
-+
-+        self.user_auto_subid(uid)
-+        info = self.get_user(uid)
-+        assert "ipasubuidcount" in info
-+
-+        subuid = info["ipasubuidnumber"]
-+        result = self.master.run_command(
-+            ["ipa", "user-match-subid", f"--subuid={subuid}", "--raw"]
-+        )
-+        match = self._parse_result(result)
-+        assert match["uid"] == uid
-+        assert match["ipasubuidnumber"] == info["ipasubuidnumber"]
-+        assert match["ipasubuidnumber"] >= SUBID_RANGE_START
-+        assert match["ipasubuidnumber"] <= SUBID_RANGE_MAX
-+        assert match["ipasubuidcount"] == SUBID_COUNT
-+        assert match["ipasubgidnumber"] == info["ipasubgidnumber"]
-+        assert match["ipasubgidnumber"] == match["ipasubuidnumber"]
-+        assert match["ipasubgidcount"] == SUBID_COUNT
-+
-+    def test_ipa_subid_script(self):
-+        tasks.kinit_admin(self.master)
-+
-+        tool = os.path.join(paths.LIBEXEC_IPA_DIR, "ipa-subids")
-+        users = []
-+        for i in range(1, 11):
-+            uid = f"testuser_script{i}"
-+            users.append(uid)
-+            tasks.user_add(self.master, uid)
-+            info = self.get_user(uid)
-+            assert "ipasubuidcount" not in info
-+
-+        cmd = [tool, "--verbose", "--group", "ipausers"]
-+        self.master.run_command(cmd)
-+
-+        for uid in users:
-+            info = self.get_user(uid)
-+            assert info["ipasubuidnumber"] >= SUBID_RANGE_START
-+            assert info["ipasubuidnumber"] <= SUBID_RANGE_MAX
-+            assert info["ipasubuidnumber"] == info["ipasubgidnumber"]
-+            assert info["ipasubuidcount"] == SUBID_COUNT
-+            assert info["ipasubuidcount"] == info["ipasubgidcount"]
-+
-+    def test_subid_selfservice(self):
-+        tasks.kinit_admin(self.master)
-+
-+        uid = "testuser_selfservice1"
-+        password = "Secret123"
-+        role = "Subordinate ID Selfservice User"
-+
-+        tasks.user_add(self.master, uid, password=password)
-+        tasks.kinit_user(
-+            self.master, uid, f"{password}\n{password}\n{password}\n"
-+        )
-+        info = self.get_user(uid)
-+        assert "ipasubuidcount" not in info
-+        result = self.user_auto_subid(uid, raiseonerr=False)
-+        assert result.returncode > 0
-+
-+        tasks.kinit_admin(self.master)
-+        self.master.run_command(
-+            ["ipa", "role-add-member", role, "--groups=ipausers"]
-+        )
-+
-+        try:
-+            tasks.kinit_user(self.master, uid, password)
-+            self.user_auto_subid(uid)
-+            info = self.get_user(uid)
-+            assert "ipasubuidcount" in info
-+        finally:
-+            tasks.kinit_admin(self.master)
-+            self.master.run_command(
-+                ["ipa", "role-remove-member", role, "--groups=ipausers"]
-+            )
-+
-+    def test_subid_useradmin(self):
-+        tasks.kinit_admin(self.master)
-+
-+        uid_useradmin = "testuser_usermgr_mgr1"
-+        role = "User Administrator"
-+        uid = "testuser_usermgr_user1"
-+        password = "Secret123"
-+
-+        # create user administrator
-+        tasks.user_add(self.master, uid_useradmin, password=password)
-+        # add user to user admin group
-+        tasks.kinit_admin(self.master)
-+        self.master.run_command(
-+            ["ipa", "role-add-member", role, f"--users={uid_useradmin}"],
-+        )
-+        # kinit as user admin
-+        tasks.kinit_user(
-+            self.master,
-+            uid_useradmin,
-+            f"{password}\n{password}\n{password}\n",
-+        )
-+        # create new user as user admin
-+        tasks.user_add(self.master, uid)
-+        # assign new subid to user (with useradmin credentials)
-+        self.user_auto_subid(uid)
-+
-+    def test_subordinate_default_objclass(self):
-+        tasks.kinit_admin(self.master)
-+
-+        result = self.master.run_command(
-+            ["ipa", "config-show", "--raw", "--all"]
-+        )
-+        info = self._parse_result(result)
-+        usercls = info["ipauserobjectclasses"]
-+        assert "ipasubordinateid" not in usercls
-+
-+        cmd = [
-+            "ipa",
-+            "config-mod",
-+            "--addattr",
-+            "ipaUserObjectClasses=ipasubordinateid",
-+        ]
-+        self.master.run_command(cmd)
-+
-+        uid = "testuser_usercls1"
-+        tasks.user_add(self.master, uid)
-+        info = self.get_user(uid)
-+        assert "ipasubuidcount" in info
-+
-+    def test_idrange_subid(self):
-+        tasks.kinit_admin(self.master)
-+
-+        range_name = f"{self.master.domain.realm}_subid_range"
-+
-+        result = self.master.run_command(
-+            ["ipa", "idrange-show", range_name, "--raw"]
-+        )
-+        info = self._parse_result(result)
-+
-+        # see https://github.com/SSSD/sssd/issues/5571
-+        assert info["iparangetype"] == "ipa-ad-trust"
-+        assert info["ipabaseid"] == SUBID_RANGE_START
-+        assert info["ipaidrangesize"] == SUBID_RANGE_MAX - SUBID_RANGE_START
-+        assert info["ipabaserid"] < SUBID_RANGE_START
-+        assert "ipasecondarybaserid" not in info
-+        assert info["ipanttrusteddomainsid"].startswith(
-+            "S-1-5-21-738065-838566-"
-+        )
-diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py
-index c756bb7941d6c2acae89d44d6c89abc6b80ef5f7..ef683f84e97cbba61972f580e84e3587fda8c63a 100644
---- a/ipatests/test_xmlrpc/test_range_plugin.py
-+++ b/ipatests/test_xmlrpc/test_range_plugin.py
-@@ -24,6 +24,7 @@ Test the `ipaserver/plugins/idrange.py` module, and XML-RPC in general.
- import six
- 
- from ipalib import api, errors, messages
-+from ipalib import constants
- from ipaplatform import services
- from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid
- from ipatests.test_xmlrpc import objectclasses
-@@ -46,6 +47,12 @@ rid_shift = 0
- for idrange in api.Command['idrange_find']()['result']:
-     size = int(idrange['ipaidrangesize'][0])
-     base_id = int(idrange['ipabaseid'][0])
-+    rtype = idrange['iparangetype'][0]
-+
-+    if rtype == 'ipa-local-subid' or base_id == constants.SUBID_RANGE_START:
-+        # ignore subordinate id range. It would push values beyond uint32_t.
-+        # There is plenty of space below SUBUID_RANGE_START.
-+        continue
- 
-     id_end = base_id + size
-     rid_end = 0
--- 
-2.26.3
-
diff --git a/SOURCES/0009-Redesign-subid-feature.patch b/SOURCES/0009-Redesign-subid-feature.patch
deleted file mode 100644
index da0b878..0000000
--- a/SOURCES/0009-Redesign-subid-feature.patch
+++ /dev/null
@@ -1,2906 +0,0 @@
-From f6fd0abeaa64d927f2d993235e97ac3009e64f2c Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Wed, 14 Apr 2021 15:21:18 +0200
-Subject: [PATCH] Redesign subid feature
-
-Subordinate ids are now handled by a new plugin class and stored in
-separate entries in the cn=subids,cn=accounts subtree.
-
-Signed-off-by: Christian Heimes <cheimes@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ACI.txt                                       |  12 +-
- API.txt                                       | 153 +++--
- doc/designs/subordinate-ids.md                | 358 ++++++++---
- install/share/60basev4.ldif                   |  25 +-
- install/share/60ipaconfig.ldif                |   5 +-
- install/share/dna.ldif                        |   8 +-
- install/share/memberof-conf.ldif              |   4 +-
- install/ui/src/freeipa/app.js                 |   1 +
- .../ui/src/freeipa/navigation/menu_spec.js    |   3 +-
- install/ui/src/freeipa/serverconfig.js        |   4 +
- install/ui/src/freeipa/subid.js               |  92 +++
- install/ui/src/freeipa/user.js                | 108 ++--
- install/updates/10-uniqueness.update          |  19 +
- install/updates/20-indices.update             |  12 +
- install/updates/25-referint.update            |   1 +
- install/updates/73-subid.update               |  28 +-
- ipalib/constants.py                           |   6 +-
- ipaserver/install/ipa_subids.py               |  18 +-
- .../plugins/update_dna_shared_config.py       |   2 +-
- ipaserver/plugins/baseldap.py                 |  10 +-
- ipaserver/plugins/baseuser.py                 | 272 +-------
- ipaserver/plugins/config.py                   |   8 +-
- ipaserver/plugins/internal.py                 |   2 +-
- ipaserver/plugins/subid.py                    | 608 ++++++++++++++++++
- ipaserver/plugins/user.py                     |  39 +-
- ipatests/test_integration/test_subids.py      | 182 +++---
- 26 files changed, 1389 insertions(+), 591 deletions(-)
- create mode 100644 install/ui/src/freeipa/subid.js
- create mode 100644 ipaserver/plugins/subid.py
-
-diff --git a/ACI.txt b/ACI.txt
-index fce02a333b212de9b61f920515eed3e356b1391b..e985461cd1c10cc98d1080daa81cfd90e2433dbb 100644
---- a/ACI.txt
-+++ b/ACI.txt
-@@ -61,7 +61,7 @@ aci: (targetattr = "cn || description || ipacertprofilestoreissued")(targetfilte
- dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
- aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacertprofilestoreissued || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Read Certificate Profiles";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=ipaconfig,cn=etc,dc=ipa,dc=example
--aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipadomainresolutionorder || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxhostnamelength || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";)
-+aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipadomainresolutionorder || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxhostnamelength || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserdefaultsubordinateid || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
- aci: (targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Add Group Password Policy costemplate";allow (add) groupdn = "ldap:///cn=System: Add Group Password Policy costemplate,cn=permissions,cn=pbac,dc=ipa,dc=example";)
- dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
-@@ -318,6 +318,14 @@ dn: cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example
- aci: (targetattr = "krblastpwdchange || krbpasswordexpiration || krbprincipalkey || userpassword")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Reset Preserved User password";allow (read,search,write) groupdn = "ldap:///cn=System: Reset Preserved User password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
- dn: dc=ipa,dc=example
- aci: (target_to = "ldap:///cn=users,cn=accounts,dc=ipa,dc=example")(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Undelete User";allow (moddn) groupdn = "ldap:///cn=System: Undelete User,cn=permissions,cn=pbac,dc=ipa,dc=example";)
-+dn: cn=subids,cn=accounts,dc=ipa,dc=example
-+aci: (targetattr = "description || ipaowner")(targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Manage Subordinate Ids";allow (write) groupdn = "ldap:///cn=System: Manage Subordinate Ids,cn=permissions,cn=pbac,dc=ipa,dc=example";)
-+dn: cn=subids,cn=accounts,dc=ipa,dc=example
-+aci: (targetattr = "createtimestamp || description || entryusn || ipaowner || ipasubgidcount || ipasubgidnumber || ipasubuidcount || ipasubuidnumber || ipauniqueid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Read Subordinate Id Attributes";allow (compare,read,search) userdn = "ldap:///all";)
-+dn: cn=subids,cn=accounts,dc=ipa,dc=example
-+aci: (targetattr = "numsubordinates")(target = "ldap:///cn=subids,cn=accounts,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Subordinate Id Count";allow (compare,read,search) userdn = "ldap:///all";)
-+dn: cn=subids,cn=accounts,dc=ipa,dc=example
-+aci: (targetfilter = "(objectclass=ipasubordinateidentry)")(version 3.0;acl "permission:System: Remove Subordinate Ids";allow (delete) groupdn = "ldap:///cn=System: Remove Subordinate Ids,cn=permissions,cn=pbac,dc=ipa,dc=example";)
- dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
- aci: (targetfilter = "(objectclass=ipasudocmd)")(version 3.0;acl "permission:System: Add Sudo Command";allow (add) groupdn = "ldap:///cn=System: Add Sudo Command,cn=permissions,cn=pbac,dc=ipa,dc=example";)
- dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
-@@ -375,7 +383,7 @@ aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber
- dn: dc=ipa,dc=example
- aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
--aci: (targetattr = "ipasshpubkey || ipasubgidcount || ipasubgidnumber || ipasubuidcount || ipasubuidnumber || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
-+aci: (targetattr = "ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
- aci: (targetattr = "krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || krbprincipaltype || nsaccountlock")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Kerberos Attributes";allow (compare,read,search) userdn = "ldap:///all";)
- dn: cn=users,cn=accounts,dc=ipa,dc=example
-diff --git a/API.txt b/API.txt
-index 262b4d6a72c7d7032a7027116f7a4f65aa620615..6c80028bfe8e9b739637fa11e015441efbf984b5 100644
---- a/API.txt
-+++ b/API.txt
-@@ -1076,7 +1076,7 @@ args: 0,1,1
- option: Str('version?')
- output: Output('result')
- command: config_mod/1
--args: 0,28,3
-+args: 0,29,3
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
- option: Str('ca_renewal_master_server?', autofill=False)
-@@ -1099,6 +1099,7 @@ option: Int('ipasearchtimelimit?', autofill=False, cli_name='searchtimelimit')
- option: Str('ipaselinuxusermapdefault?', autofill=False)
- option: Str('ipaselinuxusermaporder?', autofill=False)
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened', u'disabled'])
-+option: Bool('ipauserdefaultsubordinateid?', autofill=False, cli_name='user_default_subid')
- option: Str('ipauserobjectclasses*', autofill=False, cli_name='userobjectclasses')
- option: IA5Str('ipausersearchfields?', autofill=False, cli_name='usersearch')
- option: Flag('raw', autofill=True, cli_name='raw', default=False)
-@@ -4974,7 +4975,7 @@ output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
- command: stageuser_add/1
--args: 1,46,3
-+args: 1,45,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -4992,7 +4993,6 @@ option: Str('givenname', cli_name='first')
- option: Str('homedirectory?', cli_name='homedir')
- option: Str('initials?', autofill=True)
- option: Str('ipasshpubkey*', cli_name='sshpubkey')
--option: Int('ipasubuidnumber?', cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', cli_name='radius')
- option: Str('ipatokenradiususername?', cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -5099,14 +5099,13 @@ option: Str('in_group*', cli_name='in_groups')
- option: Str('in_hbacrule*', cli_name='in_hbacrules')
- option: Str('in_netgroup*', cli_name='in_netgroups')
- option: Str('in_role*', cli_name='in_roles')
-+option: Str('in_subid*', cli_name='in_subids')
- option: Str('in_sudorule*', cli_name='in_sudorules')
- option: Str('initials?', autofill=False)
- option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
- option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
--option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
--option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -5123,6 +5122,7 @@ option: Str('not_in_group*', cli_name='not_in_groups')
- option: Str('not_in_hbacrule*', cli_name='not_in_hbacrules')
- option: Str('not_in_netgroup*', cli_name='not_in_netgroups')
- option: Str('not_in_role*', cli_name='not_in_roles')
-+option: Str('not_in_subid*', cli_name='not_in_subids')
- option: Str('not_in_sudorule*', cli_name='not_in_sudorules')
- option: Str('ou?', autofill=False, cli_name='orgunit')
- option: Str('pager*', autofill=False)
-@@ -5148,7 +5148,7 @@ output: ListOfEntries('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: Output('truncated', type=[<type 'bool'>])
- command: stageuser_mod/1
--args: 1,52,3
-+args: 1,51,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -5170,7 +5170,6 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
- option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
--option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -5263,6 +5262,100 @@ option: Str('version?')
- output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
-+command: subid_add/1
-+args: 1,8,3
-+arg: Str('ipauniqueid?', cli_name='id')
-+option: Str('addattr*', cli_name='addattr')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Str('description?', cli_name='desc')
-+option: Str('ipaowner', cli_name='owner')
-+option: Int('ipasubuidnumber?', cli_name='subuid')
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Str('setattr*', cli_name='setattr')
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: PrimaryKey('value')
-+command: subid_del/1
-+args: 1,2,3
-+arg: Str('ipauniqueid+', cli_name='id')
-+option: Flag('continue', autofill=True, cli_name='continue', default=False)
-+option: Str('version?')
-+output: Output('result', type=[<type 'dict'>])
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: ListOfPrimaryKeys('value')
-+command: subid_find/1
-+args: 1,11,4
-+arg: Str('criteria?')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Str('description?', autofill=False, cli_name='desc')
-+option: Str('ipaowner?', autofill=False, cli_name='owner')
-+option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
-+option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
-+option: Str('ipauniqueid?', autofill=False, cli_name='id')
-+option: Flag('pkey_only?', autofill=True, default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Int('sizelimit?', autofill=False)
-+option: Int('timelimit?', autofill=False)
-+option: Str('version?')
-+output: Output('count', type=[<type 'int'>])
-+output: ListOfEntries('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: Output('truncated', type=[<type 'bool'>])
-+command: subid_generate/1
-+args: 0,4,3
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Str('ipaowner?', cli_name='owner')
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: PrimaryKey('value')
-+command: subid_match/1
-+args: 1,7,4
-+arg: Str('criteria?')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Int('ipasubuidnumber', autofill=False, cli_name='subuid')
-+option: Flag('pkey_only?', autofill=True, default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Int('sizelimit?', autofill=False)
-+option: Int('timelimit?', autofill=False)
-+option: Str('version?')
-+output: Output('count', type=[<type 'int'>])
-+output: ListOfEntries('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: Output('truncated', type=[<type 'bool'>])
-+command: subid_mod/1
-+args: 1,8,3
-+arg: Str('ipauniqueid', cli_name='id')
-+option: Str('addattr*', cli_name='addattr')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Str('delattr*', cli_name='delattr')
-+option: Str('description?', autofill=False, cli_name='desc')
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Flag('rights', autofill=True, default=False)
-+option: Str('setattr*', cli_name='setattr')
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: PrimaryKey('value')
-+command: subid_show/1
-+args: 1,4,3
-+arg: Str('ipauniqueid', cli_name='id')
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Flag('rights', autofill=True, default=False)
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-+output: PrimaryKey('value')
-+command: subid_stats/1
-+args: 0,3,2
-+option: Flag('all', autofill=True, cli_name='all', default=False)
-+option: Flag('raw', autofill=True, cli_name='raw', default=False)
-+option: Str('version?')
-+output: Entry('result')
-+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- command: sudocmd_add/1
- args: 1,7,3
- arg: Str('sudocmd', cli_name='command')
-@@ -6062,7 +6155,7 @@ output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
- command: user_add/1
--args: 1,47,3
-+args: 1,46,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -6079,7 +6172,6 @@ option: Str('givenname', cli_name='first')
- option: Str('homedirectory?', cli_name='homedir')
- option: Str('initials?', autofill=True)
- option: Str('ipasshpubkey*', cli_name='sshpubkey')
--option: Int('ipasubuidnumber?', cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', cli_name='radius')
- option: Str('ipatokenradiususername?', cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -6161,16 +6253,6 @@ option: Str('version?')
- output: Entry('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: PrimaryKey('value')
--command: user_auto_subid/1
--args: 1,4,3
--arg: Str('uid', cli_name='login')
--option: Flag('all', autofill=True, cli_name='all', default=False)
--option: Flag('no_members', autofill=True, default=False)
--option: Flag('raw', autofill=True, cli_name='raw', default=False)
--option: Str('version?')
--output: Entry('result')
--output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
--output: PrimaryKey('value')
- command: user_del/1
- args: 1,3,3
- arg: Str('uid+', cli_name='login')
-@@ -6213,14 +6295,13 @@ option: Str('in_group*', cli_name='in_groups')
- option: Str('in_hbacrule*', cli_name='in_hbacrules')
- option: Str('in_netgroup*', cli_name='in_netgroups')
- option: Str('in_role*', cli_name='in_roles')
-+option: Str('in_subid*', cli_name='in_subids')
- option: Str('in_sudorule*', cli_name='in_sudorules')
- option: Str('initials?', autofill=False)
- option: Str('ipanthomedirectory?', autofill=False, cli_name='smb_home_dir')
- option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_drive', values=[u'A:', u'B:', u'C:', u'D:', u'E:', u'F:', u'G:', u'H:', u'I:', u'J:', u'K:', u'L:', u'M:', u'N:', u'O:', u'P:', u'Q:', u'R:', u'S:', u'T:', u'U:', u'V:', u'W:', u'X:', u'Y:', u'Z:'])
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
--option: Int('ipasubgidnumber?', autofill=False, cli_name='subgid')
--option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -6237,6 +6318,7 @@ option: Str('not_in_group*', cli_name='not_in_groups')
- option: Str('not_in_hbacrule*', cli_name='not_in_hbacrules')
- option: Str('not_in_netgroup*', cli_name='not_in_netgroups')
- option: Str('not_in_role*', cli_name='not_in_roles')
-+option: Str('not_in_subid*', cli_name='not_in_subids')
- option: Str('not_in_sudorule*', cli_name='not_in_sudorules')
- option: Bool('nsaccountlock?', autofill=False, cli_name='disabled', default=False)
- option: Str('ou?', autofill=False, cli_name='orgunit')
-@@ -6264,23 +6346,8 @@ output: Output('count', type=[<type 'int'>])
- output: ListOfEntries('result')
- output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
- output: Output('truncated', type=[<type 'bool'>])
--command: user_match_subid/1
--args: 1,8,4
--arg: Str('criteria?')
--option: Flag('all', autofill=True, cli_name='all', default=False)
--option: Int('ipasubuidnumber', autofill=False, cli_name='subuid')
--option: Flag('no_members', autofill=True, default=True)
--option: Flag('pkey_only?', autofill=True, default=False)
--option: Flag('raw', autofill=True, cli_name='raw', default=False)
--option: Int('sizelimit?', autofill=False)
--option: Int('timelimit?', autofill=False)
--option: Str('version?')
--output: Output('count', type=[<type 'int'>])
--output: ListOfEntries('result')
--output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
--output: Output('truncated', type=[<type 'bool'>])
- command: user_mod/1
--args: 1,53,3
-+args: 1,52,3
- arg: Str('uid', cli_name='login')
- option: Str('addattr*', cli_name='addattr')
- option: Flag('all', autofill=True, cli_name='all', default=False)
-@@ -6302,7 +6369,6 @@ option: StrEnum('ipanthomedirectorydrive?', autofill=False, cli_name='smb_home_d
- option: Str('ipantlogonscript?', autofill=False, cli_name='smb_logon_script')
- option: Str('ipantprofilepath?', autofill=False, cli_name='smb_profile_path')
- option: Str('ipasshpubkey*', autofill=False, cli_name='sshpubkey')
--option: Int('ipasubuidnumber?', autofill=False, cli_name='subuid')
- option: Str('ipatokenradiusconfiglink?', autofill=False, cli_name='radius')
- option: Str('ipatokenradiususername?', autofill=False, cli_name='radius_username')
- option: StrEnum('ipauserauthtype*', autofill=False, cli_name='user_auth_type', values=[u'password', u'radius', u'otp', u'pkinit', u'hardened'])
-@@ -7138,6 +7204,15 @@ default: stageuser_remove_certmapdata/1
- default: stageuser_remove_manager/1
- default: stageuser_remove_principal/1
- default: stageuser_show/1
-+default: subid/1
-+default: subid_add/1
-+default: subid_del/1
-+default: subid_find/1
-+default: subid_generate/1
-+default: subid_match/1
-+default: subid_mod/1
-+default: subid_show/1
-+default: subid_stats/1
- default: sudocmd/1
- default: sudocmd_add/1
- default: sudocmd_del/1
-@@ -7216,12 +7291,10 @@ default: user_add_cert/1
- default: user_add_certmapdata/1
- default: user_add_manager/1
- default: user_add_principal/1
--default: user_auto_subid/1
- default: user_del/1
- default: user_disable/1
- default: user_enable/1
- default: user_find/1
--default: user_match_subid/1
- default: user_mod/1
- default: user_remove_cert/1
- default: user_remove_certmapdata/1
-diff --git a/doc/designs/subordinate-ids.md b/doc/designs/subordinate-ids.md
-index 1b578667a8cfdda223af38a14d142c72a5d5c073..b3be3bcfa275e836e777f807d5210a4db6be0f79 100644
---- a/doc/designs/subordinate-ids.md
-+++ b/doc/designs/subordinate-ids.md
-@@ -1,5 +1,9 @@
- # Central management of subordinate user and group ids
- 
-+## OUTDATED
-+
-+**The design document does not reflect new implementation yet!**
-+
- Subordinate ids are a Linux Kernel feature to grant a user additional
- user and group id ranges. Amongst others the feature can be used
- by container runtime engies to implement rootless containers.
-@@ -74,8 +78,10 @@ basic use cases. Some restrictions may be lifted in the future.
-   to the same value.
- * counts are hard-coded to value 65536
- * once assigned subids cannot be removed
--* IPA does not support multiple subordinate id ranges. Contrary to
--  ``/etc/subuid``, users are limited to one set of subordinate ids.
-+* IPA does not support multiple subordinate id ranges, yet. Contrary to
-+  ``/etc/subuid``, users are limited to one set of subordinate ids. The
-+  limitation is implemented with a unique index on owner reference and
-+  can be lifted in the future.
- * subids are auto-assigned. Auto-assignment is currently emulated
-   until 389-DS has been extended to support DNA with step interval.
- * subids are allocated from hard-coded range
-@@ -118,20 +124,21 @@ to servers. The DNA plug-in guarantees uniqueness across servers.
- ### LDAP schema extension
- 
- The subordinate id feature introduces a new auxiliar object class
--``ipaSubordinateId`` with four required attributes ``ipaSubUidNumber``,
--``ipaSubUidCount``, ``ipaSubGidNumber``, and ``ipaSubGidCount``. The
--attributes with ``number`` suffix store the start value of the interval.
--The ``count`` attributes contain the size of the interval including the
--start value. The maximum subid is
--``ipaSubUidNumber + ipaSubUidCount - 1``.
--
--All four attributes are single-value ``INTEGER`` type with standard
--integer matching rules. OIDs ``2.16.840.1.113730.3.8.23.8`` and
-+``ipaSubordinateId`` with five required attributes ``ipaOwner``,
-+``ipaSubUidNumber``, ``ipaSubUidCount``, ``ipaSubGidNumber``, and
-+``ipaSubGidCount``. The attributes with ``number`` suffix store the
-+start value of the interval. The ``count`` attributes contain the
-+size of the interval including the start value. The maximum subid is
-+``ipaSubUidNumber + ipaSubUidCount - 1``. The ``ipaOwner`` attribute
-+is a reference to the owning user.
-+
-+All count and number attributes are single-value ``INTEGER`` type with
-+standard integer matching rules. OIDs ``2.16.840.1.113730.3.8.23.8`` and
- ``2.16.840.1.113730.3.8.23.11`` are reserved for future use.
- 
- ```raw
- attributeTypes: (
--  2.16.840.1.113730.3.8.23.6
-+  2.16.840.1.113730.3.8.23.7
-   NAME 'ipaSubUidNumber'
-   DESC 'Numerical subordinate user ID (range start value)'
-   EQUALITY integerMatch ORDERING integerOrderingMatch
-@@ -139,7 +146,7 @@ attributeTypes: (
-   X-ORIGIN 'IPA v4.9'
- )
- attributeTypes: (
--  2.16.840.1.113730.3.8.23.7
-+  2.16.840.1.113730.3.8.23.8
-   NAME 'ipaSubUidCount'
-   DESC 'Subordinate user ID count (range size)'
-   EQUALITY integerMatch ORDERING integerOrderingMatch
-@@ -147,7 +154,7 @@ attributeTypes: (
-   X-ORIGIN 'IPA v4.9'
- )
- attributeTypes: (
--  2.16.840.1.113730.3.8.23.9
-+  2.16.840.1.113730.3.8.23.10
-   NAME 'ipaSubGidNumber'
-   DESC 'Numerical subordinate group ID (range start value)'
-   EQUALITY integerMatch ORDERING integerOrderingMatch
-@@ -155,7 +162,7 @@ attributeTypes: (
-   X-ORIGIN 'IPA v4.9'
- )
- attributeTypes: (
--  2.16.840.1.113730.3.8.23.10
-+  2.16.840.1.113730.3.8.23.11
-   NAME 'ipaSubGidCount'
-   DESC 'Subordinate group ID count (range size)'
-   EQUALITY integerMatch ORDERING integerOrderingMatch
-@@ -164,51 +171,96 @@ attributeTypes: (
- )
- ```
- 
--The ``ipaSubordinateId`` object class is an auxiliar subclass of
-+The ``ipaOwner`` attribute is a single-value DN attribute that refers
-+to user entry that owns the subordinate ID entry. The proposal does not
-+reuse any of the existing attributes like ``owner`` or ``member``,
-+because they are all multi-valued.
-+
-+```
-+attributeTypes: (
-+  2.16.840.1.113730.3.8.23.13
-+  NAME 'ipaOwner'
-+  DESC 'Owner of an entry'
-+  SUP distinguishedName
-+  EQUALITY distinguishedNameMatch
-+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
-+  SINGLE-VALUE
-+  X-ORIGIN 'IPA v4.9')
-+```
-+
-+The ``ipaSubordinateId`` object class is an auxiliary subclass of
- ``top`` and requires all four subordinate id attributes as well as
--``uidNumber``. It does not subclass ``posixAccount`` to make
--the class reusable in idview overrides later.
-+``ipaOwner`` attribute``
- 
- ```raw
- objectClasses: (
-   2.16.840.1.113730.3.8.24.4
-   NAME 'ipaSubordinateId'
-   DESC 'Subordinate uid and gid for users'
--  SUP top AUXILIARY
--  MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount )
-+  SUP top
-+  AUXILIARY
-+  MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount )
-   X-ORIGIN 'IPA v4.9'
- )
- ```
- 
- The ``ipaSubordinateGid`` and ``ipaSubordinateUid`` are defined for
--future use. IPA always assumes the presence of ``ipaSubordinateId`` and
--does not use these object classes.
-+future use. IPA always assumes the presence of ``ipaSubordinateId``.
- 
- ```raw
- objectClasses: (
-   2.16.840.1.113730.3.8.24.2
-   NAME 'ipaSubordinateUid'
-   DESC 'Subordinate uids for users, see subuid(5)'
--  SUP top AUXILIARY
--  MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount )
-+  SUP top
-+  AUXILIARY
-+  MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount )
-   X-ORIGIN 'IPA v4.9'
-  )
- objectClasses: (
-   2.16.840.1.113730.3.8.24.3
-   NAME 'ipaSubordinateGid'
-   DESC 'Subordinate gids for users, see subgid(5)'
--  SUP top AUXILIARY
--  MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount )
-+  SUP top
-+  AUXILIARY
-+  MUST ( ipaOwner $ ipaSubGidNumber $ ipaSubGidCount )
-   X-ORIGIN 'IPA v4.9'
- )
- ```
- 
--### Index
-+Subordinate id entries have the structural object class
-+``ipaSubordinateIdEntry`` and one or more of the auxiliary object
-+classes ``ipaSubordinateId``, ``ipaSubordinateGid``, or
-+``ipaSubordinateUid``. ``ipaUniqueId`` is used as a primary key (RDN).
-+
-+```raw
-+objectClasses: (
-+  2.16.840.1.113730.3.8.24.5
-+  NAME 'ipaSubordinateIdEntry'
-+  DESC 'Subordinate uid and gid entry'
-+  SUP top
-+  STRUCTURAL
-+  MUST ( ipaUniqueId ) MAY ( description ) X-ORIGIN 'IPA v4.9'
-+)
-+```
-+
-+### cn=subids,cn=accounts,$SUFFIX
-+
-+Subordiante ids and ACIs are stored in the new subtree
-+``cn=subids,cn=accounts,$SUFFIX``.
-+
-+### Index, integrity, memberOf
- 
- The attributes ``ipaSubUidNumber`` and ``ipaSubGidNumber`` are index
- for ``pres`` and ``eq`` with ``nsMatchingRule: integerOrderingMatch``
- to enable efficient ``=``, ``>=``, and ``<=`` searches.
- 
-+The attribute ``ipaOwner`` is indexed for ``pres`` and ``eq``. This DN
-+attribute is also checked for referential integrity and uniqueness
-+within the ``cn=subids,cn=accounts,$SUFFIX`` subtree. The memberOf
-+plugin creates back-references for ``ipaOwner`` references.
-+
-+
- ### Distributed numeric assignment (DNA) plug-in extension
- 
- Subordinate id auto-assignment requires an extension of 389-DS'
-@@ -221,6 +273,85 @@ option ``dnaStepAttr`` (name is tentative) will tell the DNA plug-in
- to use the value of entry attributes as step size.
- 
- 
-+## IPA plugins and commands
-+
-+The config plugin has a new option to enable or disable generation of
-+subordinate id entries for new users:
-+
-+```raw
-+$ ipa config-mod --user-default-subid=true
-+```
-+
-+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``.
-+
-+```raw
-+$ ipa help subid
-+Topic commands:
-+  subid-find      Search for subordinate id.
-+  subid-generate  Generate and auto-assign subuid and subgid range to user entry
-+  subid-match     Match users by any subordinate uid in their range
-+  subid-mod       Modify a subordinate id.
-+  subid-show      Display information about a subordinate id.
-+  subid-stats     Subordinate id statistics
-+```
-+
-+```raw
-+$ ipa subid-generate --owner testuser9
-+-----------------------------------------------------------
-+Added subordinate id "aa28f132-457c-488b-82e1-d123727e4f81"
-+-----------------------------------------------------------
-+  Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
-+  Description: auto-assigned subid
-+  Owner: testuser9
-+  SubUID range start: 3922132992
-+  SubUID range size: 65536
-+  SubGID range start: 3922132992
-+  SubGID range size: 65536
-+```
-+
-+
-+```raw
-+$ ipa subid-find --owner testuser9
-+------------------------
-+1 subordinate id matched
-+------------------------
-+  Unique ID: aa28f132-457c-488b-82e1-d123727e4f81
-+  Owner: testuser9
-+  SubUID range start: 3922132992
-+  SubUID range size: 65536
-+  SubGID range start: 3922132992
-+  SubGID range size: 65536
-+----------------------------
-+Number of entries returned 1
-+----------------------------
-+```
-+
-+```raw
-+$ ipa -vv subid-stats
-+...
-+ipa: INFO: Response: {
-+    "error": null,
-+    "id": 0,
-+    "principal": "admin@IPASUBID.TEST",
-+    "result": {
-+        "result": {
-+            "assigned_subids": 20,
-+            "baseid": 2147483648,
-+            "dna_remaining": 4293394434,
-+            "rangesize": 2147352576,
-+            "remaining_subids": 65512
-+        },
-+        "summary": "65532 remaining subordinate id ranges"
-+    },
-+    "version": "4.10.0.dev"
-+}
-+-------------------------------------
-+65532 remaining subordinate id ranges
-+-------------------------------------
-+```
-+
- ## Permissions, Privileges, Roles
- 
- ### Self-servive RBAC
-@@ -246,6 +377,13 @@ be modified or deleted.
- * Privilege: *Subordinate ID Administrators*
- * default privilege role: *User Administrator*
- 
-+### Managed permissions
-+
-+* *System: Read Subordinate Id Attributes* (all authenticated users)
-+* *System: Read Subordinate Id Count* (all authenticated usrs)
-+* *System: Manage Subordinate Ids* (User Administrators)
-+* *System: Remove Subordinate Ids* (User Administrators)
-+
- 
- ## Workflows
- 
-@@ -257,12 +395,12 @@ to assign subordinate ids to users.
- 
- Users with *User Administrator* role and members of the *admins* group
- have permission to auto-assign new subordinate ids to any user. Auto
--assignment can be performed with new ``user-auto-subid`` command on the
-+assignment can be performed with new ``subid-generate`` command on the
- command line or with the *Auto assign subordinate ids* action in the
- *Actions* drop-down menu in the web UI.
- 
- ```shell
--$ ipa user-auto-subid someusername
-+$ ipa subid-generate --owner myusername
- ```
- 
- ### Self-service for group members
-@@ -279,27 +417,14 @@ $ ipa role-add-member "Subordinate ID Selfservice User" --groups=ipausers
- ```
- 
- This allows members of ``ipausers`` to request subordinate ids with
--the ``user-auto-subid`` command or the *Auto assign subordinate ids*
--action in the web UI.
--
--```shell
--$ ipa user-auto-subid myusername
--```
--
--### Auto assignment with user default object class
--
--Admins can also enable auto-assignment of subordinate ids for all new
--users by adding ``ipasubordinateid`` as a default user objectclass.
--This can be accomplished in the web UI under "IPA Server" /
--"Configuration" / "Default user objectclasses" or on the command line
--with:
-+the ``subid-generate`` command or the *Auto assign subordinate ids*
-+action in the web UI (**TODO** not implemented yet). The command picks
-+the name of the current user principal automatically.
- 
- ```shell
--$ ipa config-mod --addattr="ipaUserObjectClasses=ipasubordinateid"
-+$ ipa subid-generate
- ```
- 
--**NOTE:** The objectclass must be written all lower case.
--
- ### ipa-subid tool
- 
- Finally IPA includes a new tool for mass-assignment of subordinate ids.
-@@ -355,26 +480,25 @@ gid range. The new command ``user-match-subid`` can be used to find a
- user by any subordinate id in their range.
- 
- ```raw
--$ ipa user-match-subid --subuid=2153185287
--  User login: asmith
--  First name: Alice
--  Last name: Smith
--  ...
--  SubUID range start: 2153185280
-+$ ipa subid-match --subuid=2147549183
-+------------------------
-+1 subordinate id matched
-+------------------------
-+  Name: asmith-auto
-+  Owner: asmith
-+  SubUID range start: 2147483648
-   SubUID range size: 65536
--  SubGID range start: 2153185280
-+  SubGID range start: 2147483648
-   SubGID range size: 65536
- ----------------------------
- Number of entries returned 1
- ----------------------------
--$ ipa user-match-subid --subuid=2153185279
--  User login: bjones
--  First name: Bob
--  Last name: Jones
--  ...
--  SubUID range start: 2153119744
-+$ ipa user-match-subid --subuid=2147549184
-+  Name: bjones-auto
-+  Owner: bjones
-+  SubUID range start: 2147549184
-   SubUID range size: 65536
--  SubGID range start: 2153119744
-+  SubGID range start: 2147549184
-   SubGID range size: 65536
- ----------------------------
- Number of entries returned 1
-@@ -383,61 +507,54 @@ Number of entries returned 1
- 
- ## SSSD integration
- 
--* base: ``cn=accounts,$SUFFIX`` / ``cn=users,cn=accounts,$SUFFIX``
--* scope: ``SCOPE_SUBTREE`` (2) / ``SCOPE_ONELEVEL`` (1)
--* user filter: should include ``(objectClass=posixAccount)``
--* attributes: ``uidNumber ipaSubUidNumber ipaSubUidCount ipaSubGidNumber ipaSubGidCount``
--
--SSSD can safely assume that only *user accounts* of type ``posixAccount``
--have subordinate ids. In the first revision there are no other entries
--with subordinate ids. The ``posixAccount`` object class has ``uid``
--(user login name) and ``uidNumber`` (numeric user id) as mandatory
--attributes. The ``uid`` attribute is guaranteed to be unique across
--all user accounts in an IPA domain.
--
--The ``uidNumber`` attribute is commonly unique, too. However it's
--technically possible that an administrator has assigned the same
--numeric user id to multiple users. Automatically assigned uid numbers
--don't conflict. SSSD should treat multiple users with same numeric
--user id as an error.
-+* search base: ``cn=subids,cn=accounts,$SUFFIX``
-+* scope: ``SCOPE_ONELEVEL`` (1)
-+* filter: ``(objectClass=ipaSubordinateId)``
-+* attributes: ``ipaOwner ipaSubUidNumber ipaSubUidCount ipaSubGidNumber ipaSubGidCount``
- 
- The attribute ``ipaSubUidNumber`` is always accompanied by
- ``ipaSubUidCount`` and ``ipaSubGidNumber`` is always accompanied
- by ``ipaSubGidCount``. In revision 1 the presence of
- ``ipaSubUidNumber`` implies presence of the other three attributes.
--All four subordinate id attributes and ``uidNumber`` are single-value
--``INTEGER`` types. Any value outside of range of ``uint32_t`` must
--treated as invalid. SSSD will never see the DNA magic value ``-1``
--in ``cn=accounts,$SUFFIX`` subtree.
--
--IPA recommends that SSSD simply extends its existing query for user
--accounts and requests the four subordinate attributes additionally to
--RFC 2307 attributes ``rfc2307_user_map``. SSSD can directly take the
--values and return them without further processing, e.g.
--``uidNumber:ipaSubUidNumber:ipaSubUidCount`` for ``/etc/subuid``.
--
--Filters for additional cases:
--
--* subuid filter (find user with subuid by numeric uid):
--  ``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=$UID))``,
--  ``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
--* subuid enumeration filter:
--  ``&((objectClass=posixAccount)(ipaSubUidNumber=*)(uidNumber=*))``,
--  ``(objectClass=ipaSubordinateId)``, or similar
--* subgid filter (find user with subgid by numeric uid):
--  ``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=$UID))``,
--  ``(&(objectClass=ipaSubordinateId)(uidNumber=$UID))``, or similar
--* subgid enumeration filter:
--  ``&((objectClass=posixAccount)(ipaSubGidNumber=*)(uidNumber=*))``,
--  ``(objectClass=ipaSubordinateId)``, or similar
-+All four subordinate id attributes are single-value ``INTEGER`` types.
-+Any value outside of range of ``uint32_t`` must treated as invalid.
-+SSSD will never see the DNA magic value ``-1`` in
-+``cn=accounts,$SUFFIX`` subtree. In revision 1 each user subordinate
-+id entry is assigned to exactly one user and each user has either 0
-+or 1 subid.
-+
-+IPA recommends that SSSD uses LDAP deref controls for ``ipaOwner``
-+attribute to fetch ``uidNumber`` from the user object.
-+
-+### Deref control example
-+
-+```raw
-+$ ldapsearch -L -E '!deref=ipaOwner:uid,uidNumber' \
-+      -b 'cn=subids,cn=accounts,dc=ipasubid,dc=test' \
-+      '(ipaOwner=uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test)'
-+
-+dn: ipauniqueid=35c02c93-3799-4551-a355-ebbf042e431c,cn=subids,cn=accounts,dc=ipasubid,dc=test
-+# control: 1.3.6.1.4.1.4203.666.5.16 false MIQAAABxMIQAAABrBAhpcGFPd25lcgQ3dWlk
-+ PXRlc3R1c2VyMTAsY249dXNlcnMsY249YWNjb3VudHMsZGM9aXBhc3ViaWQsZGM9dGVzdKCEAAAAIj
-+ CEAAAAHAQJdWlkTnVtYmVyMYQAAAALBAk2MjgwMDAwMTE=
-+# ipaOwner: <uidNumber=628000011>;uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test
-+
-+ipaOwner: uid=testuser10,cn=users,cn=accounts,dc=ipasubid,dc=test
-+ipaUniqueID: 35c02c93-3799-4551-a355-ebbf042e431c
-+description: auto-assigned subid
-+objectClass: ipasubordinateidentry
-+objectClass: ipasubordinategid
-+objectClass: ipasubordinateuid
-+objectClass: ipasubordinateid
-+objectClass: top
-+ipaSubUidNumber: 3434020864
-+ipaSubGidNumber: 3434020864
-+ipaSubUidCount: 65536
-+ipaSubGidCount: 65536
-+```
- 
- ## Implementation details
- 
--* The four subid attributes are not included in
--  ``baseuser.default_attributes`` on purpose. The ``config-mod``
--  command does not permit removal of a user default objectclasses
--  when the class is the last provider of an attribute in
--  ``default_attributes``.
- * ``ipaSubordinateId`` object class does not subclass the other two
-   object classes. LDAP supports
-   ``SUP ( ipaSubordinateGid $ ipaSubordinateUid )`` but 389-DS only
-@@ -459,6 +576,13 @@ Filters for additional cases:
- * Shared DNA configuration entries in ``cn=dna,cn=ipa,cn=etc,$SUFFIX``
-   are automatically removed by existing code. Server and replication
-   plug-ins search and delete entries by ``dnaHostname`` attribute.
-+* ``ipaSubordinateId`` entries no longer contains ``uidNumber``
-+  attribute. I considered to use CoS plugin to provide ``uidNumber``
-+  as virtual attribute. However it's not possible to
-+  ``objectClass: cosIndirectDefinition`` with
-+  ``cosIndirectSpecifier: ipaOwner`` and
-+  ``cosAttribute: uidNumber override`` for the task. Indexes and
-+  searches don't work with virtual attributes.
- 
- ### TODO
- 
-@@ -466,3 +590,23 @@ Filters for additional cases:
- * remove ``fake_dna_plugin`` hack from ``baseuser`` plug-in.
- * add custom range type for idranges and teach AD trust, sidgen, and
-   range overlap check code to deal with new range type.
-+
-+#### user-del --preserve
-+
-+Preserving a user with ``ipa user-del --preserve`` currently fails with
-+an ObjectclassViolation error (err=65). The problem is caused by
-+configuration of referential integrity postoperation plug-in. The
-+plug-in excludes the subtree
-+``nsslapd-pluginexcludeentryscope: cn=provisioning,$SUFFX``. Preserved
-+users are moved into the staging area of the provisioning subtree.
-+Since the ``ipaOwner`` DN target is now out of scope, the plug-in
-+attempts to delete references. However ``ipaOwner`` is a required
-+attribute, which triggers the objectclass violation.
-+
-+Possible solutions
-+
-+* Don't preserve subid entries
-+* Implement preserve feature for subid entries and move subids of
-+  preserved users into
-+  ``cn=deleted subids,cn=accounts,cn=provisioning,$SUFFIX`` subtree.
-+* Change ``nsslapd-pluginexcludeentryscope`` setting
-diff --git a/install/share/60basev4.ldif b/install/share/60basev4.ldif
-index 7f5173e593ff68a03d4005957b1dc9b9eb489dc5..c48b0c36a3012d86a74e12a77695e29cceafb698 100644
---- a/install/share/60basev4.ldif
-+++ b/install/share/60basev4.ldif
-@@ -5,15 +5,16 @@
- ##
- dn: cn=schema
- # subordinate ids
--# range ceiling OIDs are reserved for future use (operational attribute?)
--# object class requires uidNumber but does not subclass posixAccount so we
--# can re-use the object class in idview overrides later.
--attributeTypes: ( 2.16.840.1.113730.3.8.23.6 NAME 'ipaSubUidNumber' DESC 'Numerical subordinate user ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--attributeTypes: ( 2.16.840.1.113730.3.8.23.7 NAME 'ipaSubUidCount' DESC 'Subordinate user ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--# attributeTypes: ( 2.16.840.1.113730.3.8.23.8 NAME 'ipaSubUidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--attributeTypes: ( 2.16.840.1.113730.3.8.23.9 NAME 'ipaSubGidNumber' DESC 'Numerical subordinate group ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--attributeTypes: ( 2.16.840.1.113730.3.8.23.10 NAME 'ipaSubGidCount' DESC 'Subordinate group ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--# attributeTypes: ( 2.16.840.1.113730.3.8.23.11 NAME 'ipaSubGidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE  X-ORIGIN 'IPA v4.9')
--objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subordinate uids for users, see subuid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount ) X-ORIGIN 'IPA v4.9')
--objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( uidNumber $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
--objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( uidNumber $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
-+# range ceiling OIDs are reserved for future use
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.7 NAME 'ipaSubUidNumber' DESC 'Numerical subordinate user ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.8 NAME 'ipaSubUidCount' DESC 'Subordinate user ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+# attributeTypes: ( 2.16.840.1.113730.3.8.23.9 NAME 'ipaSubUidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.10 NAME 'ipaSubGidNumber' DESC 'Numerical subordinate group ID (range start value)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.11 NAME 'ipaSubGidCount' DESC 'Subordinate group ID count (range size)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+# attributeTypes: ( 2.16.840.1.113730.3.8.23.12 NAME 'ipaSubGidCeiling' DESC 'Numerical subordinate user ID ceiling (largest value in range)' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.13 NAME 'ipaOwner' DESC 'Owner of an entry' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+# attribute 2.16.840.1.113730.3.8.23.14 'ipaUserDefaultSubordinateId' is defined in 60ipaconfig.ldif
-+objectClasses: (2.16.840.1.113730.3.8.24.2 NAME 'ipaSubordinateUid' DESC 'Subordinate uids for users, see subuid(5)' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount ) X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.3 NAME 'ipaSubordinateGid' DESC 'Subordinate gids for users, see subgid(5)' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.4 NAME 'ipaSubordinateId' DESC 'Subordinate uid and gid for users' SUP top AUXILIARY MUST ( ipaOwner $ ipaSubUidNumber $ ipaSubUidCount $ ipaSubGidNumber $ ipaSubGidCount ) X-ORIGIN 'IPA v4.9')
-+objectClasses: (2.16.840.1.113730.3.8.24.5 NAME 'ipaSubordinateIdEntry' DESC 'Subordinate uid and gid entry' SUP top STRUCTURAL MUST ( ipaUniqueId ) MAY ( description ) X-ORIGIN 'IPA v4.9')
-diff --git a/install/share/60ipaconfig.ldif b/install/share/60ipaconfig.ldif
-index dbcf9ee603b361f6aac1413d0a53fff5561b6f89..f84b38ead1d70ff408f5669029f1517b0c98ecf1 100644
---- a/install/share/60ipaconfig.ldif
-+++ b/install/share/60ipaconfig.ldif
-@@ -6,6 +6,7 @@
- ## ObjectClasses:	2.16.840.1.113730.3.8.2 - V1
- ## Attributes:		2.16.840.1.113730.3.8.3 - V2
- ## ObjectClasses:	2.16.840.1.113730.3.8.4 - V2
-+## Attributes:		2.16.840.1.113730.3.8.23 - V4 base attributes
- dn: cn=schema
- ###############################################
- ##
-@@ -45,11 +46,13 @@ attributeTypes: ( 2.16.840.1.113730.3.8.3.26 NAME 'ipaSELinuxUserMapDefault' DES
- attributeTypes: ( 2.16.840.1.113730.3.8.3.27 NAME 'ipaSELinuxUserMapOrder' DESC 'Available SELinux user context ordering' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
- ## ipaMaxHostnameLength - maximum hostname length to allow
- attributeTypes: ( 2.16.840.1.113730.3.8.1.28 NAME 'ipaMaxHostnameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
-+# ipaUserDefaultSubordinateId - if TRUE new user entries gain subordinate id by default
-+attributeTypes: ( 2.16.840.1.113730.3.8.3.23.14 NAME 'ipaUserDefaultSubordinateId' DESC 'Enable adding user entries with subordinate id' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
- ###############################################
- ##
- ## ObjectClasses
- ##
- ## ipaGuiConfig - GUI config parameters objectclass
--objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData $ ipaMaxHostnameLength) )
-+objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData $ ipaMaxHostnameLength $ ipaUserDefaultSubordinateId) )
- ## ipaConfigObject - Generic config strings object holder
- objectClasses: (2.16.840.1.113730.3.8.4.13 NAME 'ipaConfigObject' DESC 'generic config object for IPA' AUXILIARY MAY ( ipaConfigString ) X-ORIGIN 'IPA v2' )
-diff --git a/install/share/dna.ldif b/install/share/dna.ldif
-index 649313e72fc58112865e5901125923b3704276b1..735faab8261feef59486f7c933b01c57ad511166 100644
---- a/install/share/dna.ldif
-+++ b/install/share/dna.ldif
-@@ -29,12 +29,12 @@ dnaMagicRegen: -1
- dnaFilter: (objectClass=ipaSubordinateId)
- dnaScope: $SUFFIX
- dnaThreshold: eval($SUBID_DNA_THRESHOLD)
--# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
--# dnaStepAttr: ipaSubUidCount
--# dnaStepAttr: ipaSubGidCount
--# dnaStepAllowedValues: eval($SUBID_COUNT)
- dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
- dnaExcludeScope: cn=provisioning,$SUFFIX
-+# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
-+# dnaIntervalAttr: ipasubuidcount
-+# dnaIntervalAttr: ipasubgidcount
-+# dnaMaxInterval: eval($SUBID_COUNT)
- 
- # Enable the DNA plugin
- dn: cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
-diff --git a/install/share/memberof-conf.ldif b/install/share/memberof-conf.ldif
-index 79ad647e76feb6647524553a634c91c66ebd178e..3c22dfa9e52b8005bac88cd0962571c6fea18e7b 100644
---- a/install/share/memberof-conf.ldif
-+++ b/install/share/memberof-conf.ldif
-@@ -8,4 +8,6 @@ memberofgroupattr: memberUser
- -
- add: memberofgroupattr
- memberofgroupattr: memberHost
--
-+-
-+add: memberofgroupattr
-+memberofgroupattr: ipaOwner
-diff --git a/install/ui/src/freeipa/app.js b/install/ui/src/freeipa/app.js
-index 093737b8f923b41e8a1eabc90f66c6709991e239..9e0007528febb3d6641d152c72c51328d2d72cf6 100644
---- a/install/ui/src/freeipa/app.js
-+++ b/install/ui/src/freeipa/app.js
-@@ -52,6 +52,7 @@ define([
-     './serverconfig',
-     './service',
-     './stageuser',
-+    './subid',
-     './sudo',
-     './trust',
-     './topology',
-diff --git a/install/ui/src/freeipa/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
-index 0c30459691d8f652dc35ccf74ed27fae7654020d..6ccd06919fbe04c7e8d2034ff7a1f644f373c607 100644
---- a/install/ui/src/freeipa/navigation/menu_spec.js
-+++ b/install/ui/src/freeipa/navigation/menu_spec.js
-@@ -103,7 +103,8 @@ var nav = {};
-                             ]
-                         }
-                     ]
--                }
-+                },
-+                { entity: 'subid' }
-             ]
-         },
-         {
-diff --git a/install/ui/src/freeipa/serverconfig.js b/install/ui/src/freeipa/serverconfig.js
-index bb26b107317ae12ee032fb767fcfc9060690c66e..9de0bab4b64abf1094e52dffda8add5349dc0e3c 100644
---- a/install/ui/src/freeipa/serverconfig.js
-+++ b/install/ui/src/freeipa/serverconfig.js
-@@ -123,6 +123,10 @@ return {
-                             $type: 'checkbox',
-                             name: 'ipamigrationenabled'
-                         },
-+                        {
-+                            $type: 'checkbox',
-+                            name: 'ipauserdefaultsubordinateid'
-+                        },
-                         {
-                             $type: 'multivalued',
-                             name: 'ipauserobjectclasses'
-diff --git a/install/ui/src/freeipa/subid.js b/install/ui/src/freeipa/subid.js
-new file mode 100644
-index 0000000000000000000000000000000000000000..f286165070b08badf77cac6c30e93cab916c2acc
---- /dev/null
-+++ b/install/ui/src/freeipa/subid.js
-@@ -0,0 +1,92 @@
-+/*
-+ * Copyright (C) 2021  FreeIPA Contributors see COPYING for license
-+ */
-+
-+define([
-+        'dojo/on',
-+        './ipa',
-+        './jquery',
-+        './phases',
-+        './reg',
-+        './details',
-+        './search',
-+        './association',
-+        './entity'],
-+            function(on, IPA, $, phases, reg) {
-+
-+var exp = IPA.subid = {};
-+
-+var make_spec = function() {
-+return {
-+    name: 'subid',
-+    facets: [
-+        {
-+            $type: 'search',
-+            columns: [
-+                'ipauniqueid',
-+                'ipaowner',
-+                'ipasubgidnumber',
-+                'ipasubuidnumber'
-+            ]
-+        },
-+        {
-+            $type: 'details',
-+            sections: [
-+                {
-+                    name: 'details',
-+                    fields: [
-+                        'ipauniqueid',
-+                        'description',
-+                        {
-+                            name: 'ipaowner',
-+                            label: '@i18n:objects.subid.ipaowner',
-+                            title: '@mo-param:subid:ipaowner:label'
-+                        },
-+                        {
-+                            name: 'ipasubgidnumber',
-+                            label: '@i18n:objects.subid.ipasubgidnumber',
-+                            title: '@mo-param:subid:ipasubgidnumber:label'
-+                        },
-+                        {
-+                            name: 'ipasubgidcount',
-+                            label: '@i18n:objects.subid.ipasubgidcount',
-+                            title: '@mo-param:subid:ipasubgidcount:label'
-+                        },
-+                        {
-+                            name: 'ipasubuidnumber',
-+                            label: '@i18n:objects.subid.ipasubuidnumber',
-+                            title: '@mo-param:subid:ipasubuidnumber:label'
-+                        },
-+                        {
-+                            name: 'ipasubuidcount',
-+                            label: '@i18n:objects.subid.ipasubuidcount',
-+                            title: '@mo-param:subid:ipasubuidcount:label'
-+                        }
-+                    ]
-+                }
-+            ]
-+        }
-+    ],
-+    adder_dialog: {
-+        title: '@i18n:objects.subid.add',
-+        method: 'generate',
-+        fields: [
-+            {
-+                $type: 'entity_select',
-+                name: 'ipaowner',
-+                other_entity: 'user',
-+                other_field: 'uid'
-+            }
-+        ]
-+    }
-+};};
-+
-+exp.entity_spec = make_spec();
-+exp.register = function() {
-+    var e = reg.entity;
-+    e.register({type: 'subid', spec: exp.entity_spec});
-+};
-+phases.on('registration', exp.register);
-+
-+return {};
-+});
-diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
-index 5b49b0f6edbbbb6c802afb803a6406a0ab796c44..56bb6f4feffb637d33a57aecf9a98f08d4639550 100644
---- a/install/ui/src/freeipa/user.js
-+++ b/install/ui/src/freeipa/user.js
-@@ -259,33 +259,6 @@ return {
-                         }
-                     ]
-                 },
--                {
--                    name: 'subordinate',
--                    label: '@i18n:objects.subordinate.identity',
--                    fields: [
--                        {
--                            name: 'ipasubuidnumber',
--                            label: '@i18n:objects.subordinate.subuidnumber',
--                            read_only: true
--                        },
--                        {
--                            name: 'ipasubuidcount',
--                            label: '@i18n:objects.subordinate.subuidcount',
--                            read_only: true
--
--                        },
--                        {
--                            name: 'ipasubgidnumber',
--                            label: '@i18n:objects.subordinate.subgidnumber',
--                            read_only: true
--                        },
--                        {
--                            name: 'ipasubgidcount',
--                            label: '@i18n:objects.subordinate.subgidcount',
--                            read_only: true
--                        }
--                    ]
--                },
-                 {
-                     name: 'pwpolicy',
-                     label: '@i18n:objects.pwpolicy.identity',
-@@ -478,16 +451,6 @@ return {
-                     enable_cond: ['is-locked'],
-                     confirm_msg: '@i18n:objects.user.unlock_confirm'
-                 },
--                {
--                    $factory: IPA.object_action,
--                    name: 'auto_subid',
--                    method: 'auto_subid',
--                    label: '@i18n:objects.user.auto_subid',
--                    needs_confirm: true,
--                    hide_cond: ['preserved-user'],
--                    enable_cond: ['no-subid'],
--                    confirm_msg: '@i18n:objects.user.auto_subid_confirm'
--                },
-                 {
-                     $type: 'automember_rebuild',
-                     name: 'automember_rebuild',
-@@ -500,20 +463,15 @@ return {
-                     title: '@i18n:objects.cert.issue_for_user'
-                 },
-                 {
--                    $factory: IPA.object_action,
--                    name: 'auto_subid',
--                    method: 'auto_subid',
--                    label: '@i18n:objects.user.auto_subid',
--                    needs_confirm: true,
-+                    $type: 'subid_generate',
-                     hide_cond: ['preserved-user'],
--                    enable_cond: ['no-subid'],
--                    confirm_msg: '@i18n:objects.user.auto_subid_confirm'
-+                    enable_cond: ['no-subid']
-                 }
-             ],
-             header_actions: [
-                 'reset_password', 'enable', 'disable', 'stage', 'undel',
-                 'delete_active_user', 'delete', 'unlock', 'add_otptoken',
--                'automember_rebuild', 'request_cert', 'auto_subid'
-+                'automember_rebuild', 'request_cert', 'subid_generate'
-             ],
-             state: {
-                 evaluators: [
-@@ -532,7 +490,8 @@ return {
-                     IPA.user.self_service_other_user_evaluator,
-                     IPA.user.preserved_user_evaluator,
-                     IPA.user.is_locked_evaluator,
--                    IPA.cert.certificate_evaluator
-+                    IPA.cert.certificate_evaluator,
-+                    IPA.user.has_subid_evaluator
-                 ],
-                 summary_conditions: [
-                     {
-@@ -593,6 +552,12 @@ return {
-             add_title: '@i18n:objects.user.add_into_sudo',
-             remove_method: 'remove_user',
-             remove_title: '@i18n:objects.user.remove_from_sudo'
-+        },
-+        {
-+            $type: 'association',
-+            name: 'memberof_subid',
-+            associator: IPA.serial_associator,
-+            read_only: true
-         }
-     ],
-     standard_association_facets: {
-@@ -1206,7 +1171,31 @@ IPA.user.is_locked_evaluator = function(spec) {
-             }
-         }
- 
--        if (!user.ipasubuidnumber) {
-+        that.notify_on_change(old_state);
-+    };
-+
-+    return that;
-+};
-+
-+IPA.user.has_subid_evaluator = function(spec) {
-+
-+    spec = spec || {};
-+    spec.event = spec.event || 'post_load';
-+
-+    var that = IPA.state_evaluator(spec);
-+    that.name = spec.name || 'has_subid_evaluator';
-+    that.param = spec.param || 'memberof_subid';
-+
-+    /**
-+     * Evaluates if user already has a subid
-+     */
-+    that.on_event = function(data) {
-+
-+        var old_state = that.state;
-+        that.state = [];
-+
-+        var value = that.adapter.load(data);
-+        if (value.length === 0) {
-             that.state.push('no-subid');
-         }
- 
-@@ -1216,6 +1205,30 @@ IPA.user.is_locked_evaluator = function(spec) {
-     return that;
- };
- 
-+IPA.user.subid_generate_action = function(spec) {
-+
-+    spec = spec || {};
-+    spec.name = spec.name || 'subid_generate';
-+    spec.label = spec.label || '@i18n:objects.user.auto_subid';
-+    spec.hide_cond = spec.hide_cond || ['preserved-user'];
-+    spec.confirm_msg = spec.confirm_msg || '@i18n:objects.user.auto_subid_confirm';
-+
-+    var that = IPA.action(spec);
-+
-+    that.execute_action = function(facet) {
-+
-+        var subid_e = reg.entity.get('subid');
-+        var dialog = subid_e.get_dialog('add');
-+        dialog.open();
-+        if (!IPA.is_selfservice) {
-+            var owner = facet.get_pkey();
-+            dialog.get_field('ipaowner').set_value([owner]);
-+        }
-+    };
-+
-+    return that;
-+};
-+
- exp.entity_spec = make_spec();
- exp.register = function() {
-     var e = reg.entity;
-@@ -1225,6 +1238,7 @@ exp.register = function() {
-     a.register('reset_password', IPA.user.reset_password_action);
-     a.register('add_otptoken', IPA.user.add_otptoken_action);
-     a.register('delete_active_user', IPA.user.delete_active_user_action);
-+    a.register('subid_generate', IPA.user.subid_generate_action);
-     d.copy('password', 'user_password', {
-         factory: IPA.user.password_dialog,
-         pre_ops: [IPA.user.password_dialog_pre_op]
-diff --git a/install/updates/10-uniqueness.update b/install/updates/10-uniqueness.update
-index 77facba195cb5a1564818010f97afdd15d65a274..699de3b4d3305def5d81aeb945106b80eef0ef40 100644
---- a/install/updates/10-uniqueness.update
-+++ b/install/updates/10-uniqueness.update
-@@ -109,3 +109,22 @@ default:nsslapd-plugin-depends-on-type: database
- default:nsslapd-pluginId: NSUniqueAttr
- default:nsslapd-pluginVersion: 1.1.0
- default:nsslapd-pluginVendor: Fedora Project
-+
-+dn: cn=ipaSubordinateIdEntry ipaOwner uniqueness,cn=plugins,cn=config
-+default:objectClass: top
-+default:objectClass: nsSlapdPlugin
-+default:objectClass: extensibleObject
-+default:cn: ipaSubordinateIdEntry ipaOwner uniqueness
-+default:nsslapd-pluginDescription: Enforce unique attribute values of ipaOwner
-+default:nsslapd-pluginPath: libattr-unique-plugin
-+default:nsslapd-pluginInitfunc: NSUniqueAttr_Init
-+default:nsslapd-pluginType: preoperation
-+default:nsslapd-pluginEnabled: on
-+default:uniqueness-attribute-name: ipaOwner
-+default:uniqueness-subtrees: cn=subids,cn=accounts,$SUFFIX
-+default:uniqueness-across-all-subtrees: on
-+default:uniqueness-subtree-entries-oc: ipaSubordinateIdEntry
-+default:nsslapd-plugin-depends-on-type: database
-+default:nsslapd-pluginId: NSUniqueAttr
-+default:nsslapd-pluginVersion: 1.1.0
-+default:nsslapd-pluginVendor: Fedora Project
-diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
-index 7f83ab9f04c565a59efdd2f41c3e7ee30f5da9c7..d6df5b37d0a9092e936d2c6002bce71ed876ec27 100644
---- a/install/updates/20-indices.update
-+++ b/install/updates/20-indices.update
-@@ -263,6 +263,14 @@ default:nsSystemIndex: false
- add:nsIndexType: eq
- add:nsIndexType: pres
- 
-+dn: cn=ipaOwner,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
-+only:cn: ipaOwner
-+default:objectClass: nsIndex
-+default:objectClass: top
-+default:nsSystemIndex: false
-+add:nsIndexType: eq
-+add:nsIndexType: pres
-+
- dn: cn=ipasudorunas,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
- only:cn: ipasudorunas
- default:objectClass: nsIndex
-@@ -425,6 +433,10 @@ default:nsSystemIndex: false
- add:nsIndexType: eq
- add:nsIndexType: pres
- 
-+dn: cn=memberOf,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
-+only:cn: member
-+add:nsIndexType: sub
-+
- dn: cn=memberPrincipal,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
- only:cn: memberPrincipal
- default:objectClass: nsIndex
-diff --git a/install/updates/25-referint.update b/install/updates/25-referint.update
-index 89bc5ef91b2f5c85ee3a4a2c7d112a0549d4e1da..b29926a4e3bd410115cde4ede3ed7886a412d300 100644
---- a/install/updates/25-referint.update
-+++ b/install/updates/25-referint.update
-@@ -21,3 +21,4 @@ add: referint-membership-attr: ipamemberca
- add: referint-membership-attr: ipamembercertprofile
- add: referint-membership-attr: ipalocation
- add: referint-membership-attr: membermanager
-+add: referint-membership-attr: ipaowner
-diff --git a/install/updates/73-subid.update b/install/updates/73-subid.update
-index 2aab3d445a33ae1663f81ca2d61b62ebc94aa37d..1aa43822a8b8c220583b81e08d70b648ca594363 100644
---- a/install/updates/73-subid.update
-+++ b/install/updates/73-subid.update
-@@ -1,5 +1,15 @@
- # subordinate ids
- 
-+# create memberOf attributes for ipaOwner
-+dn: cn=MemberOf Plugin,cn=plugins,cn=config
-+add: memberofgroupattr: ipaOwner
-+
-+# container
-+dn: cn=subids,cn=accounts,$SUFFIX
-+default: objectClass: top
-+default: objectClass: nsContainer
-+default: cn: subids
-+
- # self-service RBAC
- dn: cn=Subordinate ID Selfservice User,cn=roles,cn=accounts,$SUFFIX
- default:objectClass: groupofnames
-@@ -56,9 +66,9 @@ default:member: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
- # self-service permission when 389-DS' DNA plugin supports dnaStepAttr and
- # fake_dna_plugin hack has been removed.
- #
--dn: $SUFFIX
--add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(objectClass=ipasubordinategid)(objectClass=ipasubordinateuid)) && ipasubuidnumber:(|(ipasubuidnumber>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(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 (write) userdn = "ldap:///self" and groupdn="ldap:///cn=Self-service subordinate ID,cn=permissions,cn=pbac,$SUFFIX";)
--add: aci: (targetfilter = "(objectclass=posixaccount)")(targattrfilters = "add=objectClass:(|(objectClass=ipasubordinateid)(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 (write) groupdn="ldap:///cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX";)
-+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>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(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
-@@ -78,14 +88,14 @@ default: dnaMagicRegen: -1
- default: dnaFilter: (objectClass=ipaSubordinateId)
- default: dnaScope: $SUFFIX
- default: dnaThreshold: eval($SUBID_DNA_THRESHOLD)
--# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
--# default: dnaStepAttr: ipaSubUidCount
--# default: dnaStepAttr: ipaSubGidCount
--# default: dnaStepAllowedValues: eval($SUBID_COUNT)
- default: dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
- default: dnaExcludeScope: cn=provisioning,$SUFFIX
--default: 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";)
--default: 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";)
-+# TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
-+# add: dnaIntervalAttr: ipasubuidcount
-+# add: dnaIntervalAttr: ipasubgidcount
-+# addifnew: dnaMaxInterval: 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
-diff --git a/ipalib/constants.py b/ipalib/constants.py
-index bee4c92fb39769d427e315116575f217924915be..bff899ba64c75832e2037870ccbca4587458d97b 100644
---- a/ipalib/constants.py
-+++ b/ipalib/constants.py
-@@ -131,6 +131,9 @@ DEFAULT_CONFIG = (
-     ('container_ranges', DN(('cn', 'ranges'), ('cn', 'etc'))),
-     ('container_dna', DN(('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
-     ('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
-+    ('container_dna_subordinate_ids', DN(
-+        ('cn', 'subordinate-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc')
-+    )),
-     ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))),
-     ('container_otp', DN(('cn', 'otp'))),
-     ('container_radiusproxy', DN(('cn', 'radiusproxy'))),
-@@ -148,6 +151,7 @@ DEFAULT_CONFIG = (
-     ('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'))),
-     ('container_ca_renewal',
-         DN(('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'))),
-+    ('container_subids', DN(('cn', 'subids'), ('cn', 'accounts'))),
- 
-     # Ports, hosts, and URIs:
-     # Following values do not have any reasonable default.
-@@ -355,4 +359,4 @@ SUBID_RANGE_START = 2 ** 31
- SUBID_RANGE_MAX = (2 ** 32) - (2 * SUBID_COUNT)
- SUBID_RANGE_SIZE = SUBID_RANGE_MAX - SUBID_RANGE_START
- # threshold before DNA plugin requests a new range
--SUBID_DNA_THRESHOLD = 500 * SUBID_COUNT
-+SUBID_DNA_THRESHOLD = 500
-diff --git a/ipaserver/install/ipa_subids.py b/ipaserver/install/ipa_subids.py
-index ac77a4008aec58d92c8b24df5e00b83c6998401f..2b33b667084f529fa50e2f11eeefda8a8927f68c 100644
---- a/ipaserver/install/ipa_subids.py
-+++ b/ipaserver/install/ipa_subids.py
-@@ -10,7 +10,7 @@ from ipalib.facts import is_ipa_configured
- from ipaplatform.paths import paths
- from ipapython.admintool import AdminTool, ScriptError
- from ipapython.dn import DN
--from ipaserver.plugins.baseldap import DNA_MAGIC
-+from ipapython.version import API_VERSION
- 
- logger = logging.getLogger(__name__)
- 
-@@ -77,7 +77,7 @@ class IPASubids(AdminTool):
-             # only users with posixAccount
-             "(objectClass=posixAccount)",
-             # without subordinate ids
--            "(!(objectClass=ipaSubordinateId))",
-+            f"(!(memberOf=*,cn=subids,cn=accounts,{api.env.basedn}))",
-         ]
-         if groupinfo is not None:
-             filters.append(
-@@ -89,7 +89,7 @@ class IPASubids(AdminTool):
- 
-     def search_users(self, filters):
-         users_dn = DN(api.env.container_user, api.env.basedn)
--        attrs = ["objectclass", "uid", "uidnumber"]
-+        attrs = ["objectclass", "uid"]
- 
-         logger.debug("basedn: %s", users_dn)
-         logger.debug("attrs: %s", attrs)
-@@ -116,7 +116,7 @@ class IPASubids(AdminTool):
-         api.finalize()
-         api.Backend.ldap2.connect()
-         self.ldap2 = api.Backend.ldap2
--        user_obj = api.Object["user"]
-+        subid_generate = api.Command.subid_generate
- 
-         dry_run = self.safe_options.dry_run
-         group_info = self.get_group_info()
-@@ -136,11 +136,13 @@ class IPASubids(AdminTool):
-                 i,
-                 total
-             )
--            user_obj.set_subordinate_ids(
--                self.ldap2, entry.dn, entry, DNA_MAGIC
--            )
-             if not dry_run:
--                self.ldap2.update_entry(entry)
-+                # TODO: check for duplicate entry (race condition)
-+                # TODO: log new subid
-+                subid_generate(
-+                    ipaowner=entry.single_value["uid"],
-+                    version=API_VERSION
-+                )
- 
-         if dry_run:
-             logger.info("Dry run mode, no user was modified")
-diff --git a/ipaserver/install/plugins/update_dna_shared_config.py b/ipaserver/install/plugins/update_dna_shared_config.py
-index 0f704c68a0551a7b7db6d42275498d02885b70fc..955bee5dd830f0dcad3f0810e7e2f1a1c725a0aa 100644
---- a/ipaserver/install/plugins/update_dna_shared_config.py
-+++ b/ipaserver/install/plugins/update_dna_shared_config.py
-@@ -17,7 +17,7 @@ register = Registry()
- 
- @register()
- class update_dna_shared_config(Updater):
--    dna_plugin_names = ('posix IDs',)
-+    dna_plugin_names = ('posix IDs', 'Subordinate IDs')
- 
-     dna_plugin_dn = DN(
-         ('cn', 'Distributed Numeric Assignment Plugin'),
-diff --git a/ipaserver/plugins/baseldap.py b/ipaserver/plugins/baseldap.py
-index 3ccd2e38a254e274ba3685b9233f23b2313f8eec..0b7839536f66740a60377460c7432ade7c0654c2 100644
---- a/ipaserver/plugins/baseldap.py
-+++ b/ipaserver/plugins/baseldap.py
-@@ -121,6 +121,8 @@ global_output_params = (
-     Str('memberof_hbacrule?',
-         label='Member of HBAC rule',
-     ),
-+    Str('memberof_subid?',
-+        label='Subordinate ids',),
-     Str('member_idoverrideuser?',
-         label=_('Member ID user overrides'),),
-     Str('memberindirect_idoverrideuser?',
-@@ -795,7 +797,13 @@ class LDAPObject(Object):
- 
-         dn = entry.dn
-         filter = self.backend.make_filter(
--            {'member': dn, 'memberuser': dn, 'memberhost': dn})
-+            {
-+                'member': dn,
-+                'memberuser': dn,
-+                'memberhost': dn,
-+                'ipaowner': dn
-+            }
-+        )
-         try:
-             result = self.backend.get_entries(
-                 self.api.env.basedn,
-diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
-index 12ff03c2302ff08aabb9369306965e0c125724f8..14a71b2217d3370701662b35c43f4207c1ab9b95 100644
---- a/ipaserver/plugins/baseuser.py
-+++ b/ipaserver/plugins/baseuser.py
-@@ -17,10 +17,9 @@
- # You should have received a copy of the GNU General Public License
- # along with this program.  If not, see <http://www.gnu.org/licenses/>.
- 
--import random
- import six
- 
--from ipalib import api, errors, output, constants
-+from ipalib import api, errors, constants
- from ipalib import (
-     Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
- from ipalib.parameters import Principal, Certificate
-@@ -28,9 +27,9 @@ from ipalib.plugable import Registry
- from .baseldap import (
-     DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
-     LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute,
--    LDAPQuery, LDAPAddMember, LDAPRemoveMember,
-+    LDAPAddMember, LDAPRemoveMember,
-     LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
--    add_missing_object_class, DNA_MAGIC, pkey_to_value, entry_to_dict
-+    add_missing_object_class
- )
- from ipaserver.plugins.service import (validate_realm, normalize_principal)
- from ipalib.request import context
-@@ -162,7 +161,7 @@ class baseuser(LDAPObject):
-     possible_objectclasses = [
-         'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
-         'ipatokenradiusproxyuser', 'ipacertmapobject',
--        'ipantuserattrs', 'ipasubordinateid',
-+        'ipantuserattrs',
-     ]
-     disallow_object_classes = ['krbticketpolicyaux']
-     permission_filter_objectclasses = ['posixaccount']
-@@ -183,13 +182,13 @@ class baseuser(LDAPObject):
-         'krbprincipalname', 'loginshell',
-         'mail', 'telephonenumber', 'title', 'nsaccountlock',
-         'uidnumber', 'gidnumber', 'sshpubkeyfp',
--        'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
--        'ipasubgidcount',
-     ]
-     uuid_attribute = 'ipauniqueid'
-     attribute_members = {
-         'manager': ['user'],
--        'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
-+        'memberof': [
-+            'group', 'netgroup', 'role', 'hbacrule', 'sudorule', 'subid'
-+        ],
-         'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
-     }
-     allow_rename = True
-@@ -432,41 +431,6 @@ class baseuser(LDAPObject):
-                     'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:',
-                     'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'),
-                 ),
--        Int(
--            'ipasubuidnumber?',
--            label=_('SubUID range start'),
--            cli_name='subuid',
--            doc=_('Start value for subordinate user ID (subuid) range'),
--            minvalue=constants.SUBID_RANGE_START,
--            maxvalue=constants.SUBID_RANGE_MAX,
--        ),
--        Int(
--            'ipasubuidcount?',
--            label=_('SubUID range size'),
--            cli_name='subuidcount',
--            doc=_('Subordinate user ID count'),
--            flags={'no_create', 'no_update', 'no_search'},
--            minvalue=constants.SUBID_COUNT,
--            maxvalue=constants.SUBID_COUNT,
--        ),
--        Int(
--            'ipasubgidnumber?',
--            label=_('SubGID range start'),
--            cli_name='subgid',
--            doc=_('Start value for subordinate group ID (subgid) range'),
--            flags={'no_create', 'no_update'},
--            minvalue=constants.SUBID_RANGE_START,
--            maxvalue=constants.SUBID_RANGE_MAX,
--        ),
--        Int(
--            'ipasubgidcount?',
--            label=_('SubGID range size'),
--            cli_name='subgidcount',
--            doc=_('Subordinate group ID count'),
--            flags={'no_create', 'no_update', 'no_search'},
--            minvalue=constants.SUBID_COUNT,
--            maxvalue=constants.SUBID_COUNT,
--        ),
-     )
- 
-     def normalize_and_validate_email(self, email, config=None):
-@@ -564,131 +528,6 @@ class baseuser(LDAPObject):
-         except KeyError:
-             pass
- 
--    def handle_subordinate_ids(self, ldap, dn, entry_attrs):
--        """Handle ipaSubordinateId object class
--        """
--        obj_classes = entry_attrs.get("objectclass")
--        new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
--        new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
--
--        # entry has object class ipaSubordinateId
--        # default to auto-assigment of subuids
--        if (
--            new_subuid is None
--            and obj_classes is not None
--            and self.has_objectclass(obj_classes, "ipasubordinateid")
--        ):
--            new_subuid = DNA_MAGIC
--
--        # neither auto-assignment nor explicit assignment
--        if new_subuid is None:
--            # nothing to do
--            return False
--
--        # enforce subuid == subgid
--        if new_subgid is not None and new_subgid != new_subuid:
--            raise errors.ValidationError(
--                name="ipasubgidnumber",
--                error=_("subgidnumber must be equal to subuidnumber")
--            )
--
--        self.set_subordinate_ids(ldap, dn, entry_attrs, new_subuid)
--        return True
--
--    def set_subordinate_ids(self, ldap, dn, entry_attrs, subuid):
--        """Set subuid value of an entry
--
--        Takes care of objectclass and sibbling attributes
--        """
--        if "objectclass" in entry_attrs:
--            obj_classes = entry_attrs["objectclass"]
--        else:
--            _entry_attrs = ldap.get_entry(dn, ["objectclass"])
--            entry_attrs["objectclass"] = _entry_attrs["objectclass"]
--            obj_classes = entry_attrs["objectclass"]
--
--        if not self.has_objectclass(obj_classes, "ipasubordinateid"):
--            # could append ipasubordinategid and ipasubordinateuid, too
--            obj_classes.append("ipasubordinateid")
--
--        # XXX HACK, remove later
--        if subuid == DNA_MAGIC:
--            subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
--
--        entry_attrs["ipasubuidnumber"] = subuid
--        # enforice subuid == subgid for now
--        entry_attrs["ipasubgidnumber"] = subuid
--        # hard-coded constants
--        entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
--        entry_attrs["ipasubgidcount"] = constants.SUBID_COUNT
--
--    def get_subid_match_candidate_filter(
--        self, ldap, *, subuid, subgid, extra_filters=(), offset=None,
--    ):
--        """Create LDAP filter to locate matching/overlapping subids
--        """
--        if subuid is None and subgid is None:
--            raise ValueError("subuid and subgid are both None")
--        if offset is None:
--            # assumes that no subordinate count is larger than SUBID_COUNT
--            offset = constants.SUBID_COUNT - 1
--
--        class_filters = "(objectclass=ipasubordinateid)"
--        subid_filters = []
--        if subuid is not None:
--            subid_filters.append(
--                ldap.combine_filters(
--                    [
--                        f"(ipasubuidnumber>={subuid - offset})",
--                        f"(ipasubuidnumber<={subuid + offset})",
--                    ],
--                    rules=ldap.MATCH_ALL
--                )
--            )
--        if subgid is not None:
--            subid_filters.append(
--                ldap.combine_filters(
--                    [
--                        f"(ipasubgidnumber>={subgid - offset})",
--                        f"(ipasubgidnumber<={subgid + offset})",
--                    ],
--                    rules=ldap.MATCH_ALL
--                )
--            )
--
--        subid_filters = ldap.combine_filters(
--            subid_filters, rules=ldap.MATCH_ANY
--        )
--        filters = [class_filters, subid_filters]
--        filters.extend(extra_filters)
--        return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
--
--    def _fake_dna_plugin(self, ldap, dn, entry_attrs):
--        """XXX HACK, remove when 389-DS DNA plugin supports steps"""
--        uidnumber = entry_attrs.single_value.get("uidnumber")
--        if uidnumber is None:
--            entry = ldap.get_entry(dn, ["uidnumber"])
--            uidnumber = entry.single_value["uidnumber"]
--        uidnumber = int(uidnumber)
--
--        if uidnumber == DNA_MAGIC:
--            return (
--                3221225472
--                + random.randint(1, 16382) * constants.SUBID_COUNT
--            )
--
--        if not hasattr(context, "idrange_ipabaseid"):
--            range_name = f"{self.api.env.realm}_id_range"
--            range = self.api.Command.idrange_show(range_name)["result"]
--            context.idrange_ipabaseid = int(range["ipabaseid"][0])
--
--        range_start = context.idrange_ipabaseid
--
--        assert uidnumber >= range_start
--        assert uidnumber < range_start + 2**14
--
--        return (uidnumber - range_start) * constants.SUBID_COUNT + 2**31
--
- 
- class baseuser_add(LDAPCreate):
-     """
-@@ -699,7 +538,6 @@ class baseuser_add(LDAPCreate):
-         assert isinstance(dn, DN)
-         set_krbcanonicalname(entry_attrs)
-         self.obj.convert_usercertificate_pre(entry_attrs)
--        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
-         if entry_attrs.get('ipatokenradiususername', None):
-             add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
-                                      entry_attrs, update=False)
-@@ -852,7 +690,6 @@ class baseuser_mod(LDAPUpdate):
- 
-         self.check_objectclass(ldap, dn, entry_attrs)
-         self.obj.convert_usercertificate_pre(entry_attrs)
--        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
-         self.preserve_krbprincipalname_pre(ldap, entry_attrs, *keys, **options)
-         update_samba_attrs(ldap, dn, entry_attrs, **options)
- 
-@@ -1133,98 +970,3 @@ class baseuser_remove_certmapdata(ModCertMapData,
-                                   LDAPRemoveAttribute):
-     __doc__ = _("Remove one or more certificate mappings from the user entry.")
-     msg_summary = _('Removed certificate mappings from user "%(value)s"')
--
--
--class baseuser_auto_subid(LDAPQuery):
--    __doc__ = _("Auto-assign subuid and subgid range to user entry")
--
--    has_output = output.standard_entry
--
--    def execute(self, cn, **options):
--        ldap = self.obj.backend
--        dn = self.obj.get_dn(cn)
--
--        try:
--            entry_attrs = ldap.get_entry(
--                dn, ["objectclass", "ipasubuidnumber"]
--            )
--        except errors.NotFound:
--            raise self.obj.handle_not_found(cn)
--
--        if "ipasubuidnumber" in entry_attrs:
--            raise errors.AlreadyContainsValueError(attr="ipasubuidnumber")
--
--        self.obj.set_subordinate_ids(ldap, dn, entry_attrs, subuid=DNA_MAGIC)
--        ldap.update_entry(entry_attrs)
--
--        # fetch updated entry (use search display attribute to show subids)
--        if options.get('all', False):
--            attrs_list = ['*'] + self.obj.search_display_attributes
--        else:
--            attrs_list = set(self.obj.search_display_attributes)
--            attrs_list.update(entry_attrs.keys())
--            if options.get('no_members', False):
--                attrs_list.difference_update(self.obj.attribute_members)
--            attrs_list = list(attrs_list)
--
--        entry = self._exc_wrapper((cn,), options, ldap.get_entry)(
--            dn, attrs_list
--        )
--        entry_attrs = entry_to_dict(entry, **options)
--        entry_attrs['dn'] = dn
--
--        return dict(result=entry_attrs, value=pkey_to_value(cn, options))
--
--
--class baseuser_match_subid(baseuser_find):
--    __doc__ = _("Match users by any subordinate uid in their range")
--
--    _subid_attrs = {
--        "ipasubuidnumber",
--        "ipasubuidcount",
--        "ipasubgidnumber",
--        "ipasubgidcount"
--    }
--
--    def get_options(self):
--        base_options = {p.name for p in self.obj.takes_params}
--        for option in super().get_options():
--            if option.name == "ipasubuidnumber":
--                yield option.clone(
--                    label=_('SubUID match'),
--                    doc=_('Match value for subordinate user ID'),
--                    required=True,
--                )
--            elif option.name not in base_options:
--                # raw, version
--                yield option.clone()
--
--    def pre_callback(
--        self, ldap, filters, attrs_list, base_dn, scope, *args, **options
--    ):
--        # search for candidates in range
--        # Code assumes that no subordinate count is larger than SUBID_COUNT
--        filters = self.obj.get_subid_match_candidate_filter(
--            ldap, subuid=options["ipasubuidnumber"], subgid=None,
--        )
--        # always include subid attributes
--        for missing in self._subid_attrs.difference(attrs_list):
--            attrs_list.append(missing)
--
--        return filters, base_dn, scope
--
--    def post_callback(self, ldap, entries, truncated, *args, **options):
--        # filter out mismatches manually
--        osubuid = options["ipasubuidnumber"]
--        new_entries = []
--        for entry in entries:
--            esubuid = int(entry.single_value["ipasubuidnumber"])
--            esubcount = int(entry.single_value["ipasubuidcount"])
--            minsubuid = esubuid
--            maxsubuid = esubuid + esubcount - 1
--            if minsubuid <= osubuid <= maxsubuid:
--                new_entries.append(entry)
--
--        entries[:] = new_entries
--
--        return truncated
-diff --git a/ipaserver/plugins/config.py b/ipaserver/plugins/config.py
-index ace66e589e50dac098aefd6b393b5e835cac9d7f..3526153ec117a05846daca7d42447ff50b5b7934 100644
---- a/ipaserver/plugins/config.py
-+++ b/ipaserver/plugins/config.py
-@@ -121,6 +121,7 @@ class config(LDAPObject):
-         'ipapwdexpadvnotify', 'ipaselinuxusermaporder',
-         'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata',
-         'ipauserauthtype', 'ipadomainresolutionorder', 'ipamaxhostnamelength',
-+        'ipauserdefaultsubordinateid',
-     ]
-     container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'))
-     permission_filter_objectclasses = ['ipaguiconfig']
-@@ -142,7 +143,7 @@ class config(LDAPObject):
-                 'ipasearchrecordslimit', 'ipasearchtimelimit',
-                 'ipauserauthtype', 'ipauserobjectclasses',
-                 'ipausersearchfields', 'ipacustomfields',
--                'ipamaxhostnamelength',
-+                'ipamaxhostnamelength', 'ipauserdefaultsubordinateid',
-             },
-         },
-     }
-@@ -261,6 +262,11 @@ class config(LDAPObject):
-             values=(u'password', u'radius', u'otp',
-                     u'pkinit', u'hardened', u'disabled'),
-         ),
-+        Bool('ipauserdefaultsubordinateid?',
-+             cli_name='user_default_subid',
-+             label=_('Enable adding subids to new users'),
-+             doc=_('Enable adding subids to new users'),
-+             ),
-         Str(
-             'ipa_master_server*',
-             label=_('IPA masters'),
-diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
-index 199838b199eb4cdabf597bd34d571d05547fd32e..5ef940c2b88cc2b132a15d619772349b30731306 100644
---- a/ipaserver/plugins/internal.py
-+++ b/ipaserver/plugins/internal.py
-@@ -1547,7 +1547,7 @@ class i18n_messages(Command):
-                     "Drive to mount a home directory"
-                 ),
-             },
--            "subordinate": {
-+            "subid": {
-                 "identity": _("Subordinate user and group id"),
-                 "subuidnumber": _("Subordinate user id"),
-                 "subuidcount": _("Subordinate user id count"),
-diff --git a/ipaserver/plugins/subid.py b/ipaserver/plugins/subid.py
-new file mode 100644
-index 0000000000000000000000000000000000000000..7d9a2f33e84bc7cdf17900346343e49d5eda0d8c
---- /dev/null
-+++ b/ipaserver/plugins/subid.py
-@@ -0,0 +1,608 @@
-+#
-+# Copyright (C) 2021  FreeIPA Contributors see COPYING for license
-+#
-+
-+import random
-+import uuid
-+
-+from ipalib import api
-+from ipalib import constants
-+from ipalib import errors
-+from ipalib import output
-+from ipalib.plugable import Registry
-+from ipalib.parameters import Int, Str
-+from ipalib.request import context
-+from ipalib.text import _, ngettext
-+from ipapython.dn import DN
-+
-+from .baseldap import (
-+    LDAPObject,
-+    LDAPCreate,
-+    LDAPDelete,
-+    LDAPUpdate,
-+    LDAPSearch,
-+    LDAPRetrieve,
-+    LDAPQuery,
-+    DNA_MAGIC,
-+)
-+
-+__doc__ = _(
-+    """
-+Subordinate ids
-+
-+Manage subordinate user and group ids for users
-+
-+EXAMPLES:
-+
-+ Auto-assign a subordinate id range to current user
-+   ipa subid-generate
-+
-+ Auto-assign a subordinate id range to user alice:
-+   ipa subid-generate --owner=alice
-+
-+ Find subordinate ids for user alice:
-+   ipa subid-find --owner=alice
-+
-+ Match entry by any subordinate uid in range:
-+   ipa subid-match --subuid=2147483649
-+"""
-+)
-+
-+register = Registry()
-+
-+
-+@register()
-+class subid(LDAPObject):
-+    """Subordinate id object."""
-+
-+    container_dn = api.env.container_subids
-+
-+    object_name = _("Subordinate id")
-+    object_name_plural = _("Subordinate ids")
-+    label = _("Subordinate ids")
-+    label_singular = _("Subordinate id")
-+
-+    object_class = ["ipasubordinateidentry"]
-+    possible_objectclasses = [
-+        "ipasubordinategid",
-+        "ipasubordinateuid",
-+        "ipasubordinateid",
-+    ]
-+    default_attributes = [
-+        "ipauniqueid",
-+        "ipaowner",
-+        "ipasubuidnumber",
-+        "ipasubuidcount",
-+        "ipasubgidnumber",
-+        "ipasubgidcount",
-+    ]
-+    allow_rename = False
-+
-+    permission_filter_objectclasses_string = (
-+        "(objectclass=ipasubordinateidentry)"
-+    )
-+    managed_permissions = {
-+        # all authenticated principals can read subordinate id information
-+        "System: Read Subordinate Id Attributes": {
-+            "ipapermbindruletype": "all",
-+            "ipapermright": {"read", "search", "compare"},
-+            "ipapermtargetfilter": [
-+                permission_filter_objectclasses_string,
-+            ],
-+            "ipapermdefaultattr": {
-+                "objectclass",
-+                "ipauniqueid",
-+                "description",
-+                "ipaowner",
-+                "ipasubuidnumber",
-+                "ipasubuidcount",
-+                "ipasubgidnumber",
-+                "ipasubgidcount",
-+            },
-+        },
-+        "System: Read Subordinate Id Count": {
-+            "ipapermbindruletype": "all",
-+            "ipapermright": {"read", "search", "compare"},
-+            "ipapermtargetfilter": [],
-+            "ipapermtarget": DN(container_dn, api.env.basedn),
-+            "ipapermdefaultattr": {"numSubordinates"},
-+        },
-+        # user administrators can remove subordinate ids or update the
-+        # ipaowner attribute. This enables user admins to remove users
-+        # with assigned subids or move them to staging area (--preserve).
-+        "System: Manage Subordinate Ids": {
-+            "ipapermright": {"write"},
-+            "ipapermtargetfilter": [
-+                permission_filter_objectclasses_string,
-+            ],
-+            "ipapermdefaultattr": {
-+                "description",
-+                "ipaowner",  # allow user admins to preserve users
-+            },
-+            "default_privileges": {"User Administrators"},
-+        },
-+        "System: Remove Subordinate Ids": {
-+            "ipapermright": {"delete"},
-+            "ipapermtargetfilter": [
-+                permission_filter_objectclasses_string,
-+            ],
-+            "default_privileges": {"User Administrators"},
-+        },
-+    }
-+
-+    takes_params = (
-+        Str(
-+            "ipauniqueid",
-+            cli_name="id",
-+            label=_("Unique ID"),
-+            primary_key=True,
-+            flags={"optional_create"},
-+        ),
-+        Str(
-+            "description?",
-+            cli_name="desc",
-+            label=_("Description"),
-+            doc=_("Subordinate id description"),
-+        ),
-+        Str(
-+            "ipaowner",
-+            cli_name="owner",
-+            label=_("Owner"),
-+            doc=_("Owning user of subordinate id entry"),
-+            flags={"no_update"},
-+        ),
-+        Int(
-+            "ipasubuidnumber?",
-+            label=_("SubUID range start"),
-+            cli_name="subuid",
-+            doc=_("Start value for subordinate user ID (subuid) range"),
-+            flags={"no_update"},
-+            minvalue=constants.SUBID_RANGE_START,
-+            maxvalue=constants.SUBID_RANGE_MAX,
-+        ),
-+        Int(
-+            "ipasubuidcount?",
-+            label=_("SubUID range size"),
-+            cli_name="subuidcount",
-+            doc=_("Subordinate user ID count"),
-+            flags={"no_create", "no_update", "no_search"},  # auto-assigned
-+            minvalue=constants.SUBID_COUNT,
-+            maxvalue=constants.SUBID_COUNT,
-+        ),
-+        Int(
-+            "ipasubgidnumber?",
-+            label=_("SubGID range start"),
-+            cli_name="subgid",
-+            doc=_("Start value for subordinate group ID (subgid) range"),
-+            flags={"no_create", "no_update"},  # auto-assigned
-+            minvalue=constants.SUBID_RANGE_START,
-+            maxvalue=constants.SUBID_RANGE_MAX,
-+        ),
-+        Int(
-+            "ipasubgidcount?",
-+            label=_("SubGID range size"),
-+            cli_name="subgidcount",
-+            doc=_("Subordinate group ID count"),
-+            flags={"no_create", "no_update", "no_search"},  # auto-assigned
-+            minvalue=constants.SUBID_COUNT,
-+            maxvalue=constants.SUBID_COUNT,
-+        ),
-+    )
-+
-+    def fixup_objectclass(self, entry_attrs):
-+        """Add missing object classes to entry"""
-+        has_subuid = "ipasubuidnumber" in entry_attrs
-+        has_subgid = "ipasubgidnumber" in entry_attrs
-+
-+        candicates = set(self.object_class)
-+        if has_subgid:
-+            candicates.add("ipasubordinategid")
-+        if has_subuid:
-+            candicates.add("ipasubordinateuid")
-+        if has_subgid and has_subuid:
-+            candicates.add("ipasubordinateid")
-+
-+        entry_oc = entry_attrs.setdefault("objectclass", [])
-+        current_oc = {x.lower() for x in entry_oc}
-+        for oc in candicates.difference(current_oc):
-+            entry_oc.append(oc)
-+
-+    def handle_duplicate_entry(self, *keys):
-+        if hasattr(context, "subid_owner_dn"):
-+            uid = context.subid_owner_dn[0].value
-+            msg = _(
-+                '%(oname)s with with name "%(pkey)s" or for user "%(uid)s" '
-+                "already exists."
-+            ) % {
-+                "uid": uid,
-+                "pkey": keys[-1] if keys else "",
-+                "oname": self.object_name,
-+            }
-+            raise errors.DuplicateEntry(message=msg) from None
-+        else:
-+            super().handle_duplicate_entry(*keys)
-+
-+    def convert_owner(self, entry_attrs, options):
-+        """Change owner from DN to uid string"""
-+        if not options.get("raw", False) and "ipaowner" in entry_attrs:
-+            userobj = self.api.Object.user
-+            entry_attrs["ipaowner"] = [
-+                userobj.get_primary_key_from_dn(entry_attrs["ipaowner"][0])
-+            ]
-+
-+    def get_owner_dn(self, *keys, **options):
-+        """Get owning user entry entry (username or DN)"""
-+        owner = keys[-1]
-+        userobj = self.api.Object.user
-+        if isinstance(owner, DN):
-+            # it's already a DN, validate it's either an active or preserved
-+            # user. Ref integrity plugin checks that it's not a dangling DN.
-+            user_dns = (
-+                DN(userobj.active_container_dn, self.api.env.basedn),
-+                DN(userobj.delete_container_dn, self.api.env.basedn),
-+            )
-+            if not owner.endswith(user_dns):
-+                raise errors.ValidationError(
-+                    name="ipaowner",
-+                    error=_("'%(dn)s is not a valid user") % {"dn": owner},
-+                )
-+            return owner
-+
-+        # similar to user.get_either_dn() but with error reporting and
-+        # returns an entry
-+        ldap = self.backend
-+        try:
-+            active_dn = userobj.get_dn(owner, **options)
-+            entry = ldap.get_entry(active_dn, attrs_list=[])
-+            return entry.dn
-+        except errors.NotFound:
-+            # fall back to deleted user
-+            try:
-+                delete_dn = userobj.get_delete_dn(owner, **options)
-+                entry = ldap.get_entry(delete_dn, attrs_list=[])
-+                return entry.dn
-+            except errors.NotFound:
-+                raise userobj.handle_not_found(owner)
-+
-+    def handle_subordinate_ids(self, ldap, dn, entry_attrs):
-+        """Handle ipaSubordinateId object class"""
-+        new_subuid = entry_attrs.single_value.get("ipasubuidnumber")
-+        new_subgid = entry_attrs.single_value.get("ipasubgidnumber")
-+
-+        if new_subuid is None:
-+            new_subuid = DNA_MAGIC
-+
-+        # enforce subuid == subgid
-+        if new_subgid is not None and new_subgid != new_subuid:
-+            raise errors.ValidationError(
-+                name="ipasubgidnumber",
-+                error=_("subgidnumber must be equal to subuidnumber"),
-+            )
-+
-+        self.set_subordinate_ids(ldap, dn, entry_attrs, new_subuid)
-+        return True
-+
-+    def set_subordinate_ids(self, ldap, dn, entry_attrs, subuid):
-+        """Set subuid value of an entry
-+
-+        Takes care of objectclass and sibbling attributes
-+        """
-+        if "objectclass" not in entry_attrs:
-+            _entry_attrs = ldap.get_entry(dn, ["objectclass"])
-+            entry_attrs["objectclass"] = _entry_attrs["objectclass"]
-+
-+        # XXX HACK, remove later
-+        if subuid == DNA_MAGIC:
-+            subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
-+
-+        entry_attrs["ipasubuidnumber"] = subuid
-+        # enforice subuid == subgid for now
-+        entry_attrs["ipasubgidnumber"] = subuid
-+        # hard-coded constants
-+        entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
-+        entry_attrs["ipasubgidcount"] = constants.SUBID_COUNT
-+
-+        self.fixup_objectclass(entry_attrs)
-+
-+    def get_subid_match_candidate_filter(
-+        self,
-+        ldap,
-+        *,
-+        subuid,
-+        subgid,
-+        extra_filters=(),
-+        offset=None,
-+    ):
-+        """Create LDAP filter to locate matching/overlapping subids"""
-+        if subuid is None and subgid is None:
-+            raise ValueError("subuid and subgid are both None")
-+        if offset is None:
-+            # assumes that no subordinate count is larger than SUBID_COUNT
-+            offset = constants.SUBID_COUNT - 1
-+
-+        class_filters = "(objectclass=ipasubordinateid)"
-+        subid_filters = []
-+        if subuid is not None:
-+            subid_filters.append(
-+                ldap.combine_filters(
-+                    [
-+                        f"(ipasubuidnumber>={subuid - offset})",
-+                        f"(ipasubuidnumber<={subuid + offset})",
-+                    ],
-+                    rules=ldap.MATCH_ALL,
-+                )
-+            )
-+        if subgid is not None:
-+            subid_filters.append(
-+                ldap.combine_filters(
-+                    [
-+                        f"(ipasubgidnumber>={subgid - offset})",
-+                        f"(ipasubgidnumber<={subgid + offset})",
-+                    ],
-+                    rules=ldap.MATCH_ALL,
-+                )
-+            )
-+
-+        subid_filters = ldap.combine_filters(
-+            subid_filters, rules=ldap.MATCH_ANY
-+        )
-+        filters = [class_filters, subid_filters]
-+        filters.extend(extra_filters)
-+        return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
-+
-+    def _fake_dna_plugin(self, ldap, dn, entry_attrs):
-+        """XXX HACK, remove when 389-DS DNA plugin supports steps"""
-+        return (
-+            constants.SUBID_RANGE_START
-+            + random.randint(1, 32764 - 2) * constants.SUBID_COUNT
-+        )
-+
-+
-+@register()
-+class subid_add(LDAPCreate):
-+    __doc__ = _("Add a new subordinate id.")
-+    msg_summary = _('Added subordinate id "%(value)s"')
-+
-+    # internal command, use subid-auto to auto-assign subids
-+    NO_CLI = True
-+
-+    def pre_callback(
-+        self, ldap, dn, entry_attrs, attrs_list, *keys, **options
-+    ):
-+        # XXX let ref integrity plugin validate DN?
-+        owner_dn = self.obj.get_owner_dn(entry_attrs["ipaowner"], **options)
-+        context.subid_owner_dn = owner_dn
-+        entry_attrs["ipaowner"] = owner_dn
-+
-+        self.obj.handle_subordinate_ids(ldap, dn, entry_attrs)
-+        attrs_list.append("objectclass")
-+
-+        return dn
-+
-+    def execute(self, ipauniqueid=None, **options):
-+        if ipauniqueid is None:
-+            ipauniqueid = str(uuid.uuid4())
-+        return super().execute(ipauniqueid, **options)
-+
-+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-+        self.obj.convert_owner(entry_attrs, options)
-+        return super(subid_add, self).post_callback(
-+            ldap, dn, entry_attrs, *keys, **options
-+        )
-+
-+
-+@register()
-+class subid_del(LDAPDelete):
-+    __doc__ = _("Delete a subordinate id.")
-+    msg_summary = _('Deleted subordinate id "%(value)s"')
-+
-+    # internal command, subids cannot be removed
-+    NO_CLI = True
-+
-+
-+@register()
-+class subid_mod(LDAPUpdate):
-+    __doc__ = _("Modify a subordinate id.")
-+    msg_summary = _('Modified subordinate id "%(value)s"')
-+
-+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-+        self.obj.convert_owner(entry_attrs, options)
-+        return super(subid_mod, self).post_callback(
-+            ldap, dn, entry_attrs, *keys, **options
-+        )
-+
-+
-+@register()
-+class subid_find(LDAPSearch):
-+    __doc__ = _("Search for subordinate id.")
-+    msg_summary = ngettext(
-+        "%(count)d subordinate id matched",
-+        "%(count)d subordinate ids matched",
-+        0,
-+    )
-+
-+    def pre_callback(
-+        self, ldap, filters, attrs_list, base_dn, scope, *args, **options
-+    ):
-+        attrs_list.append("objectclass")
-+        return super(subid_find, self).pre_callback(
-+            ldap, filters, attrs_list, base_dn, scope, *args, **options
-+        )
-+
-+    def args_options_2_entry(self, *args, **options):
-+        entry_attrs = super(subid_find, self).args_options_2_entry(
-+            *args, **options
-+        )
-+        owner = entry_attrs.get("ipaowner")
-+        if owner is not None:
-+            owner_dn = self.obj.get_owner_dn(owner, **options)
-+            entry_attrs["ipaowner"] = owner_dn
-+        return entry_attrs
-+
-+    def post_callback(self, ldap, entries, truncated, *args, **options):
-+        for entry in entries:
-+            self.obj.convert_owner(entry, options)
-+        return super(subid_find, self).post_callback(
-+            ldap, entries, truncated, *args, **options
-+        )
-+
-+
-+@register()
-+class subid_show(LDAPRetrieve):
-+    __doc__ = _("Display information about a subordinate id.")
-+
-+    def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
-+        attrs_list.append("objectclass")
-+        return super(subid_show, self).pre_callback(
-+            ldap, dn, attrs_list, *keys, **options
-+        )
-+
-+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-+        self.obj.convert_owner(entry_attrs, options)
-+        return super(subid_show, self).post_callback(
-+            ldap, dn, entry_attrs, *keys, **options
-+        )
-+
-+
-+@register()
-+class subid_generate(LDAPQuery):
-+    __doc__ = _(
-+        "Generate and auto-assign subuid and subgid range to user entry"
-+    )
-+
-+    has_output = output.standard_entry
-+
-+    takes_options = LDAPQuery.takes_options + (
-+        Str(
-+            "ipaowner?",
-+            cli_name="owner",
-+            label=_("Owner"),
-+            doc=_("Owning user of subordinate id entry"),
-+        ),
-+    )
-+
-+    def get_args(self):
-+        return []
-+
-+    def execute(self, *keys, **options):
-+        owner_uid = options.get("ipaowner")
-+        # default to current user
-+        if owner_uid is None:
-+            owner_dn = DN(self.api.Backend.ldap2.conn.whoami_s()[4:])
-+            # validate it's a user and not a service or host
-+            owner_dn = self.obj.get_owner_dn(owner_dn)
-+            owner_uid = owner_dn[0].value
-+
-+        return self.api.Command.subid_add(
-+            description="auto-assigned subid",
-+            ipaowner=owner_uid,
-+            version=options["version"],
-+        )
-+
-+
-+@register()
-+class subid_match(subid_find):
-+    __doc__ = _("Match users by any subordinate uid in their range")
-+
-+    def get_options(self):
-+        base_options = {p.name for p in self.obj.takes_params}
-+        for option in super().get_options():
-+            if option.name == "ipasubuidnumber":
-+                yield option.clone(
-+                    label=_("SubUID match"),
-+                    doc=_("Match value for subordinate user ID"),
-+                    required=True,
-+                )
-+            elif option.name not in base_options:
-+                # raw, version
-+                yield option.clone()
-+
-+    def pre_callback(
-+        self, ldap, filters, attrs_list, base_dn, scope, *args, **options
-+    ):
-+        # search for candidates in range
-+        # Code assumes that no subordinate count is larger than SUBID_COUNT
-+        filters = self.obj.get_subid_match_candidate_filter(
-+            ldap,
-+            subuid=options["ipasubuidnumber"],
-+            subgid=None,
-+        )
-+        attrs_list.extend(self.obj.default_attributes)
-+
-+        return filters, base_dn, scope
-+
-+    def post_callback(self, ldap, entries, truncated, *args, **options):
-+        # filter out mismatches manually
-+        osubuid = options["ipasubuidnumber"]
-+        new_entries = []
-+        for entry in entries:
-+            esubuid = int(entry.single_value["ipasubuidnumber"])
-+            esubcount = int(entry.single_value["ipasubuidcount"])
-+            minsubuid = esubuid
-+            maxsubuid = esubuid + esubcount - 1
-+            if minsubuid <= osubuid <= maxsubuid:
-+                new_entries.append(entry)
-+
-+        entries[:] = new_entries
-+
-+        return truncated
-+
-+
-+@register()
-+class subid_stats(LDAPQuery):
-+    __doc__ = _("Subordinate id statistics")
-+
-+    takes_options = ()
-+    has_output = (
-+        output.summary,
-+        output.Entry("result"),
-+    )
-+
-+    def get_args(self):
-+        return ()
-+
-+    def get_remaining_dna(self, ldap, **options):
-+        base_dn = DN(
-+            self.api.env.container_dna_subordinate_ids, self.api.env.basedn
-+        )
-+        entries, _truncated = ldap.find_entries(
-+            "(objectClass=dnaSharedConfig)",
-+            attrs_list=["dnaRemainingValues"],
-+            base_dn=base_dn,
-+            scope=ldap.SCOPE_ONELEVEL,
-+        )
-+        return sum(
-+            int(entry.single_value["dnaRemainingValues"]) for entry in entries
-+        )
-+
-+    def get_idrange(self, ldap, **options):
-+        cn = f"{self.api.env.realm}_subid_range"
-+        result = self.api.Command.idrange_show(cn, version=options["version"])
-+        baseid = int(result["result"]["ipabaseid"][0])
-+        rangesize = int(result["result"]["ipaidrangesize"][0])
-+        return baseid, rangesize
-+
-+    def get_subid_assigned(self, ldap, **options):
-+        dn = DN(self.api.env.container_subids, self.api.env.basedn)
-+        entry = ldap.get_entry(dn=dn, attrs_list=["numSubordinates"])
-+        return int(entry.single_value["numSubordinates"])
-+
-+    def execute(self, *keys, **options):
-+        ldap = self.obj.backend
-+        dna_remaining = self.get_remaining_dna(ldap, **options)
-+        baseid, rangesize = self.get_idrange(ldap, **options)
-+        assigned_subids = self.get_subid_assigned(ldap, **options)
-+        remaining_subids = dna_remaining // constants.SUBID_COUNT
-+        return dict(
-+            summary=_("%(remaining)i remaining subordinate id ranges")
-+            % {
-+                "remaining": remaining_subids,
-+            },
-+            result=dict(
-+                baseid=baseid,
-+                rangesize=rangesize,
-+                dna_remaining=dna_remaining,
-+                assigned_subids=assigned_subids,
-+                remaining_subids=remaining_subids,
-+            ),
-+        )
-diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py
-index f89b3ad5d9c994fe1ceb3da560fde7cc5bf5155a..19d07e6d61a451a0b1177adf2cf8ae1b7fceeb67 100644
---- a/ipaserver/plugins/user.py
-+++ b/ipaserver/plugins/user.py
-@@ -51,8 +51,6 @@ from .baseuser import (
-     baseuser_remove_principal,
-     baseuser_add_certmapdata,
-     baseuser_remove_certmapdata,
--    baseuser_auto_subid,
--    baseuser_match_subid,
- )
- from .idviews import remove_ipaobject_overrides
- from ipalib.plugable import Registry
-@@ -205,8 +203,6 @@ class user(baseuser):
-             'ipapermright': {'read', 'search', 'compare'},
-             'ipapermdefaultattr': {
-                 'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
--                'ipasubuidnumber', 'ipasubuidcount', 'ipasubgidnumber',
--                'ipasubgidcount',
-             },
-             'fixup_function': fix_addressbook_permission_bindrule,
-         },
-@@ -670,6 +666,17 @@ class user_add(baseuser_add):
-                 # if both randompassword and userpassword options were used
-                 pass
- 
-+        # generate subid
-+        default_subid = config.single_value.get(
-+            'ipaUserDefaultSubordinateId', 'FALSE'
-+        )
-+        if default_subid == 'TRUE':
-+            result = self.api.Command.subid_generate(
-+                ipaowner=entry_attrs.single_value['uid'],
-+                version=options['version']
-+            )
-+            entry_attrs["memberOf"].append(result['result']['dn'])
-+
-         self.obj.get_preserved_attribute(entry_attrs, options)
- 
-         self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
-@@ -757,7 +764,9 @@ class user_del(baseuser_del):
-         # of OTP tokens.
-         check_protected_member(keys[-1])
- 
--        if not options.get('preserve', False):
-+        preserve = options.get('preserve', False)
-+
-+        if not preserve:
-             # Remove any ID overrides tied with this user
-             try:
-                 remove_ipaobject_overrides(self.obj.backend, self.obj.api, dn)
-@@ -780,6 +789,15 @@ class user_del(baseuser_del):
-             else:
-                 self.api.Command.otptoken_del(token)
- 
-+        # XXX: preserving doesn't work yet, see subordinate-ids.md
-+        # Delete all subid entries owned by this user.
-+        results = self.api.Command.subid_find(ipaowner=owner)["result"]
-+        for subid_entry in results:
-+            subid_pkey = self.api.Object.subid.get_primary_key_from_dn(
-+                subid_entry["dn"]
-+            )
-+            self.api.Command.subid_del(subid_pkey)
-+
-         return dn
- 
-     def execute(self, *keys, **options):
-@@ -829,6 +847,7 @@ class user_mod(baseuser_mod):
-         self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
-                                  **options)
-         validate_nsaccountlock(entry_attrs)
-+        # TODO: forward uidNumber changes and rename to subids
-         return dn
- 
-     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-@@ -1311,13 +1330,3 @@ class user_add_principal(baseuser_add_principal):
- class user_remove_principal(baseuser_remove_principal):
-     __doc__ = _('Remove principal alias from the user entry')
-     msg_summary = _('Removed aliases from user "%(value)s"')
--
--
--@register()
--class user_auto_subid(baseuser_auto_subid):
--    __doc__ = baseuser_auto_subid.__doc__
--
--
--@register()
--class user_match_subid(baseuser_match_subid):
--    __doc__ = baseuser_match_subid.__doc__
-diff --git a/ipatests/test_integration/test_subids.py b/ipatests/test_integration/test_subids.py
-index b462f22ac067f3e1e97ef3f6d63d4e14e4ae79af..48e58c26464f52605438afe865575e5ca4c8f1f8 100644
---- a/ipatests/test_integration/test_subids.py
-+++ b/ipatests/test_integration/test_subids.py
-@@ -17,6 +17,7 @@ class TestSubordinateId(IntegrationTest):
-     topology = "star"
- 
-     def _parse_result(self, result):
-+        # ipa CLI should get an --outform json option
-         info = {}
-         for line in result.stdout_text.split("\n"):
-             line = line.strip()
-@@ -42,39 +43,69 @@ class TestSubordinateId(IntegrationTest):
-                 info[k] = set(v)
-         return info
- 
--    def get_user(self, uid):
--        cmd = ["ipa", "user-show", "--all", "--raw", uid]
--        result = self.master.run_command(cmd)
--        return self._parse_result(result)
-+    def assert_subid_info(self, uid, info):
-+        assert info["ipauniqueid"]
-+        basedn = self.master.domain.basedn
-+        assert info["ipaowner"] == f"uid={uid},cn=users,cn=accounts,{basedn}"
-+        assert info["ipasubuidnumber"] == info["ipasubuidnumber"]
-+        assert info["ipasubuidnumber"] >= SUBID_RANGE_START
-+        assert info["ipasubuidnumber"] <= SUBID_RANGE_MAX
-+        assert info["ipasubuidcount"] == SUBID_COUNT
-+        assert info["ipasubgidnumber"] == info["ipasubgidnumber"]
-+        assert info["ipasubgidnumber"] == info["ipasubuidnumber"]
-+        assert info["ipasubgidcount"] == SUBID_COUNT
-+
-+    def assert_subid(self, uid, *, match):
-+        cmd = ["ipa", "subid-find", "--raw", "--owner", uid]
-+        result = self.master.run_command(cmd, raiseonerr=False)
-+        if not match:
-+            assert result.returncode >= 1
-+            if result.returncode == 1:
-+                assert "0 subordinate ids matched" in result.stdout_text
-+            elif result.returncode == 2:
-+                assert "user not found" in result.stderr_text
-+            return None
-+        else:
-+            assert result.returncode == 0
-+            assert "1 subordinate id matched" in result.stdout_text
-+            info = self._parse_result(result)
-+            self.assert_subid_info(uid, info)
-+            self.master.run_command(
-+                ["ipa", "subid-show", info["ipauniqueid"]]
-+            )
-+            return info
- 
--    def user_auto_subid(self, uid, **kwargs):
--        cmd = ["ipa", "user-auto-subid", uid]
-+    def subid_generate(self, uid, **kwargs):
-+        cmd = ["ipa", "subid-generate"]
-+        if uid is not None:
-+            cmd.extend(("--owner", uid))
-         return self.master.run_command(cmd, **kwargs)
- 
--    def test_auto_subid(self):
--        tasks.kinit_admin(self.master)
-+    def test_auto_generate_subid(self):
-         uid = "testuser_auto1"
--        tasks.user_add(self.master, uid)
--        info = self.get_user(uid)
--        assert "ipasubuidcount" not in info
-+        passwd = "Secret123"
-+        tasks.create_active_user(self.master, uid, password=passwd)
- 
--        self.user_auto_subid(uid)
--        info = self.get_user(uid)
--        assert "ipasubuidcount" in info
-+        tasks.kinit_admin(self.master)
-+        self.assert_subid(uid, match=False)
-+
-+        # add subid by name
-+        self.subid_generate(uid)
-+        info = self.assert_subid(uid, match=True)
-+
-+        # second generate fails due to unique index on ipaowner
-+        result = self.subid_generate(uid, raiseonerr=False)
-+        assert result.returncode > 0
-+        assert f'for user "{uid}" already exists' in result.stderr_text
- 
-+        # check matching
-         subuid = info["ipasubuidnumber"]
--        result = self.master.run_command(
--            ["ipa", "user-match-subid", f"--subuid={subuid}", "--raw"]
--        )
--        match = self._parse_result(result)
--        assert match["uid"] == uid
--        assert match["ipasubuidnumber"] == info["ipasubuidnumber"]
--        assert match["ipasubuidnumber"] >= SUBID_RANGE_START
--        assert match["ipasubuidnumber"] <= SUBID_RANGE_MAX
--        assert match["ipasubuidcount"] == SUBID_COUNT
--        assert match["ipasubgidnumber"] == info["ipasubgidnumber"]
--        assert match["ipasubgidnumber"] == match["ipasubuidnumber"]
--        assert match["ipasubgidcount"] == SUBID_COUNT
-+        for offset in (0, 1, 65535):
-+            result = self.master.run_command(
-+                ["ipa", "subid-match", f"--subuid={subuid + offset}", "--raw"]
-+            )
-+            match = self._parse_result(result)
-+            self.assert_subid_info(uid, match)
- 
-     def test_ipa_subid_script(self):
-         tasks.kinit_admin(self.master)
-@@ -85,34 +116,28 @@ class TestSubordinateId(IntegrationTest):
-             uid = f"testuser_script{i}"
-             users.append(uid)
-             tasks.user_add(self.master, uid)
--            info = self.get_user(uid)
--            assert "ipasubuidcount" not in info
-+            self.assert_subid(uid, match=False)
- 
-         cmd = [tool, "--verbose", "--group", "ipausers"]
-         self.master.run_command(cmd)
- 
-         for uid in users:
--            info = self.get_user(uid)
--            assert info["ipasubuidnumber"] >= SUBID_RANGE_START
--            assert info["ipasubuidnumber"] <= SUBID_RANGE_MAX
--            assert info["ipasubuidnumber"] == info["ipasubgidnumber"]
--            assert info["ipasubuidcount"] == SUBID_COUNT
--            assert info["ipasubuidcount"] == info["ipasubgidcount"]
-+            self.assert_subid(uid, match=True)
- 
-     def test_subid_selfservice(self):
--        tasks.kinit_admin(self.master)
--
--        uid = "testuser_selfservice1"
-+        uid1 = "testuser_selfservice1"
-+        uid2 = "testuser_selfservice2"
-         password = "Secret123"
-         role = "Subordinate ID Selfservice User"
- 
--        tasks.user_add(self.master, uid, password=password)
--        tasks.kinit_user(
--            self.master, uid, f"{password}\n{password}\n{password}\n"
--        )
--        info = self.get_user(uid)
--        assert "ipasubuidcount" not in info
--        result = self.user_auto_subid(uid, raiseonerr=False)
-+        tasks.create_active_user(self.master, uid1, password=password)
-+        tasks.create_active_user(self.master, uid2, password=password)
-+
-+        tasks.kinit_user(self.master, uid1, password=password)
-+        self.assert_subid(uid1, match=False)
-+        result = self.subid_generate(uid1, raiseonerr=False)
-+        assert result.returncode > 0
-+        result = self.subid_generate(None, raiseonerr=False)
-         assert result.returncode > 0
- 
-         tasks.kinit_admin(self.master)
-@@ -121,10 +146,14 @@ class TestSubordinateId(IntegrationTest):
-         )
- 
-         try:
--            tasks.kinit_user(self.master, uid, password)
--            self.user_auto_subid(uid)
--            info = self.get_user(uid)
--            assert "ipasubuidcount" in info
-+            tasks.kinit_user(self.master, uid1, password)
-+            self.subid_generate(uid1)
-+            self.assert_subid(uid1, match=True)
-+
-+            # add subid from whoami
-+            tasks.kinit_as_user(self.master, uid2, password=password)
-+            self.subid_generate(None)
-+            self.assert_subid(uid2, match=True)
-         finally:
-             tasks.kinit_admin(self.master)
-             self.master.run_command(
-@@ -140,45 +169,46 @@ class TestSubordinateId(IntegrationTest):
-         password = "Secret123"
- 
-         # create user administrator
--        tasks.user_add(self.master, uid_useradmin, password=password)
-+        tasks.create_active_user(
-+            self.master, uid_useradmin, password=password
-+        )
-         # add user to user admin group
-         tasks.kinit_admin(self.master)
-         self.master.run_command(
-             ["ipa", "role-add-member", role, f"--users={uid_useradmin}"],
-         )
-         # kinit as user admin
--        tasks.kinit_user(
--            self.master,
--            uid_useradmin,
--            f"{password}\n{password}\n{password}\n",
--        )
-+        tasks.kinit_user(self.master, uid_useradmin, password)
-+
-         # create new user as user admin
-         tasks.user_add(self.master, uid)
-         # assign new subid to user (with useradmin credentials)
--        self.user_auto_subid(uid)
--
--    def test_subordinate_default_objclass(self):
-+        self.subid_generate(uid)
-+
-+        # test that user admin can preserve and delete users with subids
-+        self.master.run_command(["ipa", "user-del", "--preserve", uid])
-+        # XXX does not work, see subordinate-ids.md
-+        # subid should still exist
-+        # self.assert_subid(uid, match=True)
-+        # final delete should remove the user and subid
-+        self.master.run_command(["ipa", "user-del", uid])
-+        self.assert_subid(uid, match=False)
-+
-+    def tset_subid_auto_assign(self):
-         tasks.kinit_admin(self.master)
-+        uid = "testuser_autoassign_user1"
- 
--        result = self.master.run_command(
--            ["ipa", "config-show", "--raw", "--all"]
-+        self.master.run_command(
-+            ["ipa", "config-mod", "--user-default-subid=true"]
-         )
--        info = self._parse_result(result)
--        usercls = info["ipauserobjectclasses"]
--        assert "ipasubordinateid" not in usercls
--
--        cmd = [
--            "ipa",
--            "config-mod",
--            "--addattr",
--            "ipaUserObjectClasses=ipasubordinateid",
--        ]
--        self.master.run_command(cmd)
- 
--        uid = "testuser_usercls1"
--        tasks.user_add(self.master, uid)
--        info = self.get_user(uid)
--        assert "ipasubuidcount" in info
-+        try:
-+            tasks.user_add(self.master, uid)
-+            self.assert_subid(uid, match=True)
-+        finally:
-+            self.master.run_command(
-+                ["ipa", "config-mod", "--user-default-subid=false"]
-+            )
- 
-     def test_idrange_subid(self):
-         tasks.kinit_admin(self.master)
-@@ -199,3 +229,7 @@ class TestSubordinateId(IntegrationTest):
-         assert info["ipanttrusteddomainsid"].startswith(
-             "S-1-5-21-738065-838566-"
-         )
-+
-+    def test_subid_stats(self):
-+        tasks.kinit_admin(self.master)
-+        self.master.run_command(["ipa", "subid-stats"])
--- 
-2.26.3
-
diff --git a/SOURCES/0010-Use-389-DS-dnaInterval-setting-to-assign-intervals.patch b/SOURCES/0010-Use-389-DS-dnaInterval-setting-to-assign-intervals.patch
deleted file mode 100644
index 33c9237..0000000
--- a/SOURCES/0010-Use-389-DS-dnaInterval-setting-to-assign-intervals.patch
+++ /dev/null
@@ -1,113 +0,0 @@
-From c9bae715b24df0f5476bdb70a2209d5f55e46a93 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Fri, 21 May 2021 09:26:33 +0200
-Subject: [PATCH] Use 389-DS' dnaInterval setting to assign intervals
-
-Signed-off-by: Christian Heimes <cheimes@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- freeipa.spec.in                 |  3 ++-
- install/share/dna.ldif          |  1 +
- install/updates/73-subid.update |  7 ++-----
- ipaserver/plugins/subid.py      | 14 +-------------
- 4 files changed, 6 insertions(+), 19 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index 044e3559975c399f6697d4da94b5a059eb5b407c..fa649cf4e1abe8e9928ef340a66d48d78f7e3521 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -106,8 +106,9 @@
- %global python_ldap_version 3.1.0-1
- 
- # Make sure to use 389-ds-base versions that fix https://github.com/389ds/389-ds-base/issues/4700
-+# and has DNA interval enabled
- %if 0%{?fedora} < 34
--%global ds_version %{lua: local v={}; v['32']='1.4.3.20-2'; v['33']='1.4.4.16-1'; print(v[rpm.expand('%{fedora}')])}
-+%global ds_version 1.4.4.16-1
- %else
- %global ds_version 2.0.5-1
- %endif
-diff --git a/install/share/dna.ldif b/install/share/dna.ldif
-index 735faab8261feef59486f7c933b01c57ad511166..9023fcd7db5a2c121c493559e2546c85c0daf69a 100644
---- a/install/share/dna.ldif
-+++ b/install/share/dna.ldif
-@@ -31,6 +31,7 @@ dnaScope: $SUFFIX
- dnaThreshold: eval($SUBID_DNA_THRESHOLD)
- dnaSharedCfgDN: cn=subordinate-ids,cn=dna,cn=ipa,cn=etc,$SUFFIX
- dnaExcludeScope: cn=provisioning,$SUFFIX
-+dnaInterval: eval($SUBID_COUNT)
- # TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
- # dnaIntervalAttr: ipasubuidcount
- # dnaIntervalAttr: ipasubgidcount
-diff --git a/install/updates/73-subid.update b/install/updates/73-subid.update
-index 1aa43822a8b8c220583b81e08d70b648ca594363..e10703aa3f9528751233ddebe00b8c8c8fc5ed3f 100644
---- a/install/updates/73-subid.update
-+++ b/install/updates/73-subid.update
-@@ -62,12 +62,8 @@ default:member: cn=Subordinate ID Administrators,cn=privileges,cn=pbac,$SUFFIX
- # The delete-when-empty check is required because IPA uses MOD_REPLACE to
- # set attributes, see https://github.com/389ds/389-ds-base/issues/4597.
- #
--# TODO: remove (ipasubuidnumber>=eval($SUBID_RANGE_START) from
--# self-service permission when 389-DS' DNA plugin supports dnaStepAttr and
--# fake_dna_plugin hack has been removed.
--#
- 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>=eval($SUBID_RANGE_START))(ipasubuidnumber=-1)) && ipasubuidcount:(ipasubuidcount=eval($SUBID_COUNT)) && ipasubgidnumber:(|(ipasubgidnumber>=eval($SUBID_RANGE_START))(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) && 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
-@@ -90,6 +86,7 @@ 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)
- # TODO: enable when 389-DS' DNA plugin supports dnaStepAttr
- # add: dnaIntervalAttr: ipasubuidcount
- # add: dnaIntervalAttr: ipasubgidcount
-diff --git a/ipaserver/plugins/subid.py b/ipaserver/plugins/subid.py
-index 7d9a2f33e84bc7cdf17900346343e49d5eda0d8c..440f24ee627f0736100f63026158c564b04520c2 100644
---- a/ipaserver/plugins/subid.py
-+++ b/ipaserver/plugins/subid.py
-@@ -2,7 +2,6 @@
- # Copyright (C) 2021  FreeIPA Contributors see COPYING for license
- #
- 
--import random
- import uuid
- 
- from ipalib import api
-@@ -291,12 +290,8 @@ class subid(LDAPObject):
-             _entry_attrs = ldap.get_entry(dn, ["objectclass"])
-             entry_attrs["objectclass"] = _entry_attrs["objectclass"]
- 
--        # XXX HACK, remove later
--        if subuid == DNA_MAGIC:
--            subuid = self._fake_dna_plugin(ldap, dn, entry_attrs)
--
-         entry_attrs["ipasubuidnumber"] = subuid
--        # enforice subuid == subgid for now
-+        # enforce subuid == subgid for now
-         entry_attrs["ipasubgidnumber"] = subuid
-         # hard-coded constants
-         entry_attrs["ipasubuidcount"] = constants.SUBID_COUNT
-@@ -350,13 +345,6 @@ class subid(LDAPObject):
-         filters.extend(extra_filters)
-         return ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
- 
--    def _fake_dna_plugin(self, ldap, dn, entry_attrs):
--        """XXX HACK, remove when 389-DS DNA plugin supports steps"""
--        return (
--            constants.SUBID_RANGE_START
--            + random.randint(1, 32764 - 2) * constants.SUBID_COUNT
--        )
--
- 
- @register()
- class subid_add(LDAPCreate):
--- 
-2.26.3
-
diff --git a/SOURCES/0011-Fix-ipa-server-upgrade.patch b/SOURCES/0011-Fix-ipa-server-upgrade.patch
deleted file mode 100644
index 055275c..0000000
--- a/SOURCES/0011-Fix-ipa-server-upgrade.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From 21574b261cf0d346da48e34c0a5383736ca8798b Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Fri, 21 May 2021 14:56:32 +0200
-Subject: [PATCH] Fix ipa-server-upgrade
-
-Signed-off-by: Christian Heimes <cheimes@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/share/bootstrap-template.ldif | 2 +-
- install/updates/73-subid.update       | 2 +-
- ipaserver/install/ldapupdate.py       | 3 +++
- 3 files changed, 5 insertions(+), 2 deletions(-)
-
-diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
-index 16f2ef822eaf56dd68d4140b22a607539645b151..325eb8450c786899e7b5e4ae2ef8978f42a8425b 100644
---- a/install/share/bootstrap-template.ldif
-+++ b/install/share/bootstrap-template.ldif
-@@ -491,7 +491,7 @@ cn: ${REALM}_subid_range
- ipaBaseID: eval($SUBID_RANGE_START)
- ipaIDRangeSize: eval($SUBID_RANGE_SIZE)
- # HACK: RIDs to work around adtrust sidgen issue
--ipaBaseRID: eval($SUBID_RANGE_START - $IDRANGE_SIZE)
-+ipaBaseRID: eval($SUBID_BASE_RID)
- # 738065-838566 = IPA-SUB
- ipaNTTrustedDomainSID: S-1-5-21-738065-838566-$DOMAIN_HASH
- # HACK: "ipa-local-subid" range type causes issues with older SSSD clients
-diff --git a/install/updates/73-subid.update b/install/updates/73-subid.update
-index e10703aa3f9528751233ddebe00b8c8c8fc5ed3f..890eb7f1f6f261af977f26b3457e765ee8e9791f 100644
---- a/install/updates/73-subid.update
-+++ b/install/updates/73-subid.update
-@@ -102,7 +102,7 @@ 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_RANGE_START - $IDRANGE_SIZE)
-+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
-diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
-index d0516dc3028366df5d03a960866abe72601aa4b6..06cb78e0b7dc2c82f0339c43228045d93b922288 100644
---- a/ipaserver/install/ldapupdate.py
-+++ b/ipaserver/install/ldapupdate.py
-@@ -59,8 +59,10 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-     """
-     if idstart is None:
-         idrange_size = None
-+        subid_base_rid = None
-     else:
-         idrange_size = idmax - idstart + 1
-+        subid_base_rid = constants.SUBID_RANGE_START - idrange_size
- 
-     return dict(
-         REALM=realm,
-@@ -81,6 +83,7 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         SUBID_RANGE_SIZE=constants.SUBID_RANGE_SIZE,
-         SUBID_RANGE_MAX=constants.SUBID_RANGE_MAX,
-         SUBID_DNA_THRESHOLD=constants.SUBID_DNA_THRESHOLD,
-+        SUBID_BASE_RID=subid_base_rid,
-         DOMAIN_HASH=murmurhash3(domain, len(domain), 0xdeadbeef),
-         MAX_DOMAIN_LEVEL=constants.MAX_DOMAIN_LEVEL,
-         MIN_DOMAIN_LEVEL=constants.MIN_DOMAIN_LEVEL,
--- 
-2.26.3
-
diff --git a/SOURCES/0012-Fix-oid-of-ipaUserDefaultSubordinateId.patch b/SOURCES/0012-Fix-oid-of-ipaUserDefaultSubordinateId.patch
deleted file mode 100644
index c964120..0000000
--- a/SOURCES/0012-Fix-oid-of-ipaUserDefaultSubordinateId.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From c8b4fd5bb773a73116350bf8e853246916fe87c2 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Tue, 15 Jun 2021 13:25:18 +0200
-Subject: [PATCH] Fix oid of ipaUserDefaultSubordinateId
-
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/share/60ipaconfig.ldif | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/install/share/60ipaconfig.ldif b/install/share/60ipaconfig.ldif
-index f84b38ead1d70ff408f5669029f1517b0c98ecf1..005c1dd11e37039132620f1d97f9662ffb8c8c59 100644
---- a/install/share/60ipaconfig.ldif
-+++ b/install/share/60ipaconfig.ldif
-@@ -47,7 +47,7 @@ attributeTypes: ( 2.16.840.1.113730.3.8.3.27 NAME 'ipaSELinuxUserMapOrder' DESC
- ## ipaMaxHostnameLength - maximum hostname length to allow
- attributeTypes: ( 2.16.840.1.113730.3.8.1.28 NAME 'ipaMaxHostnameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
- # ipaUserDefaultSubordinateId - if TRUE new user entries gain subordinate id by default
--attributeTypes: ( 2.16.840.1.113730.3.8.3.23.14 NAME 'ipaUserDefaultSubordinateId' DESC 'Enable adding user entries with subordinate id' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
-+attributeTypes: ( 2.16.840.1.113730.3.8.23.14 NAME 'ipaUserDefaultSubordinateId' DESC 'Enable adding user entries with subordinate id' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.9')
- ###############################################
- ##
- ## ObjectClasses
--- 
-2.26.3
-
diff --git a/SOURCES/0013-WebUI-Improve-subordinate-ids-user-workflow.patch b/SOURCES/0013-WebUI-Improve-subordinate-ids-user-workflow.patch
deleted file mode 100644
index f1b78ec..0000000
--- a/SOURCES/0013-WebUI-Improve-subordinate-ids-user-workflow.patch
+++ /dev/null
@@ -1,275 +0,0 @@
-From 10418b7f3ea8c682961fc201545169663d507bf6 Mon Sep 17 00:00:00 2001
-From: Serhii Tsymbaliuk <stsymbal@redhat.com>
-Date: Thu, 17 Jun 2021 13:56:19 +0200
-Subject: [PATCH] WebUI: Improve subordinate ids user workflow
-
-- add "Subordinate ID Statistics" page
-- add button for generating subid in "Subordinate ids" tab of user details page
-- allow to navigate directly to owner details from subordinate id page
-- adjust i18n strings
-
-Ticket: https://pagure.io/freeipa/issue/8361
-Signed-off-by: Serhii Tsymbaliuk <stsymbal@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/ui/src/freeipa/details.js             |  8 ++-
- .../ui/src/freeipa/navigation/menu_spec.js    | 19 ++++++-
- install/ui/src/freeipa/subid.js               | 43 +++++++++++++++-
- install/ui/src/freeipa/user.js                | 49 +++++++++++++++----
- ipaserver/plugins/internal.py                 | 22 ++++++---
- 5 files changed, 121 insertions(+), 20 deletions(-)
-
-diff --git a/install/ui/src/freeipa/details.js b/install/ui/src/freeipa/details.js
-index b557bbcef9a427a87eee3216f4345fc853cbaaff..2704cbd0ba98efa877cf5ec8a878e688ee6807e9 100644
---- a/install/ui/src/freeipa/details.js
-+++ b/install/ui/src/freeipa/details.js
-@@ -602,6 +602,12 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
-      */
-     that.facet_group = spec.facet_group || 'settings';
- 
-+    /**
-+     * Indicates if the details facet depends on pkey
-+     * @property {boolean}
-+     */
-+    that.require_pkey = spec.require_pkey !== undefined ? spec.require_pkey : true;
-+
-     /**
-      * Widgets
-      * @property {IPA.widget_container}
-@@ -1105,7 +1111,7 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
-      */
-     that.refresh = function(on_success, on_error) {
- 
--        if (!that.get_pkey() && that.entity.redirect_facet) {
-+        if (that.require_pkey && !that.get_pkey() && that.entity.redirect_facet) {
-             that.redirect();
-             return;
-         }
-diff --git a/install/ui/src/freeipa/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
-index 6ccd06919fbe04c7e8d2034ff7a1f644f373c607..a205dfade2f9508edbdc23ee6f7247508cc0479c 100644
---- a/install/ui/src/freeipa/navigation/menu_spec.js
-+++ b/install/ui/src/freeipa/navigation/menu_spec.js
-@@ -104,7 +104,24 @@ var nav = {};
-                         }
-                     ]
-                 },
--                { entity: 'subid' }
-+                {
-+                    name: 'subid',
-+                    label: '@i18n:tabs.subid',
-+                    children: [
-+                        {
-+                            name: 'subid',
-+                            entity: 'subid',
-+                            facet: 'search',
-+                            label: '@i18n:tabs.subid'
-+                        },
-+                        {
-+                            name: 'subid-stats',
-+                            entity: 'subid',
-+                            facet: 'stats',
-+                            label: '@i18n:objects.subid.stats'
-+                        }
-+                    ]
-+                }
-             ]
-         },
-         {
-diff --git a/install/ui/src/freeipa/subid.js b/install/ui/src/freeipa/subid.js
-index f286165070b08badf77cac6c30e93cab916c2acc..32f75bb7854cd3e84417a66870e99d34d49617e3 100644
---- a/install/ui/src/freeipa/subid.js
-+++ b/install/ui/src/freeipa/subid.js
-@@ -31,6 +31,7 @@ return {
-         },
-         {
-             $type: 'details',
-+            disable_facet_tabs: true,
-             sections: [
-                 {
-                     name: 'details',
-@@ -38,9 +39,11 @@ return {
-                         'ipauniqueid',
-                         'description',
-                         {
-+                            $type: 'link',
-                             name: 'ipaowner',
-                             label: '@i18n:objects.subid.ipaowner',
--                            title: '@mo-param:subid:ipaowner:label'
-+                            title: '@mo-param:subid:ipaowner:label',
-+                            other_entity: 'user'
-                         },
-                         {
-                             name: 'ipasubgidnumber',
-@@ -65,6 +68,44 @@ return {
-                     ]
-                 }
-             ]
-+        },
-+        {
-+            $type: 'details',
-+            name: 'stats',
-+            label: '@i18n:objects.subid.stats',
-+            refresh_command_name: 'stats',
-+            check_rights: false,
-+            no_update: true,
-+            disable_facet_tabs: true,
-+            disable_breadcrumb: true,
-+            require_pkey: false,
-+            fields: [
-+                {
-+                    name: 'assigned_subids',
-+                    label: '@i18n:objects.subid.assigned_subids',
-+                    read_only: true
-+                },
-+                {
-+                    name: 'baseid',
-+                    label: '@i18n:objects.subid.baseid',
-+                    read_only: true
-+                },
-+                {
-+                    name: 'dna_remaining',
-+                    label: '@i18n:objects.subid.dna_remaining',
-+                    read_only: true
-+                },
-+                {
-+                    name: 'rangesize',
-+                    label: '@i18n:objects.subid.rangesize',
-+                    read_only: true
-+                },
-+                {
-+                    name: 'remaining_subids',
-+                    label: '@i18n:objects.subid.remaining_subids',
-+                    read_only: true
-+                }
-+            ]
-         }
-     ],
-     adder_dialog: {
-diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
-index 56bb6f4feffb637d33a57aecf9a98f08d4639550..6a56320c580f58a1aba84e598736631986421113 100644
---- a/install/ui/src/freeipa/user.js
-+++ b/install/ui/src/freeipa/user.js
-@@ -464,7 +464,7 @@ return {
-                 },
-                 {
-                     $type: 'subid_generate',
--                    hide_cond: ['preserved-user'],
-+                    hide_cond: ['preserved-user', 'self-service-other'],
-                     enable_cond: ['no-subid']
-                 }
-             ],
-@@ -556,8 +556,35 @@ return {
-         {
-             $type: 'association',
-             name: 'memberof_subid',
-+            columns: [
-+                'ipauniqueid',
-+                'ipasubuidnumber',
-+                'ipasubgidnumber'
-+            ],
-             associator: IPA.serial_associator,
--            read_only: true
-+            read_only: true,
-+            state: {
-+                evaluators: [
-+                    IPA.user.self_service_other_user_evaluator,
-+                    IPA.user.preserved_user_evaluator,
-+                    IPA.user.has_subid_evaluator
-+                ]
-+            },
-+            actions: [
-+                {
-+                    $type: 'subid_generate',
-+                    name: 'subid_generate',
-+                    hide_cond: ['preserved-user', 'self-service-other'],
-+                    enable_cond: ['no-subid']
-+                }
-+            ],
-+            control_buttons: [
-+                {
-+                    name: 'subid_generate',
-+                    label: '@i18n:objects.user.auto_subid',
-+                    icon: 'fa-plus'
-+                }
-+            ]
-         }
-     ],
-     standard_association_facets: {
-@@ -1216,14 +1243,16 @@ IPA.user.subid_generate_action = function(spec) {
-     var that = IPA.action(spec);
- 
-     that.execute_action = function(facet) {
--
--        var subid_e = reg.entity.get('subid');
--        var dialog = subid_e.get_dialog('add');
--        dialog.open();
--        if (!IPA.is_selfservice) {
--            var owner = facet.get_pkey();
--            dialog.get_field('ipaowner').set_value([owner]);
--        }
-+        var owner = facet.get_pkey();
-+        var command = rpc.command({
-+            entity: 'subid',
-+            method: 'generate'
-+        });
-+        command.set_option('ipaowner', owner);
-+        command.on_success = function(data, text_status, xhr) {
-+            facet.refresh();
-+        };
-+        command.execute();
-     };
- 
-     return that;
-diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
-index 5ef940c2b88cc2b132a15d619772349b30731306..29e09f0067ec60d014e61c49313455d64478ef22 100644
---- a/ipaserver/plugins/internal.py
-+++ b/ipaserver/plugins/internal.py
-@@ -1364,6 +1364,20 @@ class i18n_messages(Command):
-                 "undel_success": _("${count} user(s) restored"),
-                 "user_categories": _("User categories"),
-             },
-+            "subid": {
-+                "add": _("Add subid"),
-+                "assigned_subids": _("Assigned subids"),
-+                "baseid": _("Base ID"),
-+                "dna_remaining": _("DNA remaining"),
-+                "ipaowner": _("Owner"),
-+                "ipasubgidcount": _("SubGID range size"),
-+                "ipasubgidnumber": _("SubGID range start"),
-+                "ipasubuidcount": _("SubUID range size"),
-+                "ipasubuidnumber": _("SubUID range start"),
-+                "rangesize": _("Range size"),
-+                "remaining_subids": _("Remaining subids"),
-+                "stats": _("Subordinate ID Statistics"),
-+            },
-             "sudocmd": {
-                 "add": _("Add sudo command"),
-                 "add_into_sudocmdgroups": _(
-@@ -1547,13 +1561,6 @@ class i18n_messages(Command):
-                     "Drive to mount a home directory"
-                 ),
-             },
--            "subid": {
--                "identity": _("Subordinate user and group id"),
--                "subuidnumber": _("Subordinate user id"),
--                "subuidcount": _("Subordinate user id count"),
--                "subgidnumber": _("Subordinate group id"),
--                "subgidcount": _("Subordinate group id count"),
--            },
-             "trustconfig": {
-                 "options": _("Options"),
-             },
-@@ -1942,6 +1949,7 @@ class i18n_messages(Command):
-             "network_services": _("Network Services"),
-             "policy": _("Policy"),
-             "role": _("Role-Based Access Control"),
-+            "subid": _("Subordinate IDs"),
-             "sudo": _("Sudo"),
-             "topology": _("Topology"),
-             "trust": _("Trusts"),
--- 
-2.26.3
-
diff --git a/SOURCES/0014-Test-DNA-plugin-configuration.patch b/SOURCES/0014-Test-DNA-plugin-configuration.patch
deleted file mode 100644
index 75244fd..0000000
--- a/SOURCES/0014-Test-DNA-plugin-configuration.patch
+++ /dev/null
@@ -1,57 +0,0 @@
-From b6ab27acdb07c21f43e9dcc9b777f8fd6a8925e1 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Fri, 18 Jun 2021 10:51:54 +0200
-Subject: [PATCH] Test DNA plugin configuration
-
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_subids.py | 22 +++++++++++++++++++++-
- 1 file changed, 21 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_subids.py b/ipatests/test_integration/test_subids.py
-index 48e58c26464f52605438afe865575e5ca4c8f1f8..28cd1f765cd63af944bce83f4676a2b1998f5f5d 100644
---- a/ipatests/test_integration/test_subids.py
-+++ b/ipatests/test_integration/test_subids.py
-@@ -6,8 +6,11 @@
- """
- import os
- 
--from ipalib.constants import SUBID_COUNT, SUBID_RANGE_START, SUBID_RANGE_MAX
-+from ipalib.constants import (
-+    SUBID_COUNT, SUBID_RANGE_START, SUBID_RANGE_MAX, SUBID_DNA_THRESHOLD
-+)
- from ipaplatform.paths import paths
-+from ipapython.dn import DN
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
- 
-@@ -81,6 +84,23 @@ class TestSubordinateId(IntegrationTest):
-             cmd.extend(("--owner", uid))
-         return self.master.run_command(cmd, **kwargs)
- 
-+    def test_dna_config(self):
-+        conn = self.master.ldap_connect()
-+        dna_cfg = DN(
-+            "cn=Subordinate IDs,cn=Distributed Numeric Assignment Plugin,"
-+            "cn=plugins,cn=config"
-+        )
-+        entry = conn.get_entry(dna_cfg)
-+
-+        def single_int(key):
-+            return int(entry.single_value[key])
-+
-+        assert single_int("dnaInterval") == SUBID_COUNT
-+        assert single_int("dnaThreshold") == SUBID_DNA_THRESHOLD
-+        assert single_int("dnaMagicRegen") == -1
-+        assert single_int("dnaMaxValue") == SUBID_RANGE_MAX
-+        assert set(entry["dnaType"]) == {"ipasubgidnumber", "ipasubuidnumber"}
-+
-     def test_auto_generate_subid(self):
-         uid = "testuser_auto1"
-         passwd = "Secret123"
--- 
-2.26.3
-
diff --git a/SOURCES/0015-Fall-back-to-krbprincipalname-when-validating-host-a.patch b/SOURCES/0015-Fall-back-to-krbprincipalname-when-validating-host-a.patch
deleted file mode 100644
index 780d75d..0000000
--- a/SOURCES/0015-Fall-back-to-krbprincipalname-when-validating-host-a.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-From 3b7f537dd3022ecb758b2f0f8b2aba530e74bff7 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 12 Jul 2021 11:02:10 -0400
-Subject: [PATCH] Fall back to krbprincipalname when validating host auth
- indicators
-
-When adding a new host the principal cannot be determined because it
-relies on either:
-
-a) an entry to already exist
-b) krbprincipalname be a component of the dn
-
-As a result the full dn is being passed into ipapython.Kerberos
-which can't parse it.
-
-Look into the entry in validate_validate_auth_indicator() for
-krbprincipalname in this case.
-
-https://pagure.io/freeipa/issue/8206
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipaserver/plugins/service.py             |  5 +++++
- ipatests/test_xmlrpc/test_host_plugin.py | 11 +++++++++++
- 2 files changed, 16 insertions(+)
-
-diff --git a/ipaserver/plugins/service.py b/ipaserver/plugins/service.py
-index cfbbff3c69c6a92535df58c51767c3d0952c7b0b..498f5e444364c6330e053d1057b727fb5181f70b 100644
---- a/ipaserver/plugins/service.py
-+++ b/ipaserver/plugins/service.py
-@@ -209,6 +209,11 @@ def validate_auth_indicator(entry):
-     # and shouldn't be allowed to have auth indicators.
-     # https://pagure.io/freeipa/issue/8206
-     pkey = api.Object['service'].get_primary_key_from_dn(entry.dn)
-+    if pkey == str(entry.dn):
-+        # krbcanonicalname may not be set yet if this is a host entry,
-+        # try krbprincipalname
-+        if 'krbprincipalname' in entry:
-+            pkey = entry['krbprincipalname']
-     principal = kerberos.Principal(pkey)
-     server = api.Command.server_find(principal.hostname)['result']
-     if server:
-diff --git a/ipatests/test_xmlrpc/test_host_plugin.py b/ipatests/test_xmlrpc/test_host_plugin.py
-index 9cfde3565d48e103a0549e2bfb7579e07668f41b..ff50e796cd19fca2c7b6c87d73940779db8daa0b 100644
---- a/ipatests/test_xmlrpc/test_host_plugin.py
-+++ b/ipatests/test_xmlrpc/test_host_plugin.py
-@@ -615,6 +615,17 @@ class TestProtectedMaster(XMLRPC_test):
-         )):
-             command()
- 
-+    def test_add_non_master_with_auth_ind(self, host5):
-+        host5.ensure_missing()
-+        command = host5.make_command(
-+            'host_add', host5.fqdn, krbprincipalauthind=['radius'],
-+            force=True
-+        )
-+        result = command()
-+        # The fact that the command succeeds exercises the change but
-+        # let's check the indicator as well.
-+        assert result['result']['krbprincipalauthind'] == ('radius',)
-+
- 
- @pytest.mark.tier1
- class TestValidation(XMLRPC_test):
--- 
-2.26.3
-
diff --git a/SOURCES/0016-spec-file-Trust-controller-role-should-pull-sssd-win.patch b/SOURCES/0016-spec-file-Trust-controller-role-should-pull-sssd-win.patch
deleted file mode 100644
index cdb9c35..0000000
--- a/SOURCES/0016-spec-file-Trust-controller-role-should-pull-sssd-win.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From aa07f41769765e55c1531b52ad9ef5876e97e0e9 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 15 Jul 2021 10:06:56 +0200
-Subject: [PATCH] spec file: Trust controller role should pull
- sssd-winbind-idmap package
-
-ipa-server-trust-ad subpackage need to pull in sssd-winbind-idmap
-Fixes: https://pagure.io/freeipa/issue/8923
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- freeipa.spec.in | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index fa649cf4e1abe8e9928ef340a66d48d78f7e3521..c33d2e216e5b0f13ae4fd3f9f506d4983493f03a 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -597,6 +597,7 @@ Requires: %{name}-common = %{version}-%{release}
- 
- Requires: samba >= %{samba_version}
- Requires: samba-winbind
-+Requires: sssd-winbind-idmap
- Requires: libsss_idmap
- %if 0%{?rhel}
- Obsoletes: ipa-idoverride-memberof-plugin <= 0.1
--- 
-2.26.3
-
diff --git a/SOURCES/0017-Use-new-method-in-check-to-prevent-removal-of-last-K.patch b/SOURCES/0017-Use-new-method-in-check-to-prevent-removal-of-last-K.patch
deleted file mode 100644
index 8a4d23d..0000000
--- a/SOURCES/0017-Use-new-method-in-check-to-prevent-removal-of-last-K.patch
+++ /dev/null
@@ -1,58 +0,0 @@
-From 0b9adf1d8d5efb48e734650e4101e8816b01e1d3 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 19 Jul 2021 17:51:44 -0400
-Subject: [PATCH] Use new method in check to prevent removal of last KRA
-
-It previously used a vault connection to determine if any
-KRA servers were installed. This would fail if the last KRA
-was not available.
-
-Use server roles instead to determine if the last KRA server
-is to be removed.
-
-https://pagure.io/freeipa/issue/8397
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- ipaserver/plugins/server.py | 24 +++++++++++++-----------
- 1 file changed, 13 insertions(+), 11 deletions(-)
-
-diff --git a/ipaserver/plugins/server.py b/ipaserver/plugins/server.py
-index b3dda8469..5fa7a58bd 100644
---- a/ipaserver/plugins/server.py
-+++ b/ipaserver/plugins/server.py
-@@ -508,17 +508,19 @@ class server_del(LDAPDelete):
- 
-         if self.api.Command.ca_is_enabled()['result']:
-             try:
--                vault_config = self.api.Command.vaultconfig_show()['result']
--                kra_servers = vault_config.get('kra_server_server', [])
--            except errors.InvocationError:
--                # KRA is not configured
--                pass
--            else:
--                if kra_servers == [hostname]:
--                    handler(
--                        _("Deleting this server is not allowed as it would "
--                          "leave your installation without a KRA."),
--                        ignore_last_of_role)
-+                roles = self.api.Command.server_role_find(
-+                    server_server=hostname,
-+                    role_servrole='KRA server',
-+                    status='enabled',
-+                    include_master=True,
-+                )['result']
-+            except errors.NotFound:
-+                roles = ()
-+            if len(roles) == 1 and roles[0]['server_server'] == hostname:
-+                handler(
-+                    _("Deleting this server is not allowed as it would "
-+                      "leave your installation without a KRA."),
-+                    ignore_last_of_role)
- 
-             ca_servers = ipa_config.get('ca_server_server', [])
-             ca_renewal_master = ipa_config.get(
--- 
-2.26.3
-
diff --git a/SOURCES/0018-ipatests-test-removing-last-KRA-when-it-is-not-runni.patch b/SOURCES/0018-ipatests-test-removing-last-KRA-when-it-is-not-runni.patch
deleted file mode 100644
index 5461afb..0000000
--- a/SOURCES/0018-ipatests-test-removing-last-KRA-when-it-is-not-runni.patch
+++ /dev/null
@@ -1,49 +0,0 @@
-From 8ea8f8b68b5a7217518f68065a5fc1df16126314 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 19 Jul 2021 21:54:22 -0400
-Subject: [PATCH] ipatests: test removing last KRA when it is not running
-
-Use the new role-based mechanism, one that doesn't rely
-on direct communication to the server, to determine whether
-the server being removed by `ipa server-del` contains the
-last KRA server.
-
-https://pagure.io/freeipa/issue/8397
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- ipatests/test_integration/test_server_del.py | 17 +++++++++++++++++
- 1 file changed, 17 insertions(+)
-
-diff --git a/ipatests/test_integration/test_server_del.py b/ipatests/test_integration/test_server_del.py
-index 5e627d5db..9d7f5ef7a 100644
---- a/ipatests/test_integration/test_server_del.py
-+++ b/ipatests/test_integration/test_server_del.py
-@@ -302,6 +302,23 @@ class TestLastServices(ServerDelBase):
-             1
-         )
- 
-+    def test_removal_of_server_raises_error_about_last_kra(self):
-+        """
-+        test that removal of server fails on the last KRA
-+
-+        We shut it down to verify that it can be removed if it failed.
-+        """
-+        tasks.install_kra(self.master)
-+        self.master.run_command(['ipactl', 'stop'])
-+        tasks.assert_error(
-+            tasks.run_server_del(self.replicas[0], self.master.hostname),
-+            "Deleting this server is not allowed as it would leave your "
-+            "installation without a KRA.",
-+            1
-+        )
-+        # Restarting the server we stopped is not necessary as it will
-+        # be removed in the next test.
-+
-     def test_forced_removal_of_master(self):
-         """
-         Tests that we can still force remove the master using
--- 
-2.26.3
-
diff --git a/SOURCES/0019-rhel-platform-add-a-named-crypto-policy-support.patch b/SOURCES/0019-rhel-platform-add-a-named-crypto-policy-support.patch
deleted file mode 100644
index 241d293..0000000
--- a/SOURCES/0019-rhel-platform-add-a-named-crypto-policy-support.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 1a5159b216455070eb51b6a11ceaf0033fc8ce4c Mon Sep 17 00:00:00 2001
-From: Alexander Bokovoy <abokovoy@redhat.com>
-Date: Fri, 16 Jul 2021 09:20:33 +0300
-Subject: [PATCH] rhel platform: add a named crypto-policy support
-
-RHEL 8+ provides bind system-wide crypto policy support, enable it.
-
-Fixes: https://pagure.io/freeipa/issue/8925
-Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Anuja More <amore@redhat.com>
----
- ipaplatform/rhel/paths.py | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/ipaplatform/rhel/paths.py b/ipaplatform/rhel/paths.py
-index c081ada32..3631550eb 100644
---- a/ipaplatform/rhel/paths.py
-+++ b/ipaplatform/rhel/paths.py
-@@ -30,6 +30,7 @@ from ipaplatform.rhel.constants import HAS_NFS_CONF
- 
- 
- class RHELPathNamespace(RedHatPathNamespace):
-+    NAMED_CRYPTO_POLICY_FILE = "/etc/crypto-policies/back-ends/bind.config"
-     if HAS_NFS_CONF:
-         SYSCONFIG_NFS = '/etc/nfs.conf'
- 
--- 
-2.26.3
-
diff --git a/SOURCES/0020-Index-Fix-definition-for-memberOf.patch b/SOURCES/0020-Index-Fix-definition-for-memberOf.patch
deleted file mode 100644
index 3cf9357..0000000
--- a/SOURCES/0020-Index-Fix-definition-for-memberOf.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From b132956e42a88ab39bb8d6a854e7c5d28d544a11 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 16 Jul 2021 09:43:54 +0200
-Subject: [PATCH] Index: Fix definition for memberOf
-
-The index definition for memberOf is inconsistent:
-
-dn: cn=memberOf,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
-cn: member
-nsIndexType: eq
-nsIndexType: sub
-nsSystemIndex: false
-objectClass: top
-objectClass: nsIndex
-
-The cn attribute should be memberOf, not member. Fix the definition.
-
-Fixes: https://pagure.io/freeipa/issue/8920
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- install/updates/20-indices.update | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
-index d6df5b37d..cb1a11dd5 100644
---- a/install/updates/20-indices.update
-+++ b/install/updates/20-indices.update
-@@ -434,7 +434,7 @@ add:nsIndexType: eq
- add:nsIndexType: pres
- 
- dn: cn=memberOf,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
--only:cn: member
-+only:cn: memberOf
- add:nsIndexType: sub
- 
- dn: cn=memberPrincipal,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
--- 
-2.26.3
-
diff --git a/SOURCES/0021-ipatests-use-whole-date-when-calling-journalctl-sinc.patch b/SOURCES/0021-ipatests-use-whole-date-when-calling-journalctl-sinc.patch
deleted file mode 100644
index 2ec50bb..0000000
--- a/SOURCES/0021-ipatests-use-whole-date-when-calling-journalctl-sinc.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From b2e6292337c6f7f68ac383db8aa54a1abfa3f6b4 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Sun, 11 Jul 2021 16:29:16 +0200
-Subject: [PATCH] ipatests: use whole date when calling journalctl --since
-
-The test TestSelfExternalSelf::test_switch_back_to_self_signed
-is checking the content of the journal using journalctl --since ...
-but provides only the time, not the whole date with year-month-day.
-As a consequence, if the test is executed around midnight it may
-find nothing in the journal because it's looking for logs after 11:50PM,
-which is a date in the future.
-Fixes: https://pagure.io/freeipa/issue/8918
-
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Anuja More <amore@redhat.com>
----
- ipatests/test_integration/test_external_ca.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py
-index 72aa57a0b..d48d73deb 100644
---- a/ipatests/test_integration/test_external_ca.py
-+++ b/ipatests/test_integration/test_external_ca.py
-@@ -301,7 +301,7 @@ class TestSelfExternalSelf(IntegrationTest):
-     def test_switch_back_to_self_signed(self):
- 
-         # for journalctl --since
--        switch_time = time.strftime('%H:%M:%S')
-+        switch_time = time.strftime('%Y-%m-%d %H:%M:%S')
-         # switch back to self-signed CA
-         result = self.master.run_command([paths.IPA_CACERT_MANAGE, 'renew',
-                                           '--self-signed'])
--- 
-2.31.1
-
diff --git a/SOURCES/0022-ipatests-Fix-for-test_source_ipahealthcheck_ipa_host.patch b/SOURCES/0022-ipatests-Fix-for-test_source_ipahealthcheck_ipa_host.patch
deleted file mode 100644
index d56f3e5..0000000
--- a/SOURCES/0022-ipatests-Fix-for-test_source_ipahealthcheck_ipa_host.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 26be7ffdba87e0e6294ea035ab3dc9bd933fba43 Mon Sep 17 00:00:00 2001
-From: Sudhir Menon <sumenon@redhat.com>
-Date: Fri, 9 Jul 2021 13:44:12 +0530
-Subject: [PATCH] ipatests: Fix for
- test_source_ipahealthcheck_ipa_host_check_ipahostkeytab
-
-Expected error message has been modified for
-test_source_ipahealthcheck_ipa_host_check_ipahostkeytab
-
-Related: https://pagure.io/freeipa/issue/8889
-
-Signed-off-by: Sudhir Menon <sumenon@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 5 +----
- 1 file changed, 1 insertion(+), 4 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 305d7b945..f6a3043f1 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -499,9 +499,6 @@ class TestIpaHealthCheck(IntegrationTest):
-         from host's keytab.
-         """
-         msg = (
--            "Failed to obtain host TGT: Major (458752): "
--            "No credentials were "
--            "supplied, or the credentials were unavailable or inaccessible, "
-             "Minor (2529639107): No credentials cache found"
-         )
- 
-@@ -514,7 +511,7 @@ class TestIpaHealthCheck(IntegrationTest):
-             )
-             assert returncode == 1
-             assert data[0]["result"] == "ERROR"
--            assert data[0]["kw"]["msg"] == msg
-+            assert msg in data[0]["kw"]["msg"]
- 
-     def test_source_ipahealthcheck_topology_IPATopologyDomainCheck(self):
-         """
--- 
-2.31.1
-
diff --git a/SOURCES/0023-ipatests-test_ipahealthcheck-print-a-message-if-a-sy.patch b/SOURCES/0023-ipatests-test_ipahealthcheck-print-a-message-if-a-sy.patch
deleted file mode 100644
index 8993bcc..0000000
--- a/SOURCES/0023-ipatests-test_ipahealthcheck-print-a-message-if-a-sy.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 7f910eb2dda8595da435b4aed6e759a2916df813 Mon Sep 17 00:00:00 2001
-From: Michal Polovka <mpolovka@redhat.com>
-Date: Wed, 23 Jun 2021 14:53:49 +0200
-Subject: [PATCH] ipatests: test_ipahealthcheck: print a message if a system is
- healthy
-
-Test if when the system is completely healthy, informative message is
-returned and not only empty output (list or json).
-
-Related: https://pagure.io/freeipa/issue/8892
-
-Signed-off-by: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_ipahealthcheck.py | 14 +++++++++++++-
- 1 file changed, 13 insertions(+), 1 deletion(-)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index f6a3043f1..36fe72be7 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -286,7 +286,7 @@ class TestIpaHealthCheck(IntegrationTest):
-         for source in sources_avail:
-             assert source in result.stdout_text
- 
--    def test_human_output(self, restart_service):
-+    def test_human_severity(self, restart_service):
-         """
-         Test that in human output the severity value is correct
- 
-@@ -306,6 +306,18 @@ class TestIpaHealthCheck(IntegrationTest):
-         assert output == \
-             "ERROR: ipahealthcheck.meta.services.sssd: sssd: not running"
- 
-+    def test_human_output(self):
-+        """
-+        Test if in case no  failures were found, informative string is printed
-+        in human output.
-+
-+        https://pagure.io/freeipa/issue/8892
-+        """
-+        returncode, output = run_healthcheck(self.master, output_type="human",
-+                                             failures_only=True)
-+        assert returncode == 0
-+        assert output == "No issues found."
-+
-     def test_ipa_healthcheck_after_certupdate(self):
-         """
-         Verify that ipa-certupdate hasn't messed up tracking
--- 
-2.31.1
-
diff --git a/SOURCES/0024-ipatests-test_installation-move-tracking_reqs-depend.patch b/SOURCES/0024-ipatests-test_installation-move-tracking_reqs-depend.patch
deleted file mode 100644
index 848571f..0000000
--- a/SOURCES/0024-ipatests-test_installation-move-tracking_reqs-depend.patch
+++ /dev/null
@@ -1,104 +0,0 @@
-From e5df4dc4884f1a66ccbca79b9a0d83874c996d1d Mon Sep 17 00:00:00 2001
-From: Michal Polovka <mpolovka@redhat.com>
-Date: Mon, 31 May 2021 14:43:28 +0200
-Subject: [PATCH] ipatests: test_installation: move tracking_reqs dependency to
- ipalib constants ipaserver: krainstance: utilize moved tracking_reqs
- dependency
-
-KRA instance import depends on lib389 package, which is not always
-installed and that results in failure. Furthermore, test_installation
-utilizes krainstance import. This fix moves relevant parts from
-krainstance to ipalib constants where those are subsequently imported
-from.
-
-Related: https://pagure.io/freeipa/issue/8795
-
-Signed-off-by: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Christian Heimes <cheimes@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipalib/constants.py                            | 8 ++++++++
- ipaserver/install/krainstance.py               | 7 ++-----
- ipatests/test_integration/test_installation.py | 7 +++----
- 3 files changed, 13 insertions(+), 9 deletions(-)
-
-diff --git a/ipalib/constants.py b/ipalib/constants.py
-index bff899ba6..2aeafac7a 100644
---- a/ipalib/constants.py
-+++ b/ipalib/constants.py
-@@ -360,3 +360,11 @@ SUBID_RANGE_MAX = (2 ** 32) - (2 * SUBID_COUNT)
- SUBID_RANGE_SIZE = SUBID_RANGE_MAX - SUBID_RANGE_START
- # threshold before DNA plugin requests a new range
- SUBID_DNA_THRESHOLD = 500
-+
-+# moved from ipaserver/install/krainstance.py::KRAInstance to avoid duplication
-+# as per https://pagure.io/freeipa/issue/8795
-+KRA_TRACKING_REQS = {
-+    'auditSigningCert cert-pki-kra': 'caAuditSigningCert',
-+    'transportCert cert-pki-kra': 'caTransportCert',
-+    'storageCert cert-pki-kra': 'caStorageCert',
-+}
-diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
-index e63db3fef..13cb2dcaa 100644
---- a/ipaserver/install/krainstance.py
-+++ b/ipaserver/install/krainstance.py
-@@ -27,6 +27,7 @@ import base64
- 
- from ipalib import api
- from ipalib import x509
-+from ipalib.constants import KRA_TRACKING_REQS
- from ipaplatform.paths import paths
- from ipapython import directivesetter
- from ipapython import ipautil
-@@ -64,11 +65,7 @@ class KRAInstance(DogtagInstance):
-     # Mapping of nicknames for tracking requests, and the profile to
-     # use for that certificate.  'configure_renewal()' reads this
-     # dict.  The profile MUST be specified.
--    tracking_reqs = {
--        'auditSigningCert cert-pki-kra': 'caAuditSigningCert',
--        'transportCert cert-pki-kra': 'caTransportCert',
--        'storageCert cert-pki-kra': 'caStorageCert',
--    }
-+    tracking_reqs = KRA_TRACKING_REQS
- 
-     def __init__(self, realm):
-         super(KRAInstance, self).__init__(
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index 0c96536f0..27f15dbe5 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -20,7 +20,7 @@ from cryptography.hazmat.primitives import hashes
- from cryptography import x509 as crypto_x509
- 
- from ipalib import x509
--from ipalib.constants import DOMAIN_LEVEL_0
-+from ipalib.constants import DOMAIN_LEVEL_0, KRA_TRACKING_REQS
- from ipalib.constants import IPA_CA_RECORD
- from ipalib.sysrestore import SYSRESTORE_STATEFILE, SYSRESTORE_INDEXFILE
- from ipapython.dn import DN
-@@ -34,7 +34,7 @@ from ipatests.pytest_ipa.integration.env_config import get_global_config
- from ipatests.test_integration.base import IntegrationTest
- from ipatests.test_integration.test_caless import CALessBase, ipa_certs_cleanup
- from ipaplatform import services
--from ipaserver.install import krainstance
-+
- 
- config = get_global_config()
- 
-@@ -1282,8 +1282,7 @@ class TestInstallMasterKRA(IntegrationTest):
-         """
-         Test that the KRA subsystem certificates renew properly
-         """
--        kra = krainstance.KRAInstance(self.master.domain.realm)
--        for nickname in kra.tracking_reqs:
-+        for nickname in KRA_TRACKING_REQS:
-             cert = tasks.certutil_fetch_cert(
-                 self.master,
-                 paths.PKI_TOMCAT_ALIAS_DIR,
--- 
-2.31.1
-
diff --git a/SOURCES/0025-webui-tests-close-notification-when-revoking-cert.patch b/SOURCES/0025-webui-tests-close-notification-when-revoking-cert.patch
deleted file mode 100644
index decc06d..0000000
--- a/SOURCES/0025-webui-tests-close-notification-when-revoking-cert.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From 40e4ccf1ea943aba4d10e8126ffa49feddd2e683 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 13 Jul 2021 18:38:22 +0200
-Subject: [PATCH] webui tests: close notification when revoking cert
-
-When a cert is revoked, a notification is displayed
-and may obscure the buttons. Make sure to close the
-notification before moving to the next step.
-
-Fixes: https://pagure.io/freeipa/issue/8911
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
----
- ipatests/test_webui/test_cert.py | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/ipatests/test_webui/test_cert.py b/ipatests/test_webui/test_cert.py
-index 53dc76faa..7a8ffde91 100644
---- a/ipatests/test_webui/test_cert.py
-+++ b/ipatests/test_webui/test_cert.py
-@@ -107,6 +107,7 @@ class test_cert(UI_driver):
-         self.action_list_action('revoke_cert', False)
-         self.select('select[name=revocation_reason]', reason)
-         self.dialog_button_click('ok')
-+        self.close_notifications()
-         self.navigate_to_entity(ENTITY)
- 
-         return cert
--- 
-2.31.1
-
diff --git a/SOURCES/0026-ipatests-Test-ipa-cert-fix-warns-when-startup-direct.patch b/SOURCES/0026-ipatests-Test-ipa-cert-fix-warns-when-startup-direct.patch
deleted file mode 100644
index 8d8a2d6..0000000
--- a/SOURCES/0026-ipatests-Test-ipa-cert-fix-warns-when-startup-direct.patch
+++ /dev/null
@@ -1,153 +0,0 @@
-From 02c0da3ef74948579106aab4b669f6e64dd60b24 Mon Sep 17 00:00:00 2001
-From: Mohammad Rizwan <myusuf@redhat.com>
-Date: Thu, 24 Jun 2021 13:10:00 +0530
-Subject: [PATCH] ipatests: Test ipa-cert-fix warns when startup directive is
- missing from CS.cfg
-
-Earlier it used to fail when startup directive missing from CS.cfg.
-With https://github.com/dogtagpki/pki/pull/3466, it changed to display
-a warning than failing.
-
-related: https://pagure.io/freeipa/issue/8890
-
-Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipa_cert_fix.py     | 92 ++++++++++++++++++-
- 1 file changed, 90 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index b2e92d4dc..394e85603 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -48,6 +48,16 @@ def check_status(host, cert_count, state, timeout=600):
-     return count
- 
- 
-+def move_date(host, chrony_state, date_str):
-+    """Helper method to move the date on given host
-+    :param host: The host on which date is to be moved
-+    :param chrony_state: State to which chrony service to be moved
-+    :param date_str: date string to move the date i.e 2years1month1days
-+    """
-+    host.run_command(['systemctl', chrony_state, 'chronyd'])
-+    host.run_command(['date', '-s', date_str])
-+
-+
- @pytest.fixture
- def expire_cert_critical():
-     """
-@@ -82,6 +92,17 @@ class TestIpaCertFix(IntegrationTest):
-         # the fixture
-         pass
- 
-+    @pytest.fixture
-+    def expire_ca_cert(self):
-+        tasks.install_master(self.master, setup_dns=False,
-+                             extra_args=['--no-ntp'])
-+        move_date(self.master, 'stop', '+20Years+1day')
-+
-+        yield
-+
-+        tasks.uninstall_master(self.master)
-+        move_date(self.master, 'start', '-20Years-1day')
-+
-     def test_missing_csr(self, expire_cert_critical):
-         """
-         Test that ipa-cert-fix succeeds when CSR is missing from CS.cfg
-@@ -122,7 +143,8 @@ class TestIpaCertFix(IntegrationTest):
- 
-         # Because of BZ 1897120, pki-cert-fix fails on pki-core 10.10.0
-         # https://bugzilla.redhat.com/show_bug.cgi?id=1897120
--        if tasks.get_pki_version(self.master) != tasks.parse_version('10.10.0'):
-+        if (tasks.get_pki_version(self.master)
-+           != tasks.parse_version('10.10.0')):
-             assert result.returncode == 0
- 
-             # get the number of certs track by certmonger
-@@ -180,6 +202,72 @@ class TestIpaCertFix(IntegrationTest):
-                                          raiseonerr=False)
-         assert result.returncode == 2
- 
-+    def test_missing_startup(self, expire_cert_critical):
-+        """
-+        Test ipa-cert-fix fails/warns when startup directive is missing
-+
-+        This test checks that if 'selftests.container.order.startup' directive
-+        is missing from CS.cfg, ipa-cert-fix fails and throw proper error
-+        message. It also checks that underlying command 'pki-server cert-fix'
-+        should fail to renew the cert.
-+
-+        related: https://pagure.io/freeipa/issue/8721
-+
-+        With https://github.com/dogtagpki/pki/pull/3466, it changed to display
-+        a warning than failing.
-+
-+        This test also checks that if 'selftests.container.order.startup'
-+        directive is missing from CS.cfg, ipa-cert-fix dsplay proper warning
-+        (depending on pki version)
-+
-+        related: https://pagure.io/freeipa/issue/8890
-+        """
-+        expire_cert_critical(self.master)
-+        # pki must be stopped in order to edit CS.cfg
-+        self.master.run_command(['ipactl', 'stop'])
-+        self.master.run_command([
-+            'sed', '-i', r'/selftests\.container\.order\.startup/d',
-+            paths.CA_CS_CFG_PATH
-+        ])
-+        # dirsrv needs to be up in order to run ipa-cert-fix
-+        self.master.run_command(['ipactl', 'start',
-+                                 '--ignore-service-failures'])
-+
-+        result = self.master.run_command(['ipa-cert-fix', '-v'],
-+                                         stdin_text='yes\n',
-+                                         raiseonerr=False)
-+
-+        err_msg1 = "ERROR: 'selftests.container.order.startup'"
-+        # check that pki-server cert-fix command fails
-+        err_msg2 = ("ERROR: CalledProcessError(Command "
-+                    "['pki-server', 'cert-fix'")
-+        warn_msg = ("WARNING: No selftests configured in "
-+                    f"{paths.CA_CS_CFG_PATH} "
-+                    "(selftests.container.order.startup)")
-+
-+        if (tasks.get_pki_version(self.master)
-+           < tasks.parse_version('10.11.0')):
-+            assert (err_msg1 in result.stderr_text
-+                    and err_msg2 in result.stderr_text)
-+        else:
-+            assert warn_msg in result.stdout_text
-+
-+    def test_expired_CA_cert(self, expire_ca_cert):
-+        """Test to check ipa-cert-fix when CA certificate is expired
-+
-+        In order to fix expired certs using ipa-cert-fix, CA cert should be
-+        valid. If CA cert expired, ipa-cert-fix won't work.
-+
-+        related: https://pagure.io/freeipa/issue/8721
-+        """
-+        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'")
-+        assert err_msg in result.stderr_text
-+
- 
- class TestIpaCertFixThirdParty(CALessBase):
-     """
-@@ -219,7 +307,7 @@ class TestIpaCertFixThirdParty(CALessBase):
-                 '--pin', self.master.config.admin_password,
-                 '-d', 'server.p12']
-         self.master.run_command(args)
--        self.master.run_command(['ipactl', 'restart',])
-+        self.master.run_command(['ipactl', 'restart'])
- 
-         # Run ipa-cert-fix. This is basically a no-op but tests that
-         # the DS nickname is used and not a hardcoded value.
--- 
-2.31.1
-
diff --git a/SOURCES/0027-webui-tests-fix-algo-for-finding-available-idrange.patch b/SOURCES/0027-webui-tests-fix-algo-for-finding-available-idrange.patch
deleted file mode 100644
index 92c1528..0000000
--- a/SOURCES/0027-webui-tests-fix-algo-for-finding-available-idrange.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From f7997ed0b7d5b915c0184bf8e8864ff935cd6232 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Fri, 16 Jul 2021 15:21:48 +0200
-Subject: [PATCH] webui tests: fix algo for finding available idrange
-
-The webui tests for ID range evaluate a potentially free id range
-by looking for existing ranges and picking a range = max value
-+ 1 million.
-
-With the addition of subuid range this algorithm produces values
-over the limit because the subuid range goes from
-2,147,483,648 to 4,294,836,224 and the max base id is 4,294,967,295.
-
-Ignore the subuid range when picking a potential range.
-Fixes: https://pagure.io/freeipa/issue/8919
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_webui/task_range.py | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/ipatests/test_webui/task_range.py b/ipatests/test_webui/task_range.py
-index db34f6f11..f0664d35a 100644
---- a/ipatests/test_webui/task_range.py
-+++ b/ipatests/test_webui/task_range.py
-@@ -64,6 +64,12 @@ class range_tasks(UI_driver):
-         max_rid = 0
- 
-         for idrange in idranges:
-+            # IPA.TEST_subid_range is automatically created near the end
-+            # of the allowed ids, taking from 2,147,483,648 to 4,294,836,224
-+            # Ignore this range when looking for available ids otherwise
-+            # we won't find any value < max baseid 4,294,967,295
-+            if idrange['cn'][0].endswith("_subid_range"):
-+                continue
-             size = int(idrange['ipaidrangesize'][0])
-             base_id = int(idrange['ipabaseid'][0])
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0028-ipatests-smbclient-k-use-kerberos-desired.patch b/SOURCES/0028-ipatests-smbclient-k-use-kerberos-desired.patch
deleted file mode 100644
index 38d2842..0000000
--- a/SOURCES/0028-ipatests-smbclient-k-use-kerberos-desired.patch
+++ /dev/null
@@ -1,61 +0,0 @@
-From 161d5844eb1214e60c636bdb73713c6a43f1e75c Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Mon, 19 Jul 2021 15:59:01 +0200
-Subject: [PATCH] ipatests: smbclient "-k" => "--use-kerberos=desired"
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Change documentation:
-https://download.samba.org/pub/samba/rc/samba-4.15.0rc1.WHATSNEW.txt
-
-As of Samba 4.15rc1, smbclient does not accept "-k" anymore.
-The "-k|--kerberos" option ("Try to authenticate with kerberos.")
-has been replaced with "--use-kerberos=required|desired|off".
-
-Fixes: https://pagure.io/freeipa/issue/8926
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
----
- ipatests/test_integration/test_smb.py | 23 +++++++++++++++++++++--
- 1 file changed, 21 insertions(+), 2 deletions(-)
-
-diff --git a/ipatests/test_integration/test_smb.py b/ipatests/test_integration/test_smb.py
-index 399ad6209..b2b7ce2e4 100644
---- a/ipatests/test_integration/test_smb.py
-+++ b/ipatests/test_integration/test_smb.py
-@@ -166,9 +166,28 @@ class TestSMB(IntegrationTest):
-                 encoding='utf-8')
-             assert file_contents_at_server == test_string
- 
--            # check access using smbclient utility
-+            # Detect whether smbclient uses -k or --use-kerberos=required
-+            # https://pagure.io/freeipa/issue/8926
-+            # then check access using smbclient.
-             res = run_smb_client(
--                ['smbclient', '-k', share['unc'], '-c', 'dir'])
-+                [
-+                    "smbclient",
-+                    "-h",
-+                ], raiseonerr=False
-+            )
-+            if "[-k|--kerberos]" in res.stderr_text:
-+                smbclient_krb5_knob = "-k"
-+            else:
-+                smbclient_krb5_knob = "--use-kerberos=desired"
-+            res = run_smb_client(
-+                [
-+                    "smbclient",
-+                    smbclient_krb5_knob,
-+                    share["unc"],
-+                    "-c",
-+                    "dir",
-+                ]
-+            )
-             assert test_dir in res.stdout_text
- 
-             # check file and dir removal from client side
--- 
-2.31.1
-
diff --git a/SOURCES/0029-test_acme-refactor-with-tasks.patch b/SOURCES/0029-test_acme-refactor-with-tasks.patch
deleted file mode 100644
index 5add22b..0000000
--- a/SOURCES/0029-test_acme-refactor-with-tasks.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From 86869364a30f071ee79974b301ff68e80c0950ba Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Tue, 20 Jul 2021 20:19:16 +0200
-Subject: [PATCH] test_acme: refactor with tasks
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
----
- ipatests/pytest_ipa/integration/tasks.py | 11 +++++++++++
- ipatests/test_integration/test_acme.py   | 19 ++++---------------
- 2 files changed, 15 insertions(+), 15 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index 22c7ba782..c2e548617 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -2800,3 +2800,14 @@ def is_package_installed(host, pkg):
-             'is_package_installed: unknown platform %s' % platform
-         )
-     return result.returncode == 0
-+
-+
-+def move_date(host, chrony_cmd, date_str):
-+    """Helper method to move system date
-+    :param host: host on which date is to be manipulated
-+    :param chrony_cmd: systemctl command to apply to
-+                       chrony service, for instance 'start', 'stop'
-+    :param date_str: date string to change the date i.e '3years2months1day1'
-+    """
-+    host.run_command(['systemctl', chrony_cmd, 'chronyd'])
-+    host.run_command(['date', '-s', date_str])
-diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py
-index d90f1ff7d..b4aa1b351 100644
---- a/ipatests/test_integration/test_acme.py
-+++ b/ipatests/test_integration/test_acme.py
-@@ -35,17 +35,6 @@ skip_mod_md_tests = osinfo.id not in ['rhel', 'fedora', ]
- CERTBOT_DNS_IPA_SCRIPT = '/usr/libexec/ipa/acme/certbot-dns-ipa'
- 
- 
--def move_date(host, chrony_cmd, date_str):
--    """Helper method to move system date
--    :param host: host on which date is to be manipulated
--    :param chrony_cmd: systemctl command to apply to
--                       chrony service, for instance 'start', 'stop'
--    :param date_str: date string to change the date i.e '3years2months1day1'
--    """
--    host.run_command(['systemctl', chrony_cmd, 'chronyd'])
--    host.run_command(['date', '-s', date_str])
--
--
- def check_acme_status(host, exp_status, timeout=60):
-     """Helper method to check the status of acme server"""
-     for _i in range(0, timeout, 5):
-@@ -598,8 +587,8 @@ class TestACMERenew(IntegrationTest):
-         )
-         # move system date to expire acme cert
-         for host in self.clients[0], self.master:
--            host.run_command(['kdestroy', '-A'])
--            move_date(host, 'stop', '+90days')
-+            tasks.kdestroy_all(host)
-+            tasks.move_date(host, 'stop', '+90days')
-         self.clients[0].run_command(
-             ['kinit', 'admin'],
-             stdin_text=cmd_input.format(
-@@ -611,8 +600,8 @@ class TestACMERenew(IntegrationTest):
- 
-         # move back date
-         for host in self.clients[0], self.master:
--            host.run_command(['kdestroy', '-A'])
--            move_date(host, 'start', '-90days')
-+            tasks.kdestroy_all(host)
-+            tasks.move_date(host, 'start', '-90days')
-             tasks.kinit_admin(host)
- 
-     @pytest.mark.skipif(skip_certbot_tests, reason='certbot not available')
--- 
-2.31.1
-
diff --git a/SOURCES/0030-test_acme-make-password-renewal-more-robust.patch b/SOURCES/0030-test_acme-make-password-renewal-more-robust.patch
deleted file mode 100644
index f1e3334..0000000
--- a/SOURCES/0030-test_acme-make-password-renewal-more-robust.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-From 701adb9185c77194ba1ad0c5fd2f13484417ef6f Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Tue, 20 Jul 2021 20:22:23 +0200
-Subject: [PATCH] test_acme: make password renewal more robust
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-A kinit immediately following a password change can fail.
-Setting KRB5_TRACE and retrieving kdcinfo will help to understand
-the cause of failure.
-
-Fixes: https://pagure.io/freeipa/issue/8929
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
----
- ipatests/test_integration/test_acme.py | 28 +++++++++++++-------------
- 1 file changed, 14 insertions(+), 14 deletions(-)
-
-diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py
-index b4aa1b351..10195a95f 100644
---- a/ipatests/test_integration/test_acme.py
-+++ b/ipatests/test_integration/test_acme.py
-@@ -576,25 +576,25 @@ class TestACMERenew(IntegrationTest):
-         # request a standalone acme cert
-         certbot_standalone_cert(self.clients[0], self.acme_server)
- 
--        cmd_input = (
--            # Password for admin@{REALM}:
--            "{pwd}\n"
--            # Password expired.  You must change it now.
--            # Enter new password:
--            "{pwd}\n"
--            # Enter it again:
--            "{pwd}\n"
--        )
-         # move system date to expire acme cert
-         for host in self.clients[0], self.master:
-             tasks.kdestroy_all(host)
-             tasks.move_date(host, 'stop', '+90days')
--        self.clients[0].run_command(
--            ['kinit', 'admin'],
--            stdin_text=cmd_input.format(
--                pwd=self.clients[0].config.admin_password
--            )
-+
-+        tasks.get_kdcinfo(host)
-+        # Note raiseonerr=False:
-+        # the assert is located after kdcinfo retrieval.
-+        result = host.run_command(
-+            "KRB5_TRACE=/dev/stdout kinit %s" % 'admin',
-+            stdin_text='{0}\n{0}\n{0}\n'.format(
-+                self.clients[0].config.admin_password
-+            ),
-+            raiseonerr=False
-         )
-+        # Retrieve kdc.$REALM after the password change, just in case SSSD
-+        # domain status flipped to online during the password change.
-+        tasks.get_kdcinfo(host)
-+        assert result.returncode == 0
- 
-         yield
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0031-tasks.py-fix-flake8-reported-issues.patch b/SOURCES/0031-tasks.py-fix-flake8-reported-issues.patch
deleted file mode 100644
index 548f2f5..0000000
--- a/SOURCES/0031-tasks.py-fix-flake8-reported-issues.patch
+++ /dev/null
@@ -1,58 +0,0 @@
-From 5b826ab3582566b15a618f57cb2e002a9c16ef64 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Tue, 20 Jul 2021 20:29:00 +0200
-Subject: [PATCH] tasks.py: fix flake8-reported issues
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Fixes: https://pagure.io/freeipa/issue/8931
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
----
- ipatests/pytest_ipa/integration/tasks.py | 14 ++++++++------
- 1 file changed, 8 insertions(+), 6 deletions(-)
-
-diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
-index c2e548617..075c05cde 100755
---- a/ipatests/pytest_ipa/integration/tasks.py
-+++ b/ipatests/pytest_ipa/integration/tasks.py
-@@ -597,7 +597,9 @@ def install_adtrust(host):
-     dig_command = ['dig', 'SRV', '+short', '@localhost',
-                    '_ldap._tcp.%s' % host.domain.name]
-     dig_output = '0 100 389 %s.' % host.hostname
--    dig_test = lambda x: re.search(re.escape(dig_output), x)
-+
-+    def dig_test(x):
-+        return re.search(re.escape(dig_output), x)
- 
-     run_repeatedly(host, dig_command, test=dig_test)
- 
-@@ -2122,8 +2124,8 @@ def create_active_user(host, login, password, first='test', last='user',
-         result = host.run_command(
-             "KRB5_TRACE=/dev/stdout kinit %s" % login,
-             stdin_text='{0}\n{1}\n{1}\n'.format(
--                temp_password, password, raiseonerr=False
--            )
-+                temp_password, password
-+            ), raiseonerr=False
-         )
-         # Retrieve kdc.$REALM after the password change, just in case SSSD
-         # domain status flipped to online during the password change.
-@@ -2264,10 +2266,10 @@ class KerberosKeyCopier:
-             [paths.KLIST, "-eK", "-k", keytab], log_stdout=False)
- 
-         keys_to_sync = []
--        for l in result.stdout_text.splitlines():
--            if (princ in l and any(e in l for e in self.valid_etypes)):
-+        for line in result.stdout_text.splitlines():
-+            if (princ in line and any(e in line for e in self.valid_etypes)):
- 
--                els = l.split()
-+                els = line.split()
-                 els[-2] = els[-2].strip('()')
-                 els[-1] = els[-1].strip('()')
-                 keys_to_sync.append(KeyEntry._make(els))
--- 
-2.31.1
-
diff --git a/SOURCES/0032-Fix-ldapupdate.get_sub_dict-for-missing-named-user.patch b/SOURCES/0032-Fix-ldapupdate.get_sub_dict-for-missing-named-user.patch
deleted file mode 100644
index e48eddf..0000000
--- a/SOURCES/0032-Fix-ldapupdate.get_sub_dict-for-missing-named-user.patch
+++ /dev/null
@@ -1,142 +0,0 @@
-From a1eb13cdbc109da8c028bb886a1207ea2cc23cee Mon Sep 17 00:00:00 2001
-From: Christian Heimes <cheimes@redhat.com>
-Date: Tue, 27 Jul 2021 11:54:20 +0200
-Subject: [PATCH] Fix ldapupdate.get_sub_dict() for missing named user
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-The named user may not be present when ipa-server-dns and bind are not
-installed. NAMED_UID and NAMED_GID constants are only used with local
-DNS support.
-
-Fixes: https://pagure.io/freeipa/issue/8936
-Signed-off-by: Christian Heimes <cheimes@redhat.com>
-Co-authored-by: François Cami <fcami@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/install/ldapupdate.py               | 14 +++++++---
- .../nightly_ipa-4-9_latest.yaml               | 12 +++++++++
- .../nightly_ipa-4-9_previous.yaml             | 12 +++++++++
- .../test_integration/test_installation.py     | 27 +++++++++++++++++++
- 4 files changed, 62 insertions(+), 3 deletions(-)
-
-diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
-index 06cb78e0b..f0e7d6162 100644
---- a/ipaserver/install/ldapupdate.py
-+++ b/ipaserver/install/ldapupdate.py
-@@ -64,6 +64,15 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         idrange_size = idmax - idstart + 1
-         subid_base_rid = constants.SUBID_RANGE_START - idrange_size
- 
-+    # uid / gid for autobind
-+    # user is only defined when ipa-server-dns and bind are installed
-+    try:
-+        named_uid = platformconstants.NAMED_USER.uid
-+        named_gid = platformconstants.NAMED_GROUP.gid
-+    except ValueError:
-+        named_uid = None
-+        named_gid = None
-+
-     return dict(
-         REALM=realm,
-         DOMAIN=domain,
-@@ -99,9 +108,8 @@ def get_sub_dict(realm, domain, suffix, fqdn, idstart=None, idmax=None):
-         DEFAULT_ADMIN_SHELL=platformconstants.DEFAULT_ADMIN_SHELL,
-         SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
-         SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
--        # uid / gid for autobind
--        NAMED_UID=platformconstants.NAMED_USER.uid,
--        NAMED_GID=platformconstants.NAMED_GROUP.gid,
-+        NAMED_UID=named_uid,
-+        NAMED_GID=named_gid,
-     )
- 
- 
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
-index 939ee2b7d..1c8c5ddfc 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
-@@ -547,6 +547,18 @@ jobs:
-         timeout: 4800
-         topology: *master_1repl_1client
- 
-+  fedora-latest-ipa-4-9/test_installation_TestInstallWithoutNamed:
-+    requires: [fedora-latest-ipa-4-9/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-latest-ipa-4-9/build_url}'
-+        test_suite: test_integration/test_installation.py::TestInstallWithoutNamed
-+        template: *ci-ipa-4-9-latest
-+        timeout: 4800
-+        topology: *master_1repl
-+
-   fedora-latest-ipa-4-9/test_idviews:
-     requires: [fedora-latest-ipa-4-9/build]
-     priority: 50
-diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
-index 03658a934..6d121d59f 100644
---- a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
-+++ b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
-@@ -547,6 +547,18 @@ jobs:
-         timeout: 4800
-         topology: *master_1repl_1client
- 
-+  fedora-previous-ipa-4-9/test_installation_TestInstallWithoutNamed:
-+    requires: [fedora-previous-ipa-4-9/build]
-+    priority: 50
-+    job:
-+      class: RunPytest
-+      args:
-+        build_url: '{fedora-previous-ipa-4-9/build_url}'
-+        test_suite: test_integration/test_installation.py::TestInstallWithoutNamed
-+        template: *ci-ipa-4-9-previous
-+        timeout: 4800
-+        topology: *master_1repl
-+
-   fedora-previous-ipa-4-9/test_idviews:
-     requires: [fedora-previous-ipa-4-9/build]
-     priority: 50
-diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
-index e76fd0efe..e3c41eaa1 100644
---- a/ipatests/test_integration/test_installation.py
-+++ b/ipatests/test_integration/test_installation.py
-@@ -1853,3 +1853,30 @@ class TestInstallWithoutSudo(IntegrationTest):
-         result = tasks.install_client(self.master, self.clients[0])
-         assert self.no_sudo_str not in result.stderr_text
-         assert self.sudo_version_str not in result.stdout_text
-+
-+
-+class TestInstallWithoutNamed(IntegrationTest):
-+    num_replicas = 1
-+
-+    @classmethod
-+    def remove_named(cls, host):
-+        # remove the bind package and make sure the named user does not exist.
-+        # https://pagure.io/freeipa/issue/8936
-+        result = host.run_command(['id', 'named'], raiseonerr=False)
-+        if result.returncode == 0:
-+            tasks.uninstall_packages(host, ['bind'])
-+            host.run_command(['userdel', constants.NAMED_USER])
-+        assert host.run_command(
-+            ['id', 'named'], raiseonerr=False
-+        ).returncode == 1
-+
-+    @classmethod
-+    def install(cls, mh):
-+        for tgt in (cls.master, cls.replicas[0]):
-+            cls.remove_named(tgt)
-+        tasks.install_master(cls.master, setup_dns=False)
-+
-+    def test_replica0_install(self):
-+        tasks.install_replica(
-+            self.master, self.replicas[0], setup_ca=False, setup_dns=False
-+        )
--- 
-2.31.1
-
diff --git a/SOURCES/0033-freeipa.spec.in-remove-python3-pexpect-from-Requires.patch b/SOURCES/0033-freeipa.spec.in-remove-python3-pexpect-from-Requires.patch
deleted file mode 100644
index 31489e5..0000000
--- a/SOURCES/0033-freeipa.spec.in-remove-python3-pexpect-from-Requires.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From e0e1d6f94dd16c8066be8ce3c75ef306890a3e2b Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Wed, 28 Jul 2021 18:47:02 +0200
-Subject: [PATCH] freeipa.spec.in: remove python3-pexpect from Requires
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-python3-pexpect will be removed in RHEL9.
-Update BuildRequires/Requires accordingly.
-
-Fixes: https://pagure.io/freeipa/issue/8938
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Antonio Torres <antorres@redhat.com>
----
- freeipa.spec.in | 14 ++++++++++----
- 1 file changed, 10 insertions(+), 4 deletions(-)
-
-diff --git a/freeipa.spec.in b/freeipa.spec.in
-index c33d2e216..9440f3602 100755
---- a/freeipa.spec.in
-+++ b/freeipa.spec.in
-@@ -328,11 +328,18 @@ BuildRequires: python3-m2r
- # Build dependencies for lint and fastcheck
- #
- %if %{with lint}
--BuildRequires:  git
--%if 0%{?fedora} < 34
-+
-+# python3-pexpect might not be available in RHEL9
-+%if 0%{?fedora} || 0%{?rhel} < 9
-+BuildRequires:  python3-pexpect
-+%endif
-+
- # jsl is orphaned in Fedora 34+
-+%if 0%{?fedora} < 34
- BuildRequires:  jsl
- %endif
-+
-+BuildRequires:  git
- BuildRequires:  nss-tools
- BuildRequires:  rpmlint
- BuildRequires:  softhsm
-@@ -357,7 +364,6 @@ BuildRequires:  python3-lxml
- BuildRequires:  python3-netaddr >= %{python_netaddr_version}
- BuildRequires:  python3-netifaces
- BuildRequires:  python3-paste
--BuildRequires:  python3-pexpect
- BuildRequires:  python3-pki >= %{pki_version}
- BuildRequires:  python3-polib
- BuildRequires:  python3-pyasn1
-@@ -878,11 +884,11 @@ Requires: python3-ipaclient = %{version}-%{release}
- Requires: python3-ipaserver = %{version}-%{release}
- Requires: iptables
- Requires: python3-cryptography >= 1.6
--Requires: python3-pexpect
- %if 0%{?fedora}
- # These packages do not exist on RHEL and for ipatests use
- # they are installed on the controller through other means
- Requires: ldns-utils
-+Requires: python3-pexpect
- # update-crypto-policies
- Requires: crypto-policies-scripts
- Requires: python3-polib
--- 
-2.31.1
-
diff --git a/SOURCES/0034-ipa-getkeytab-add-option-to-discover-servers-using-D.patch b/SOURCES/0034-ipa-getkeytab-add-option-to-discover-servers-using-D.patch
deleted file mode 100644
index 798f9d2..0000000
--- a/SOURCES/0034-ipa-getkeytab-add-option-to-discover-servers-using-D.patch
+++ /dev/null
@@ -1,365 +0,0 @@
-From 42206df69adc9c1eefa3ee576891b2ae3ac269e0 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 15 Jul 2021 15:11:28 -0400
-Subject: [PATCH] ipa-getkeytab: add option to discover servers using DNS SRV
-
-The basic flow is:
-
-- If server is provided by the user then use it
-- If server the magic value '_srv', check for _ldap._tcp SRV records for
-  the domain in /etc/ipa/default.conf
-- If no servers are found use the server from default.conf
-
-https://pagure.io/freeipa/issue/8478
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- client/Makefile.am         |   1 +
- client/ipa-getkeytab.c     | 221 +++++++++++++++++++++++++++++++++++++
- client/man/ipa-getkeytab.1 |   5 +-
- configure.ac               |  10 ++
- 4 files changed, 236 insertions(+), 1 deletion(-)
-
-diff --git a/client/Makefile.am b/client/Makefile.am
-index 0031c04a5..72f4cb3dc 100644
---- a/client/Makefile.am
-+++ b/client/Makefile.am
-@@ -66,6 +66,7 @@ ipa_getkeytab_LDADD = 		\
- 	$(SASL_LIBS)		\
- 	$(POPT_LIBS)		\
- 	$(LIBINTL_LIBS)         \
-+	$(RESOLV_LIBS)		\
- 	$(INI_LIBS)		\
- 	$(NULL)
- 
-diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
-index 04786be9e..d3673eb05 100644
---- a/client/ipa-getkeytab.c
-+++ b/client/ipa-getkeytab.c
-@@ -34,9 +34,11 @@
- #include <time.h>
- #include <krb5.h>
- #include <ldap.h>
-+#include <resolv.h>
- #include <sasl/sasl.h>
- #include <popt.h>
- #include <ini_configobj.h>
-+#include <openssl/rand.h>
- 
- #include "config.h"
- 
-@@ -46,6 +48,174 @@
- #include "ipa_ldap.h"
- 
- 
-+struct srvrec {
-+    char *host;
-+    uint16_t port;
-+    int priority, weight;
-+    struct srvrec *next;
-+};
-+
-+static int
-+srvrec_priority_sort(const void *a, const void *b)
-+{
-+	const struct srvrec *sa, *sb;
-+
-+	sa = a;
-+	sb = b;
-+	return sa->priority - sb->priority;
-+}
-+
-+static int
-+srvrec_sort_weight(const void *a, const void *b)
-+{
-+	const struct srvrec *sa, *sb;
-+
-+	sa = a;
-+	sb = b;
-+	return sa->weight - sb->weight;
-+}
-+
-+/* Return a uniform random number between 0 and range */
-+static double
-+rand_inclusive(double range)
-+{
-+	long long r;
-+
-+	if (range == 0) {
-+		return 0;
-+	}
-+
-+	if (RAND_bytes((unsigned char *) &r, sizeof(r)) == -1) {
-+		return 0;
-+	}
-+	if (r < 0) {
-+		r = -r;
-+	}
-+	return ((double)r / (double)LLONG_MAX) * range;
-+}
-+
-+static void
-+sort_prio_weight(struct srvrec *res, int len)
-+{
-+	int i, j;
-+	double tweight;
-+	struct srvrec tmp;
-+	double r;
-+
-+	qsort(res, len, sizeof(res[0]), srvrec_sort_weight);
-+	for (i = 0; i < len - 1; i++) {
-+		tweight = 0;
-+		for (j = i; j < len; j++) {
-+            /* Give records with 0 weight a small chance */
-+			tweight += res[j].weight ? res[j].weight : 0.01;
-+		}
-+		r = rand_inclusive(tweight);
-+		tweight = 0;
-+		for (j = i; j < len; j++) {
-+			tweight += res[j].weight ? res[j].weight : 0.01;
-+			if (tweight >= r) {
-+				break;
-+			}
-+		}
-+		if (j >= len) {
-+			continue;
-+		}
-+		memcpy(&tmp, &res[i], sizeof(tmp));
-+		memcpy(&res[i], &res[j], sizeof(tmp));
-+		memcpy(&res[j], &tmp, sizeof(tmp));
-+	}
-+}
-+
-+/* The caller is responsible for freeing the results */
-+static int
-+query_srv(const char *name, const char *domain, struct srvrec **results)
-+{
-+	int i, j, len;
-+	unsigned char *answer = NULL;
-+	size_t answer_len = NS_MAXMSG;
-+	struct srvrec *res = NULL;
-+	ns_msg msg;
-+	ns_rr rr;
-+	int rv = -1;
-+
-+	*results = NULL;
-+	if ((name == NULL) || (strlen(name) == 0) ||
-+	    (domain == NULL) || (strlen(domain) == 0)) {
-+		return -1;
-+	}
-+
-+	res_init();
-+	answer = malloc(answer_len + 1);
-+	if (answer == NULL) {
-+		return -1;
-+	}
-+	memset(answer, 0, answer_len + 1);
-+	i = res_querydomain(name, domain, C_IN, T_SRV, answer, answer_len);
-+	if (i == -1) {
-+		goto error;
-+	}
-+	answer_len = i;
-+	memset(&msg, 0, sizeof(msg));
-+	if (ns_initparse(answer, answer_len, &msg) != 0) {
-+		goto error;
-+	}
-+	memset(&rr, 0, sizeof(rr));
-+	for (i = 0; ns_parserr(&msg, ns_s_an, i, &rr) == 0; i++) {
-+		continue;
-+	}
-+	if (i == 0) {
-+		goto error;
-+	}
-+	len = i;
-+	res = malloc(sizeof(*res) * i);
-+	if (res == NULL) {
-+		goto error;
-+	}
-+	memset(res, 0, sizeof(*res) * i);
-+	for (i = 0, j = 0; i < len; i++) {
-+		if (ns_parserr(&msg, ns_s_an, i, &rr) != 0) {
-+			continue;
-+		}
-+		if (rr.rdlength < 6) {
-+			continue;
-+		}
-+		res[j].host = malloc(rr.rdlength - 6 + 1);
-+		if (res[j].host == NULL) {
-+			goto error;
-+		}
-+		res[j].priority = ntohs(*(uint16_t *)rr.rdata);
-+		res[j].weight = ntohs(*(uint16_t *)(rr.rdata + 2));
-+		res[j].port = ntohs(*(uint16_t *)(rr.rdata + 4));
-+		memcpy(res[j].host, rr.rdata + 6, rr.rdlength - 6);
-+		if (ns_name_ntop(rr.rdata + 6, res[j].host, rr.rdlength - 6) == -1) {
-+			continue;
-+		}
-+		res[j].host[rr.rdlength - 6] = '\0';
-+		j++;
-+	}
-+	len = j;
-+	qsort(res, len, sizeof(res[0]), srvrec_priority_sort);
-+	i = 0;
-+	while (i < len) {
-+		j = i + 1;
-+		while (j < len && (res[j].priority == res[i].priority)) {
-+			j++;
-+		}
-+		sort_prio_weight(res + i, j - i);
-+		i = j;
-+	}
-+	/* Fixup the linked-list pointers */
-+	for (i = 0; i < len - 1; i++) {
-+		res[i].next = &res[i + 1];
-+	}
-+	*results = res;
-+	rv = 0;
-+
-+error:
-+	free(answer);
-+	return rv;
-+}
-+
- static int check_sasl_mech(const char *mech)
- {
-     int i;
-@@ -619,6 +789,7 @@ static char *ask_password(krb5_context krbctx, char *prompt1, char *prompt2,
- 
- struct ipa_config {
-     const char *server_name;
-+    const char *domain;
- };
- 
- static int config_from_file(struct ini_cfgobj *cfgctx)
-@@ -688,6 +859,11 @@ int read_ipa_config(struct ipa_config **ipacfg)
-     if (ret == 0 && obj != NULL) {
-         (*ipacfg)->server_name = ini_get_string_config_value(obj, &ret);
-     }
-+    ret = ini_get_config_valueobj("global", "domain", cfgctx,
-+                                  INI_GET_LAST_VALUE, &obj);
-+    if (ret == 0 && obj != NULL) {
-+        (*ipacfg)->domain = ini_get_string_config_value(obj, &ret);
-+    }
- 
-     return 0;
- }
-@@ -754,6 +930,7 @@ int main(int argc, const char *argv[])
- 	static const char *sasl_mech = NULL;
- 	static const char *ca_cert_file = NULL;
- 	int quiet = 0;
-+	int verbose = 0;
- 	int askpass = 0;
- 	int askbindpw = 0;
- 	int permitted_enctypes = 0;
-@@ -761,6 +938,8 @@ int main(int argc, const char *argv[])
-         struct poptOption options[] = {
-             { "quiet", 'q', POPT_ARG_NONE, &quiet, 0,
-               _("Print as little as possible"), _("Output only on errors")},
-+            { "verbose", 'v', POPT_ARG_NONE, &verbose, 0,
-+              _("Print debugging information"), _("Output debug info")},
-             { "server", 's', POPT_ARG_STRING, &server, 0,
-               _("Contact this specific KDC Server"),
-               _("Server Name") },
-@@ -906,6 +1085,41 @@ int main(int argc, const char *argv[])
-         exit(2);
-     }
- 
-+    if (server && (strcasecmp(server, "_srv_") == 0)) {
-+        struct srvrec *srvrecs, *srv;
-+        struct ipa_config *ipacfg = NULL;
-+
-+        ret = read_ipa_config(&ipacfg);
-+        if (ret == 0 && ipacfg->domain && verbose) {
-+            fprintf(stderr, _("DNS discovery for domain %s\n"), ipacfg->domain);
-+        }
-+        if (query_srv("_ldap._tcp", ipacfg->domain, &srvrecs) == 0) {
-+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
-+				if (verbose) {
-+            	    fprintf(stderr, _("Discovered server %s\n"), srv->host);
-+                }
-+            }
-+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
-+                server = strdup(srv->host);
-+				if (verbose) {
-+            		fprintf(stderr, _("Using discovered server %s\n"), server);
-+				}
-+                break;
-+            }
-+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
-+				free(srv->host);
-+            }
-+        } else {
-+			if (verbose) {
-+           		fprintf(stderr, _("DNS Discovery failed\n"));
-+			}
-+        }
-+        if (strcasecmp(server, "_srv_") == 0) {
-+            /* Discovery failed, fall through to option methods */
-+            server = NULL;
-+        }
-+    }
-+
-     if (!server && !ldap_uri) {
-         struct ipa_config *ipacfg = NULL;
- 
-@@ -915,10 +1129,17 @@ int main(int argc, const char *argv[])
-             ipacfg->server_name = NULL;
-         }
-         free(ipacfg);
-+		if (verbose && server) {
-+            fprintf(stderr, _("Using server from config %s\n"), server);
-+        }
-         if (!server) {
-             fprintf(stderr, _("Server name not provided and unavailable\n"));
-             exit(2);
-         }
-+    } else {
-+        if (verbose) {
-+            fprintf(stderr, _("Using provided server %s\n"), server);
-+        }
-     }
-     if (server) {
-         ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri);
-diff --git a/client/man/ipa-getkeytab.1 b/client/man/ipa-getkeytab.1
-index b57c5489c..07d2d73b3 100644
---- a/client/man/ipa-getkeytab.1
-+++ b/client/man/ipa-getkeytab.1
-@@ -78,7 +78,10 @@ arcfour\-hmac
- \fB\-s ipaserver\fR
- The IPA server to retrieve the keytab from (FQDN). If this option is not
- provided the server name is read from the IPA configuration file
--(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR.
-+(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. If the
-+value is _srv_ then DNS discovery will be used to determine a server.
-+If this discovery fails then it will fall back to using the configuration
-+file.
- .TP
- \fB\-q\fR
- Quiet mode. Only errors are displayed.
-diff --git a/configure.ac b/configure.ac
-index dc79d5dce..9d7a33825 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -108,6 +108,16 @@ LDAP_CFLAGS=""
- AC_SUBST(LDAP_LIBS)
- AC_SUBST(LDAP_CFLAGS)
- 
-+dnl ---------------------------------------------------------------------------
-+dnl - Check for resolv library
-+dnl ---------------------------------------------------------------------------
-+
-+SAVE_CPPFLAGS=$CPPFLAGS
-+CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
-+AC_CHECK_LIB(resolv,main,RESOLV_LIBS=-lresolv)
-+AC_CHECK_HEADERS(resolv.h)
-+AC_SUBST(RESOLV_LIBS)
-+
- dnl ---------------------------------------------------------------------------
- dnl - Check for OpenSSL Crypto library
- dnl ---------------------------------------------------------------------------
--- 
-2.31.1
-
diff --git a/SOURCES/0035-ipa-getkeytab-fix-compiler-warnings.patch b/SOURCES/0035-ipa-getkeytab-fix-compiler-warnings.patch
deleted file mode 100644
index 734e8e9..0000000
--- a/SOURCES/0035-ipa-getkeytab-fix-compiler-warnings.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From 0114d24ea160676b784ef7010c19bbacc67ceea0 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 15 Jul 2021 17:52:54 -0400
-Subject: [PATCH] ipa-getkeytab: fix compiler warnings
-
-Make read_ipa_config and filter_keys static to avoid
-"no previous prototype" warnings.
-
-Use correct datatype of return value for ber_scanf to
-correct different signedness comparision.
-
-Fixed while working on https://pagure.io/freeipa/issue/8478
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- client/ipa-getkeytab.c | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
-index d3673eb05..309b3c704 100644
---- a/client/ipa-getkeytab.c
-+++ b/client/ipa-getkeytab.c
-@@ -291,7 +291,7 @@ static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *s
- 	return ret;
- }
- 
--int filter_keys(krb5_context krbctx, struct keys_container *keys,
-+static int filter_keys(krb5_context krbctx, struct keys_container *keys,
-                 ber_int_t *enctypes)
- {
-     struct krb_key_salt *ksdata;
-@@ -507,7 +507,7 @@ static int ldap_set_keytab(krb5_context krbctx,
- 	BerElement *sctrl = NULL;
- 	struct berval *control = NULL;
- 	LDAPControl **srvctrl = NULL;
--	int ret;
-+	ber_tag_t ret;
- 	int kvno, i;
- 	ber_tag_t rtag;
- 	ber_int_t *encs = NULL;
-@@ -826,7 +826,7 @@ static int config_from_file(struct ini_cfgobj *cfgctx)
-     return 0;
- }
- 
--int read_ipa_config(struct ipa_config **ipacfg)
-+static int read_ipa_config(struct ipa_config **ipacfg)
- {
-     struct ini_cfgobj *cfgctx = NULL;
-     struct value_obj *obj = NULL;
--- 
-2.31.1
-
diff --git a/SOURCES/0036-ipatests-test-ipa-getkeytab-server-option.patch b/SOURCES/0036-ipatests-test-ipa-getkeytab-server-option.patch
deleted file mode 100644
index 0b3e60c..0000000
--- a/SOURCES/0036-ipatests-test-ipa-getkeytab-server-option.patch
+++ /dev/null
@@ -1,92 +0,0 @@
-From 7a13200fd8b92dd90ebc4b6416ef25659df8aa71 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Fri, 16 Jul 2021 12:59:47 -0400
-Subject: [PATCH] ipatests: test ipa-getkeytab server option
-
-Test various usages of the -s/--server option:
-* -s is defined, use it as the server
-* no -s, use the host value from /etc/ipa/default.conf
-* -s is '_srv_', do DNS discovery
-
-https://pagure.io/freeipa/issue/8478
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_commands.py | 58 ++++++++++++++++++++++
- 1 file changed, 58 insertions(+)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index d64519eb7..2035ced56 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -1467,6 +1467,64 @@ class TestIPACommand(IntegrationTest):
-             assert 'This account is currently not available' in \
-                 result.stdout_text
- 
-+    def test_ipa_getkeytab_server(self):
-+        """
-+        Exercise the ipa-getkeytab server options
-+
-+        This relies on the behavior that without a TGT
-+        ipa-getkeytab will quit and not do much of anything.
-+
-+        A bogus keytab and principal are passed in to satisfy the
-+        minimum requirements.
-+        """
-+        tasks.kdestroy_all(self.master)
-+
-+        # Pass in a server name to use
-+        result = self.master.run_command(
-+            [
-+                paths.IPA_GETKEYTAB,
-+                "-k",
-+                "/tmp/keytab",
-+                "-p",
-+                "foo",
-+                "-s",
-+                self.master.hostname,
-+                "-v",
-+            ], raiseonerr=False).stderr_text
-+
-+        assert 'Using provided server %s' % self.master.hostname in result
-+
-+        # Don't pass in a name, should use /etc/ipa/default.conf
-+        result = self.master.run_command(
-+            [
-+                paths.IPA_GETKEYTAB,
-+                "-k",
-+                "/tmp/keytab",
-+                "-p",
-+                "foo",
-+                "-v",
-+            ], raiseonerr=False).stderr_text
-+
-+        assert (
-+            'Using server from config %s' % self.master.hostname
-+            in result
-+        )
-+
-+        # Use DNS SRV lookup
-+        result = self.master.run_command(
-+            [
-+                paths.IPA_GETKEYTAB,
-+                "-k",
-+                "/tmp/keytab",
-+                "-p",
-+                "foo",
-+                "-s",
-+                "_srv_",
-+                "-v",
-+            ], raiseonerr=False).stderr_text
-+
-+        assert 'Discovered server %s' % self.master.hostname in result
-+
- 
- class TestIPACommandWithoutReplica(IntegrationTest):
-     """
--- 
-2.31.1
-
diff --git a/SOURCES/0037-ipatests-Test-for-OTP-when-the-LDAP-connection-timed.patch b/SOURCES/0037-ipatests-Test-for-OTP-when-the-LDAP-connection-timed.patch
deleted file mode 100644
index d24f83c..0000000
--- a/SOURCES/0037-ipatests-Test-for-OTP-when-the-LDAP-connection-timed.patch
+++ /dev/null
@@ -1,91 +0,0 @@
-From 25a4acf3ad5964eacddbcb83ddf9f84432968918 Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Thu, 22 Jul 2021 14:55:50 +0530
-Subject: [PATCH] ipatests: Test for OTP when the LDAP connection timed out.
-
-Test to verify that when the idle timeout is exceeded (30s idle,
-60s sleep) then the ipa-otpd process should exit without error.
-
-Related : https://pagure.io/freeipa/issue/6587
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipatests/test_integration/test_otp.py | 56 +++++++++++++++++++++++++++
- 1 file changed, 56 insertions(+)
-
-diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
-index b2e65af1b..fd55898ca 100644
---- a/ipatests/test_integration/test_otp.py
-+++ b/ipatests/test_integration/test_otp.py
-@@ -20,6 +20,7 @@ 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
- 
- 
- PASSWORD = "DummyPassword123"
-@@ -309,3 +310,58 @@ class TestOTPToken(IntegrationTest):
-             master.run_command(['ipa', 'user-del', USER2])
-             self.master.run_command(['semanage', 'login', '-D'])
-             sssd_conf_backup.restore()
-+
-+    @pytest.fixture
-+    def setup_otp_nsslapd(self):
-+        # setting nsslapd-idletimeout
-+        new_limit = 30
-+        conn = self.master.ldap_connect()
-+        dn = DN(('cn', 'config'))
-+        entry = conn.get_entry(dn)  # pylint: disable=no-member
-+        orig_limit = entry.single_value.get('nsslapd-idletimeout')
-+        ldap_query = textwrap.dedent("""
-+            dn: cn=config
-+            changetype: modify
-+            replace: nsslapd-idletimeout
-+            nsslapd-idletimeout: {limit}
-+        """)
-+        tasks.ldapmodify_dm(self.master, ldap_query.format(limit=new_limit))
-+        # Be sure no services are running and failed units
-+        self.master.run_command(['killall', 'ipa-otpd'], raiseonerr=False)
-+        check_services = self.master.run_command(
-+            ['systemctl', 'list-units', '--state=failed']
-+        )
-+        assert "0 loaded units listed" in check_services.stdout_text
-+        assert "ipa-otpd" not in check_services.stdout_text
-+        yield
-+        # cleanup
-+        tasks.ldapmodify_dm(self.master, ldap_query.format(limit=orig_limit))
-+
-+    def test_check_otpd_after_idle_timeout(self, setup_otp_nsslapd):
-+        """Test for OTP when the LDAP connection timed out.
-+
-+        Test for : https://pagure.io/freeipa/issue/6587
-+
-+        ipa-otpd was exiting with failure when LDAP connection timed out.
-+        Test to verify that when the nsslapd-idletimeout is exceeded (30s idle,
-+        60s sleep) then the ipa-otpd process should exit without error.
-+        """
-+        since = time.strftime('%H:%M:%S')
-+        tasks.kinit_admin(self.master)
-+        otpuid, totp = add_otptoken(self.master, USER, otptype="totp")
-+        try:
-+            # kinit with OTP auth
-+            otpvalue = totp.generate(int(time.time())).decode("ascii")
-+            kinit_otp(self.master, USER, password=PASSWORD, otp=otpvalue)
-+            time.sleep(60)
-+            failed_services = self.master.run_command(
-+                ['systemctl', 'list-units', '--state=failed']
-+            )
-+            assert "ipa-otpd" not in failed_services.stdout_text
-+            cmd_jornalctl = self.master.run_command(
-+                ['journalctl', '--since={}'.format(since)]
-+            )
-+            regex = r".*ipa-otpd@.*\sSucceeded"
-+            assert re.search(regex, cmd_jornalctl.stdout_text)
-+        finally:
-+            del_otptoken(self.master, otpuid)
--- 
-2.31.1
-
diff --git a/SOURCES/0038-ipatests-verify-that-getcert-output-includes-the-iss.patch b/SOURCES/0038-ipatests-verify-that-getcert-output-includes-the-iss.patch
deleted file mode 100644
index af90a1f..0000000
--- a/SOURCES/0038-ipatests-verify-that-getcert-output-includes-the-iss.patch
+++ /dev/null
@@ -1,51 +0,0 @@
-From 826b5825bd644fc69a9bee17626d71fe03cc0190 Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Mon, 26 Jul 2021 16:14:19 -0400
-Subject: [PATCH] ipatests: verify that getcert output includes the issued date
-
-certmonger 0.79.14 included a new feature that provides the
-NotBefore (or issued) date to the certificate list output.
-
-Verify that it is present in the output.
-
-https://bugzilla.redhat.com/show_bug.cgi?id=1940261
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipatests/test_integration/test_cert.py | 11 +++++++++++
- 1 file changed, 11 insertions(+)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index b6bb2f08a..9a90db5e2 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -19,6 +19,7 @@ from ipaplatform.paths import paths
- from cryptography import x509
- from cryptography.x509.oid import ExtensionOID
- from cryptography.hazmat.backends import default_backend
-+from pkg_resources import parse_version
- 
- from ipatests.pytest_ipa.integration import tasks
- from ipatests.test_integration.base import IntegrationTest
-@@ -257,6 +258,16 @@ class TestInstallMasterClient(IntegrationTest):
-             raise AssertionError("certmonger request is "
-                                  "in state {}". format(status))
- 
-+    def test_getcert_notafter_output(self):
-+        """Test that currrent certmonger includes NotBefore in output"""
-+        result = self.master.run_command(["certmonger", "-v"]).stdout_text
-+        if parse_version(result.split()[1]) < parse_version('0.79.14'):
-+            raise pytest.skip("not_before not provided in this version")
-+        result = self.master.run_command(
-+            ["getcert", "list", "-f", paths.HTTPD_CERT_FILE]
-+        ).stdout_text
-+        assert 'issued:' in result
-+
- 
- class TestCertmongerRekey(IntegrationTest):
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0039-ipatests-Look-for-warning-into-stderr-instead-of-std.patch b/SOURCES/0039-ipatests-Look-for-warning-into-stderr-instead-of-std.patch
deleted file mode 100644
index a231506..0000000
--- a/SOURCES/0039-ipatests-Look-for-warning-into-stderr-instead-of-std.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 96dd8ac1cd2e7fb8177d83e7ba5c6d79f4216ea3 Mon Sep 17 00:00:00 2001
-From: Mohammad Rizwan <myusuf@redhat.com>
-Date: Mon, 2 Aug 2021 19:26:28 +0530
-Subject: [PATCH] ipatests: Look for warning into stderr instead of stdout
-
-In https://github.com/freeipa/freeipa/pull/5855 was looking
-into stdout_text for warning instead of stderr_text, hence
-was failing for pki version > 10.11.0.
-
-related: https://pagure.io/freeipa/issue/8890
-
-Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipa_cert_fix.py | 6 ++----
- 1 file changed, 2 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index 394e85603..f3cf59afc 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -241,16 +241,14 @@ class TestIpaCertFix(IntegrationTest):
-         # check that pki-server cert-fix command fails
-         err_msg2 = ("ERROR: CalledProcessError(Command "
-                     "['pki-server', 'cert-fix'")
--        warn_msg = ("WARNING: No selftests configured in "
--                    f"{paths.CA_CS_CFG_PATH} "
--                    "(selftests.container.order.startup)")
-+        warn_msg = "WARNING: No selftests configured in"
- 
-         if (tasks.get_pki_version(self.master)
-            < tasks.parse_version('10.11.0')):
-             assert (err_msg1 in result.stderr_text
-                     and err_msg2 in result.stderr_text)
-         else:
--            assert warn_msg in result.stdout_text
-+            assert warn_msg in result.stderr_text
- 
-     def test_expired_CA_cert(self, expire_ca_cert):
-         """Test to check ipa-cert-fix when CA certificate is expired
--- 
-2.31.1
-
diff --git a/SOURCES/0040-ipatests-use-krb5_trace-in-TestIpaAdTrustInstall.patch b/SOURCES/0040-ipatests-use-krb5_trace-in-TestIpaAdTrustInstall.patch
deleted file mode 100644
index 56e1db3..0000000
--- a/SOURCES/0040-ipatests-use-krb5_trace-in-TestIpaAdTrustInstall.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 9ae23e1257478bfee04b08b54f36dda7f5850348 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Thu, 5 Aug 2021 11:37:35 +0200
-Subject: [PATCH] ipatests: use krb5_trace in TestIpaAdTrustInstall
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-tasks.create_active_user can fail in a subtle way when there
-are two IPA servers due to replication delays.
-Using the debug-enabled version of create_active_user helps
-determine whether there is another underlying issue and, in
-general, prevents the above problem.
-
-Fixes: https://pagure.io/freeipa/issue/8944
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Anuja More <amore@redhat.com>
----
- ipatests/test_integration/test_adtrust_install.py | 14 ++++++++++----
- 1 file changed, 10 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
-index bbbb385a5..f23221186 100644
---- a/ipatests/test_integration/test_adtrust_install.py
-+++ b/ipatests/test_integration/test_adtrust_install.py
-@@ -257,8 +257,11 @@ class TestIpaAdTrustInstall(IntegrationTest):
-         user_princ = '@'.join([user, self.master.domain.realm])
-         passwd = 'Secret123'
-         # Create a user with a password
--        tasks.create_active_user(self.master, user, passwd, extra_args=[
--            '--homedir', '/home/{}'.format(user)])
-+        tasks.create_active_user(
-+            self.master, user, passwd,
-+            extra_args=["--homedir", "/home/{}".format(user)],
-+            krb5_trace=True
-+        )
-         try:
-             # Defaults: host/... principal for service
-             # keytab in /etc/krb5.keytab
-@@ -282,8 +285,11 @@ class TestIpaAdTrustInstall(IntegrationTest):
-         user_princ = '@'.join([user, self.master.domain.realm])
-         passwd = 'Secret123'
-         # Create a user with a password
--        tasks.create_active_user(self.master, user, passwd, extra_args=[
--            '--homedir', '/home/{}'.format(user)])
-+        tasks.create_active_user(
-+            self.master, user, passwd,
-+            extra_args=["--homedir", "/home/{}".format(user)],
-+            krb5_trace=True
-+        )
-         try:
-             # Defaults: host/... principal for service
-             # keytab in /etc/krb5.keytab
--- 
-2.31.1
-
diff --git a/SOURCES/0041-ipatests-Test-ldapsearch-with-base-scope-works-with-.patch b/SOURCES/0041-ipatests-Test-ldapsearch-with-base-scope-works-with-.patch
deleted file mode 100644
index f616c59..0000000
--- a/SOURCES/0041-ipatests-Test-ldapsearch-with-base-scope-works-with-.patch
+++ /dev/null
@@ -1,45 +0,0 @@
-From a3d71eb72a6125a80a9d7b698f34dcb95dc25184 Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Thu, 5 Aug 2021 20:03:21 +0530
-Subject: [PATCH] ipatests: Test ldapsearch with base scope works with compat
- tree.
-
-Added test to verify that ldapsearch for compat tree
-with scope base and sub is not failing.
-
-Related: https://bugzilla.redhat.com/show_bug.cgi?id=1958909
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipatests/test_integration/test_commands.py | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 2035ced56..e3a0d867e 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -1558,6 +1558,19 @@ class TestIPACommandWithoutReplica(IntegrationTest):
-         # Run the command again after cache is removed
-         self.master.run_command(['ipa', 'user-show', 'ipauser1'])
- 
-+    def test_basesearch_compat_tree(self):
-+        """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
-+        """
-+        tasks.kinit_admin(self.master)
-+        base_dn = str(self.master.domain.basedn)
-+        base = "cn=admins,cn=groups,cn=compat,{basedn}".format(basedn=base_dn)
-+        tasks.ldapsearch_dm(self.master, base, ldap_args=[], scope='sub')
-+        tasks.ldapsearch_dm(self.master, base, ldap_args=[], scope='base')
-+
- 
- class TestIPAautomount(IntegrationTest):
-     @classmethod
--- 
-2.31.1
-
diff --git a/SOURCES/0042-ipatests-skip-test_basesearch_compat_tree-on-fedora.patch b/SOURCES/0042-ipatests-skip-test_basesearch_compat_tree-on-fedora.patch
deleted file mode 100644
index 012b885..0000000
--- a/SOURCES/0042-ipatests-skip-test_basesearch_compat_tree-on-fedora.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-From d4062e407d242a72b9d4e32f4fdd6aed086ce005 Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Thu, 5 Aug 2021 20:23:15 +0530
-Subject: [PATCH] ipatests: skip test_basesearch_compat_tree on fedora.
-
-slapi-nis with fix is not part of fedora yet.
-test requires with fix:
-https://pagure.io/slapi-nis/c/61ea8f6a104da25329e301a8f56944f860de8177?
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- ipatests/test_integration/test_commands.py | 7 +++++++
- 1 file changed, 7 insertions(+)
-
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index e3a0d867e..4d9a81652 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -38,6 +38,7 @@ 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 ipaserver.install.installutils import realm_to_serverid
-+from pkg_resources import parse_version
- 
- logger = logging.getLogger(__name__)
- 
-@@ -1565,6 +1566,12 @@ class TestIPACommandWithoutReplica(IntegrationTest):
- 
-         related: https://bugzilla.redhat.com/show_bug.cgi?id=1958909
-         """
-+        version = self.master.run_command(
-+            ["rpm", "-qa", "--qf", "%{VERSION}", "slapi-nis"]
-+        )
-+        if tasks.get_platform(self.master) == "fedora" and parse_version(
-+                version.stdout_text) <= parse_version("0.56.7"):
-+            pytest.skip("Test requires slapi-nis with fix on fedora")
-         tasks.kinit_admin(self.master)
-         base_dn = str(self.master.domain.basedn)
-         base = "cn=admins,cn=groups,cn=compat,{basedn}".format(basedn=base_dn)
--- 
-2.31.1
-
diff --git a/SOURCES/0043-ipatests-Refactor-test_check_otpd_after_idle_timeout.patch b/SOURCES/0043-ipatests-Refactor-test_check_otpd_after_idle_timeout.patch
deleted file mode 100644
index cbba6ba..0000000
--- a/SOURCES/0043-ipatests-Refactor-test_check_otpd_after_idle_timeout.patch
+++ /dev/null
@@ -1,89 +0,0 @@
-From eac03d6828d0bac1925c897090fc77e250eaee04 Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Thu, 5 Aug 2021 12:27:38 +0530
-Subject: [PATCH] ipatests: Refactor test_check_otpd_after_idle_timeout
-
-Use whole date when calling journalctl --since
-ipa-otpd don't flush its logs to syslog immediately,
-so check with run_repeatedly.
-Also list failed units when ldap connection is
-timed out.
-
-Related: https://pagure.io/freeipa/issue/6587
-
-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_otp.py | 31 ++++++++++++++++-----------
- 1 file changed, 18 insertions(+), 13 deletions(-)
-
-diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
-index fd55898ca..353470897 100644
---- a/ipatests/test_integration/test_otp.py
-+++ b/ipatests/test_integration/test_otp.py
-@@ -313,6 +313,13 @@ class TestOTPToken(IntegrationTest):
- 
-     @pytest.fixture
-     def setup_otp_nsslapd(self):
-+        check_services = self.master.run_command(
-+            ['systemctl', 'list-units', '--state=failed']
-+        )
-+        assert "0 loaded units listed" in check_services.stdout_text
-+        assert "ipa-otpd" not in check_services.stdout_text
-+        # Be sure no services are running and failed units
-+        self.master.run_command(['killall', 'ipa-otpd'], raiseonerr=False)
-         # setting nsslapd-idletimeout
-         new_limit = 30
-         conn = self.master.ldap_connect()
-@@ -326,13 +333,6 @@ class TestOTPToken(IntegrationTest):
-             nsslapd-idletimeout: {limit}
-         """)
-         tasks.ldapmodify_dm(self.master, ldap_query.format(limit=new_limit))
--        # Be sure no services are running and failed units
--        self.master.run_command(['killall', 'ipa-otpd'], raiseonerr=False)
--        check_services = self.master.run_command(
--            ['systemctl', 'list-units', '--state=failed']
--        )
--        assert "0 loaded units listed" in check_services.stdout_text
--        assert "ipa-otpd" not in check_services.stdout_text
-         yield
-         # cleanup
-         tasks.ldapmodify_dm(self.master, ldap_query.format(limit=orig_limit))
-@@ -346,7 +346,7 @@ class TestOTPToken(IntegrationTest):
-         Test to verify that when the nsslapd-idletimeout is exceeded (30s idle,
-         60s sleep) then the ipa-otpd process should exit without error.
-         """
--        since = time.strftime('%H:%M:%S')
-+        since = time.strftime('%Y-%m-%d %H:%M:%S')
-         tasks.kinit_admin(self.master)
-         otpuid, totp = add_otptoken(self.master, USER, otptype="totp")
-         try:
-@@ -354,14 +354,19 @@ class TestOTPToken(IntegrationTest):
-             otpvalue = totp.generate(int(time.time())).decode("ascii")
-             kinit_otp(self.master, USER, password=PASSWORD, otp=otpvalue)
-             time.sleep(60)
-+
-+            def test_cb(cmd_jornalctl):
-+                # check if LDAP connection is timed out
-+                expected_msg = "Can't contact LDAP server"
-+                return expected_msg in cmd_jornalctl
-+
-+            # ipa-otpd don't flush its logs to syslog immediately
-+            cmd = ['journalctl', '--since={}'.format(since)]
-+            tasks.run_repeatedly(
-+                self.master, command=cmd, test=test_cb, timeout=90)
-             failed_services = self.master.run_command(
-                 ['systemctl', 'list-units', '--state=failed']
-             )
-             assert "ipa-otpd" not in failed_services.stdout_text
--            cmd_jornalctl = self.master.run_command(
--                ['journalctl', '--since={}'.format(since)]
--            )
--            regex = r".*ipa-otpd@.*\sSucceeded"
--            assert re.search(regex, cmd_jornalctl.stdout_text)
-         finally:
-             del_otptoken(self.master, otpuid)
--- 
-2.31.1
-
diff --git a/SOURCES/0044-ipatests-Test-unsecure-nsupdate.patch b/SOURCES/0044-ipatests-Test-unsecure-nsupdate.patch
deleted file mode 100644
index a223c94..0000000
--- a/SOURCES/0044-ipatests-Test-unsecure-nsupdate.patch
+++ /dev/null
@@ -1,162 +0,0 @@
-From 4fdab0c94c4e17e42e5f38a0e671bea39bcc9b74 Mon Sep 17 00:00:00 2001
-From: Anuja More <amore@redhat.com>
-Date: Mon, 9 Aug 2021 20:57:22 +0530
-Subject: [PATCH] ipatests: Test unsecure nsupdate.
-
-The test configures an external bind server on the ipa-server
-(not the IPA-embedded DNS server) that allows unauthenticated nsupdates.
-
-When the IPA client is registered using ipa-client-install,
-DNS records are added for the client in the bind server using nsupdate.
-The first try is using GSS-TIG but fails as expected, and the client
-installer then tries with unauthenticated nsupdate.
-
-Related : https://pagure.io/freeipa/issue/8402
-
-Signed-off-by: Anuja More <amore@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
----
- .../test_installation_client.py               | 118 ++++++++++++++++++
- 1 file changed, 118 insertions(+)
-
-diff --git a/ipatests/test_integration/test_installation_client.py b/ipatests/test_integration/test_installation_client.py
-index fa59a5255..014b0f6ab 100644
---- a/ipatests/test_integration/test_installation_client.py
-+++ b/ipatests/test_integration/test_installation_client.py
-@@ -8,10 +8,15 @@ Module provides tests for various options of ipa-client-install.
- 
- from __future__ import absolute_import
- 
-+import pytest
-+import re
- import shlex
-+import textwrap
- 
-+from ipaplatform.paths import paths
- from ipatests.test_integration.base import IntegrationTest
- from ipatests.pytest_ipa.integration import tasks
-+from ipatests.pytest_ipa.integration.firewall import Firewall
- 
- 
- class TestInstallClient(IntegrationTest):
-@@ -70,3 +75,116 @@ class TestInstallClient(IntegrationTest):
-                              extra_args=['--ssh-trust-dns'])
-         result = self.clients[0].run_command(['cat', '/etc/ssh/ssh_config'])
-         assert 'HostKeyAlgorithms' not in result.stdout_text
-+
-+
-+class TestClientInstallBind(IntegrationTest):
-+    """
-+    The test configures an external bind server on the ipa-server
-+    (not the IPA-embedded DNS server) that allows unauthenticated nsupdates.
-+    When the IPA client is registered using ipa-client-install,
-+    DNS records are added for the client in the bind server using nsupdate.
-+    The first try is using GSS-TIG but fails as expected, and the client
-+    installer then tries with unauthenticated nsupdate.
-+    """
-+
-+    num_clients = 1
-+
-+    @classmethod
-+    def install(cls, mh):
-+        cls.client = cls.clients[0]
-+
-+    @pytest.fixture
-+    def setup_bindserver(self):
-+        bindserver = self.master
-+        named_conf_backup = tasks.FileBackup(self.master, paths.NAMED_CONF)
-+        # create a zone in the BIND server that is identical to the IPA
-+        add_zone = textwrap.dedent("""
-+        zone "{domain}" IN {{ type master;
-+        file "{domain}.db"; allow-query {{ any; }};
-+        allow-update {{ any; }}; }};
-+        """).format(domain=bindserver.domain.name)
-+
-+        namedcfg = bindserver.get_file_contents(
-+            paths.NAMED_CONF, encoding='utf-8')
-+        namedcfg += '\n' + add_zone
-+        bindserver.put_file_contents(paths.NAMED_CONF, namedcfg)
-+
-+        def update_contents(path, pattern, replace):
-+            contents = bindserver.get_file_contents(path, encoding='utf-8')
-+            namedcfg_query = re.sub(pattern, replace, contents)
-+            bindserver.put_file_contents(path, namedcfg_query)
-+
-+        update_contents(paths.NAMED_CONF, 'localhost;', 'any;')
-+        update_contents(paths.NAMED_CONF, "listen-on port 53 { 127.0.0.1; };",
-+                        "#listen-on port 53 { 127.0.0.1; };")
-+        update_contents(paths.NAMED_CONF, "listen-on-v6 port 53 { ::1; };",
-+                        "#listen-on-v6 port 53 { ::1; };")
-+
-+        add_records = textwrap.dedent("""
-+        @   IN  SOA     {fqdn}. root.{domain}. (
-+        1001    ;Serial
-+        3H      ;Refresh
-+        15M     ;Retry
-+        1W      ;Expire
-+        1D      ;Minimum 1D
-+        )
-+        @      IN  NS      {fqdn}.
-+        ns1 IN  A       {bindserverip}
-+        _kerberos.{domain}. IN TXT {zoneupper}
-+        {fqdn}.    IN  A       {bindserverip}
-+        ipa-ca.{domain}.        IN  A       {bindserverip}
-+        _kerberos-master._tcp.{domain}. IN SRV 0 100 88 {fqdn}.
-+        _kerberos-master._udp.{domain}. IN SRV 0 100 88 {fqdn}.
-+        _kerberos._tcp.{domain}. 	IN SRV 0 100 88 {fqdn}.
-+        _kerberos._udp.{domain}. 	IN SRV 0 100 88 {fqdn}.
-+        _kpasswd._tcp.{domain}. 	IN SRV 0 100 464 {fqdn}.
-+        _kpasswd._udp.{domain}. 	IN SRV 0 100 464 {fqdn}.
-+        _ldap._tcp.{domain}. 		IN SRV 0 100 389 {fqdn}.
-+        """).format(
-+            fqdn=bindserver.hostname,
-+            domain=bindserver.domain.name,
-+            bindserverip=bindserver.ip,
-+            zoneupper=bindserver.domain.name.upper()
-+        )
-+        bindserverdb = "/var/named/{0}.db".format(bindserver.domain.name)
-+        bindserver.put_file_contents(bindserverdb, add_records)
-+        bindserver.run_command(['systemctl', 'start', 'named'])
-+        Firewall(bindserver).enable_services(["dns"])
-+        yield
-+        named_conf_backup.restore()
-+        bindserver.run_command(['rm', '-rf', bindserverdb])
-+
-+    def test_client_nsupdate(self, setup_bindserver):
-+        """Test secure nsupdate failed, then try unsecure nsupdate..
-+
-+        Test to verify when bind is configured with dynamic update policy,
-+        and during client-install 'nsupdate -g' fails then it should run with
-+        second call using unauthenticated nsupdate.
-+
-+        Related : https://pagure.io/freeipa/issue/8402
-+        """
-+        # with pre-configured bind server, install ipa-server without dns.
-+        tasks.install_master(self.master, setup_dns=False)
-+        self.client.resolver.backup()
-+        self.client.resolver.setup_resolver(
-+            self.master.ip, self.master.domain.name)
-+        try:
-+            self.client.run_command(['ipa-client-install', '-U',
-+                                     '--domain', self.client.domain.name,
-+                                     '--realm', self.client.domain.realm,
-+                                     '-p', self.client.config.admin_name,
-+                                     '-w', self.client.config.admin_password,
-+                                     '--server', self.master.hostname])
-+            # call unauthenticated nsupdate if GSS-TSIG nsupdate failed.
-+            str1 = "nsupdate (GSS-TSIG) failed"
-+            str2 = "'/usr/bin/nsupdate', '/etc/ipa/.dns_update.txt'"
-+            client_log = self.client.get_file_contents(
-+                paths.IPACLIENT_INSTALL_LOG, encoding='utf-8'
-+            )
-+            assert str1 in client_log and str2 in client_log
-+            dig_after = self.client.run_command(
-+                ['dig', '@{0}'.format(self.master.ip), self.client.hostname,
-+                 '-t', 'SSHFP'])
-+            assert "ANSWER: 0" not in dig_after.stdout_text.strip()
-+        finally:
-+            self.client.resolver.restore()
--- 
-2.31.1
-
diff --git a/SOURCES/0045-ipatests-Fix-TestAJPSecretUpgrade-tests-on-systems-w.patch b/SOURCES/0045-ipatests-Fix-TestAJPSecretUpgrade-tests-on-systems-w.patch
deleted file mode 100644
index 5e978b4..0000000
--- a/SOURCES/0045-ipatests-Fix-TestAJPSecretUpgrade-tests-on-systems-w.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-From c9bc471e063f2865d6423e4f1c9b81e73a45e43f Mon Sep 17 00:00:00 2001
-From: Stanislav Levin <slev@altlinux.org>
-Date: Wed, 4 Aug 2021 18:38:16 +0300
-Subject: [PATCH] ipatests: Fix TestAJPSecretUpgrade tests on systems without
- pkiuser
-
-Tests in `test_ipaserver.test_secure_ajp_connector' assume that there
-is pkiuser in OS, but this is not always true (for example, in systems
-having minimum installed dependencies, in particular, without pki-server
-RPM package). Since the tests already use the mock and pkiuser entity is
-not the subject of testing the pwd.getpwnam has been mocked.
-
-Fixes: https://pagure.io/freeipa/issue/8942
-Signed-off-by: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../test_secure_ajp_connector.py              | 40 ++++++++++++++++---
- 1 file changed, 34 insertions(+), 6 deletions(-)
-
-diff --git a/ipatests/test_ipaserver/test_secure_ajp_connector.py b/ipatests/test_ipaserver/test_secure_ajp_connector.py
-index 2719dbc48..35ef7407a 100644
---- a/ipatests/test_ipaserver/test_secure_ajp_connector.py
-+++ b/ipatests/test_ipaserver/test_secure_ajp_connector.py
-@@ -1,5 +1,6 @@
- # Copyright (C) 2021  FreeIPA Project Contributors - see LICENSE file
- 
-+from collections import namedtuple
- from io import BytesIO
- from lxml.etree import parse as myparse  # pylint: disable=no-name-in-module
- import pytest
-@@ -32,6 +33,32 @@ def mock_etree_parse(data):
-     return myparse(f)
- 
- 
-+def mock_pkiuser_entity():
-+    """Return struct_passwd for mocked pkiuser"""
-+    StructPasswd = namedtuple(
-+        "StructPasswd",
-+        [
-+            "pw_name",
-+            "pw_passwd",
-+            "pw_uid",
-+            "pw_gid",
-+            "pw_gecos",
-+            "pw_dir",
-+            "pw_shell",
-+        ]
-+    )
-+    pkiuser_entity = StructPasswd(
-+        constants.PKI_USER,
-+        pw_passwd="x",
-+        pw_uid=-1,
-+        pw_gid=-1,
-+        pw_gecos="",
-+        pw_dir="/dev/null",
-+        pw_shell="/sbin/nologin",
-+    )
-+    return pkiuser_entity
-+
-+
- # Format of test_data is:
- #    (
- #        is_newer_tomcat (boolean),
-@@ -148,14 +175,15 @@ test_data = (
- 
- 
- class TestAJPSecretUpgrade:
--    @patch('os.chown')
--    @patch('lxml.etree.parse')
--    @pytest.mark.parametrize('is_newer, data, secret, expect, rewrite',
--                             test_data)
--    def test_connecter(self, mock_parse, mock_chown, is_newer, data, secret,
--                       expect, rewrite):
-+    @patch("ipaplatform.base.constants.pwd.getpwnam")
-+    @patch("ipaplatform.base.constants.os.chown")
-+    @patch("ipaserver.install.dogtaginstance.lxml.etree.parse")
-+    @pytest.mark.parametrize("test_data", test_data)
-+    def test_connecter(self, mock_parse, mock_chown, mock_getpwnam, test_data):
-+        is_newer, data, secret, expect, rewrite = test_data
-         mock_chown.return_value = None
-         mock_parse.return_value = mock_etree_parse(data)
-+        mock_getpwnam.return_value = mock_pkiuser_entity()
- 
-         dogtag = MyDogtagInstance(is_newer)
-         with patch('ipaserver.install.dogtaginstance.open', mock_open()) \
--- 
-2.31.1
-
diff --git a/SOURCES/0046-ipatests-test_ipahealthcheck-Verify-permissions-for-.patch b/SOURCES/0046-ipatests-test_ipahealthcheck-Verify-permissions-for-.patch
deleted file mode 100644
index 23d092b..0000000
--- a/SOURCES/0046-ipatests-test_ipahealthcheck-Verify-permissions-for-.patch
+++ /dev/null
@@ -1,54 +0,0 @@
-From 488ac7e3ba9f36d6b187687d120920d2d80d8b7f Mon Sep 17 00:00:00 2001
-From: Michal Polovka <mpolovka@redhat.com>
-Date: Tue, 10 Aug 2021 18:11:05 +0200
-Subject: [PATCH] ipatests: test_ipahealthcheck: Verify permissions for
- /var/log/ files
-
-Test if files in /var/log are being checked with ipahealthcheck.ipa.files source.
-
-Resolves: https://pagure.io/freeipa/issue/8949
-
-Signed-off-by: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipahealthcheck.py   | 23 +++++++++++++++++++
- 1 file changed, 23 insertions(+)
-
-diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
-index 36fe72be7..089793a2f 100644
---- a/ipatests/test_integration/test_ipahealthcheck.py
-+++ b/ipatests/test_integration/test_ipahealthcheck.py
-@@ -1227,6 +1227,29 @@ class TestIpaHealthCheck(IntegrationTest):
-         )
-         assert msg in cmd.stdout_text
- 
-+    def test_ipahealthcheck_verify_perms_for_source_files(self,
-+                                                          modify_permissions):
-+        """
-+        This tests checks if files in /var/log are checked with ipa.files
-+        source.
-+        The test modifies permissions of ipainstall log file and checks the
-+        response from healthcheck.
-+
-+        https://pagure.io/freeipa/issue/8949
-+        """
-+        modify_permissions(self.master, path=paths.IPASERVER_INSTALL_LOG,
-+                           mode="0644")
-+        returncode, data = run_healthcheck(
-+            self.master, "ipahealthcheck.ipa.files", failures_only=True)
-+
-+        assert returncode == 1
-+        assert len(data) == 1
-+        assert data[0]["result"] == "WARNING"
-+        assert data[0]["kw"]["path"] == paths.IPASERVER_INSTALL_LOG
-+        assert data[0]["kw"]["type"] == "mode"
-+        assert data[0]["kw"]["expected"] == "0600"
-+
-+
-     @pytest.fixture
-     def remove_healthcheck(self):
-         """
--- 
-2.31.1
-
diff --git a/SOURCES/0047-ipatests-test-to-renew-certs-on-replica-using-ipa-ce.patch b/SOURCES/0047-ipatests-test-to-renew-certs-on-replica-using-ipa-ce.patch
deleted file mode 100644
index 843f0a7..0000000
--- a/SOURCES/0047-ipatests-test-to-renew-certs-on-replica-using-ipa-ce.patch
+++ /dev/null
@@ -1,127 +0,0 @@
-From e0aef5296b66c0b460f7e10993610fe68b312241 Mon Sep 17 00:00:00 2001
-From: Mohammad Rizwan <myusuf@redhat.com>
-Date: Mon, 19 Apr 2021 12:08:28 +0530
-Subject: [PATCH] ipatests: test to renew certs on replica using ipa-cert-fix
-
-This test checks if ipa-cert-fix renews the certs on replica
-after cert renewal on master.
-
-related: https://pagure.io/freeipa/issue/7885
-
-ipatests: refactor expire_cert_critical fixture
-
-Defined method to move the date and refactor
-expire_cert_critical fixture using it
-
-ipatests: PEP8 fixes
-
-Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipa_cert_fix.py     | 74 ++++++++++++++++++-
- 1 file changed, 70 insertions(+), 4 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index f3cf59afc..a20996737 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -6,6 +6,7 @@
- Module provides tests for ipa-cert-fix CLI.
- """
- import pytest
-+import re
- import time
- 
- import logging
-@@ -74,15 +75,15 @@ def expire_cert_critical():
-                              extra_args=['--no-ntp'])
-         if setup_kra:
-             tasks.install_kra(host)
--        host.run_command(['systemctl', 'stop', 'chronyd'])
--        host.run_command(['date', '-s', '+3Years+1day'])
-+
-+        # move date to expire certs
-+        move_date(host, 'stop', '+3Years+1day')
- 
-     yield _expire_cert_critical
- 
-     host = hosts.pop('host')
-     tasks.uninstall_master(host)
--    host.run_command(['date', '-s', '-3Years-1day'])
--    host.run_command(['systemctl', 'start', 'chronyd'])
-+    move_date(host, 'start', '-3Years-1day')
- 
- 
- class TestIpaCertFix(IntegrationTest):
-@@ -336,3 +337,68 @@ class TestCertFixKRA(IntegrationTest):
-         self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')
- 
-         check_status(self.master, 12, "MONITORING")
-+
-+
-+class TestCertFixReplica(IntegrationTest):
-+
-+    num_replicas = 1
-+
-+    @classmethod
-+    def install(cls, mh):
-+        tasks.install_master(
-+            mh.master, setup_dns=False, extra_args=['--no-ntp']
-+        )
-+        tasks.install_replica(
-+            mh.master, mh.replicas[0],
-+            setup_dns=False, extra_args=['--no-ntp']
-+        )
-+
-+    def test_renew_expired_cert_replica(self):
-+        """Test renewal of certificates on replica with ipa-cert-fix
-+
-+        This is to check that ipa-cert-fix renews the certificates
-+        on replica
-+
-+        related: https://pagure.io/freeipa/issue/7885
-+        """
-+        move_date(self.master, 'stop', '+3years+1days')
-+
-+        # wait for cert expiry
-+        check_status(self.master, 8, "CA_UNREACHABLE")
-+
-+        self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')
-+
-+        check_status(self.master, 9, "MONITORING")
-+
-+        # move system date to expire cert on replica
-+        move_date(self.replicas[0], 'stop', '+3years+1days')
-+
-+        # RA agent cert will be expired and in CA_UNREACHABLE state
-+        check_status(self.replicas[0], 1, "CA_UNREACHABLE")
-+
-+        # renew RA agent cert
-+        self.replicas[0].run_command(
-+            ['ipa-cert-fix', '-v'], stdin_text='yes\n'
-+        )
-+
-+        # LDAP/HTTP/PKINIT certs will be renewed automaticaly
-+        # after moving date on replica. This 3, 1 CA cert,
-+        # 1 RA agent cert. Check for total 5 valid certs.
-+        check_status(self.replicas[0], 5, "MONITORING")
-+
-+        # get the req ids of all certs to renew remaining
-+        # certs by re-submitting it
-+        result = self.replicas[0].run_command(['getcert', 'list'])
-+        req_ids = re.findall(r'\d{14}', result.stdout_text)
-+
-+        # resubmit the certs to renew them
-+        for req_id in req_ids:
-+            self.replicas[0].run_command(
-+                ['getcert', 'resubmit', '-i', req_id]
-+            )
-+
-+        check_status(self.master, 9, "MONITORING")
-+
-+        # move date back on replica and master
-+        move_date(self.replicas[0], 'start', '-3years-1days')
-+        move_date(self.master, 'start', '-3years-1days')
--- 
-2.31.1
-
diff --git a/SOURCES/0048-ipatests-wait-while-http-ldap-pkinit-cert-get-renew-.patch b/SOURCES/0048-ipatests-wait-while-http-ldap-pkinit-cert-get-renew-.patch
deleted file mode 100644
index 910d4c8..0000000
--- a/SOURCES/0048-ipatests-wait-while-http-ldap-pkinit-cert-get-renew-.patch
+++ /dev/null
@@ -1,252 +0,0 @@
-From a620e5e9e152defe144705913521c3cf556faa0e Mon Sep 17 00:00:00 2001
-From: Mohammad Rizwan <myusuf@redhat.com>
-Date: Mon, 26 Apr 2021 15:50:20 +0530
-Subject: [PATCH] ipatests: wait while http/ldap/pkinit cert get renew on
- replica
-
-LDAP/HTTP/PKINIT certificates should be renewd on replica after
-moving system date. Test was failing because ipa-cert-fix ran
-while these cert was not renewd and it tried to fix it.
-
-This test adds check for replication before calling ipa-cert-fix
-on replica.
-
-Fixes: https://pagure.io/freeipa/issue/8815
-
-Signed-off-by: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Sergey Orlov <sorlov@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- .../test_integration/test_ipa_cert_fix.py     | 172 +++++++++++++++---
- 1 file changed, 144 insertions(+), 28 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index a20996737..fa69743e2 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -5,16 +5,19 @@
- """
- Module provides tests for ipa-cert-fix CLI.
- """
-+from cryptography.hazmat.backends import default_backend
-+from cryptography import x509
-+from datetime import datetime, date
- import pytest
--import re
- import time
- 
- import logging
- from ipaplatform.paths import paths
-+from ipapython.ipaldap import realm_to_serverid
- from ipatests.pytest_ipa.integration import tasks
- 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
- 
- logger = logging.getLogger(__name__)
- 
-@@ -59,6 +62,49 @@ def move_date(host, chrony_state, date_str):
-     host.run_command(['date', '-s', date_str])
- 
- 
-+def needs_resubmit(host, req_id):
-+    """Helper method to identify if cert request needs to be resubmitted
-+    :param host: the host
-+    :param req_id: request id to perform operation for
-+
-+    Returns True if resubmit needed else False
-+    """
-+    # check if cert is in monitoring state
-+    tasks.wait_for_certmonger_status(
-+        host, ('MONITORING'), req_id, timeout=600
-+    )
-+
-+    # check if cert is valid and not expired
-+    cmd = host.run_command(
-+        'getcert list -i {} | grep expires'.format(req_id)
-+    )
-+    cert_expiry = cmd.stdout_text.split(' ')
-+    cert_expiry = datetime.strptime(cert_expiry[1], '%Y-%m-%d').date()
-+    if cert_expiry > date.today():
-+        return False
-+    else:
-+        return True
-+
-+
-+def get_cert_expiry(host, nssdb_path, cert_nick):
-+    """Method to get cert expiry date of given certificate
-+
-+    :param host: the host
-+    :param nssdb_path: nssdb path of certificate
-+    :param cert_nick: certificate nick name for extracting cert from nssdb
-+    """
-+    # get initial expiry date to compare later with renewed cert
-+    host.run_command([
-+        'certutil', '-L', '-a',
-+        '-d', nssdb_path,
-+        '-n', cert_nick,
-+        '-o', '/root/cert.pem'
-+    ])
-+    data = host.get_file_contents('/root/cert.pem')
-+    cert = x509.load_pem_x509_certificate(data, backend=default_backend())
-+    return cert.not_valid_after
-+
-+
- @pytest.fixture
- def expire_cert_critical():
-     """
-@@ -353,7 +399,19 @@ class TestCertFixReplica(IntegrationTest):
-             setup_dns=False, extra_args=['--no-ntp']
-         )
- 
--    def test_renew_expired_cert_replica(self):
-+    @pytest.fixture
-+    def expire_certs(self):
-+        # move system date to expire certs
-+        for host in self.master, self.replicas[0]:
-+            tasks.move_date(host, 'stop', '+3years+1days')
-+
-+        yield
-+
-+        # move date back on replica and master
-+        for host in self.master, self.replicas[0]:
-+            tasks.move_date(host, 'start', '-3years-1days')
-+
-+    def test_renew_expired_cert_replica(self, expire_certs):
-         """Test renewal of certificates on replica with ipa-cert-fix
- 
-         This is to check that ipa-cert-fix renews the certificates
-@@ -361,8 +419,6 @@ class TestCertFixReplica(IntegrationTest):
- 
-         related: https://pagure.io/freeipa/issue/7885
-         """
--        move_date(self.master, 'stop', '+3years+1days')
--
-         # wait for cert expiry
-         check_status(self.master, 8, "CA_UNREACHABLE")
- 
-@@ -370,35 +426,95 @@ class TestCertFixReplica(IntegrationTest):
- 
-         check_status(self.master, 9, "MONITORING")
- 
--        # move system date to expire cert on replica
--        move_date(self.replicas[0], 'stop', '+3years+1days')
--
--        # RA agent cert will be expired and in CA_UNREACHABLE state
--        check_status(self.replicas[0], 1, "CA_UNREACHABLE")
--
--        # renew RA agent cert
--        self.replicas[0].run_command(
--            ['ipa-cert-fix', '-v'], stdin_text='yes\n'
-+        # replica operations
-+        # 'Server-Cert cert-pki-ca' cert will be in CA_UNREACHABLE state
-+        cmd = self.replicas[0].run_command(
-+            ['getcert', 'list',
-+             '-d', paths.PKI_TOMCAT_ALIAS_DIR,
-+             '-n', 'Server-Cert cert-pki-ca']
-+        )
-+        req_id = get_certmonger_fs_id(cmd.stdout_text)
-+        tasks.wait_for_certmonger_status(
-+            self.replicas[0], ('CA_UNREACHABLE'), req_id, timeout=600
-+        )
-+        # get initial expiry date to compare later with renewed cert
-+        initial_expiry = get_cert_expiry(
-+            self.replicas[0],
-+            paths.PKI_TOMCAT_ALIAS_DIR,
-+            'Server-Cert cert-pki-ca'
-         )
- 
--        # LDAP/HTTP/PKINIT certs will be renewed automaticaly
--        # after moving date on replica. This 3, 1 CA cert,
--        # 1 RA agent cert. Check for total 5 valid certs.
--        check_status(self.replicas[0], 5, "MONITORING")
-+        # check that HTTP,LDAP,PKINIT are renewed and in MONITORING state
-+        instance = realm_to_serverid(self.master.domain.realm)
-+        dirsrv_cert = paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance
-+        for cert in (paths.KDC_CERT, paths.HTTPD_CERT_FILE):
-+            cmd = self.replicas[0].run_command(
-+                ['getcert', 'list', '-f', cert]
-+            )
-+            req_id = get_certmonger_fs_id(cmd.stdout_text)
-+            tasks.wait_for_certmonger_status(
-+                self.replicas[0], ('MONITORING'), req_id, timeout=600
-+            )
- 
--        # get the req ids of all certs to renew remaining
--        # certs by re-submitting it
--        result = self.replicas[0].run_command(['getcert', 'list'])
--        req_ids = re.findall(r'\d{14}', result.stdout_text)
-+        cmd = self.replicas[0].run_command(
-+            ['getcert', 'list', '-d', dirsrv_cert]
-+        )
-+        req_id = get_certmonger_fs_id(cmd.stdout_text)
-+        tasks.wait_for_certmonger_status(
-+            self.replicas[0], ('MONITORING'), req_id, timeout=600
-+        )
- 
--        # resubmit the certs to renew them
--        for req_id in req_ids:
-+        # check if replication working fine
-+        testuser = 'testuser1'
-+        password = 'Secret@123'
-+        stdin = (f"{self.master.config.admin_password}\n"
-+                 f"{self.master.config.admin_password}\n"
-+                 f"{self.master.config.admin_password}\n")
-+        self.master.run_command(['kinit', 'admin'], stdin_text=stdin)
-+        tasks.user_add(self.master, testuser, password=password)
-+        self.replicas[0].run_command(['kinit', 'admin'], stdin_text=stdin)
-+        self.replicas[0].run_command(['ipa', 'user-show', testuser])
-+
-+        # renew shared certificates by resubmitting to certmonger
-+        cmd = self.replicas[0].run_command(
-+            ['getcert', 'list', '-f', paths.RA_AGENT_PEM]
-+        )
-+        req_id = get_certmonger_fs_id(cmd.stdout_text)
-+        if needs_resubmit(self.replicas[0], req_id):
-             self.replicas[0].run_command(
-                 ['getcert', 'resubmit', '-i', req_id]
-             )
-+            tasks.wait_for_certmonger_status(
-+                self.replicas[0], ('MONITORING'), req_id, timeout=600
-+            )
-+        for cert_nick in ('auditSigningCert cert-pki-ca',
-+                          'ocspSigningCert cert-pki-ca',
-+                          'subsystemCert cert-pki-ca'):
-+            cmd = self.replicas[0].run_command(
-+                ['getcert', 'list',
-+                 '-d', paths.PKI_TOMCAT_ALIAS_DIR,
-+                 '-n', cert_nick]
-+            )
-+            req_id = get_certmonger_fs_id(cmd.stdout_text)
-+            if needs_resubmit(self.replicas[0], req_id):
-+                self.replicas[0].run_command(
-+                    ['getcert', 'resubmit', '-i', req_id]
-+                )
-+                tasks.wait_for_certmonger_status(
-+                    self.replicas[0], ('MONITORING'), req_id, timeout=600
-+                )
- 
--        check_status(self.master, 9, "MONITORING")
-+        self.replicas[0].run_command(
-+            ['ipa-cert-fix', '-v'], stdin_text='yes\n'
-+        )
- 
--        # move date back on replica and master
--        move_date(self.replicas[0], 'start', '-3years-1days')
--        move_date(self.master, 'start', '-3years-1days')
-+        check_status(self.replicas[0], 9, "MONITORING")
-+
-+        # Sometimes certmonger takes time to update the cert status
-+        # So check in nssdb instead of relying on getcert command
-+        renewed_expiry = get_cert_expiry(
-+            self.replicas[0],
-+            paths.PKI_TOMCAT_ALIAS_DIR,
-+            'Server-Cert cert-pki-ca'
-+        )
-+        assert renewed_expiry > initial_expiry
--- 
-2.31.1
-
diff --git a/SOURCES/0049-ipatests-refactor-test_ipa_cert_fix-with-tasks.patch b/SOURCES/0049-ipatests-refactor-test_ipa_cert_fix-with-tasks.patch
deleted file mode 100644
index 2566b9f..0000000
--- a/SOURCES/0049-ipatests-refactor-test_ipa_cert_fix-with-tasks.patch
+++ /dev/null
@@ -1,73 +0,0 @@
-From 4a3a15f45aad016730252c09e3e173a18184603e Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Wed, 21 Jul 2021 14:29:31 +0200
-Subject: [PATCH] ipatests: refactor test_ipa_cert_fix with tasks
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Fixes: https://pagure.io/freeipa/issue/8932
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Michal Polovka <mpolovka@redhat.com>
-Reviewed-By: Armando Neto <abiagion@redhat.com>
-Reviewed-By: Mohammad Rizwan <myusuf@redhat.com>
-Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
----
- ipatests/test_integration/test_ipa_cert_fix.py | 18 ++++--------------
- 1 file changed, 4 insertions(+), 14 deletions(-)
-
-diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
-index fa69743e2..39904d5de 100644
---- a/ipatests/test_integration/test_ipa_cert_fix.py
-+++ b/ipatests/test_integration/test_ipa_cert_fix.py
-@@ -52,16 +52,6 @@ def check_status(host, cert_count, state, timeout=600):
-     return count
- 
- 
--def move_date(host, chrony_state, date_str):
--    """Helper method to move the date on given host
--    :param host: The host on which date is to be moved
--    :param chrony_state: State to which chrony service to be moved
--    :param date_str: date string to move the date i.e 2years1month1days
--    """
--    host.run_command(['systemctl', chrony_state, 'chronyd'])
--    host.run_command(['date', '-s', date_str])
--
--
- def needs_resubmit(host, req_id):
-     """Helper method to identify if cert request needs to be resubmitted
-     :param host: the host
-@@ -123,13 +113,13 @@ def expire_cert_critical():
-             tasks.install_kra(host)
- 
-         # move date to expire certs
--        move_date(host, 'stop', '+3Years+1day')
-+        tasks.move_date(host, 'stop', '+3Years+1day')
- 
-     yield _expire_cert_critical
- 
-     host = hosts.pop('host')
-     tasks.uninstall_master(host)
--    move_date(host, 'start', '-3Years-1day')
-+    tasks.move_date(host, 'start', '-3Years-1day')
- 
- 
- class TestIpaCertFix(IntegrationTest):
-@@ -143,12 +133,12 @@ class TestIpaCertFix(IntegrationTest):
-     def expire_ca_cert(self):
-         tasks.install_master(self.master, setup_dns=False,
-                              extra_args=['--no-ntp'])
--        move_date(self.master, 'stop', '+20Years+1day')
-+        tasks.move_date(self.master, 'stop', '+20Years+1day')
- 
-         yield
- 
-         tasks.uninstall_master(self.master)
--        move_date(self.master, 'start', '-20Years-1day')
-+        tasks.move_date(self.master, 'start', '-20Years-1day')
- 
-     def test_missing_csr(self, expire_cert_critical):
-         """
--- 
-2.31.1
-
diff --git a/SOURCES/0050-ipatests-use-whole-date-for-journalctl-since.patch b/SOURCES/0050-ipatests-use-whole-date-for-journalctl-since.patch
deleted file mode 100644
index 97b9650..0000000
--- a/SOURCES/0050-ipatests-use-whole-date-for-journalctl-since.patch
+++ /dev/null
@@ -1,65 +0,0 @@
-From b5036b5ce9ae4fab011e57fe2b37a35fdd098a70 Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Thu, 19 Aug 2021 10:51:01 +0200
-Subject: [PATCH] ipatests: use whole date for journalctl --since
-
-When a test is executed around midnight and is checking the
-journal content with --since=date, it needs to specify the
-whole date (with day and time) to avoid missing entries.
-
-If for instance --since=23:59:00 is used and the current time is
-now 00:01:00, --since=23:59:00 would refer to a date in the
-future and no journal entry will be found.
-
-Fixes: https://pagure.io/freeipa/issue/8953
-Reviewed-By: Stanislav Levin <slev@altlinux.org>
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- ipatests/test_integration/test_cert.py     | 2 +-
- ipatests/test_integration/test_commands.py | 3 ++-
- ipatests/test_integration/test_nfs.py      | 2 +-
- 3 files changed, 4 insertions(+), 3 deletions(-)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 9a90db5e2..7d51b76ee 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -69,7 +69,7 @@ class TestInstallMasterClient(IntegrationTest):
- 
-         # time to look into journal logs in
-         # test_certmonger_ipa_responder_jsonrpc
--        cls.since = time.strftime('%H:%M:%S')
-+        cls.since = time.strftime('%Y-%m-%d %H:%M:%S')
- 
-     def test_cacert_file_appear_with_option_F(self):
-         """Test if getcert creates cacert file with -F option
-diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
-index 4d9a81652..fd5d1b472 100644
---- a/ipatests/test_integration/test_commands.py
-+++ b/ipatests/test_integration/test_commands.py
-@@ -1208,7 +1208,8 @@ class TestIPACommand(IntegrationTest):
-         # start to look at logs a bit before "now"
-         # https://pagure.io/freeipa/issue/8432
-         since = time.strftime(
--            '%H:%M:%S', (datetime.now() - timedelta(seconds=10)).timetuple()
-+            '%Y-%m-%d %H:%M:%S',
-+            (datetime.now() - timedelta(seconds=10)).timetuple()
-         )
- 
-         password = 'WrongPassword'
-diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
-index 9a6153409..dc53a6da9 100644
---- a/ipatests/test_integration/test_nfs.py
-+++ b/ipatests/test_integration/test_nfs.py
-@@ -130,7 +130,7 @@ class TestNFS(IntegrationTest):
-         nfsclt = self.clients[1]
- 
-         # for journalctl --since
--        since = time.strftime('%H:%M:%S')
-+        since = time.strftime('%Y-%m-%d %H:%M:%S')
-         nfsclt.run_command(["systemctl", "restart", "rpc-gssd"])
-         time.sleep(WAIT_AFTER_INSTALL)
-         mountpoints = ("/mnt/krb", "/mnt/std", "/home")
--- 
-2.31.1
-
diff --git a/SOURCES/0051-selinux-policy-allow-custodia-to-access-proc-cpuinfo.patch b/SOURCES/0051-selinux-policy-allow-custodia-to-access-proc-cpuinfo.patch
deleted file mode 100644
index d06f248..0000000
--- a/SOURCES/0051-selinux-policy-allow-custodia-to-access-proc-cpuinfo.patch
+++ /dev/null
@@ -1,41 +0,0 @@
-From 07e2bf732f54f936cccc4e0c7b468d77f97e911a Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Mon, 30 Aug 2021 18:40:24 +0200
-Subject: [PATCH] selinux policy: allow custodia to access /proc/cpuinfo
-
-On aarch64, custodia creates AVC when accessing /proc/cpuinfo.
-
-According to gcrypt manual
-(https://gnupg.org/documentation/manuals/gcrypt/Configuration.html),
-/proc/cpuinfo is used on ARM architecture to read the hardware
-capabilities of the CPU. This explains why the issue happens only
-on aarch64.
-
-audit2allow suggests to add the following:
-allow ipa_custodia_t proc_t:file { getattr open read };
-
-but this policy would be too broad. Instead, the patch is using
-the interface kernel_read_system_state.
-
-Fixes: https://pagure.io/freeipa/issue/8972
-Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
-Reviewed-By: Christian Heimes <cheimes@redhat.com>
----
- selinux/ipa.te | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/selinux/ipa.te b/selinux/ipa.te
-index 68e10941951ac391fda7854d1403558c069dad46..7492fca04d4f0d031ecd83871078247d73cc87e0 100644
---- a/selinux/ipa.te
-+++ b/selinux/ipa.te
-@@ -364,6 +364,7 @@ files_tmp_filetrans(ipa_custodia_t, ipa_custodia_tmp_t, { dir file })
- 
- kernel_dgram_send(ipa_custodia_t)
- kernel_read_network_state(ipa_custodia_t)
-+kernel_read_system_state(ipa_custodia_t)
- 
- auth_read_passwd(ipa_custodia_t)
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0052-extdom-return-LDAP_NO_SUCH_OBJECT-if-domains-differ.patch b/SOURCES/0052-extdom-return-LDAP_NO_SUCH_OBJECT-if-domains-differ.patch
deleted file mode 100644
index e8dfa24..0000000
--- a/SOURCES/0052-extdom-return-LDAP_NO_SUCH_OBJECT-if-domains-differ.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 4fca95751ca32a1ed16a6d8a4e557c5799ec5c78 Mon Sep 17 00:00:00 2001
-From: Sumit Bose <sbose@redhat.com>
-Date: Wed, 25 Aug 2021 17:10:29 +0200
-Subject: [PATCH] extdom: return LDAP_NO_SUCH_OBJECT if domains differ
-
-If a client sends a request to lookup an object from a given trusted
-domain by UID or GID and an object with matching ID is only found in a
-different domain the extdom should return LDAP_NO_SUCH_OBJECT to
-indicate to the client that the requested ID does not exists in the
-given domain.
-
-Resolves: https://pagure.io/freeipa/issue/8965
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- .../ipa-extdom-extop/ipa_extdom_common.c                  | 8 ++++++--
- 1 file changed, 6 insertions(+), 2 deletions(-)
-
-diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
-index 5d97ff6137d9d660f6121f468261c6878a9aa12a..6f646b9f49ef31e1872e87640c524db972e53b6d 100644
---- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
-+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
-@@ -542,7 +542,9 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx,
-         if (strcasecmp(locat+1, domain_name) == 0  ) {
-             locat[0] = '\0';
-         } else {
--            ret = LDAP_INVALID_SYNTAX;
-+            /* The found object is from a different domain than requested,
-+             * that means it does not exist in the requested domain */
-+            ret = LDAP_NO_SUCH_OBJECT;
-             goto done;
-         }
-     }
-@@ -655,7 +657,9 @@ int pack_ber_group(enum response_types response_type,
-         if (strcasecmp(locat+1, domain_name) == 0  ) {
-             locat[0] = '\0';
-         } else {
--            ret = LDAP_INVALID_SYNTAX;
-+            /* The found object is from a different domain than requested,
-+             * that means it does not exist in the requested domain */
-+            ret = LDAP_NO_SUCH_OBJECT;
-             goto done;
-         }
-     }
--- 
-2.31.1
-
diff --git a/SOURCES/0053-subid-subid-match-display-the-owner-s-ID-not-DN.patch b/SOURCES/0053-subid-subid-match-display-the-owner-s-ID-not-DN.patch
deleted file mode 100644
index a36c923..0000000
--- a/SOURCES/0053-subid-subid-match-display-the-owner-s-ID-not-DN.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 4785a90946ec694ccc082f062b2181b23c7099e3 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fran=C3=A7ois=20Cami?= <fcami@redhat.com>
-Date: Thu, 2 Sep 2021 16:17:01 +0200
-Subject: [PATCH] subid: subid-match: display the owner's ID not DN
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Previously, the subid-match command would output the full
-DN of the owner of the matched range.
-With this change, the UID of the owner is displayed, just like
-for other subid- commands.
-
-Fixes: https://github.com/freeipa/freeipa/pull/6001
-Signed-off-by: François Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipaserver/plugins/subid.py | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/ipaserver/plugins/subid.py b/ipaserver/plugins/subid.py
-index 440f24ee627f0736100f63026158c564b04520c2..132c85c7f198217ba70f2332306ee2550be86035 100644
---- a/ipaserver/plugins/subid.py
-+++ b/ipaserver/plugins/subid.py
-@@ -524,6 +524,7 @@ class subid_match(subid_find):
-         osubuid = options["ipasubuidnumber"]
-         new_entries = []
-         for entry in entries:
-+            self.obj.convert_owner(entry, options)
-             esubuid = int(entry.single_value["ipasubuidnumber"])
-             esubcount = int(entry.single_value["ipasubuidcount"])
-             minsubuid = esubuid
--- 
-2.31.1
-
diff --git a/SOURCES/0054-migrate-ds-workaround-to-detect-compat-tree.patch b/SOURCES/0054-migrate-ds-workaround-to-detect-compat-tree.patch
deleted file mode 100644
index 16dac6f..0000000
--- a/SOURCES/0054-migrate-ds-workaround-to-detect-compat-tree.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 3c4f9e7347965ff9a887147df34e720224ffa7cc Mon Sep 17 00:00:00 2001
-From: Florence Blanc-Renaud <flo@redhat.com>
-Date: Tue, 7 Sep 2021 17:06:53 +0200
-Subject: [PATCH] migrate-ds: workaround to detect compat tree
-
-Migrate-ds needs to check if compat tree is enabled before
-migrating users and groups. The check is doing a base
-search on cn=compat,$SUFFIX and considers the compat tree
-enabled when the entry exists.
-
-Due to a bug in slapi-nis, the base search may return NotFound
-even though the compat tree is enabled. The workaround is to
-perform a base search on cn=users,cn=compat,$SUFFIX instead.
-
-Fixes: https://pagure.io/freeipa/issue/8984
-Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
----
- ipaserver/plugins/migration.py | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/plugins/migration.py b/ipaserver/plugins/migration.py
-index db5241915497b14a12ed2c33003e1c4fc1a5369f..6ee205fc836a463ac250baa6131e43acb0c00efa 100644
---- a/ipaserver/plugins/migration.py
-+++ b/ipaserver/plugins/migration.py
-@@ -922,7 +922,8 @@ migration process might be incomplete\n''')
-         # check whether the compat plugin is enabled
-         if not options.get('compat'):
-             try:
--                ldap.get_entry(DN(('cn', 'compat'), (api.env.basedn)))
-+                ldap.get_entry(DN(('cn', 'users'), ('cn', 'compat'),
-+                                  (api.env.basedn)))
-                 return dict(result={}, failed={}, enabled=True, compat=False)
-             except errors.NotFound:
-                 pass
--- 
-2.31.1
-
diff --git a/SOURCES/0055-Don-t-store-entries-with-a-usercertificate-in-the-LD.patch b/SOURCES/0055-Don-t-store-entries-with-a-usercertificate-in-the-LD.patch
deleted file mode 100644
index b9c02d6..0000000
--- a/SOURCES/0055-Don-t-store-entries-with-a-usercertificate-in-the-LD.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From be1e3bbfc13aff9a583108376f245b81cc3666fb Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Thu, 9 Sep 2021 15:26:55 -0400
-Subject: [PATCH] Don't store entries with a usercertificate in the LDAP cache
-
-usercertificate often has a subclass and both the plain and
-subclassed (binary) values are queried. I'm concerned that
-they are used more or less interchangably in places so not
-caching these entries is the safest path forward for now until
-we can dedicate the time to find all usages, determine their
-safety and/or perhaps handle this gracefully within the cache
-now.
-
-What we see in this bug is that usercertificate;binary holds the
-first certificate value but a user-mod is done with
-setattr usercertificate=<new_cert>. Since there is no
-usercertificate value (remember, it's usercertificate;binary)
-a replace is done and 389-ds wipes the existing value as we've
-asked it to.
-
-I'm not comfortable with simply treating them the same because
-in LDAP they are not.
-
-https://pagure.io/freeipa/issue/8986
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
----
- ipapython/ipaldap.py | 14 +++++++++++---
- 1 file changed, 11 insertions(+), 3 deletions(-)
-
-diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
-index f94b784d680f33d026e4d56ec8627d4d2ab87931..ced8f1bd66dc8f1f5c206677d2725d1e72b489f9 100644
---- a/ipapython/ipaldap.py
-+++ b/ipapython/ipaldap.py
-@@ -1821,9 +1821,17 @@ class LDAPCache(LDAPClient):
-                         entry=None, exception=None):
-         # idnsname - caching prevents delete when mod value to None
-         # cospriority - in a Class of Service object, uncacheable
--        # TODO - usercertificate was banned at one point and I don't remember
--        #        why...
--        BANNED_ATTRS = {'idnsname', 'cospriority'}
-+        # usercertificate* - caching subtypes is tricky, trade less
-+        #                    complexity for performance
-+        #
-+        # TODO: teach the cache about subtypes
-+
-+        BANNED_ATTRS = {
-+            'idnsname',
-+            'cospriority',
-+            'usercertificate',
-+            'usercertificate;binary'
-+        }
-         if not self._enable_cache:
-             return
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0056-ipatests-Test-that-a-user-can-be-issued-multiple-cer.patch b/SOURCES/0056-ipatests-Test-that-a-user-can-be-issued-multiple-cer.patch
deleted file mode 100644
index db49c3c..0000000
--- a/SOURCES/0056-ipatests-Test-that-a-user-can-be-issued-multiple-cer.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From 86588640137562b2016fdb0f91142d00bc38e54a Mon Sep 17 00:00:00 2001
-From: Rob Crittenden <rcritten@redhat.com>
-Date: Fri, 10 Sep 2021 09:01:48 -0400
-Subject: [PATCH] ipatests: Test that a user can be issued multiple
- certificates
-
-Prevent regressions in the LDAP cache layer that caused newly
-issued certificates to overwrite existing ones.
-
-https://pagure.io/freeipa/issue/8986
-
-Signed-off-by: Rob Crittenden <rcritten@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
----
- ipatests/test_integration/test_cert.py | 29 ++++++++++++++++++++++++++
- 1 file changed, 29 insertions(+)
-
-diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
-index 7d51b76ee347237450b7484cf48c2e6a1bed7f7d..b4e85eadcf41212fdd16f0f3aa130a916b5019fa 100644
---- a/ipatests/test_integration/test_cert.py
-+++ b/ipatests/test_integration/test_cert.py
-@@ -16,6 +16,7 @@ import string
- import time
- 
- from ipaplatform.paths import paths
-+from ipapython.dn import DN
- from cryptography import x509
- from cryptography.x509.oid import ExtensionOID
- from cryptography.hazmat.backends import default_backend
-@@ -183,6 +184,34 @@ class TestInstallMasterClient(IntegrationTest):
-         )
-         assert "profile: caServerCert" in result.stdout_text
- 
-+    def test_multiple_user_certificates(self):
-+        """Test that a user may be issued multiple certificates"""
-+        ldap = self.master.ldap_connect()
-+
-+        user = 'user1'
-+
-+        tasks.kinit_admin(self.master)
-+        tasks.user_add(self.master, user)
-+
-+        for id in (0,1):
-+            csr_file = f'{id}.csr'
-+            key_file = f'{id}.key'
-+            cert_file = f'{id}.crt'
-+            openssl_cmd = [
-+                'openssl', 'req', '-newkey', 'rsa:2048', '-keyout', key_file,
-+                '-nodes', '-out', csr_file, '-subj', '/CN=' + user]
-+            self.master.run_command(openssl_cmd)
-+
-+            cmd_args = ['ipa', 'cert-request', '--principal', user,
-+                        '--certificate-out', cert_file, csr_file]
-+            self.master.run_command(cmd_args)
-+
-+        # easier to count by pulling the LDAP entry
-+        entry = ldap.get_entry(DN(('uid', user), ('cn', 'users'),
-+                               ('cn', 'accounts'), self.master.domain.basedn))
-+
-+        assert len(entry.get('usercertificate')) == 2
-+
-     @pytest.fixture
-     def test_subca_certs(self):
-         """
--- 
-2.31.1
-
diff --git a/SOURCES/0057-Parse-getStatus-as-JSON-not-XML.patch b/SOURCES/0057-Parse-getStatus-as-JSON-not-XML.patch
deleted file mode 100644
index e3cae57..0000000
--- a/SOURCES/0057-Parse-getStatus-as-JSON-not-XML.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 7fb95cc638b1c9b7f2e9a67dba859ef8126f2c5f Mon Sep 17 00:00:00 2001
-From: Chris Kelley <ckelley@redhat.com>
-Date: Tue, 27 Jul 2021 21:57:26 +0100
-Subject: [PATCH] Parse getStatus as JSON not XML
-
-On dogtagpki/pki master XML is being replaced by JSON, getStatus will
-return JSON in PKI 11.0+
-
-The PR for dogtagpki/pki that makes this change necessary is:
-https://github.com/dogtagpki/pki/pull/3674
-
-Reviewed-By: Francois Cami <fcami@redhat.com>
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- install/tools/ipa-pki-wait-running.in | 18 ++++++++++++++----
- 1 file changed, 14 insertions(+), 4 deletions(-)
-
-diff --git a/install/tools/ipa-pki-wait-running.in b/install/tools/ipa-pki-wait-running.in
-index 4f0f2f34a7b0a43210676e7fd50e7029e798f301..9ca6e974e55a4d68afd06e1d9c7b67c5f926e48c 100644
---- a/install/tools/ipa-pki-wait-running.in
-+++ b/install/tools/ipa-pki-wait-running.in
-@@ -13,6 +13,7 @@ import logging
- import sys
- import time
- from xml.etree import ElementTree
-+import json
- 
- from ipalib import api
- from ipaplatform.paths import paths
-@@ -74,10 +75,19 @@ def get_status(conn, timeout):
-     """
-     client = SystemStatusClient(conn)
-     response = client.get_status(timeout=timeout)
--    root = ElementTree.fromstring(response)
--    status = root.findtext("Status")
--    error = root.findtext("Error")
--    logging.debug("Got status '%s', error '%s'", status, error)
-+    status = None
-+    error = None
-+    try:
-+        json_response = json.loads(response)
-+        status = json_response['Response']['Status']
-+    except KeyError as e:
-+        error = repr(e)
-+    except json.JSONDecodeError:
-+        logger.debug("Response is not valid JSON, try XML")
-+        root = ElementTree.fromstring(response)
-+        status = root.findtext("Status")
-+        error = root.findtext("Error")
-+    logger.debug("Got status '%s', error '%s'", status, error)
-     return status, error
- 
- 
--- 
-2.31.1
-
diff --git a/SOURCES/0058-Parse-cert-chain-as-JSON-not-XML.patch b/SOURCES/0058-Parse-cert-chain-as-JSON-not-XML.patch
deleted file mode 100644
index ca959dd..0000000
--- a/SOURCES/0058-Parse-cert-chain-as-JSON-not-XML.patch
+++ /dev/null
@@ -1,79 +0,0 @@
-From 40f76a53f78267b4d2b890defa3e4f7d27fdfb7a Mon Sep 17 00:00:00 2001
-From: Chris Kelley <ckelley@redhat.com>
-Date: Thu, 5 Aug 2021 12:00:15 +0100
-Subject: [PATCH] Parse cert chain as JSON not XML
-
-On dogtagpki/pki master XML is being replaced by JSON in PKI 11.0+
-
-The PR for dogtagpki/pki that makes this change necessary is:
-https://github.com/dogtagpki/pki/pull/3677
-
-Reviewed-By: Rob Crittenden <rcritten@redhat.com>
----
- ipapython/dogtag.py | 28 +++++++++++++++++++---------
- 1 file changed, 19 insertions(+), 9 deletions(-)
-
-diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
-index 0503938fb9783d397cc7366339bb9fab48033985..8f0f0473ae313edb17e10de8b2ca7f43f231e706 100644
---- a/ipapython/dogtag.py
-+++ b/ipapython/dogtag.py
-@@ -20,6 +20,7 @@
- import collections
- import gzip
- import io
-+import json
- import logging
- from urllib.parse import urlencode
- import xml.dom.minidom
-@@ -100,6 +101,10 @@ def get_ca_certchain(ca_host=None):
-         data = res.read()
-         conn.close()
-         try:
-+            doc = json.loads(data)
-+            chain = doc['Response']['ChainBase64']
-+        except (json.JSONDecodeError, KeyError):
-+            logger.debug("Response is not valid JSON, try XML")
-             doc = xml.dom.minidom.parseString(data)
-             try:
-                 item_node = doc.getElementsByTagName("ChainBase64")
-@@ -107,9 +112,9 @@ def get_ca_certchain(ca_host=None):
-             except IndexError:
-                 raise error_from_xml(
-                     doc, _("Retrieving CA cert chain failed: %s"))
--        finally:
--            if doc:
--                doc.unlink()
-+            finally:
-+                if doc:
-+                    doc.unlink()
-     else:
-         raise errors.RemoteRetrieveError(
-             reason=_("request failed with HTTP status %d") % res.status)
-@@ -118,13 +123,18 @@ def get_ca_certchain(ca_host=None):
- 
- 
- def _parse_ca_status(body):
--    doc = xml.dom.minidom.parseString(body)
-     try:
--        item_node = doc.getElementsByTagName("XMLResponse")[0]
--        item_node = item_node.getElementsByTagName("Status")[0]
--        return item_node.childNodes[0].data
--    except IndexError:
--        raise error_from_xml(doc, _("Retrieving CA status failed: %s"))
-+        doc = json.loads(body)
-+        return doc['Response']['Status']
-+    except (json.JSONDecodeError, KeyError):
-+        logger.debug("Response is not valid JSON, try XML")
-+        doc = xml.dom.minidom.parseString(body)
-+        try:
-+            item_node = doc.getElementsByTagName("XMLResponse")[0]
-+            item_node = item_node.getElementsByTagName("Status")[0]
-+            return item_node.childNodes[0].data
-+        except IndexError:
-+            raise error_from_xml(doc, _("Retrieving CA status failed: %s"))
- 
- 
- def ca_status(ca_host=None):
--- 
-2.31.1
-
diff --git a/SOURCES/0059-Specify-PKI-installation-log-paths.patch b/SOURCES/0059-Specify-PKI-installation-log-paths.patch
deleted file mode 100644
index 44af243..0000000
--- a/SOURCES/0059-Specify-PKI-installation-log-paths.patch
+++ /dev/null
@@ -1,84 +0,0 @@
-From 5abf1bc79f8b32c6638ff98fbe2e4a8dec9a5010 Mon Sep 17 00:00:00 2001
-From: "Endi S. Dewata" <edewata@redhat.com>
-Date: Thu, 12 Aug 2021 13:26:42 -0500
-Subject: [PATCH] Specify PKI installation log paths
-
-The DogtagInstance.spawn_instance() and uninstall() have
-been modified to specify the paths of PKI installation
-logs using --log-file option on PKI 11.0.0 or later.
-
-This allows IPA to have a full control over the log files
-instead of relying on PKI's default log files.
-
-Fixes: https://pagure.io/freeipa/issue/8966
-Signed-off-by: Endi Sukma Dewata <edewata@redhat.com>
----
- ipaserver/install/dogtaginstance.py | 35 ++++++++++++++++++++++++++---
- 1 file changed, 32 insertions(+), 3 deletions(-)
-
-diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
-index 644acd4eacea22f41a7cd36b54553d6d7cd22690..0d9aebb542f242b81315edd016699697f2fc4091 100644
---- a/ipaserver/install/dogtaginstance.py
-+++ b/ipaserver/install/dogtaginstance.py
-@@ -36,8 +36,10 @@ from configparser import DEFAULTSECT, ConfigParser, RawConfigParser
- 
- import six
- 
-+import pki
- from pki.client import PKIConnection
- import pki.system
-+import pki.util
- 
- from ipalib import api, errors, x509
- from ipalib.install import certmonger
-@@ -202,6 +204,18 @@ class DogtagInstance(service.Service):
-                 "-f", cfg_file,
-                 "--debug"]
- 
-+        # specify --log-file <path> on PKI 11.0.0 or later
-+
-+        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-spawn.%s.log" % (self.subsystem.lower(), timestamp))
-+            args.extend(["--log-file", log_file])
-+
-         with open(cfg_file) as f:
-             logger.debug(
-                 'Contents of pkispawn configuration file (%s):\n%s',
-@@ -290,10 +304,25 @@ class DogtagInstance(service.Service):
-         if self.is_installed():
-             self.print_msg("Unconfiguring %s" % self.subsystem)
- 
-+        args = [paths.PKIDESTROY,
-+                "-i", "pki-tomcat",
-+                "-s", self.subsystem]
-+
-+        # specify --log-file <path> on PKI 11.0.0 or later
-+
-+        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])
-+
-         try:
--            ipautil.run([paths.PKIDESTROY,
--                         "-i", 'pki-tomcat',
--                         "-s", self.subsystem])
-+            ipautil.run(args)
-+
-         except ipautil.CalledProcessError as e:
-             logger.critical("failed to uninstall %s instance %s",
-                             self.subsystem, e)
--- 
-2.31.1
-
diff --git a/SOURCES/0060-Make-Dogtag-return-XML-for-ipa-cert-find.patch b/SOURCES/0060-Make-Dogtag-return-XML-for-ipa-cert-find.patch
deleted file mode 100644
index 4b5a221..0000000
--- a/SOURCES/0060-Make-Dogtag-return-XML-for-ipa-cert-find.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From d43b513927d6dd0a12464dd24287ce40ccaf33e4 Mon Sep 17 00:00:00 2001
-From: Chris Kelley <ckelley@redhat.com>
-Date: Fri, 10 Sep 2021 16:47:22 +0100
-Subject: [PATCH] Make Dogtag return XML for ipa cert-find
-
-Using JSON by default within Dogtag appears to cause ipa cert-find to
-return JSON, when the request was made with XML. We can request that XML
-is returned as before by specifying so in the request header.
-
-Fixes: https://pagure.io/freeipa/issue/8980
-Signed-off-by: Chris Kelley <ckelley@redhat.com>
-Reviewed-By: Francois Cami <fcami@redhat.com>
----
- ipaserver/plugins/dogtag.py | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
-index be2e4bb4e2a1b96c1bff6056da30c704c36789f3..b4feddfac19a4c5659d29bf7b6f5fd9b1247524c 100644
---- a/ipaserver/plugins/dogtag.py
-+++ b/ipaserver/plugins/dogtag.py
-@@ -1832,7 +1832,8 @@ class ra(rabase.rabase, RestClient):
-             method='POST',
-             headers={'Accept-Encoding': 'gzip, deflate',
-                      'User-Agent': 'IPA',
--                     'Content-Type': 'application/xml'},
-+                     'Content-Type': 'application/xml',
-+                     'Accept': 'application/xml'},
-             body=payload
-         )
- 
--- 
-2.31.1
-
diff --git a/SOURCES/freeipa-4.9.6.tar.gz.asc b/SOURCES/freeipa-4.9.6.tar.gz.asc
deleted file mode 100644
index f71d351..0000000
--- a/SOURCES/freeipa-4.9.6.tar.gz.asc
+++ /dev/null
@@ -1,16 +0,0 @@
------BEGIN PGP SIGNATURE-----
-
-iQIzBAABCgAdFiEEhAodHH8+xLL+UwQ1RxniuKu/YhoFAmDbPRQACgkQRxniuKu/
-Yhr7uBAAnpF70nH8Cn/HhKKpfafPoN3B9fDNIfAa+jsJ52OyeNMKVNi4MEob32iN
-1aMGGFCJUMle/M7v1+w8WH59eiHs1jKHcFZnl2R4Ap5SxVtypYT+ewXbNnSHII2w
-qWS5PvLkJwjh6Bw/HlyBwDRSrw9Yah4oZZbJt3zE06+Imr8BpB3IWqyhuAi7FjYO
-J9hHCwCvtJvWK4yplZSXCt8OS1JA68/Djgjecm5lUSamuqKaBVhDb+ZAPLDJpBf5
-Pz2JpUF/W/rplt+Q9wAFdhDB9iC0vd3MBkgs4KPsjuyS9+GGNu8LyXs0C1Wm/VgX
-liX2pjZmpnTrhH3QQ2nufwH784ZpinXxS2fcbvCfX1Utgr77wNHjwqDt2NBffJl1
-BM7JJr1ZwGOGSki6yjRDXbeSAsiEX9l7f2mv2t/8ZjHMRJ7mJmBbmh5Qhk5qsMou
-BptNDE20cG77xcjBtTCDpii/UatETuNAyMd/l2smfe76z8y61fQrvScxRwOCHckw
-u/ERChpBZOUlQt59Efj3ja313oXZMxXRw01n/72Hh5rnk+XZf75zQ1zUDBYnwzAr
-4cdqyrfpFkQu1sRQvgjT8ZLkP8istjRdVEI/Oj61zb5+6+scQ/Zh/R/mYGCV4/h+
-RzojBwUAXuwUMrj1jTbb5Lkz58+vY3Lk4xNOY2hSAc8rCcDVRZY=
-=TQFs
------END PGP SIGNATURE-----
diff --git a/SOURCES/freeipa-4.9.8.tar.gz.asc b/SOURCES/freeipa-4.9.8.tar.gz.asc
new file mode 100644
index 0000000..c14420c
--- /dev/null
+++ b/SOURCES/freeipa-4.9.8.tar.gz.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCAAdFiEE11Z2TU1+KXxtrRFyaYdvcqbi008FAmGf1XcACgkQaYdvcqbi
+00/kMQ//Vano94V0/L3YsLaqKiFcGo/py5pTq1Os3wB9zzCYSuU0P/eajuHLBYNe
+MfxecZihFFlmUdNooNWbewT4CE0ey1qFLwPfGXuLrse6fXVLLaYnAv2mkPUmDSpM
+XfXO0PFU0BtdkMAUsdUATngPCpQzYjVUKsAMwPovi3UcLzFZ8tWJKMA55urhwC4q
+E042wPLqzcX6Ee5JBSBkfNe35vG2LY7o3Ynh8SVCee2lBJvdWiuFT5XRhybXUsOp
+q3eTsVPz68p7CvOrjlLSsWPP0nbGF1O1UQsN+oaDZAav1Nx8lTOlxUCUQXWbs2X6
+BTUAOmZ6VjYu61sNgNSj+BSHlHIT3uRJ55JO5nLH/hLm0Oxn6SGRTVMueqV376QA
+CsIk7UrdcX9QUtu70eRxuu1aAWJ5eaF4GDWnFP+62wzd/d6LjWEE+9kXgvrcTF0C
+UzjWrmbI8x23bB4kqcROHz8lryMsBpZ94QKPHVppMiPgapDKRkculYkSeRLboADi
+q4mh2prkDSq9diWV4HvZTGwPU77oiLrQsvbGuvwD62PAlyQ4rZpfW3FllTL2Lcxy
+urA8a9UnQWQtDOsZIyxmMJ7R04gjI5fZfDhq6S09L9MfjFEKjsqO4FzXamj+SbAo
+w25sIp1qT0sV1vOt+/R/HYSIyggQyTZpQJu5UB34QLqpfDdUwFg=
+=t9up
+-----END PGP SIGNATURE-----
diff --git a/SPECS/freeipa.spec b/SPECS/freeipa.spec
index 23476ad..3c5e656 100644
--- a/SPECS/freeipa.spec
+++ b/SPECS/freeipa.spec
@@ -1,3 +1,5 @@
+%define ipa_requires_gt()  %(LC_ALL="C" echo '%*' | xargs -r rpm -q --qf 'Requires: %%{name} >= %%{epoch}:%%{version}-%%{release}\\n' | sed -e 's/ (none):/ /' -e 's/ 0:/ /' | grep -v "is not")
+
 # ipatests enabled by default, can be disabled with --without ipatests
 %bcond_without ipatests
 # default to not use XML-RPC in Rawhide, can be turned around with --with ipa_join_xml
@@ -69,7 +71,7 @@
 # 0.7.16: https://github.com/drkjam/netaddr/issues/71
 %global python_netaddr_version 0.7.19
 # Require 4.7.0 which brings Python 3 bindings
-%global samba_version 4.12.3-12
+%global samba_version 4.15.2-2
 %global selinux_policy_version 3.14.3-52
 %global slapi_nis_version 0.56.4
 %global python_ldap_version 3.1.0-1
@@ -111,7 +113,7 @@
 %if 0%{?fedora} < 34
 %global ds_version 1.4.4.16-1
 %else
-%global ds_version 2.0.5-1
+%global ds_version 2.0.7-1
 %endif
 
 # Fix for TLS 1.3 PHA, RHBZ#1775146
@@ -183,7 +185,7 @@
 
 # Work-around fact that RPM SPEC parser does not accept
 # "Version: @VERSION@" in freeipa.spec.in used for Autoconf string replacement
-%define IPA_VERSION 4.9.6
+%define IPA_VERSION 4.9.8
 # Release candidate version -- uncomment with one percent for RC versions
 #%%global rc_version %%nil
 %define AT_SIGN @
@@ -196,7 +198,7 @@
 
 Name:           %{package_name}
 Version:        %{IPA_VERSION}
-Release:        9%{?rc_version:.%rc_version}%{?dist}
+Release:        1%{?rc_version:.%rc_version}%{?dist}
 Summary:        The Identity, Policy and Audit system
 
 License:        GPLv3+
@@ -216,66 +218,8 @@ Source1:        https://releases.pagure.org/freeipa/freeipa-%{version}%{?rc_vers
 # RHEL spec file only: START
 %if %{NON_DEVELOPER_BUILD}
 %if 0%{?rhel} >= 8
-Patch0001:      0001-Remove-unneeded-dependency-on-python-coverage.patch
-Patch0002:      0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch
-Patch0003:      0003-ipatests-ensure-auth-indicators-can-t-be-added-to-in.patch
-Patch0004:      0004-stageuser-add-ipauserauthtypeclass-when-required.patch
-Patch0005:      0005-XMLRPC-test-add-a-test-for-stageuser-add-user-auth-t.patch
-Patch0006:      0006-augeas-bump-version-for-rhel9.patch
-Patch0007:      0007-man-page-update-ipa-server-upgrade.1.patch
-Patch0008:      0008-Add-basic-support-for-subordinate-user-group-ids.patch
-Patch0009:      0009-Redesign-subid-feature.patch
-Patch0010:      0010-Use-389-DS-dnaInterval-setting-to-assign-intervals.patch
-Patch0011:      0011-Fix-ipa-server-upgrade.patch
-Patch0012:      0012-Fix-oid-of-ipaUserDefaultSubordinateId.patch
-Patch0013:      0013-WebUI-Improve-subordinate-ids-user-workflow.patch
-Patch0014:      0014-Test-DNA-plugin-configuration.patch
-Patch0015:      0015-Fall-back-to-krbprincipalname-when-validating-host-a.patch
-Patch0016:      0016-spec-file-Trust-controller-role-should-pull-sssd-win.patch
-Patch0017:      0017-Use-new-method-in-check-to-prevent-removal-of-last-K.patch
-Patch0018:      0018-ipatests-test-removing-last-KRA-when-it-is-not-runni.patch
-Patch0019:      0019-rhel-platform-add-a-named-crypto-policy-support.patch
-Patch0020:      0020-Index-Fix-definition-for-memberOf.patch
-Patch0021:      0021-ipatests-use-whole-date-when-calling-journalctl-sinc.patch
-Patch0022:      0022-ipatests-Fix-for-test_source_ipahealthcheck_ipa_host.patch
-Patch0023:      0023-ipatests-test_ipahealthcheck-print-a-message-if-a-sy.patch
-Patch0024:      0024-ipatests-test_installation-move-tracking_reqs-depend.patch
-Patch0025:      0025-webui-tests-close-notification-when-revoking-cert.patch
-Patch0026:      0026-ipatests-Test-ipa-cert-fix-warns-when-startup-direct.patch
-Patch0027:      0027-webui-tests-fix-algo-for-finding-available-idrange.patch
-Patch0028:      0028-ipatests-smbclient-k-use-kerberos-desired.patch
-Patch0029:      0029-test_acme-refactor-with-tasks.patch
-Patch0030:      0030-test_acme-make-password-renewal-more-robust.patch
-Patch0031:      0031-tasks.py-fix-flake8-reported-issues.patch
-Patch0032:      0032-Fix-ldapupdate.get_sub_dict-for-missing-named-user.patch
-Patch0033:      0033-freeipa.spec.in-remove-python3-pexpect-from-Requires.patch
-Patch0034:      0034-ipa-getkeytab-add-option-to-discover-servers-using-D.patch
-Patch0035:      0035-ipa-getkeytab-fix-compiler-warnings.patch
-Patch0036:      0036-ipatests-test-ipa-getkeytab-server-option.patch
-Patch0037:      0037-ipatests-Test-for-OTP-when-the-LDAP-connection-timed.patch
-Patch0038:      0038-ipatests-verify-that-getcert-output-includes-the-iss.patch
-Patch0039:      0039-ipatests-Look-for-warning-into-stderr-instead-of-std.patch
-Patch0040:      0040-ipatests-use-krb5_trace-in-TestIpaAdTrustInstall.patch
-Patch0041:      0041-ipatests-Test-ldapsearch-with-base-scope-works-with-.patch
-Patch0042:      0042-ipatests-skip-test_basesearch_compat_tree-on-fedora.patch
-Patch0043:      0043-ipatests-Refactor-test_check_otpd_after_idle_timeout.patch
-Patch0044:      0044-ipatests-Test-unsecure-nsupdate.patch
-Patch0045:      0045-ipatests-Fix-TestAJPSecretUpgrade-tests-on-systems-w.patch
-Patch0046:      0046-ipatests-test_ipahealthcheck-Verify-permissions-for-.patch
-Patch0047:      0047-ipatests-test-to-renew-certs-on-replica-using-ipa-ce.patch
-Patch0048:      0048-ipatests-wait-while-http-ldap-pkinit-cert-get-renew-.patch
-Patch0049:      0049-ipatests-refactor-test_ipa_cert_fix-with-tasks.patch
-Patch0050:      0050-ipatests-use-whole-date-for-journalctl-since.patch
-Patch0051:      0051-selinux-policy-allow-custodia-to-access-proc-cpuinfo.patch
-Patch0052:      0052-extdom-return-LDAP_NO_SUCH_OBJECT-if-domains-differ.patch
-Patch0053:      0053-subid-subid-match-display-the-owner-s-ID-not-DN.patch
-Patch0054:      0054-migrate-ds-workaround-to-detect-compat-tree.patch
-Patch0055:      0055-Don-t-store-entries-with-a-usercertificate-in-the-LD.patch
-Patch0056:      0056-ipatests-Test-that-a-user-can-be-issued-multiple-cer.patch
-Patch0057:      0057-Parse-getStatus-as-JSON-not-XML.patch
-Patch0058:      0058-Parse-cert-chain-as-JSON-not-XML.patch
-Patch0059:      0059-Specify-PKI-installation-log-paths.patch
-Patch0060:      0060-Make-Dogtag-return-XML-for-ipa-cert-find.patch
+Patch0001:      0001-ipa-kdb-issue-PAC_REQUESTER_SID-only-for-TGTs.patch
+Patch0002:      0002-ipa-kdb-fix-requester-SID-check-according-to-MS-KILE.patch
 Patch1001:      1001-Change-branding-to-IPA-and-Identity-Management.patch
 %endif
 %endif
@@ -528,6 +472,8 @@ Requires: gssproxy >= 0.7.0-2
 Requires: sssd-dbus >= %{sssd_version}
 Requires: libpwquality
 Requires: cracklib-dicts
+# NDR libraries are internal in Samba and change with version without changing SONAME
+%ipa_requires_gt samba-client-libs
 
 Provides: %{alt_name}-server = %{version}
 Conflicts: %{alt_name}-server
@@ -627,9 +573,11 @@ Requires: %{name}-server = %{version}-%{release}
 Requires: bind-dyndb-ldap >= 11.2-2
 Requires: bind >= %{bind_version}
 Requires: bind-utils >= %{bind_version}
+# bind-dnssec-utils is required by the OpenDNSSec integration
+# https://pagure.io/freeipa/issue/9026
+Requires: bind-dnssec-utils >= %{bind_version}
 %if %{with bind_pkcs11}
 Requires: bind-pkcs11 >= %{bind_version}
-Requires: bind-pkcs11-utils >= %{bind_version}
 %else
 Requires: softhsm >= %{softhsm_version}
 Requires: openssl-pkcs11 >= %{openssl_pkcs11_version}
@@ -1433,6 +1381,7 @@ fi
 %dir %{_libexecdir}/ipa/oddjob
 %attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.conncheck
 %attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.trust-enable-agent
+%attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.config-enable-sid
 %config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freeipa.server.conf
 %config(noreplace) %{_sysconfdir}/oddjobd.conf.d/ipa-server.conf
 %dir %{_libexecdir}/ipa/certmonger
@@ -1762,6 +1711,17 @@ fi
 %endif
 
 %changelog
+* Thu Dec 02 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.8-1
+- Resolves: rhbz#2015608 - [Rebase] Rebase ipa to latest 4.9.x release RHEL9
+- Resolves: rhbz#1825010 - Concerns regarding 'ipa pwpolicy-mod --minlife 24 --maxlife 1'
+- Resolves: rhbz#1966289 - Info about searchrecordslimit set search limit to 10,000 after upgrade
+- Resolves: rhbz#1980356 - reinstalling samba client causes winbindd coredump
+- Resolves: rhbz#1986054 - fix automountlocation-tofiles output
+- Resolves: rhbz#2020205 - Missing bind-pkcs11-utils causing failures in OpenDNSSec
+- Resolves: rhbz#2021445 - CVE-2020-25719 ipa: samba: Samba AD DC did not always rely on the SID and PAC in Kerberos tickets
+  - ipa-kdb: issue PAC_REQUESTER_SID only for TGTs
+  - ipa-kdb: fix requester SID check according to MS-KILE and MS-SFU updates
+
 * Tue Oct 5 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-9
 - Resolves: rhbz#2010701 ipa-server-install fails while 'configuring certificate server instance'
   - Parse getStatus as JSON not XML