diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..34c1a4e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+SOURCES/freeipa-4.9.6.tar.gz
diff --git a/.ipa.metadata b/.ipa.metadata
new file mode 100644
index 0000000..ab790ce
--- /dev/null
+++ b/.ipa.metadata
@@ -0,0 +1 @@
+b7b91082908db35e4acbcd0221b8df4044913dc1 SOURCES/freeipa-4.9.6.tar.gz
diff --git a/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch b/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch
new file mode 100644
index 0000000..d27c680
--- /dev/null
+++ b/SOURCES/0001-Remove-unneeded-dependency-on-python-coverage.patch
@@ -0,0 +1,30 @@
+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/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch b/SOURCES/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch
new file mode 100644
index 0000000..411d510
--- /dev/null
+++ b/SOURCES/0002-Add-checks-to-prevent-adding-auth-indicators-to-inte.patch
@@ -0,0 +1,134 @@
+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/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
new file mode 100644
index 0000000..681a79d
--- /dev/null
+++ b/SOURCES/0003-ipatests-ensure-auth-indicators-can-t-be-added-to-in.patch
@@ -0,0 +1,138 @@
+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
new file mode 100644
index 0000000..2d9e34f
--- /dev/null
+++ b/SOURCES/0004-stageuser-add-ipauserauthtypeclass-when-required.patch
@@ -0,0 +1,57 @@
+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
new file mode 100644
index 0000000..d96d52f
--- /dev/null
+++ b/SOURCES/0005-XMLRPC-test-add-a-test-for-stageuser-add-user-auth-t.patch
@@ -0,0 +1,32 @@
+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
new file mode 100644
index 0000000..35859fe
--- /dev/null
+++ b/SOURCES/0006-augeas-bump-version-for-rhel9.patch
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000..df25002
--- /dev/null
+++ b/SOURCES/0007-man-page-update-ipa-server-upgrade.1.patch
@@ -0,0 +1,35 @@
+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
new file mode 100644
index 0000000..6b52566
--- /dev/null
+++ b/SOURCES/0008-Add-basic-support-for-subordinate-user-group-ids.patch
@@ -0,0 +1,2324 @@
+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
new file mode 100644
index 0000000..da0b878
--- /dev/null
+++ b/SOURCES/0009-Redesign-subid-feature.patch
@@ -0,0 +1,2906 @@
+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
new file mode 100644
index 0000000..33c9237
--- /dev/null
+++ b/SOURCES/0010-Use-389-DS-dnaInterval-setting-to-assign-intervals.patch
@@ -0,0 +1,113 @@
+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
new file mode 100644
index 0000000..055275c
--- /dev/null
+++ b/SOURCES/0011-Fix-ipa-server-upgrade.patch
@@ -0,0 +1,68 @@
+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
new file mode 100644
index 0000000..c964120
--- /dev/null
+++ b/SOURCES/0012-Fix-oid-of-ipaUserDefaultSubordinateId.patch
@@ -0,0 +1,29 @@
+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
new file mode 100644
index 0000000..f1b78ec
--- /dev/null
+++ b/SOURCES/0013-WebUI-Improve-subordinate-ids-user-workflow.patch
@@ -0,0 +1,275 @@
+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
new file mode 100644
index 0000000..75244fd
--- /dev/null
+++ b/SOURCES/0014-Test-DNA-plugin-configuration.patch
@@ -0,0 +1,57 @@
+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
new file mode 100644
index 0000000..780d75d
--- /dev/null
+++ b/SOURCES/0015-Fall-back-to-krbprincipalname-when-validating-host-a.patch
@@ -0,0 +1,69 @@
+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
new file mode 100644
index 0000000..cdb9c35
--- /dev/null
+++ b/SOURCES/0016-spec-file-Trust-controller-role-should-pull-sssd-win.patch
@@ -0,0 +1,30 @@
+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
new file mode 100644
index 0000000..8a4d23d
--- /dev/null
+++ b/SOURCES/0017-Use-new-method-in-check-to-prevent-removal-of-last-K.patch
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 0000000..5461afb
--- /dev/null
+++ b/SOURCES/0018-ipatests-test-removing-last-KRA-when-it-is-not-runni.patch
@@ -0,0 +1,49 @@
+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
new file mode 100644
index 0000000..241d293
--- /dev/null
+++ b/SOURCES/0019-rhel-platform-add-a-named-crypto-policy-support.patch
@@ -0,0 +1,30 @@
+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
new file mode 100644
index 0000000..3cf9357
--- /dev/null
+++ b/SOURCES/0020-Index-Fix-definition-for-memberOf.patch
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000..2ec50bb
--- /dev/null
+++ b/SOURCES/0021-ipatests-use-whole-date-when-calling-journalctl-sinc.patch
@@ -0,0 +1,35 @@
+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
new file mode 100644
index 0000000..d56f3e5
--- /dev/null
+++ b/SOURCES/0022-ipatests-Fix-for-test_source_ipahealthcheck_ipa_host.patch
@@ -0,0 +1,43 @@
+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
new file mode 100644
index 0000000..8993bcc
--- /dev/null
+++ b/SOURCES/0023-ipatests-test_ipahealthcheck-print-a-message-if-a-sy.patch
@@ -0,0 +1,52 @@
+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
new file mode 100644
index 0000000..848571f
--- /dev/null
+++ b/SOURCES/0024-ipatests-test_installation-move-tracking_reqs-depend.patch
@@ -0,0 +1,104 @@
+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
new file mode 100644
index 0000000..decc06d
--- /dev/null
+++ b/SOURCES/0025-webui-tests-close-notification-when-revoking-cert.patch
@@ -0,0 +1,31 @@
+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
new file mode 100644
index 0000000..8d8a2d6
--- /dev/null
+++ b/SOURCES/0026-ipatests-Test-ipa-cert-fix-warns-when-startup-direct.patch
@@ -0,0 +1,153 @@
+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
new file mode 100644
index 0000000..92c1528
--- /dev/null
+++ b/SOURCES/0027-webui-tests-fix-algo-for-finding-available-idrange.patch
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000..38d2842
--- /dev/null
+++ b/SOURCES/0028-ipatests-smbclient-k-use-kerberos-desired.patch
@@ -0,0 +1,61 @@
+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
new file mode 100644
index 0000000..5add22b
--- /dev/null
+++ b/SOURCES/0029-test_acme-refactor-with-tasks.patch
@@ -0,0 +1,81 @@
+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
new file mode 100644
index 0000000..f1e3334
--- /dev/null
+++ b/SOURCES/0030-test_acme-make-password-renewal-more-robust.patch
@@ -0,0 +1,66 @@
+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
new file mode 100644
index 0000000..548f2f5
--- /dev/null
+++ b/SOURCES/0031-tasks.py-fix-flake8-reported-issues.patch
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 0000000..e48eddf
--- /dev/null
+++ b/SOURCES/0032-Fix-ldapupdate.get_sub_dict-for-missing-named-user.patch
@@ -0,0 +1,142 @@
+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
new file mode 100644
index 0000000..31489e5
--- /dev/null
+++ b/SOURCES/0033-freeipa.spec.in-remove-python3-pexpect-from-Requires.patch
@@ -0,0 +1,68 @@
+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
new file mode 100644
index 0000000..798f9d2
--- /dev/null
+++ b/SOURCES/0034-ipa-getkeytab-add-option-to-discover-servers-using-D.patch
@@ -0,0 +1,365 @@
+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
new file mode 100644
index 0000000..734e8e9
--- /dev/null
+++ b/SOURCES/0035-ipa-getkeytab-fix-compiler-warnings.patch
@@ -0,0 +1,55 @@
+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
new file mode 100644
index 0000000..0b3e60c
--- /dev/null
+++ b/SOURCES/0036-ipatests-test-ipa-getkeytab-server-option.patch
@@ -0,0 +1,92 @@
+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
new file mode 100644
index 0000000..d24f83c
--- /dev/null
+++ b/SOURCES/0037-ipatests-Test-for-OTP-when-the-LDAP-connection-timed.patch
@@ -0,0 +1,91 @@
+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
new file mode 100644
index 0000000..af90a1f
--- /dev/null
+++ b/SOURCES/0038-ipatests-verify-that-getcert-output-includes-the-iss.patch
@@ -0,0 +1,51 @@
+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
new file mode 100644
index 0000000..a231506
--- /dev/null
+++ b/SOURCES/0039-ipatests-Look-for-warning-into-stderr-instead-of-std.patch
@@ -0,0 +1,43 @@
+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
new file mode 100644
index 0000000..56e1db3
--- /dev/null
+++ b/SOURCES/0040-ipatests-use-krb5_trace-in-TestIpaAdTrustInstall.patch
@@ -0,0 +1,56 @@
+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
new file mode 100644
index 0000000..f616c59
--- /dev/null
+++ b/SOURCES/0041-ipatests-Test-ldapsearch-with-base-scope-works-with-.patch
@@ -0,0 +1,45 @@
+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
new file mode 100644
index 0000000..012b885
--- /dev/null
+++ b/SOURCES/0042-ipatests-skip-test_basesearch_compat_tree-on-fedora.patch
@@ -0,0 +1,44 @@
+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
new file mode 100644
index 0000000..cbba6ba
--- /dev/null
+++ b/SOURCES/0043-ipatests-Refactor-test_check_otpd_after_idle_timeout.patch
@@ -0,0 +1,89 @@
+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
new file mode 100644
index 0000000..a223c94
--- /dev/null
+++ b/SOURCES/0044-ipatests-Test-unsecure-nsupdate.patch
@@ -0,0 +1,162 @@
+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
new file mode 100644
index 0000000..5e978b4
--- /dev/null
+++ b/SOURCES/0045-ipatests-Fix-TestAJPSecretUpgrade-tests-on-systems-w.patch
@@ -0,0 +1,88 @@
+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
new file mode 100644
index 0000000..23d092b
--- /dev/null
+++ b/SOURCES/0046-ipatests-test_ipahealthcheck-Verify-permissions-for-.patch
@@ -0,0 +1,54 @@
+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
new file mode 100644
index 0000000..843f0a7
--- /dev/null
+++ b/SOURCES/0047-ipatests-test-to-renew-certs-on-replica-using-ipa-ce.patch
@@ -0,0 +1,127 @@
+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
new file mode 100644
index 0000000..910d4c8
--- /dev/null
+++ b/SOURCES/0048-ipatests-wait-while-http-ldap-pkinit-cert-get-renew-.patch
@@ -0,0 +1,252 @@
+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
new file mode 100644
index 0000000..2566b9f
--- /dev/null
+++ b/SOURCES/0049-ipatests-refactor-test_ipa_cert_fix-with-tasks.patch
@@ -0,0 +1,73 @@
+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
new file mode 100644
index 0000000..97b9650
--- /dev/null
+++ b/SOURCES/0050-ipatests-use-whole-date-for-journalctl-since.patch
@@ -0,0 +1,65 @@
+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
new file mode 100644
index 0000000..d06f248
--- /dev/null
+++ b/SOURCES/0051-selinux-policy-allow-custodia-to-access-proc-cpuinfo.patch
@@ -0,0 +1,41 @@
+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
new file mode 100644
index 0000000..e8dfa24
--- /dev/null
+++ b/SOURCES/0052-extdom-return-LDAP_NO_SUCH_OBJECT-if-domains-differ.patch
@@ -0,0 +1,46 @@
+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
new file mode 100644
index 0000000..a36c923
--- /dev/null
+++ b/SOURCES/0053-subid-subid-match-display-the-owner-s-ID-not-DN.patch
@@ -0,0 +1,35 @@
+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
new file mode 100644
index 0000000..16dac6f
--- /dev/null
+++ b/SOURCES/0054-migrate-ds-workaround-to-detect-compat-tree.patch
@@ -0,0 +1,37 @@
+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
new file mode 100644
index 0000000..b9c02d6
--- /dev/null
+++ b/SOURCES/0055-Don-t-store-entries-with-a-usercertificate-in-the-LD.patch
@@ -0,0 +1,60 @@
+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
new file mode 100644
index 0000000..db49c3c
--- /dev/null
+++ b/SOURCES/0056-ipatests-Test-that-a-user-can-be-issued-multiple-cer.patch
@@ -0,0 +1,68 @@
+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
new file mode 100644
index 0000000..e3cae57
--- /dev/null
+++ b/SOURCES/0057-Parse-getStatus-as-JSON-not-XML.patch
@@ -0,0 +1,56 @@
+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
new file mode 100644
index 0000000..ca959dd
--- /dev/null
+++ b/SOURCES/0058-Parse-cert-chain-as-JSON-not-XML.patch
@@ -0,0 +1,79 @@
+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
new file mode 100644
index 0000000..44af243
--- /dev/null
+++ b/SOURCES/0059-Specify-PKI-installation-log-paths.patch
@@ -0,0 +1,84 @@
+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
new file mode 100644
index 0000000..4b5a221
--- /dev/null
+++ b/SOURCES/0060-Make-Dogtag-return-XML-for-ipa-cert-find.patch
@@ -0,0 +1,33 @@
+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/1001-Change-branding-to-IPA-and-Identity-Management.patch b/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch
new file mode 100644
index 0000000..0e9a6de
--- /dev/null
+++ b/SOURCES/1001-Change-branding-to-IPA-and-Identity-Management.patch
@@ -0,0 +1,223 @@
+From ac3ba2b4ff4cd3ca85c1ff07c2b050f8b5eb7c2b Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Sun, 7 Oct 2018 12:25:40 +0300
+Subject: [PATCH 1/3] install/ui/css/patternfly.css: Change branding to IPA and
+ Identity Management
+
+---
+ install/ui/css/patternfly.css | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/install/ui/css/patternfly.css b/install/ui/css/patternfly.css
+index ee92053..de574a8 100644
+--- a/install/ui/css/patternfly.css
++++ b/install/ui/css/patternfly.css
+@@ -4,4 +4,4 @@
+  *
+  * Copyright 2013 bootstrap-select
+  * Licensed under the MIT license
+- */.bootstrap-select.btn-group,.bootstrap-select.btn-group[class*=span]{float:none;display:inline-block;margin-bottom:10px;margin-left:0}.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group,.form-search .bootstrap-select.btn-group{margin-bottom:0}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.btn-group.pull-right,.bootstrap-select.btn-group[class*=span].pull-right,.row-fluid .bootstrap-select.btn-group[class*=span].pull-right{float:right}.input-append .bootstrap-select.btn-group{margin-left:-1px}.input-prepend .bootstrap-select.btn-group{margin-right:-1px}.bootstrap-select:not([class*=span]):not([class*=col-]):not([class*=form-control]){width:220px}.bootstrap-select{width:220px\9}.bootstrap-select.form-control:not([class*=span]){width:100%}.bootstrap-select>.btn{width:100%}.error .bootstrap-select .btn{border:1px solid #b94a48}.dropdown-menu{z-index:2000}.bootstrap-select.show-menu-arrow.open>.btn{z-index:2051}.bootstrap-select .btn:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.btn-group .btn .filter-option{overflow:hidden;position:absolute;left:12px;right:25px;text-align:left}.bootstrap-select.btn-group .btn .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group .dropdown-menu li.disabled>a,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group[class*=span] .btn{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;border:0;padding:0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu dt{display:block;padding:3px 20px;cursor:default}.bootstrap-select.btn-group .div-contain{overflow:hidden}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li>a.opt{position:relative;padding-left:35px}.bootstrap-select.btn-group .dropdown-menu li>a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:400}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a i.check-mark{display:inline-block;position:absolute;right:15px;margin-top:2.5px}.bootstrap-select.btn-group .dropdown-menu li a i.check-mark{display:none}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu li.active:not(.disabled)>a small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:focus small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:hover small{color:#64b1d8;color:rgba(255,255,255,.4)}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:400}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #CCC;border-bottom-color:rgba(0,0,0,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,.2)}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px}.mobile-device{position:absolute;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select.btn-group.fit-width .btn .filter-option{position:static}.bootstrap-select.btn-group.fit-width .btn .caret{position:static;top:auto;margin-top:-1px}.control-group.error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select-searchbox{padding:4px 8px}.bootstrap-select-searchbox input{margin-bottom:0}.alert{border-width:1px;padding-left:47px;padding-right:14px;position:relative}.alert .alert-link{color:#0088ce}.alert .alert-link:hover{color:#00659c}.alert>.btn.pull-right{margin-top:-3px}.alert>.pficon{font-size:22px;position:absolute;left:13px;top:10px}.alert .close{opacity:.85;filter:alpha(opacity=85)}.alert .close:focus,.alert .close:hover{opacity:1;filter:alpha(opacity=100)}.alert .pficon-info{color:#4d5258}.alert-dismissable{padding-right:28px}.alert-dismissable .close{right:-13px;top:1px}.badge{margin-left:6px}.nav-pills>li>a>.badge{margin-left:6px}.bootstrap-select.btn-group.form-control{margin-bottom:0}.bootstrap-select.btn-group .btn{-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-select.btn-group .btn:hover{border-color:#7dc3e8}.bootstrap-select.btn-group .btn .caret{margin-top:-4px}.bootstrap-select.btn-group .btn:focus{border-color:#0088ce;outline:0!important;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 8px rgba(0,136,206,.6);box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 8px rgba(0,136,206,.6)}.has-error .bootstrap-select.btn-group .btn{border-color:#c00}.has-error .bootstrap-select.btn-group .btn:focus{border-color:#900;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #f33;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #f33}.has-success .bootstrap-select.btn-group .btn{border-color:#3c763d}.has-success .bootstrap-select.btn-group .btn:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #67b168}.has-warning .bootstrap-select.btn-group .btn{border-color:#ec7a08}.has-warning .bootstrap-select.btn-group .btn:focus{border-color:#bb6106;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #faad60;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #faad60}.bootstrap-select.btn-group .dropdown-menu>.active>a,.bootstrap-select.btn-group .dropdown-menu>.active>a:active{background-color:#def3ff!important;border-color:#bee1f4!important;color:#363636!important}.bootstrap-select.btn-group .dropdown-menu>.active>a small,.bootstrap-select.btn-group .dropdown-menu>.active>a:active small{color:#9c9c9c!important}.bootstrap-select.btn-group .dropdown-menu>.disabled>a{color:#9c9c9c!important}.bootstrap-select.btn-group .dropdown-menu>.selected>a{background-color:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.bootstrap-select.btn-group .dropdown-menu>.selected>a small{color:rgba(255,255,255,.5)!important}.bootstrap-select.btn-group .dropdown-menu .divider{background:#ededed!important;margin:4px 1px!important}.bootstrap-select.btn-group .dropdown-menu dt{color:#8b8d8f;font-weight:400;padding:1px 10px}.bootstrap-select.btn-group .dropdown-menu li>a.opt{padding:1px 10px}.bootstrap-select.btn-group .dropdown-menu li a:active small{color:rgba(255,255,255,.5)!important}.bootstrap-select.btn-group .dropdown-menu li a:focus small,.bootstrap-select.btn-group .dropdown-menu li a:hover small{color:#9c9c9c}.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:focus small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:hover small{color:#9c9c9c}.combobox-container.combobox-selected .glyphicon-remove{display:inline-block}.combobox-container .caret{margin-left:0}.combobox-container .combobox::-ms-clear{display:none}.combobox-container .dropdown-menu{margin-top:-1px;width:100%}.combobox-container .glyphicon-remove{display:none;top:auto;width:12px}.combobox-container .glyphicon-remove:before{content:"\e60b";font-family:PatternFlyIcons-webfont}.combobox-container .input-group-addon{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;position:relative}.combobox-container .input-group-addon.active,.combobox-container .input-group-addon:active,.combobox-container .input-group-addon:focus,.combobox-container .input-group-addon:hover,.open .dropdown-toggle.combobox-container .input-group-addon{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.combobox-container .input-group-addon.active,.combobox-container .input-group-addon:active,.open .dropdown-toggle.combobox-container .input-group-addon{background-image:none}.combobox-container .input-group-addon.active.focus,.combobox-container .input-group-addon.active:focus,.combobox-container .input-group-addon.active:hover,.combobox-container .input-group-addon:active.focus,.combobox-container .input-group-addon:active:focus,.combobox-container .input-group-addon:active:hover,.open .dropdown-toggle.combobox-container .input-group-addon.focus,.open .dropdown-toggle.combobox-container .input-group-addon:focus,.open .dropdown-toggle.combobox-container .input-group-addon:hover{background-color:#e5e5e5;border-color:#a9a9a9}.combobox-container .input-group-addon.disabled,.combobox-container .input-group-addon.disabled.active,.combobox-container .input-group-addon.disabled:active,.combobox-container .input-group-addon.disabled:focus,.combobox-container .input-group-addon.disabled:hover,.combobox-container .input-group-addon[disabled],.combobox-container .input-group-addon[disabled].active,.combobox-container .input-group-addon[disabled]:active,.combobox-container .input-group-addon[disabled]:focus,.combobox-container .input-group-addon[disabled]:hover,fieldset[disabled] .combobox-container .input-group-addon,fieldset[disabled] .combobox-container .input-group-addon.active,fieldset[disabled] .combobox-container .input-group-addon:active,fieldset[disabled] .combobox-container .input-group-addon:focus,fieldset[disabled] .combobox-container .input-group-addon:hover{background-color:#f1f1f1;border-color:#bbb}.combobox-container .input-group-addon:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.treeview .list-group{border-top:0}.treeview .list-group-item{background:0 0;border-bottom:1px solid transparent!important;border-top:1px solid transparent!important;margin-bottom:0;padding:0 10px}.treeview .list-group-item:hover{background:#def3ff!important;border-color:#bee1f4!important}.treeview .list-group-item.node-selected{background:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.treeview span.icon{display:inline-block;font-size:13px;min-width:10px;text-align:center}.treeview span.icon>[class*=fa-angle]{font-size:15px}.treeview span.indent{margin-right:5px}.breadcrumb{padding-left:0}.breadcrumb>.active strong{font-weight:600}.breadcrumb>li{display:inline}.breadcrumb>li+li:before{color:#9c9c9c;content:"\f101";font-family:FontAwesome;font-size:11px;padding:0 9px 0 7px}.btn{-webkit-box-shadow:0 2px 3px rgba(3,3,3,.1);box-shadow:0 2px 3px rgba(3,3,3,.1)}.btn:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{background-color:#fafafa!important;background-image:none!important;border-color:#d1d1d1!important;color:#8b8d8f!important;opacity:1}.btn.disabled:active,.btn[disabled]:active,fieldset[disabled] .btn:active{-webkit-box-shadow:none;box-shadow:none}.btn.disabled.btn-link,.btn[disabled].btn-link,fieldset[disabled] .btn.btn-link{background-color:transparent!important;border:0}.btn-danger{background-color:#a30000;background-image:-webkit-linear-gradient(top,#c00 0,#a30000 100%);background-image:-o-linear-gradient(top,#c00 0,#a30000 100%);background-image:linear-gradient(to bottom,#c00 0,#a30000 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffcc0000', endColorstr='#ffa30000', GradientType=0);border-color:#8b0000;color:#fff}.btn-danger.active,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open .dropdown-toggle.btn-danger{background-color:#a30000;background-image:none;border-color:#8b0000;color:#fff}.btn-danger.active,.btn-danger:active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open .dropdown-toggle.btn-danger.focus,.open .dropdown-toggle.btn-danger:focus,.open .dropdown-toggle.btn-danger:hover{background-color:#8a0000;border-color:#670000}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#a30000;border-color:#8b0000}.btn-default{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258}.btn-default.active,.btn-default:active,.btn-default:focus,.btn-default:hover,.open .dropdown-toggle.btn-default{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.btn-default.active,.btn-default:active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open .dropdown-toggle.btn-default.focus,.open .dropdown-toggle.btn-default:focus,.open .dropdown-toggle.btn-default:hover{background-color:#e5e5e5;border-color:#a9a9a9}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#f1f1f1;border-color:#bbb}.btn-link,.btn-link:active{-webkit-box-shadow:none;box-shadow:none}.btn-primary{background-color:#0088ce;background-image:-webkit-linear-gradient(top,#39a5dc 0,#0088ce 100%);background-image:-o-linear-gradient(top,#39a5dc 0,#0088ce 100%);background-image:linear-gradient(to bottom,#39a5dc 0,#0088ce 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff39a5dc', endColorstr='#ff0088ce', GradientType=0);border-color:#00659c;color:#fff}.btn-primary.active,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open .dropdown-toggle.btn-primary{background-color:#0088ce;background-image:none;border-color:#00659c;color:#fff}.btn-primary.active,.btn-primary:active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open .dropdown-toggle.btn-primary.focus,.open .dropdown-toggle.btn-primary:focus,.open .dropdown-toggle.btn-primary:hover{background-color:#0077b5;border-color:#004e78}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#0088ce;border-color:#00659c}.btn-group-xs .btn,.btn-group-xs>.btn,.btn-xs{font-weight:400}.close{text-shadow:none;opacity:.6;filter:alpha(opacity=60)}.close:focus,.close:hover{opacity:.9;filter:alpha(opacity=90)}.ColVis_Button:active:focus{outline:0}.ColVis_catcher{position:absolute;z-index:999}.ColVis_collection{background-color:#fff;border:1px solid #bbb;border-radius:1px;-webkit-box-shadow:0 6px 12px rgba(3,3,3,.175);box-shadow:0 6px 12px rgba(3,3,3,.175);background-clip:padding-box;list-style:none;margin:-1px 0 0 0;padding:5px 10px;width:150px;z-index:1000}.ColVis_collection label{font-weight:400;margin-bottom:5px;margin-top:5px;padding-left:20px}.ColVis_collectionBackground{background-color:#fff;height:100%;left:0;position:fixed;top:0;width:100%;z-index:998}.dataTables_header{background-color:#f5f5f5;border:1px solid #d1d1d1;border-bottom:none;padding:5px;position:relative;text-align:center}.dataTables_header .btn{-webkit-box-shadow:none;box-shadow:none}.dataTables_header .ColVis{position:absolute;right:5px;text-align:left;top:5px}.dataTables_header .ColVis+.dataTables_info{padding-right:30px}.dataTables_header .dataTables_filter{position:absolute}.dataTables_header .dataTables_filter input{border:1px solid #bbb;height:24px}@media (max-width:767px){.dataTables_header .dataTables_filter input{width:100px}}.dataTables_header .dataTables_info{padding:2px 0}@media (max-width:480px){.dataTables_header .dataTables_info{text-align:right}}.dataTables_header .dataTables_info b{font-weight:700}.dataTables_footer{background-color:#fff;border:1px solid #d1d1d1;border-top:none;overflow:hidden}.dataTables_paginate{background:#fafafa;float:right;margin:0}.dataTables_paginate .pagination{float:left;margin:0}.dataTables_paginate .pagination>li>span{border-color:#fff #d1d1d1 #f5f5f5;border-width:0 1px;font-size:16px;font-weight:400;padding:0;text-align:center;width:31px}.dataTables_paginate .pagination>li>span:focus,.dataTables_paginate .pagination>li>span:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dataTables_paginate .pagination>li.last>span{border-right:none}.dataTables_paginate .pagination>li.disabled>span{background:#f5f5f5;border-left-color:#ededed;border-right-color:#ededed;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dataTables_paginate .pagination-input{float:left;font-size:12px;line-height:1em;padding:4px 15px 0;text-align:right}.dataTables_paginate .pagination-input .paginate_input{border:1px solid #d1d1d1;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075);box-shadow:inset 0 1px 1px rgba(3,3,3,.075);font-size:12px;font-weight:600;height:19px;margin-right:8px;padding-right:3px;text-align:right;width:30px}.dataTables_paginate .pagination-input .paginate_of{position:relative}.dataTables_paginate .pagination-input .paginate_of b{margin-left:3px}.dataTables_wrapper{margin:20px 0}@media (max-width:767px){.dataTables_wrapper .table-responsive{margin-bottom:0}}.DTCR_clonedTable{background-color:rgba(255,255,255,.7);z-index:202}.DTCR_pointer{background-color:#0088ce;width:1px;z-index:201}table.datatable{margin-bottom:0;max-width:none!important}table.datatable thead .sorting,table.datatable thead .sorting_asc,table.datatable thead .sorting_asc_disabled,table.datatable thead .sorting_desc,table.datatable thead .sorting_desc_disabled{cursor:pointer}table.datatable thead .sorting_asc,table.datatable thead .sorting_desc{color:#0088ce!important;position:relative}table.datatable thead .sorting_asc:after,table.datatable thead .sorting_desc:after{content:"\f107";font-family:FontAwesome;font-size:10px;font-weight:400;height:9px;left:7px;line-height:12px;position:relative;top:2px;vertical-align:baseline;width:12px}table.datatable thead .sorting_asc:before,table.datatable thead .sorting_desc:before{background:#0088ce;content:'';height:2px;position:absolute;left:0;top:0;width:100%}table.datatable thead .sorting_asc:after{content:"\f106";top:-3px}table.datatable th:active{outline:0}.caret{font-family:FontAwesome;font-weight:400;height:9px;position:relative;vertical-align:baseline;width:12px}.caret:before{bottom:0;content:"\f107";left:0;line-height:12px;position:absolute;text-align:center;top:-1px;right:0}.dropup .caret:before{content:"\f106"}.dropdown-menu .divider{background-color:#ededed;height:1px;margin:4px 1px;overflow:hidden}.dropdown-menu>li>a{border-color:transparent;border-style:solid;border-width:1px 0;padding:1px 10px}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{border-color:#bee1f4;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>li>a:active{background-color:#0088ce;border-color:#0088ce;color:#fff!important;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#0088ce!important;border-color:#0088ce!important;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{border-color:transparent}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{border-color:transparent}.dropdown-header{padding-left:10px;padding-right:10px;text-transform:uppercase}.btn-group>.dropdown-menu,.dropdown>.dropdown-menu,.input-group-btn>.dropdown-menu{margin-top:-1px}.dropup .dropdown-menu{margin-bottom:-1px}.dropdown-submenu{position:relative}.dropdown-submenu:hover>a{background-color:#def3ff;border-color:#bee1f4}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropdown-submenu.pull-left{float:none!important}.dropdown-submenu.pull-left>.dropdown-menu{left:auto;margin-left:10px;right:100%}.dropdown-submenu>a{padding-right:20px!important}.dropdown-submenu>a:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:2px}.dropdown-submenu>.dropdown-menu{left:100%;margin-top:0;top:-6px}.dropup .dropdown-submenu>.dropdown-menu{bottom:-5px;top:auto}.open .dropdown-submenu.active>.dropdown-menu{display:block}.dropdown-kebab-pf .btn-link{color:#252525;font-size:16px;line-height:1;padding:4px 0}.dropdown-kebab-pf .btn-link:active,.dropdown-kebab-pf .btn-link:focus,.dropdown-kebab-pf .btn-link:hover{color:#0088ce}.dropdown-kebab-pf .dropdown-menu{left:-15px;margin-top:11px}.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right{left:auto;right:-15px}.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right:after,.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right:before{left:auto;right:6px}.dropdown-kebab-pf .dropdown-menu:after,.dropdown-kebab-pf .dropdown-menu:before{border-bottom-color:#bbb;border-bottom-style:solid;border-bottom-width:10px;border-left:10px solid transparent;border-right:10px solid transparent;content:"";display:inline-block;left:6px;position:absolute;top:-11px}.dropdown-kebab-pf .dropdown-menu:after{border-bottom-color:#fff;top:-10px}.dropdown-kebab-pf.dropup .dropdown-menu{margin-bottom:11px;margin-top:0}.dropdown-kebab-pf.dropup .dropdown-menu:after,.dropdown-kebab-pf.dropup .dropdown-menu:before{border-bottom:none;border-top-color:#bbb;border-top-style:solid;border-top-width:10px;bottom:-11px;top:auto}.dropdown-kebab-pf.dropup .dropdown-menu:after{border-top-color:#fff;bottom:-10px}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;src:local('Open Sans'),local('OpenSans'),url(../fonts/open-sans/OpenSans-Regular.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;src:local('OpenSans-Light'),local('Open Sans Light'),url(../fonts/open-sans/OpenSans-Light.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;src:local('Open Sans Semibold'),local('OpenSans-Semibold'),url(../fonts/open-sans/OpenSans-Semibold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(../fonts/open-sans/OpenSans-Bold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:800;src:local('Open Sans Extrabold'),local('OpenSans-Extrabold'),url(../fonts/open-sans/OpenSans-ExtraBold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;src:local('Open Sans Light Italic'),local('OpenSansLight-Italic'),url(../fonts/open-sans/OpenSans-LightItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;src:local('Open Sans Italic'),local('OpenSans-Italic'),url(../fonts/open-sans/OpenSans-Italic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;src:local('Open Sans Semibold Italic'),local('OpenSans-SemiboldItalic'),url(../fonts/open-sans/OpenSans-SemiboldItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:700;src:local('Open Sans Bold Italic'),local('OpenSans-BoldItalic'),url(../fonts/open-sans/OpenSans-BoldItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:800;src:local('Open Sans Extrabold Italic'),local('OpenSans-ExtraboldItalic'),url(../fonts/open-sans/OpenSans-ExtraBoldItalic.ttf) format('truetype')}.chars-remaining-pf span{font-weight:600;padding-right:5px}.chars-warn-remaining-pf{color:#c00}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{-webkit-box-shadow:none;box-shadow:none;color:#8b8d8f}.form-control[disabled]:hover,.form-control[readonly]:hover,fieldset[disabled] .form-control:hover{border-color:#bbb}.form-control:hover{border-color:#7dc3e8}.has-error .form-control:hover{border-color:#900}.has-success .form-control:hover{border-color:#2b542c}.has-warning .form-control:hover{border-color:#bb6106}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label,.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label,.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#363636}.help-block{margin-bottom:0}.input-group .input-group-btn .btn{-webkit-box-shadow:none;box-shadow:none}label{font-weight:600}.navbar-nav>li>.dropdown-menu.infotip{border-top-width:1px!important;margin-top:10px}@media (max-width:767px){.navbar-pf .navbar-nav .open .dropdown-menu.infotip{background-color:#fff!important;margin-top:0}}.infotip{min-width:235px;padding:0}.infotip .list-group{border-top:0;margin:0;padding:8px 0}.infotip .list-group .list-group-item{border:none;margin:0 15px 0 34px;padding:5px 0}.infotip .list-group .list-group-item>.i{color:#4d5258;font-size:13px;left:-20px;position:absolute;top:8px}.infotip .list-group .list-group-item>a{color:#4d5258;line-height:13px}.infotip .list-group .list-group-item>.close{float:right}.infotip .footer{background-color:#f5f5f5;padding:6px 15px}.infotip .footer a:hover{color:#0088ce}.infotip .arrow,.infotip .arrow:after{border-color:transparent;border-style:solid;display:block;height:0;position:absolute;width:0}.infotip .arrow{border-width:11px}.infotip .arrow:after{border-width:10px;content:""}.infotip.bottom .arrow,.infotip.bottom-left .arrow,.infotip.bottom-right .arrow{border-bottom-color:#999;border-bottom-color:#bbb;border-top-width:0;left:50%;margin-left:-11px;top:-11px}.infotip.bottom .arrow:after,.infotip.bottom-left .arrow:after,.infotip.bottom-right .arrow:after{border-top-width:0;border-bottom-color:#fff;content:" ";margin-left:-10px;top:1px}.infotip.bottom-left .arrow{left:20%}.infotip.bottom-right .arrow{left:80%}.infotip.top .arrow{border-bottom-width:0;border-top-color:#999;border-top-color:#bbb;bottom:-11px;left:50%;margin-left:-11px}.infotip.top .arrow:after{border-bottom-width:0;border-top-color:#f5f5f5;bottom:1px;content:" ";margin-left:-10px}.infotip.right .arrow{border-left-width:0;border-right-color:#999;border-right-color:#bbb;left:-11px;margin-top:-11px;top:50%}.infotip.right .arrow:after{bottom:-10px;border-left-width:0;border-right-color:#fff;content:" ";left:1px}.infotip.left .arrow{border-left-color:#999;border-left-color:#bbb;border-right-width:0;margin-top:-11px;right:-11px;top:50%}.infotip.left .arrow:after{border-left-color:#fff;border-right-width:0;bottom:-10px;content:" ";right:1px}.label{border-radius:0;font-size:100%;font-weight:600}h1 .label,h2 .label,h3 .label,h4 .label,h5 .label,h6 .label{font-size:75%}.list-group{border-top:1px solid #ededed}.list-group .list-group-item:first-child{border-top:0}.list-group-item{border-top:0;border-left:0;border-right:0;margin-bottom:0}.list-group-item-heading{font-weight:600}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{border-top:solid 1px #39a5dc;margin-top:-1px;z-index:auto}.list-group-item.active:first-child{border-top:1px solid #39a5dc!important;margin-top:-1px}.login-pf{height:100%}.login-pf #brand{position:relative;top:-70px}.login-pf #brand img{display:block;height:18px;margin:0 auto;max-width:100%}@media (min-width:768px){.login-pf #brand img{margin:0;text-align:left}}.login-pf #badge{display:block;margin:20px auto 70px;position:relative;text-align:center}@media (min-width:768px){.login-pf #badge{float:right;margin-right:64px;margin-top:50px}}.login-pf body{background:#030303 url(../img/bg-login.jpg) repeat-x 50% 0;background-size:auto}@media (min-width:768px){.login-pf body{background-size:100% auto}}.login-pf .container{background-color:rgba(255,255,255,.055);clear:right;color:#fff;padding-bottom:40px;padding-top:20px;width:auto}@media (min-width:768px){.login-pf .container{bottom:13%;padding-left:80px;position:absolute;width:100%}}.login-pf .container [class^=alert]{background:0 0;color:#fff}.login-pf .container .details p:first-child{border-top:1px solid rgba(255,255,255,.3);padding-top:25px;margin-top:25px}@media (min-width:768px){.login-pf .container .details{border-left:1px solid rgba(255,255,255,.3);padding-left:40px}.login-pf .container .details p:first-child{border-top:0;padding-top:0;margin-top:0}}.login-pf .container .details p{margin-bottom:2px}.login-pf .container .form-horizontal .control-label{font-size:13px;font-weight:400;text-align:left}.login-pf .container .form-horizontal .form-group:last-child,.login-pf .container .form-horizontal .form-group:last-child .help-block:last-child{margin-bottom:0}.login-pf .container .help-block{color:#fff}@media (min-width:768px){.login-pf .container .login{padding-right:40px}}.login-pf .container .submit{text-align:right}.modal-header{background-color:#f5f5f5;border-bottom:none;padding:10px 18px}.modal-header .close{margin-top:2px}.modal-title{font-size:13px;font-weight:700}.modal-footer{border-top:none;margin-top:15px;padding:14px 15px 15px}.modal-footer>.btn{padding-left:10px;padding-right:10px}.modal-footer>.btn>.fa-angle-left{margin-right:5px}.modal-footer>.btn>.fa-angle-right{margin-left:5px}.navbar-pf{background:#030303;border:0;border-radius:0;border-top:3px solid #39a5dc;margin-bottom:0;min-height:0}.navbar-pf .navbar-brand{color:#f5f5f5;height:auto;padding:12px 0;margin:0 0 0 20px}.navbar-pf .navbar-brand img{display:block}.navbar-pf .navbar-collapse{border-top:0;-webkit-box-shadow:none;box-shadow:none;padding:0}.navbar-pf .navbar-header{border-bottom:1px solid #292929;float:none}.navbar-pf .navbar-nav{margin:0}.navbar-pf .navbar-nav>.active>a,.navbar-pf .navbar-nav>.active>a:focus,.navbar-pf .navbar-nav>.active>a:hover{background-color:#232323;color:#f5f5f5}.navbar-pf .navbar-nav>li>a{color:#d1d1d1;line-height:1;padding:10px 20px;text-shadow:none}.navbar-pf .navbar-nav>li>a:focus,.navbar-pf .navbar-nav>li>a:hover{color:#f5f5f5}.navbar-pf .navbar-nav>.open>a,.navbar-pf .navbar-nav>.open>a:focus,.navbar-pf .navbar-nav>.open>a:hover{background-color:#232323;color:#f5f5f5}@media (max-width:767px){.navbar-pf .navbar-nav .active .dropdown-menu,.navbar-pf .navbar-nav .active .navbar-persistent,.navbar-pf .navbar-nav .open .dropdown-menu{background-color:#171717!important;margin-left:0;padding-bottom:0;padding-top:0}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a,.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a:focus,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a:focus,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a:hover{background-color:#1f1f1f!important;color:#f5f5f5}.navbar-pf .navbar-nav .active .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent>li>a,.navbar-pf .navbar-nav .open .dropdown-menu>li>a{background-color:transparent;border:0;color:#d1d1d1;outline:0;padding-left:30px}.navbar-pf .navbar-nav .active .dropdown-menu>li>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent>li>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu>li>a:hover{color:#f5f5f5}.navbar-pf .navbar-nav .active .dropdown-menu .divider,.navbar-pf .navbar-nav .active .navbar-persistent .divider,.navbar-pf .navbar-nav .open .dropdown-menu .divider{background-color:#292929;margin:0 1px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-header,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-header,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-header{padding-bottom:0;padding-left:30px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open .dropdown-toggle,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open .dropdown-toggle,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open .dropdown-toggle{color:#f5f5f5}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.pull-left,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.pull-left,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.pull-left{float:none!important}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu>a:after,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu>a:after,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu>a:after{display:none}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-header,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-header,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-header{padding-left:45px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu{border:0;bottom:auto;-webkit-box-shadow:none;box-shadow:none;display:block;float:none;margin:0;min-width:0;padding:0;position:relative;left:auto;right:auto;top:auto}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu>li>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu>li>a{padding:5px 15px 5px 45px;line-height:20px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu .dropdown-menu>li>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu .dropdown-menu>li>a{padding-left:60px}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open .dropdown-menu{display:block}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu>a:after{display:inline-block!important;position:relative;right:auto;top:1px}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu{display:none}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-submenu>a:after{display:none!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu{background-color:#fff!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a:active{background-color:#def3ff!important;border-color:#bee1f4!important;color:#363636!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a:active small{color:#9c9c9c!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.disabled>a{color:#9c9c9c!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a:active{background-color:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a:active small{color:rgba(255,255,255,.5)!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li>a.opt{border-bottom:1px solid transparent;border-top:1px solid transparent;color:#363636;padding-left:10px;padding-right:10px}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:active small{color:rgba(255,255,255,.5)!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:focus small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:hover small{color:#9c9c9c}.navbar-pf .navbar-nav .context-bootstrap-select>.open>.dropdown-menu{padding-bottom:5px;padding-top:5px}}.navbar-pf .navbar-persistent{display:none}.navbar-pf .active>.navbar-persistent{display:block}.navbar-pf .navbar-primary{float:none}.navbar-pf .navbar-primary .context{border-bottom:1px solid #292929}.navbar-pf .navbar-primary .context.context-bootstrap-select .bootstrap-select.btn-group,.navbar-pf .navbar-primary .context.context-bootstrap-select .bootstrap-select.btn-group[class*=span]{margin:8px 20px 9px;width:auto}.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a{position:relative}.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a:after{content:"\f107";display:inline-block;font-family:FontAwesome;font-weight:400}@media (max-width:767px){.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a:after{height:10px;margin-left:4px;vertical-align:baseline}}.navbar-pf .navbar-toggle{border:0;margin:0;padding:10px 20px}.navbar-pf .navbar-toggle:focus,.navbar-pf .navbar-toggle:hover{background-color:transparent;outline:0}.navbar-pf .navbar-toggle:focus .icon-bar,.navbar-pf .navbar-toggle:hover .icon-bar{-webkit-box-shadow:0 0 3px #fff;box-shadow:0 0 3px #fff}.navbar-pf .navbar-toggle .icon-bar{background-color:#fff}.navbar-pf .navbar-utility{border-bottom:1px solid #292929}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle{padding-left:36px;position:relative}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle .pficon-user{left:20px;position:absolute;top:10px}@media (max-width:767px){.navbar-pf .navbar-utility>li+li{border-top:1px solid #292929}}@media (min-width:768px){.navbar-pf .navbar-brand{padding:8px 0 7px}.navbar-pf .navbar-nav>li>a{padding-bottom:14px;padding-top:14px}.navbar-pf .navbar-persistent{font-size:14px}.navbar-pf .navbar-primary{font-size:14px;background-image:-webkit-linear-gradient(top,#1d1d1d 0,#030303 100%);background-image:-o-linear-gradient(top,#1d1d1d 0,#030303 100%);background-image:linear-gradient(to bottom,#1d1d1d 0,#030303 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1d1d1d', endColorstr='#ff030303', GradientType=0)}.navbar-pf .navbar-primary.persistent-secondary .context .dropdown-menu{top:auto}.navbar-pf .navbar-primary.persistent-secondary .dropup .dropdown-menu{bottom:-5px;top:auto}.navbar-pf .navbar-primary.persistent-secondary>li{position:static}.navbar-pf .navbar-primary.persistent-secondary>li.active{margin-bottom:32px}.navbar-pf .navbar-primary.persistent-secondary>li.active>.navbar-persistent{display:block;left:0;position:absolute}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent{background:#f5f5f5;border-bottom:1px solid #d1d1d1;padding:0;width:100%}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent a{text-decoration:none!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:before,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:hover:before{background:#0088ce;bottom:-1px;content:'';display:block;height:2px;left:20px;position:absolute;right:20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:hover>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active>a:hover{color:#0088ce!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active .active>a{color:#f5f5f5}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu:hover>.dropdown-menu{display:none}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-menu{display:block;left:20px;margin-top:1px;top:100%}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-toggle{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-toggle:after{border-top-color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu>.dropdown-toggle{padding-right:35px!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu>.dropdown-toggle:after{position:absolute;right:20px;top:10px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open:before,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover:before{background:#bbb;bottom:-1px;content:'';display:block;height:2px;left:20px;position:absolute;right:20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover>a{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open>a:after,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover>a:after{border-top-color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a{background-color:transparent;display:block;line-height:1;padding:9px 20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a.dropdown-toggle{padding-right:35px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a.dropdown-toggle:after{font-size:15px;position:absolute;right:20px;top:9px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a:hover{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li a{color:#4d5258}.navbar-pf .navbar-primary>li>a{border-bottom:1px solid transparent;border-top:1px solid transparent;position:relative;margin:-1px 0 0}.navbar-pf .navbar-primary>li>a:hover{background-color:#1d1d1d;border-top-color:#5c5c5c;color:#d1d1d1;background-image:-webkit-linear-gradient(top,#363636 0,#1d1d1d 100%);background-image:-o-linear-gradient(top,#363636 0,#1d1d1d 100%);background-image:linear-gradient(to bottom,#363636 0,#1d1d1d 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff363636', endColorstr='#ff1d1d1d', GradientType=0)}.navbar-pf .navbar-primary>.active>a,.navbar-pf .navbar-primary>.active>a:focus,.navbar-pf .navbar-primary>.active>a:hover,.navbar-pf .navbar-primary>.open>a,.navbar-pf .navbar-primary>.open>a:focus,.navbar-pf .navbar-primary>.open>a:hover{background-color:#303030;border-bottom-color:#303030;border-top-color:#696969;-webkit-box-shadow:none;box-shadow:none;color:#f5f5f5;background-image:-webkit-linear-gradient(top,#434343 0,#303030 100%);background-image:-o-linear-gradient(top,#434343 0,#303030 100%);background-image:linear-gradient(to bottom,#434343 0,#303030 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff434343', endColorstr='#ff303030', GradientType=0)}.navbar-pf .navbar-primary li.context.context-bootstrap-select .filter-option{max-width:160px;text-overflow:ellipsis}.navbar-pf .navbar-primary li.context.dropdown{border-bottom:0}.navbar-pf .navbar-primary li.context.context-bootstrap-select,.navbar-pf .navbar-primary li.context>a{background-color:#1f1f1f;border-bottom-color:#3e3e3e;border-right:1px solid #3e3e3e;border-top-color:#3b3b3b;font-weight:600;background-image:-webkit-linear-gradient(top,#323232 0,#1f1f1f 100%);background-image:-o-linear-gradient(top,#323232 0,#1f1f1f 100%);background-image:linear-gradient(to bottom,#323232 0,#1f1f1f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff323232', endColorstr='#ff1f1f1f', GradientType=0)}.navbar-pf .navbar-primary li.context.context-bootstrap-select:hover,.navbar-pf .navbar-primary li.context>a:hover{background-color:#323232;border-bottom-color:#4a4a4a;border-right-color:#4a4a4a;border-top-color:#4a4a4a;background-image:-webkit-linear-gradient(top,#3f3f3f 0,#323232 100%);background-image:-o-linear-gradient(top,#3f3f3f 0,#323232 100%);background-image:linear-gradient(to bottom,#3f3f3f 0,#323232 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3f3f3f', endColorstr='#ff323232', GradientType=0)}.navbar-pf .navbar-primary li.context.open>a{background-color:#454545;border-bottom-color:#575757;border-right-color:#575757;border-top-color:#5a5a5a;background-image:-webkit-linear-gradient(top,#4c4c4c 0,#454545 100%);background-image:-o-linear-gradient(top,#4c4c4c 0,#454545 100%);background-image:linear-gradient(to bottom,#4c4c4c 0,#454545 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4c4c4c', endColorstr='#ff454545', GradientType=0)}.navbar-pf .navbar-utility{border-bottom:0;font-size:11px;position:absolute;right:0;top:0}.navbar-pf .navbar-utility>.active>a,.navbar-pf .navbar-utility>.active>a:focus,.navbar-pf .navbar-utility>.active>a:hover,.navbar-pf .navbar-utility>.open>a,.navbar-pf .navbar-utility>.open>a:focus,.navbar-pf .navbar-utility>.open>a:hover{background:#363636;color:#d1d1d1}.navbar-pf .navbar-utility>li>a{border-left:1px solid #2b2b2b;color:#d1d1d1!important;padding:7px 10px}.navbar-pf .navbar-utility>li>a:hover{background:#232323;border-left-color:#373737}.navbar-pf .navbar-utility>li.open>a{border-left-color:#444;color:#f5f5f5!important}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle{padding-left:26px}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle .pficon-user{left:10px;top:7px}.navbar-pf .navbar-utility .open .dropdown-menu{left:auto;right:0}.navbar-pf .navbar-utility .open .dropdown-menu .dropdown-menu{left:auto;right:100%}.navbar-pf .navbar-utility .open .dropdown-menu{border-top-width:0}.navbar-pf .open .dropdown-submenu>.dropdown-menu,.navbar-pf .open.bootstrap-select .dropdown-menu{border-top-width:1px!important}}@media (max-width:360px){.navbar-pf .navbar-brand{margin-left:10px;width:75%}.navbar-pf .navbar-brand img{height:auto;max-width:100%}.navbar-pf .navbar-toggle{padding-left:0}}.drawer-pf{background-color:#fafafa;border:1px solid #d1d1d1;-webkit-box-shadow:0 6px 12px rgba(3,3,3,.175);box-shadow:0 6px 12px rgba(3,3,3,.175);overflow-y:auto;position:absolute;right:0;width:320px;z-index:2}.drawer-pf .panel{border-bottom:none;border-left:none;border-right:none}.drawer-pf .panel-group .panel-heading+.panel-collapse .panel-body{border-top:none;border-bottom:1px solid #d1d1d1;padding:0}.drawer-pf .panel-counter{display:block;font-style:italic;line-height:1.2;padding-left:18px;padding-top:5px}.drawer-pf .panel-heading{border-bottom:1px solid #d1d1d1}.drawer-pf .panel-group{bottom:0;margin-bottom:0;position:absolute;top:25px;width:100%}.drawer-pf .panel-title a{cursor:pointer;display:block}.drawer-pf.drawer-pf-expanded{left:270px;width:inherit}.drawer-pf.drawer-pf-expanded .drawer-pf-toggle-expand:before{content:"\f101"}.drawer-pf-toggle-expand{color:inherit;cursor:pointer;left:0;padding:2px 5px;position:absolute}.drawer-pf-toggle-expand:before{content:"\f100";font-family:FontAwesome}.drawer-pf-toggle-expand:focus,.drawer-pf-toggle-expand:hover{color:inherit;text-decoration:none}.drawer-pf-action .btn-link{color:#0088ce;padding:10px 0}.drawer-pf-action .btn-link:hover{color:#00659c}.drawer-pf-loading{color:#4d5258;font-size:14px;padding:20px 15px}.drawer-pf-notification{border-bottom:1px solid #d1d1d1;padding:15px}.drawer-pf-notification .date{border-right:1px solid #aaa;display:inline-block;line-height:1;margin-right:5px;padding-right:9px}.drawer-pf-notification .pficon{font-size:14px;margin-top:3px}.drawer-pf-notification:last-of-type{border-bottom:none}.drawer-pf-notification:hover{background-color:#def3ff}.drawer-pf-notification.unread .drawer-pf-notification-message{font-weight:700}.drawer-pf-notification.expanded-notification .date{border-right:none;padding-right:0}.drawer-pf-notification-info,.drawer-pf-notification-message{display:block;padding-left:27px;padding-right:19px}.expanded-notification .drawer-pf-notification-info,.expanded-notification .drawer-pf-notification-message{display:inline-block}.drawer-pf-notifications-non-clickable .drawer-pf-notification:hover{background-color:#fff}.drawer-pf-title{background-color:#fafafa;border-bottom:1px solid #d1d1d1;position:absolute;width:318px}.drawer-pf-title h3{font-size:12px;margin:0;padding:6px 15px}.navbar-pf-vertical .drawer-pf{height:calc(100vh - 80px);top:58px}.navbar-pf-vertical .nav .drawer-pf-trigger .drawer-pf-trigger-icon{border-left:1px solid #2b2b2b;border-right:1px solid #2b2b2b;padding-left:15px;padding-right:15px}.navbar-pf-vertical .nav .drawer-pf-trigger.open .drawer-pf-trigger-icon{background-color:#232323}.navbar-pf .drawer-pf{height:calc(100vh - 46px);top:26px}.navbar-pf .drawer-pf-trigger-icon{cursor:pointer}.pager li>a,.pager li>span{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;font-weight:600;line-height:22px;padding:2px 14px}.open .dropdown-toggle.pager li>a,.open .dropdown-toggle.pager li>span,.pager li>a.active,.pager li>a:active,.pager li>a:focus,.pager li>a:hover,.pager li>span.active,.pager li>span:active,.pager li>span:focus,.pager li>span:hover{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.open .dropdown-toggle.pager li>a,.open .dropdown-toggle.pager li>span,.pager li>a.active,.pager li>a:active,.pager li>span.active,.pager li>span:active{background-image:none}.open .dropdown-toggle.pager li>a.focus,.open .dropdown-toggle.pager li>a:focus,.open .dropdown-toggle.pager li>a:hover,.open .dropdown-toggle.pager li>span.focus,.open .dropdown-toggle.pager li>span:focus,.open .dropdown-toggle.pager li>span:hover,.pager li>a.active.focus,.pager li>a.active:focus,.pager li>a.active:hover,.pager li>a:active.focus,.pager li>a:active:focus,.pager li>a:active:hover,.pager li>span.active.focus,.pager li>span.active:focus,.pager li>span.active:hover,.pager li>span:active.focus,.pager li>span:active:focus,.pager li>span:active:hover{background-color:#e5e5e5;border-color:#a9a9a9}.pager li>a.disabled,.pager li>a.disabled.active,.pager li>a.disabled:active,.pager li>a.disabled:focus,.pager li>a.disabled:hover,.pager li>a[disabled],.pager li>a[disabled].active,.pager li>a[disabled]:active,.pager li>a[disabled]:focus,.pager li>a[disabled]:hover,.pager li>span.disabled,.pager li>span.disabled.active,.pager li>span.disabled:active,.pager li>span.disabled:focus,.pager li>span.disabled:hover,.pager li>span[disabled],.pager li>span[disabled].active,.pager li>span[disabled]:active,.pager li>span[disabled]:focus,.pager li>span[disabled]:hover,fieldset[disabled] .pager li>a,fieldset[disabled] .pager li>a.active,fieldset[disabled] .pager li>a:active,fieldset[disabled] .pager li>a:focus,fieldset[disabled] .pager li>a:hover,fieldset[disabled] .pager li>span,fieldset[disabled] .pager li>span.active,fieldset[disabled] .pager li>span:active,fieldset[disabled] .pager li>span:focus,fieldset[disabled] .pager li>span:hover{background-color:#f1f1f1;border-color:#bbb}.pager li>a>.i,.pager li>span>.i{font-size:18px;vertical-align:top;margin:2px 0}.pager li>a:hover>a:focus{color:#4d5258}.pager li a:active{background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(3,3,3,.125);box-shadow:inset 0 3px 5px rgba(3,3,3,.125);outline:0}.pager .disabled>a,.pager .disabled>a:active,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{background:#f5f5f5;-webkit-box-shadow:none;box-shadow:none;color:#8b8d8f;cursor:default}.pager .next>a>.i,.pager .next>span>.i{margin-left:5px}.pager .previous>a>.i,.pager .previous>span>.i{margin-right:5px}.pager-sm li>a,.pager-sm li>span{font-weight:400;line-height:16px;padding:1px 10px}.pager-sm li>a>.i,.pager-sm li>span>.i{font-size:12px}.pagination>li>a,.pagination>li>span{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;cursor:default;font-weight:600;padding:2px 10px}.open .dropdown-toggle.pagination>li>a,.open .dropdown-toggle.pagination>li>span,.pagination>li>a.active,.pagination>li>a:active,.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span.active,.pagination>li>span:active,.pagination>li>span:focus,.pagination>li>span:hover{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.open .dropdown-toggle.pagination>li>a,.open .dropdown-toggle.pagination>li>span,.pagination>li>a.active,.pagination>li>a:active,.pagination>li>span.active,.pagination>li>span:active{background-image:none}.open .dropdown-toggle.pagination>li>a.focus,.open .dropdown-toggle.pagination>li>a:focus,.open .dropdown-toggle.pagination>li>a:hover,.open .dropdown-toggle.pagination>li>span.focus,.open .dropdown-toggle.pagination>li>span:focus,.open .dropdown-toggle.pagination>li>span:hover,.pagination>li>a.active.focus,.pagination>li>a.active:focus,.pagination>li>a.active:hover,.pagination>li>a:active.focus,.pagination>li>a:active:focus,.pagination>li>a:active:hover,.pagination>li>span.active.focus,.pagination>li>span.active:focus,.pagination>li>span.active:hover,.pagination>li>span:active.focus,.pagination>li>span:active:focus,.pagination>li>span:active:hover{background-color:#e5e5e5;border-color:#a9a9a9}.pagination>li>a.disabled,.pagination>li>a.disabled.active,.pagination>li>a.disabled:active,.pagination>li>a.disabled:focus,.pagination>li>a.disabled:hover,.pagination>li>a[disabled],.pagination>li>a[disabled].active,.pagination>li>a[disabled]:active,.pagination>li>a[disabled]:focus,.pagination>li>a[disabled]:hover,.pagination>li>span.disabled,.pagination>li>span.disabled.active,.pagination>li>span.disabled:active,.pagination>li>span.disabled:focus,.pagination>li>span.disabled:hover,.pagination>li>span[disabled],.pagination>li>span[disabled].active,.pagination>li>span[disabled]:active,.pagination>li>span[disabled]:focus,.pagination>li>span[disabled]:hover,fieldset[disabled] .pagination>li>a,fieldset[disabled] .pagination>li>a.active,fieldset[disabled] .pagination>li>a:active,fieldset[disabled] .pagination>li>a:focus,fieldset[disabled] .pagination>li>a:hover,fieldset[disabled] .pagination>li>span,fieldset[disabled] .pagination>li>span.active,fieldset[disabled] .pagination>li>span:active,fieldset[disabled] .pagination>li>span:focus,fieldset[disabled] .pagination>li>span:hover{background-color:#f1f1f1;border-color:#bbb}.pagination>li>a>.i,.pagination>li>span>.i{font-size:15px;vertical-align:top;margin:2px 0}.pagination>li>a:active,.pagination>li>span:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{background-color:#f1f1f1;border-color:#bbb;-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2);color:#4d5258;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{-webkit-box-shadow:none;box-shadow:none;cursor:default;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.pagination-sm>li>a,.pagination-sm>li>span{padding:2px 6px;font-size:11px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:1px;border-top-left-radius:1px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:1px;border-top-right-radius:1px}.pagination-sm>li>a,.pagination-sm>li>span{font-weight:400}.pagination-sm>li>a>.i,.pagination-sm>li>span>.i{font-size:12px;margin-top:2px}.panel-title{font-weight:700}.panel-group .panel{color:#4d5258}.panel-group .panel+.panel{margin-top:-1px}.panel-group .panel-default{border-color:#bbb;border-top-color:#bbb}.panel-group .panel-heading{background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #d1d1d1}.panel-group .panel-title{font-weight:500;line-height:1}.panel-group .panel-title>a{color:#4d5258;font-weight:600}.panel-group .panel-title>a:before{content:"\f107";display:inline-block;font-family:FontAwesome;font-size:13px;margin-right:5px;text-align:center;vertical-align:0;width:8px}.panel-group .panel-title>a:focus{outline:0;text-decoration:none}.panel-group .panel-title>a:hover{text-decoration:none}.panel-group .panel-title>a.collapsed:before{content:"\f105"}.popover{-webkit-box-shadow:0 2px 2px rgba(3,3,3,.08);box-shadow:0 2px 2px rgba(3,3,3,.08);padding:0}.popover-content{color:#4d5258;line-height:18px;padding:10px 14px}.popover-title{border-bottom:none;border-radius:0;color:#4d5258;font-size:13px;font-weight:700;min-height:34px}.popover-title .close{height:22px;position:absolute;right:8px;top:6px}.popover-title.closable{padding-right:30px}@-webkit-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}.progress{-webkit-box-shadow:inset 0 0 1px rgba(3,3,3,.25);box-shadow:inset 0 0 1px rgba(3,3,3,.25)}.progress.progress-label-left,.progress.progress-label-top-right{overflow:visible;position:relative}.progress.progress-label-left{margin-left:40px}.progress.progress-sm{height:14px;margin-bottom:14px}.progress.progress-xs{height:6px;margin-bottom:6px}td>.progress:first-child:last-child{margin-bottom:0;margin-top:3px}.progress-bar{box-shadow:none}.progress-label-left .progress-bar span,.progress-label-right .progress-bar span,.progress-label-top-right .progress-bar span{color:#363636;position:absolute;text-align:right}.progress-label-left .progress-bar span{font-size:14px;left:-40px;top:0;width:35px}.progress-label-right .progress-bar span,.progress-label-top-right .progress-bar span{font-size:11px;overflow:hidden;right:0;text-overflow:ellipsis;white-space:nowrap}.progress-label-right .progress-bar span strong,.progress-label-top-right .progress-bar span strong{font-weight:600}.progress-label-right .progress-bar span{max-width:85px;top:0}.progress-label-top-right .progress-bar span{max-width:47%;top:-30px}.progress-label-left.progress-sm .progress-bar span,.progress-label-top-right.progress-sm .progress-bar span{font-size:12px}.progress-sm .progress-bar{line-height:14px}.progress-xs .progress-bar{line-height:6px}.progress-bar-remaining{background:0 0}.progress-container{position:relative}.progress-container.progress-description-left{padding-left:90px}.progress-container.progress-label-right{padding-right:90px}.progress-description{margin-bottom:10px;max-width:52%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.progress-description .count{font-size:20px;font-weight:300;line-height:1;margin-right:5px}.progress-description .fa,.progress-description .pficon{font-size:14px;margin-right:3px}.progress-description-left .progress-description{left:0;margin-bottom:0;max-width:85px;position:absolute;top:0}.progress-description .tooltip{white-space:normal}.search-pf.has-button{border-collapse:separate;display:table}.search-pf.has-button .form-group{display:table-cell;width:100%}.search-pf.has-button .form-group .btn{-webkit-box-shadow:none;box-shadow:none;float:left;margin-left:-1px}.search-pf.has-button .form-group .btn.btn-lg{font-size:14.5px}.search-pf.has-button .form-group .btn.btn-sm{font-size:10.7px}.search-pf.has-button .form-group .form-control{float:left}.search-pf .has-clear .clear{background:0 0;background:rgba(255,255,255,0);border:0;height:25px;line-height:1;padding:0;position:absolute;right:1px;top:1px;width:28px}.search-pf .has-clear .clear:focus{outline:0}.search-pf .has-clear .form-control{padding-right:30px}.search-pf .has-clear .form-control::-ms-clear{display:none}.search-pf .has-clear .input-lg+.clear{height:31px;width:28px}.search-pf .has-clear .input-sm+.clear{height:20px;width:28px}.search-pf .has-clear .input-sm+.clear span{font-size:10px}.search-pf .has-clear .search-pf-input-group{position:relative}.sidebar-header{border-bottom:1px solid #ececec;padding-bottom:11px;margin:50px 0 20px}.sidebar-header .actions{margin-top:-2px}.sidebar-pf .sidebar-header+.list-group{border-top:0;margin-top:-10px}.sidebar-pf .sidebar-header+.list-group .list-group-item{background:0 0;border-color:#ececec;padding-left:0}.sidebar-pf .sidebar-header+.list-group .list-group-item-heading{font-size:12px}.sidebar-pf .nav-category h2{color:#9c9c9c;font-size:12px;font-weight:400;line-height:21px;margin:0;padding:8px 0}.sidebar-pf .nav-category+.nav-category{margin-top:10px}.sidebar-pf .nav-pills>li.active>a{background:#0088ce!important;border-color:#0088ce!important;color:#fff}@media (min-width:768px){.sidebar-pf .nav-pills>li.active>a:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:1px}}.sidebar-pf .nav-pills>li.active>a .fa{color:#fff}.sidebar-pf .nav-pills>li>a{border-bottom:1px solid transparent;border-radius:0;border-top:1px solid transparent;color:#363636;font-size:13px;line-height:21px;padding:1px 20px}.sidebar-pf .nav-pills>li>a:hover{background:#def3ff;border-color:#bee1f4}.sidebar-pf .nav-pills>li>a .fa{color:#6a7079;font-size:15px;margin-right:10px;text-align:center;vertical-align:middle;width:15px}.sidebar-pf .nav-stacked{margin-left:-20px;margin-right:-20px}.sidebar-pf .nav-stacked li+li{margin-top:0}.sidebar-pf .panel{background:0 0}.sidebar-pf .panel-body{padding:6px 20px}.sidebar-pf .panel-body .nav-pills>li>a{padding-left:37px}.sidebar-pf .panel-heading{padding:9px 20px}.sidebar-pf .panel-title{font-size:12px}.sidebar-pf .panel-title>a:before{display:inline-block;margin-left:1px;margin-right:4px;width:9px}.sidebar-pf .panel-title>a.collapsed:before{margin-left:3px;margin-right:2px}@media (min-width:767px){.sidebar-header-bleed-left{margin-left:-20px}.sidebar-header-bleed-left>h2{margin-left:20px}.sidebar-header-bleed-right{margin-right:-20px}.sidebar-header-bleed-right .actions{margin-right:20px}.sidebar-header-bleed-right>h2{margin-right:20px}.sidebar-header-bleed-right+.list-group{margin-right:-20px}.sidebar-pf .panel-group .panel-default,.sidebar-pf .treeview{border-left:0;border-right:0;margin-left:-20px;margin-right:-20px}.sidebar-pf .treeview{margin-top:5px}.sidebar-pf .treeview .list-group-item{padding-left:20px;padding-right:20px}.sidebar-pf .treeview .list-group-item.node-selected:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:1px}}@media (min-width:768px){.sidebar-pf{background:#fafafa}.sidebar-pf.sidebar-pf-left{border-right:1px solid #d1d1d1}.sidebar-pf.sidebar-pf-right{border-left:1px solid #d1d1d1}.sidebar-pf>.nav-category,.sidebar-pf>.nav-stacked{margin-top:5px}}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(359deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}.spinner{-webkit-animation:rotation .6s infinite linear;animation:rotation .6s infinite linear;border-bottom:4px solid rgba(3,3,3,.25);border-left:4px solid rgba(3,3,3,.25);border-right:4px solid rgba(3,3,3,.25);border-radius:100%;border-top:4px solid rgba(3,3,3,.75);height:24px;margin:0 auto;position:relative;width:24px}.spinner.spinner-inline{display:inline-block;margin-right:3px}.spinner.spinner-lg{border-width:5px;height:30px;width:30px}.spinner.spinner-sm{border-width:3px;height:18px;width:18px}.spinner.spinner-xs{border-width:2px;height:12px;width:12px}.spinner.spinner-inverse{border-bottom-color:rgba(255,255,255,.25);border-left-color:rgba(255,255,255,.25);border-right-color:rgba(255,255,255,.25);border-top-color:rgba(255,255,255,.75)}.ie9 .spinner{background:url(../img/spinner.gif) no-repeat;border:0}.ie9 .spinner.spinner-inverse{background-image:url(../img/spinner-inverse.gif)}.ie9 .spinner.spinner-inverse-lg{background-image:url(../img/spinner-inverse-lg.gif)}.ie9 .spinner.spinner-inverse-sm{background-image:url(../img/spinner-inverse-sm.gif)}.ie9 .spinner.spinner-inverse-xs{background-image:url(../img/spinner-inverse-xs.gif)}.ie9 .spinner.spinner-lg{background-image:url(../img/spinner-lg.gif)}.ie9 .spinner.spinner-sm{background-image:url(../img/spinner-sm.gif)}.ie9 .spinner.spinner-xs{background-image:url(../img/spinner-xs.gif)}.prettyprint .atn,.prettyprint .com,.prettyprint .fun,.prettyprint .var{color:#3f9c35}.prettyprint .atv,.prettyprint .str{color:#a30000}.prettyprint .clo,.prettyprint .dec,.prettyprint .kwd,.prettyprint .opn,.prettyprint .pln,.prettyprint .pun{color:#363636}.prettyprint .lit,.prettyprint .tag,.prettyprint .typ{color:#00659c}.prettyprint ol.linenums{margin-bottom:0}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:2px 10px 3px}.table>tbody>tr>td>a:hover,.table>tbody>tr>th>a:hover,.table>tfoot>tr>td>a:hover,.table>tfoot>tr>th>a:hover,.table>thead>tr>td>a:hover,.table>thead>tr>th>a:hover{text-decoration:none}.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>th{font-family:'Open Sans';font-style:normal;font-weight:600}.table>thead{background-clip:padding-box;background-color:#f5f5f5;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.table-bordered{border:1px solid #d1d1d1}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #d1d1d1}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:1px}.table-striped>tbody>tr:nth-of-type(even){background-color:#f5f5f5}.table-striped>tbody>tr:nth-of-type(odd){background-color:transparent}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#def3ff;border-bottom-color:#7dc3e8}.table-treegrid span.indent{margin-left:10px;margin-right:10px}.table-treegrid span.icon{display:inline-block;font-size:13px;margin-right:5px;min-width:10px;text-align:center}.table-treegrid span.collapse-icon,.table-treegrid span.expand-icon{cursor:pointer}.table-treegrid>tbody>tr.odd{background-color:#f5f5f5}.nav-tabs{font-size:14px}.nav-tabs>li>a{color:#4d5258;margin-right:-1px;padding-bottom:5px;padding-top:5px}.nav-tabs>li>a:active,.nav-tabs>li>a:focus,.nav-tabs>li>a:hover{background:0 0;border-color:#ededed;color:#252525}.nav-tabs>li>.dropdown-menu{border-top:0;border-color:#ededed}.nav-tabs>li>.dropdown-menu.pull-right{right:-1px}.nav-tabs+.nav-tabs-pf{font-size:12px}.nav-tabs+.nav-tabs-pf>li:first-child>a{padding-left:15px}.nav-tabs+.nav-tabs-pf>li:first-child>a:before{left:15px!important}.nav-tabs .open>a,.nav-tabs .open>a:focus,.nav-tabs .open>a:hover{background-color:transparent;border-color:#ededed}@media (min-width:768px){.nav-tabs-pf.nav-justified{border-bottom:1px solid #ededed}}.nav-tabs-pf.nav-justified>li:first-child>a{padding-left:15px}.nav-tabs-pf.nav-justified>li>a{border-bottom:0}.nav-tabs-pf.nav-justified>li>a:before{left:0!important;right:0!important}.nav-tabs-pf>li{margin-bottom:0}.nav-tabs-pf>li.active>a:before{background:#0088ce;bottom:-1px;content:'';display:block;height:2px;left:15px;position:absolute;right:15px}.nav-tabs-pf>li.active>a,.nav-tabs-pf>li.active>a:active,.nav-tabs-pf>li.active>a:focus,.nav-tabs-pf>li.active>a:hover{background-color:transparent;border:0!important;color:#0088ce}.nav-tabs-pf>li.active>a:active:before,.nav-tabs-pf>li.active>a:before,.nav-tabs-pf>li.active>a:focus:before,.nav-tabs-pf>li.active>a:hover:before{background:#0088ce}.nav-tabs-pf>li:first-child>a{padding-left:0}.nav-tabs-pf>li:first-child>a:before{left:0!important}.nav-tabs-pf>li>a{border:0;line-height:1;margin-right:0;padding-bottom:10px;padding-top:10px}.nav-tabs-pf>li>a:active:before,.nav-tabs-pf>li>a:focus:before,.nav-tabs-pf>li>a:hover:before{background:#bbb;bottom:-1px;content:'';display:block;height:2px;left:15px;position:absolute;right:15px}.nav-tabs-pf>li>.dropdown-menu{left:15px;margin-top:1px}.nav-tabs-pf>li>.dropdown-menu.pull-right{left:auto;right:15px}.nav-tabs-pf .open>a,.nav-tabs-pf .open>a:focus,.nav-tabs-pf .open>a:hover{background-color:transparent}.tooltip{font-size:12px;line-height:1.4}.tooltip-inner{padding:7px 12px;text-align:left}.h1,.h2,h1,h2{font-weight:300}.page-header .actions{margin-top:8px}.page-header .actions a>.pficon{margin-right:4px}@media (min-width:767px){.page-header-bleed-left{margin-left:-20px}.page-header-bleed-right{margin-right:-20px}.page-header-bleed-right .actions{margin-right:20px}}
+\ No newline at end of file
++ */.bootstrap-select.btn-group,.bootstrap-select.btn-group[class*=span]{float:none;display:inline-block;margin-bottom:10px;margin-left:0}.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group,.form-search .bootstrap-select.btn-group{margin-bottom:0}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.btn-group.pull-right,.bootstrap-select.btn-group[class*=span].pull-right,.row-fluid .bootstrap-select.btn-group[class*=span].pull-right{float:right}.input-append .bootstrap-select.btn-group{margin-left:-1px}.input-prepend .bootstrap-select.btn-group{margin-right:-1px}.bootstrap-select:not([class*=span]):not([class*=col-]):not([class*=form-control]){width:220px}.bootstrap-select{width:220px\9}.bootstrap-select.form-control:not([class*=span]){width:100%}.bootstrap-select>.btn{width:100%}.error .bootstrap-select .btn{border:1px solid #b94a48}.dropdown-menu{z-index:2000}.bootstrap-select.show-menu-arrow.open>.btn{z-index:2051}.bootstrap-select .btn:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.btn-group .btn .filter-option{overflow:hidden;position:absolute;left:12px;right:25px;text-align:left}.bootstrap-select.btn-group .btn .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group .dropdown-menu li.disabled>a,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group[class*=span] .btn{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;border:0;padding:0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu dt{display:block;padding:3px 20px;cursor:default}.bootstrap-select.btn-group .div-contain{overflow:hidden}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li>a.opt{position:relative;padding-left:35px}.bootstrap-select.btn-group .dropdown-menu li>a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:400}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a i.check-mark{display:inline-block;position:absolute;right:15px;margin-top:2.5px}.bootstrap-select.btn-group .dropdown-menu li a i.check-mark{display:none}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu li.active:not(.disabled)>a small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:focus small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:hover small{color:#64b1d8;color:rgba(255,255,255,.4)}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:400}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #CCC;border-bottom-color:rgba(0,0,0,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,.2)}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px}.mobile-device{position:absolute;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select.btn-group.fit-width .btn .filter-option{position:static}.bootstrap-select.btn-group.fit-width .btn .caret{position:static;top:auto;margin-top:-1px}.control-group.error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select-searchbox{padding:4px 8px}.bootstrap-select-searchbox input{margin-bottom:0}.alert{border-width:1px;padding-left:47px;padding-right:14px;position:relative}.alert .alert-link{color:#0088ce}.alert .alert-link:hover{color:#00659c}.alert>.btn.pull-right{margin-top:-3px}.alert>.pficon{font-size:22px;position:absolute;left:13px;top:10px}.alert .close{opacity:.85;filter:alpha(opacity=85)}.alert .close:focus,.alert .close:hover{opacity:1;filter:alpha(opacity=100)}.alert .pficon-info{color:#4d5258}.alert-dismissable{padding-right:28px}.alert-dismissable .close{right:-13px;top:1px}.badge{margin-left:6px}.nav-pills>li>a>.badge{margin-left:6px}.bootstrap-select.btn-group.form-control{margin-bottom:0}.bootstrap-select.btn-group .btn{-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-select.btn-group .btn:hover{border-color:#7dc3e8}.bootstrap-select.btn-group .btn .caret{margin-top:-4px}.bootstrap-select.btn-group .btn:focus{border-color:#0088ce;outline:0!important;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 8px rgba(0,136,206,.6);box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 8px rgba(0,136,206,.6)}.has-error .bootstrap-select.btn-group .btn{border-color:#c00}.has-error .bootstrap-select.btn-group .btn:focus{border-color:#900;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #f33;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #f33}.has-success .bootstrap-select.btn-group .btn{border-color:#3c763d}.has-success .bootstrap-select.btn-group .btn:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #67b168}.has-warning .bootstrap-select.btn-group .btn{border-color:#ec7a08}.has-warning .bootstrap-select.btn-group .btn:focus{border-color:#bb6106;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #faad60;box-shadow:inset 0 1px 1px rgba(3,3,3,.075),0 0 6px #faad60}.bootstrap-select.btn-group .dropdown-menu>.active>a,.bootstrap-select.btn-group .dropdown-menu>.active>a:active{background-color:#def3ff!important;border-color:#bee1f4!important;color:#363636!important}.bootstrap-select.btn-group .dropdown-menu>.active>a small,.bootstrap-select.btn-group .dropdown-menu>.active>a:active small{color:#9c9c9c!important}.bootstrap-select.btn-group .dropdown-menu>.disabled>a{color:#9c9c9c!important}.bootstrap-select.btn-group .dropdown-menu>.selected>a{background-color:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.bootstrap-select.btn-group .dropdown-menu>.selected>a small{color:rgba(255,255,255,.5)!important}.bootstrap-select.btn-group .dropdown-menu .divider{background:#ededed!important;margin:4px 1px!important}.bootstrap-select.btn-group .dropdown-menu dt{color:#8b8d8f;font-weight:400;padding:1px 10px}.bootstrap-select.btn-group .dropdown-menu li>a.opt{padding:1px 10px}.bootstrap-select.btn-group .dropdown-menu li a:active small{color:rgba(255,255,255,.5)!important}.bootstrap-select.btn-group .dropdown-menu li a:focus small,.bootstrap-select.btn-group .dropdown-menu li a:hover small{color:#9c9c9c}.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:focus small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:hover small{color:#9c9c9c}.combobox-container.combobox-selected .glyphicon-remove{display:inline-block}.combobox-container .caret{margin-left:0}.combobox-container .combobox::-ms-clear{display:none}.combobox-container .dropdown-menu{margin-top:-1px;width:100%}.combobox-container .glyphicon-remove{display:none;top:auto;width:12px}.combobox-container .glyphicon-remove:before{content:"\e60b";font-family:PatternFlyIcons-webfont}.combobox-container .input-group-addon{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;position:relative}.combobox-container .input-group-addon.active,.combobox-container .input-group-addon:active,.combobox-container .input-group-addon:focus,.combobox-container .input-group-addon:hover,.open .dropdown-toggle.combobox-container .input-group-addon{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.combobox-container .input-group-addon.active,.combobox-container .input-group-addon:active,.open .dropdown-toggle.combobox-container .input-group-addon{background-image:none}.combobox-container .input-group-addon.active.focus,.combobox-container .input-group-addon.active:focus,.combobox-container .input-group-addon.active:hover,.combobox-container .input-group-addon:active.focus,.combobox-container .input-group-addon:active:focus,.combobox-container .input-group-addon:active:hover,.open .dropdown-toggle.combobox-container .input-group-addon.focus,.open .dropdown-toggle.combobox-container .input-group-addon:focus,.open .dropdown-toggle.combobox-container .input-group-addon:hover{background-color:#e5e5e5;border-color:#a9a9a9}.combobox-container .input-group-addon.disabled,.combobox-container .input-group-addon.disabled.active,.combobox-container .input-group-addon.disabled:active,.combobox-container .input-group-addon.disabled:focus,.combobox-container .input-group-addon.disabled:hover,.combobox-container .input-group-addon[disabled],.combobox-container .input-group-addon[disabled].active,.combobox-container .input-group-addon[disabled]:active,.combobox-container .input-group-addon[disabled]:focus,.combobox-container .input-group-addon[disabled]:hover,fieldset[disabled] .combobox-container .input-group-addon,fieldset[disabled] .combobox-container .input-group-addon.active,fieldset[disabled] .combobox-container .input-group-addon:active,fieldset[disabled] .combobox-container .input-group-addon:focus,fieldset[disabled] .combobox-container .input-group-addon:hover{background-color:#f1f1f1;border-color:#bbb}.combobox-container .input-group-addon:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.treeview .list-group{border-top:0}.treeview .list-group-item{background:0 0;border-bottom:1px solid transparent!important;border-top:1px solid transparent!important;margin-bottom:0;padding:0 10px}.treeview .list-group-item:hover{background:#def3ff!important;border-color:#bee1f4!important}.treeview .list-group-item.node-selected{background:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.treeview span.icon{display:inline-block;font-size:13px;min-width:10px;text-align:center}.treeview span.icon>[class*=fa-angle]{font-size:15px}.treeview span.indent{margin-right:5px}.breadcrumb{padding-left:0}.breadcrumb>.active strong{font-weight:600}.breadcrumb>li{display:inline}.breadcrumb>li+li:before{color:#9c9c9c;content:"\f101";font-family:FontAwesome;font-size:11px;padding:0 9px 0 7px}.btn{-webkit-box-shadow:0 2px 3px rgba(3,3,3,.1);box-shadow:0 2px 3px rgba(3,3,3,.1)}.btn:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{background-color:#fafafa!important;background-image:none!important;border-color:#d1d1d1!important;color:#8b8d8f!important;opacity:1}.btn.disabled:active,.btn[disabled]:active,fieldset[disabled] .btn:active{-webkit-box-shadow:none;box-shadow:none}.btn.disabled.btn-link,.btn[disabled].btn-link,fieldset[disabled] .btn.btn-link{background-color:transparent!important;border:0}.btn-danger{background-color:#a30000;background-image:-webkit-linear-gradient(top,#c00 0,#a30000 100%);background-image:-o-linear-gradient(top,#c00 0,#a30000 100%);background-image:linear-gradient(to bottom,#c00 0,#a30000 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffcc0000', endColorstr='#ffa30000', GradientType=0);border-color:#8b0000;color:#fff}.btn-danger.active,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open .dropdown-toggle.btn-danger{background-color:#a30000;background-image:none;border-color:#8b0000;color:#fff}.btn-danger.active,.btn-danger:active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open .dropdown-toggle.btn-danger.focus,.open .dropdown-toggle.btn-danger:focus,.open .dropdown-toggle.btn-danger:hover{background-color:#8a0000;border-color:#670000}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#a30000;border-color:#8b0000}.btn-default{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258}.btn-default.active,.btn-default:active,.btn-default:focus,.btn-default:hover,.open .dropdown-toggle.btn-default{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.btn-default.active,.btn-default:active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open .dropdown-toggle.btn-default.focus,.open .dropdown-toggle.btn-default:focus,.open .dropdown-toggle.btn-default:hover{background-color:#e5e5e5;border-color:#a9a9a9}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#f1f1f1;border-color:#bbb}.btn-link,.btn-link:active{-webkit-box-shadow:none;box-shadow:none}.btn-primary{background-color:#0088ce;background-image:-webkit-linear-gradient(top,#39a5dc 0,#0088ce 100%);background-image:-o-linear-gradient(top,#39a5dc 0,#0088ce 100%);background-image:linear-gradient(to bottom,#39a5dc 0,#0088ce 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff39a5dc', endColorstr='#ff0088ce', GradientType=0);border-color:#00659c;color:#fff}.btn-primary.active,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open .dropdown-toggle.btn-primary{background-color:#0088ce;background-image:none;border-color:#00659c;color:#fff}.btn-primary.active,.btn-primary:active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open .dropdown-toggle.btn-primary.focus,.open .dropdown-toggle.btn-primary:focus,.open .dropdown-toggle.btn-primary:hover{background-color:#0077b5;border-color:#004e78}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#0088ce;border-color:#00659c}.btn-group-xs .btn,.btn-group-xs>.btn,.btn-xs{font-weight:400}.close{text-shadow:none;opacity:.6;filter:alpha(opacity=60)}.close:focus,.close:hover{opacity:.9;filter:alpha(opacity=90)}.ColVis_Button:active:focus{outline:0}.ColVis_catcher{position:absolute;z-index:999}.ColVis_collection{background-color:#fff;border:1px solid #bbb;border-radius:1px;-webkit-box-shadow:0 6px 12px rgba(3,3,3,.175);box-shadow:0 6px 12px rgba(3,3,3,.175);background-clip:padding-box;list-style:none;margin:-1px 0 0 0;padding:5px 10px;width:150px;z-index:1000}.ColVis_collection label{font-weight:400;margin-bottom:5px;margin-top:5px;padding-left:20px}.ColVis_collectionBackground{background-color:#fff;height:100%;left:0;position:fixed;top:0;width:100%;z-index:998}.dataTables_header{background-color:#f5f5f5;border:1px solid #d1d1d1;border-bottom:none;padding:5px;position:relative;text-align:center}.dataTables_header .btn{-webkit-box-shadow:none;box-shadow:none}.dataTables_header .ColVis{position:absolute;right:5px;text-align:left;top:5px}.dataTables_header .ColVis+.dataTables_info{padding-right:30px}.dataTables_header .dataTables_filter{position:absolute}.dataTables_header .dataTables_filter input{border:1px solid #bbb;height:24px}@media (max-width:767px){.dataTables_header .dataTables_filter input{width:100px}}.dataTables_header .dataTables_info{padding:2px 0}@media (max-width:480px){.dataTables_header .dataTables_info{text-align:right}}.dataTables_header .dataTables_info b{font-weight:700}.dataTables_footer{background-color:#fff;border:1px solid #d1d1d1;border-top:none;overflow:hidden}.dataTables_paginate{background:#fafafa;float:right;margin:0}.dataTables_paginate .pagination{float:left;margin:0}.dataTables_paginate .pagination>li>span{border-color:#fff #d1d1d1 #f5f5f5;border-width:0 1px;font-size:16px;font-weight:400;padding:0;text-align:center;width:31px}.dataTables_paginate .pagination>li>span:focus,.dataTables_paginate .pagination>li>span:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dataTables_paginate .pagination>li.last>span{border-right:none}.dataTables_paginate .pagination>li.disabled>span{background:#f5f5f5;border-left-color:#ededed;border-right-color:#ededed;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dataTables_paginate .pagination-input{float:left;font-size:12px;line-height:1em;padding:4px 15px 0;text-align:right}.dataTables_paginate .pagination-input .paginate_input{border:1px solid #d1d1d1;-webkit-box-shadow:inset 0 1px 1px rgba(3,3,3,.075);box-shadow:inset 0 1px 1px rgba(3,3,3,.075);font-size:12px;font-weight:600;height:19px;margin-right:8px;padding-right:3px;text-align:right;width:30px}.dataTables_paginate .pagination-input .paginate_of{position:relative}.dataTables_paginate .pagination-input .paginate_of b{margin-left:3px}.dataTables_wrapper{margin:20px 0}@media (max-width:767px){.dataTables_wrapper .table-responsive{margin-bottom:0}}.DTCR_clonedTable{background-color:rgba(255,255,255,.7);z-index:202}.DTCR_pointer{background-color:#0088ce;width:1px;z-index:201}table.datatable{margin-bottom:0;max-width:none!important}table.datatable thead .sorting,table.datatable thead .sorting_asc,table.datatable thead .sorting_asc_disabled,table.datatable thead .sorting_desc,table.datatable thead .sorting_desc_disabled{cursor:pointer}table.datatable thead .sorting_asc,table.datatable thead .sorting_desc{color:#0088ce!important;position:relative}table.datatable thead .sorting_asc:after,table.datatable thead .sorting_desc:after{content:"\f107";font-family:FontAwesome;font-size:10px;font-weight:400;height:9px;left:7px;line-height:12px;position:relative;top:2px;vertical-align:baseline;width:12px}table.datatable thead .sorting_asc:before,table.datatable thead .sorting_desc:before{background:#0088ce;content:'';height:2px;position:absolute;left:0;top:0;width:100%}table.datatable thead .sorting_asc:after{content:"\f106";top:-3px}table.datatable th:active{outline:0}.caret{font-family:FontAwesome;font-weight:400;height:9px;position:relative;vertical-align:baseline;width:12px}.caret:before{bottom:0;content:"\f107";left:0;line-height:12px;position:absolute;text-align:center;top:-1px;right:0}.dropup .caret:before{content:"\f106"}.dropdown-menu .divider{background-color:#ededed;height:1px;margin:4px 1px;overflow:hidden}.dropdown-menu>li>a{border-color:transparent;border-style:solid;border-width:1px 0;padding:1px 10px}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{border-color:#bee1f4;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>li>a:active{background-color:#0088ce;border-color:#0088ce;color:#fff!important;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#0088ce!important;border-color:#0088ce!important;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{border-color:transparent}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{border-color:transparent}.dropdown-header{padding-left:10px;padding-right:10px;text-transform:uppercase}.btn-group>.dropdown-menu,.dropdown>.dropdown-menu,.input-group-btn>.dropdown-menu{margin-top:-1px}.dropup .dropdown-menu{margin-bottom:-1px}.dropdown-submenu{position:relative}.dropdown-submenu:hover>a{background-color:#def3ff;border-color:#bee1f4}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropdown-submenu.pull-left{float:none!important}.dropdown-submenu.pull-left>.dropdown-menu{left:auto;margin-left:10px;right:100%}.dropdown-submenu>a{padding-right:20px!important}.dropdown-submenu>a:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:2px}.dropdown-submenu>.dropdown-menu{left:100%;margin-top:0;top:-6px}.dropup .dropdown-submenu>.dropdown-menu{bottom:-5px;top:auto}.open .dropdown-submenu.active>.dropdown-menu{display:block}.dropdown-kebab-pf .btn-link{color:#252525;font-size:16px;line-height:1;padding:4px 0}.dropdown-kebab-pf .btn-link:active,.dropdown-kebab-pf .btn-link:focus,.dropdown-kebab-pf .btn-link:hover{color:#0088ce}.dropdown-kebab-pf .dropdown-menu{left:-15px;margin-top:11px}.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right{left:auto;right:-15px}.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right:after,.dropdown-kebab-pf .dropdown-menu.dropdown-menu-right:before{left:auto;right:6px}.dropdown-kebab-pf .dropdown-menu:after,.dropdown-kebab-pf .dropdown-menu:before{border-bottom-color:#bbb;border-bottom-style:solid;border-bottom-width:10px;border-left:10px solid transparent;border-right:10px solid transparent;content:"";display:inline-block;left:6px;position:absolute;top:-11px}.dropdown-kebab-pf .dropdown-menu:after{border-bottom-color:#fff;top:-10px}.dropdown-kebab-pf.dropup .dropdown-menu{margin-bottom:11px;margin-top:0}.dropdown-kebab-pf.dropup .dropdown-menu:after,.dropdown-kebab-pf.dropup .dropdown-menu:before{border-bottom:none;border-top-color:#bbb;border-top-style:solid;border-top-width:10px;bottom:-11px;top:auto}.dropdown-kebab-pf.dropup .dropdown-menu:after{border-top-color:#fff;bottom:-10px}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;src:local('Open Sans'),local('OpenSans'),url(../fonts/open-sans/OpenSans-Regular.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;src:local('OpenSans-Light'),local('Open Sans Light'),url(../fonts/open-sans/OpenSans-Light.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;src:local('Open Sans Semibold'),local('OpenSans-Semibold'),url(../fonts/open-sans/OpenSans-Semibold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(../fonts/open-sans/OpenSans-Bold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:normal;font-weight:800;src:local('Open Sans Extrabold'),local('OpenSans-Extrabold'),url(../fonts/open-sans/OpenSans-ExtraBold.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;src:local('Open Sans Light Italic'),local('OpenSansLight-Italic'),url(../fonts/open-sans/OpenSans-LightItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;src:local('Open Sans Italic'),local('OpenSans-Italic'),url(../fonts/open-sans/OpenSans-Italic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;src:local('Open Sans Semibold Italic'),local('OpenSans-SemiboldItalic'),url(../fonts/open-sans/OpenSans-SemiboldItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:700;src:local('Open Sans Bold Italic'),local('OpenSans-BoldItalic'),url(../fonts/open-sans/OpenSans-BoldItalic.ttf) format('truetype')}@font-face{font-family:'Open Sans';font-style:italic;font-weight:800;src:local('Open Sans Extrabold Italic'),local('OpenSans-ExtraboldItalic'),url(../fonts/open-sans/OpenSans-ExtraBoldItalic.ttf) format('truetype')}.chars-remaining-pf span{font-weight:600;padding-right:5px}.chars-warn-remaining-pf{color:#c00}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{-webkit-box-shadow:none;box-shadow:none;color:#8b8d8f}.form-control[disabled]:hover,.form-control[readonly]:hover,fieldset[disabled] .form-control:hover{border-color:#bbb}.form-control:hover{border-color:#7dc3e8}.has-error .form-control:hover{border-color:#900}.has-success .form-control:hover{border-color:#2b542c}.has-warning .form-control:hover{border-color:#bb6106}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label,.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label,.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#363636}.help-block{margin-bottom:0}.input-group .input-group-btn .btn{-webkit-box-shadow:none;box-shadow:none}label{font-weight:600}.navbar-nav>li>.dropdown-menu.infotip{border-top-width:1px!important;margin-top:10px}@media (max-width:767px){.navbar-pf .navbar-nav .open .dropdown-menu.infotip{background-color:#fff!important;margin-top:0}}.infotip{min-width:235px;padding:0}.infotip .list-group{border-top:0;margin:0;padding:8px 0}.infotip .list-group .list-group-item{border:none;margin:0 15px 0 34px;padding:5px 0}.infotip .list-group .list-group-item>.i{color:#4d5258;font-size:13px;left:-20px;position:absolute;top:8px}.infotip .list-group .list-group-item>a{color:#4d5258;line-height:13px}.infotip .list-group .list-group-item>.close{float:right}.infotip .footer{background-color:#f5f5f5;padding:6px 15px}.infotip .footer a:hover{color:#0088ce}.infotip .arrow,.infotip .arrow:after{border-color:transparent;border-style:solid;display:block;height:0;position:absolute;width:0}.infotip .arrow{border-width:11px}.infotip .arrow:after{border-width:10px;content:""}.infotip.bottom .arrow,.infotip.bottom-left .arrow,.infotip.bottom-right .arrow{border-bottom-color:#999;border-bottom-color:#bbb;border-top-width:0;left:50%;margin-left:-11px;top:-11px}.infotip.bottom .arrow:after,.infotip.bottom-left .arrow:after,.infotip.bottom-right .arrow:after{border-top-width:0;border-bottom-color:#fff;content:" ";margin-left:-10px;top:1px}.infotip.bottom-left .arrow{left:20%}.infotip.bottom-right .arrow{left:80%}.infotip.top .arrow{border-bottom-width:0;border-top-color:#999;border-top-color:#bbb;bottom:-11px;left:50%;margin-left:-11px}.infotip.top .arrow:after{border-bottom-width:0;border-top-color:#f5f5f5;bottom:1px;content:" ";margin-left:-10px}.infotip.right .arrow{border-left-width:0;border-right-color:#999;border-right-color:#bbb;left:-11px;margin-top:-11px;top:50%}.infotip.right .arrow:after{bottom:-10px;border-left-width:0;border-right-color:#fff;content:" ";left:1px}.infotip.left .arrow{border-left-color:#999;border-left-color:#bbb;border-right-width:0;margin-top:-11px;right:-11px;top:50%}.infotip.left .arrow:after{border-left-color:#fff;border-right-width:0;bottom:-10px;content:" ";right:1px}.label{border-radius:0;font-size:100%;font-weight:600}h1 .label,h2 .label,h3 .label,h4 .label,h5 .label,h6 .label{font-size:75%}.list-group{border-top:1px solid #ededed}.list-group .list-group-item:first-child{border-top:0}.list-group-item{border-top:0;border-left:0;border-right:0;margin-bottom:0}.list-group-item-heading{font-weight:600}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{border-top:solid 1px #39a5dc;margin-top:-1px;z-index:auto}.list-group-item.active:first-child{border-top:1px solid #39a5dc!important;margin-top:-1px}.login-pf{height:100%}.login-pf #brand{position:relative;top:-70px}.login-pf #brand img{display:block;height:18px;margin:0 auto;max-width:100%}@media (min-width:768px){.login-pf #brand img{margin:0;text-align:left}}.login-pf #badge{display:block;margin:20px auto 70px;position:relative;text-align:center}@media (min-width:768px){.login-pf #badge{float:right;margin-right:64px;margin-top:50px}}.login-pf body{background:#1a1a1a url(../img/bg-login.jpg) repeat-x 50% 0;background-size:auto}@media (min-width:768px){.login-pf body{background-size:100% auto}}.login-pf .container{background-color:transparent;clear:right;color:#fff;padding-bottom:40px;padding-top:20px;width:auto}@media (min-width:768px){.login-pf .container{bottom:13%;padding-left:80px;position:absolute;width:100%}}.login-pf .container [class^=alert]{background:0 0;color:#fff}.login-pf .container .details p:first-child{border-top:1px solid rgba(255,255,255,.3);padding-top:25px;margin-top:25px}@media (min-width:768px){.login-pf .container .details{border-left:1px solid rgba(255,255,255,.3);padding-left:40px}.login-pf .container .details p:first-child{border-top:0;padding-top:0;margin-top:0}}.login-pf .container .details p{margin-bottom:2px}.login-pf .container .form-horizontal .control-label{font-size:13px;font-weight:400;text-align:left}.login-pf .container .form-horizontal .form-group:last-child,.login-pf .container .form-horizontal .form-group:last-child .help-block:last-child{margin-bottom:0}.login-pf .container .help-block{color:#fff}@media (min-width:768px){.login-pf .container .login{padding-right:40px}}.login-pf .container .submit{text-align:right}.modal-header{background-color:#f5f5f5;border-bottom:none;padding:10px 18px}.modal-header .close{margin-top:2px}.modal-title{font-size:13px;font-weight:700}.modal-footer{border-top:none;margin-top:15px;padding:14px 15px 15px}.modal-footer>.btn{padding-left:10px;padding-right:10px}.modal-footer>.btn>.fa-angle-left{margin-right:5px}.modal-footer>.btn>.fa-angle-right{margin-left:5px}.navbar-pf{background:#393F45;border:0;border-radius:0;border-top:3px solid #c00;margin-bottom:0;min-height:0}.navbar-pf .navbar-brand{color:#fff;height:auto;padding:12px 0;margin:0 0 0 20px}.navbar-pf .navbar-brand img{display:block}.navbar-pf .navbar-collapse{border-top:0;-webkit-box-shadow:none;box-shadow:none;padding:0}.navbar-pf .navbar-header{border-bottom:1px solid #53565b;float:none}.navbar-pf .navbar-nav{margin:0}.navbar-pf .navbar-nav>.active>a,.navbar-pf .navbar-nav>.active>a:focus,.navbar-pf .navbar-nav>.active>a:hover{background-color:#454C53;color:#fff}.navbar-pf .navbar-nav>li>a{color:#dbdada;line-height:1;padding:10px 20px;text-shadow:none}.navbar-pf .navbar-nav>li>a:focus,.navbar-pf .navbar-nav>li>a:hover{color:#fff}.navbar-pf .navbar-nav>.open>a,.navbar-pf .navbar-nav>.open>a:focus,.navbar-pf .navbar-nav>.open>a:hover{background-color:#454C53;color:#fff}@media (max-width:767px){.navbar-pf .navbar-nav .active .dropdown-menu,.navbar-pf .navbar-nav .active .navbar-persistent,.navbar-pf .navbar-nav .open .dropdown-menu{background-color:#3c434a!important;margin-left:0;padding-bottom:0;padding-top:0}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a,.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a:focus,.navbar-pf .navbar-nav .active .dropdown-menu>.active>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a:focus,.navbar-pf .navbar-nav .active .navbar-persistent>.active>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a:focus,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-pf .navbar-nav .open .dropdown-menu>.active>a:hover{background-color:#424950!important;color:#fff}.navbar-pf .navbar-nav .active .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent>li>a,.navbar-pf .navbar-nav .open .dropdown-menu>li>a{background-color:transparent;border:0;color:#dbdada;outline:0;padding-left:30px}.navbar-pf .navbar-nav .active .dropdown-menu>li>a:hover,.navbar-pf .navbar-nav .active .navbar-persistent>li>a:hover,.navbar-pf .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff}.navbar-pf .navbar-nav .active .dropdown-menu .divider,.navbar-pf .navbar-nav .active .navbar-persistent .divider,.navbar-pf .navbar-nav .open .dropdown-menu .divider{background-color:#53565b;margin:0 1px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-header,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-header,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-header{padding-bottom:0;padding-left:30px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.open .dropdown-toggle,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open .dropdown-toggle,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.open .dropdown-toggle{color:#fff}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu.pull-left,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.pull-left,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu.pull-left{float:none!important}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu>a:after,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu>a:after,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu>a:after{display:none}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-header,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-header,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-header{padding-left:45px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu{border:0;bottom:auto;-webkit-box-shadow:none;box-shadow:none;display:block;float:none;margin:0;min-width:0;padding:0;position:relative;left:auto;right:auto;top:auto}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu>li>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu>li>a{padding:5px 15px 5px 45px;line-height:20px}.navbar-pf .navbar-nav .active .dropdown-menu .dropdown-submenu .dropdown-menu .dropdown-menu>li>a,.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu .dropdown-menu>li>a,.navbar-pf .navbar-nav .open .dropdown-menu .dropdown-submenu .dropdown-menu .dropdown-menu>li>a{padding-left:60px}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu.open .dropdown-menu{display:block}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu>a:after{display:inline-block!important;position:relative;right:auto;top:1px}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-menu{display:none}.navbar-pf .navbar-nav .active .navbar-persistent .dropdown-submenu .dropdown-submenu>a:after{display:none!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu{background-color:#fff!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a:active{background-color:#def3ff!important;border-color:#bee1f4!important;color:#363636!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.active>a:active small{color:#9c9c9c!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.disabled>a{color:#9c9c9c!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a:active{background-color:#0088ce!important;border-color:#0088ce!important;color:#fff!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu>.selected>a:active small{color:rgba(255,255,255,.5)!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li>a.opt{border-bottom:1px solid transparent;border-top:1px solid transparent;color:#363636;padding-left:10px;padding-right:10px}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:active small{color:rgba(255,255,255,.5)!important}.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:focus small,.navbar-pf .navbar-nav .context-bootstrap-select .open>.dropdown-menu li a:hover small{color:#9c9c9c}.navbar-pf .navbar-nav .context-bootstrap-select>.open>.dropdown-menu{padding-bottom:5px;padding-top:5px}}.navbar-pf .navbar-persistent{display:none}.navbar-pf .active>.navbar-persistent{display:block}.navbar-pf .navbar-primary{float:none}.navbar-pf .navbar-primary .context{border-bottom:1px solid #53565b}.navbar-pf .navbar-primary .context.context-bootstrap-select .bootstrap-select.btn-group,.navbar-pf .navbar-primary .context.context-bootstrap-select .bootstrap-select.btn-group[class*=span]{margin:8px 20px 9px;width:auto}.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a{position:relative}.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a:after{content:"\f107";display:inline-block;font-family:FontAwesome;font-weight:400}@media (max-width:767px){.navbar-pf .navbar-primary>li>.navbar-persistent>.dropdown-submenu>a:after{height:10px;margin-left:4px;vertical-align:baseline}}.navbar-pf .navbar-toggle{border:0;margin:0;padding:10px 20px}.navbar-pf .navbar-toggle:focus,.navbar-pf .navbar-toggle:hover{background-color:transparent;outline:0}.navbar-pf .navbar-toggle:focus .icon-bar,.navbar-pf .navbar-toggle:hover .icon-bar{-webkit-box-shadow:0 0 3px #fff;box-shadow:0 0 3px #fff}.navbar-pf .navbar-toggle .icon-bar{background-color:#fff}.navbar-pf .navbar-utility{border-bottom:1px solid #53565b}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle{padding-left:36px;position:relative}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle .pficon-user{left:20px;position:absolute;top:10px}@media (max-width:767px){.navbar-pf .navbar-utility>li+li{border-top:1px solid #53565b}}@media (min-width:768px){.navbar-pf .navbar-brand{padding:7px 0 8px}.navbar-pf .navbar-nav>li>a{padding-bottom:14px;padding-top:14px}.navbar-pf .navbar-persistent{font-size:14px}.navbar-pf .navbar-primary{font-size:14px;background-image:-webkit-linear-gradient(top,#474c50 0,#383f43 100%);background-image:-o-linear-gradient(top,#474c50 0,#383f43 100%);background-image:linear-gradient(to bottom,#474c50 0,#383f43 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff474c50', endColorstr='#ff383f43', GradientType=0)}.navbar-pf .navbar-primary.persistent-secondary .context .dropdown-menu{top:auto}.navbar-pf .navbar-primary.persistent-secondary .dropup .dropdown-menu{bottom:-5px;top:auto}.navbar-pf .navbar-primary.persistent-secondary>li{position:static}.navbar-pf .navbar-primary.persistent-secondary>li.active{margin-bottom:32px}.navbar-pf .navbar-primary.persistent-secondary>li.active>.navbar-persistent{display:block;left:0;position:absolute}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent{background:#f6f6f6;border-bottom:1px solid #cecdcd;padding:0;width:100%}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent a{text-decoration:none!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:before,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:hover:before{background:#0088ce;bottom:-1px;content:'';display:block;height:2px;left:20px;position:absolute;right:20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active:hover>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active>a:hover{color:#0088ce!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.active .active>a{color:#fff}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu:hover>.dropdown-menu{display:none}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-menu{display:block;left:20px;margin-top:1px;top:100%}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-toggle{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu.open>.dropdown-toggle:after{border-top-color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu>.dropdown-toggle{padding-right:35px!important}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.dropdown-submenu>.dropdown-toggle:after{position:absolute;right:20px;top:10px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open:before,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover:before{background:#bbb;bottom:-1px;content:'';display:block;height:2px;left:20px;position:absolute;right:20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open>a,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover>a{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li.open>a:after,.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li:hover>a:after{border-top-color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a{background-color:transparent;display:block;line-height:1;padding:9px 20px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a.dropdown-toggle{padding-right:35px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a.dropdown-toggle:after{font-size:15px;position:absolute;right:20px;top:9px}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li>a:hover{color:#252525}.navbar-pf .navbar-primary.persistent-secondary>li>.navbar-persistent>li a{color:#4d5258}.navbar-pf .navbar-primary>li>a{border-bottom:1px solid transparent;border-top:1px solid transparent;position:relative;margin:-1px 0 0}.navbar-pf .navbar-primary>li>a:hover{background-color:#4b5053;border-top-color:#949699;color:#dbdada;background-image:-webkit-linear-gradient(top,#5c6165 0,#4b5053 100%);background-image:-o-linear-gradient(top,#5c6165 0,#4b5053 100%);background-image:linear-gradient(to bottom,#5c6165 0,#4b5053 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5c6165', endColorstr='#ff4b5053', GradientType=0)}.navbar-pf .navbar-primary>.active>a,.navbar-pf .navbar-primary>.active>a:focus,.navbar-pf .navbar-primary>.active>a:hover,.navbar-pf .navbar-primary>.open>a,.navbar-pf .navbar-primary>.open>a:focus,.navbar-pf .navbar-primary>.open>a:hover{background-color:#64686c;border-bottom-color:#64686c;border-top-color:#949699;-webkit-box-shadow:none;box-shadow:none;color:#fff;background-image:-webkit-linear-gradient(top,#72757a 0,#64686c 100%);background-image:-o-linear-gradient(top,#72757a 0,#64686c 100%);background-image:linear-gradient(to bottom,#72757a 0,#64686c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff72757a', endColorstr='#ff64686c', GradientType=0)}.navbar-pf .navbar-primary li.context.context-bootstrap-select .filter-option{max-width:160px;text-overflow:ellipsis}.navbar-pf .navbar-primary li.context.dropdown{border-bottom:0}.navbar-pf .navbar-primary li.context.context-bootstrap-select,.navbar-pf .navbar-primary li.context>a{background-color:#505458;border-bottom-color:#65696d;border-right:1px solid #65696d;border-top-color:#64696d;font-weight:600;background-image:-webkit-linear-gradient(top,#585d61 0,#505458 100%);background-image:-o-linear-gradient(top,#585d61 0,#505458 100%);background-image:linear-gradient(to bottom,#585d61 0,#505458 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff585d61', endColorstr='#ff505458', GradientType=0)}.navbar-pf .navbar-primary li.context.context-bootstrap-select:hover,.navbar-pf .navbar-primary li.context>a:hover{background-color:#5a5e62;border-bottom-color:#6e7276;border-right-color:#6e7276;border-top-color:#6c7276;background-image:-webkit-linear-gradient(top,#62676b 0,#5a5e62 100%);background-image:-o-linear-gradient(top,#62676b 0,#5a5e62 100%);background-image:linear-gradient(to bottom,#62676b 0,#5a5e62 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62676b', endColorstr='#ff5a5e62', GradientType=0)}.navbar-pf .navbar-primary li.context.open>a{background-color:#65696d;border-bottom-color:#6e7276;border-right-color:#777a7e;border-top-color:#767a7e;background-image:-webkit-linear-gradient(top,#6b7175 0,#65696d 100%);background-image:-o-linear-gradient(top,#6b7175 0,#65696d 100%);background-image:linear-gradient(to bottom,#6b7175 0,#65696d 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff6b7175', endColorstr='#ff65696d', GradientType=0)}.navbar-pf .navbar-utility{border-bottom:0;font-size:11px;position:absolute;right:0;top:0}.navbar-pf .navbar-utility>.active>a,.navbar-pf .navbar-utility>.active>a:focus,.navbar-pf .navbar-utility>.active>a:hover,.navbar-pf .navbar-utility>.open>a,.navbar-pf .navbar-utility>.open>a:focus,.navbar-pf .navbar-utility>.open>a:hover{background:#5b6165;color:#fff}.navbar-pf .navbar-utility>li>a{border-left:1px solid #53565b;color:#fff!important;padding:7px 10px}.navbar-pf .navbar-utility>li>a:hover{background:#4a5053;border-left-color:#636466}.navbar-pf .navbar-utility>li.open>a{border-left-color:#6c6e70;color:#fff!important}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle{padding-left:26px}.navbar-pf .navbar-utility li.dropdown>.dropdown-toggle .pficon-user{left:10px;top:7px}.navbar-pf .navbar-utility .open .dropdown-menu{left:auto;right:0}.navbar-pf .navbar-utility .open .dropdown-menu .dropdown-menu{left:auto;right:100%}.navbar-pf .navbar-utility .open .dropdown-menu{border-top-width:0}.navbar-pf .open .dropdown-submenu>.dropdown-menu,.navbar-pf .open.bootstrap-select .dropdown-menu{border-top-width:1px!important}}@media (max-width:360px){.navbar-pf .navbar-brand{margin-left:10px;width:75%}.navbar-pf .navbar-brand img{height:auto;max-width:100%}.navbar-pf .navbar-toggle{padding-left:0}}.drawer-pf{background-color:#fafafa;border:1px solid #d1d1d1;-webkit-box-shadow:0 6px 12px rgba(3,3,3,.175);box-shadow:0 6px 12px rgba(3,3,3,.175);overflow-y:auto;position:absolute;right:0;width:320px;z-index:2}.drawer-pf .panel{border-bottom:none;border-left:none;border-right:none}.drawer-pf .panel-group .panel-heading+.panel-collapse .panel-body{border-top:none;border-bottom:1px solid #d1d1d1;padding:0}.drawer-pf .panel-counter{display:block;font-style:italic;line-height:1.2;padding-left:18px;padding-top:5px}.drawer-pf .panel-heading{border-bottom:1px solid #d1d1d1}.drawer-pf .panel-group{bottom:0;margin-bottom:0;position:absolute;top:25px;width:100%}.drawer-pf .panel-title a{cursor:pointer;display:block}.drawer-pf.drawer-pf-expanded{left:270px;width:inherit}.drawer-pf.drawer-pf-expanded .drawer-pf-toggle-expand:before{content:"\f101"}.drawer-pf-toggle-expand{color:inherit;cursor:pointer;left:0;padding:2px 5px;position:absolute}.drawer-pf-toggle-expand:before{content:"\f100";font-family:FontAwesome}.drawer-pf-toggle-expand:focus,.drawer-pf-toggle-expand:hover{color:inherit;text-decoration:none}.drawer-pf-action .btn-link{color:#0088ce;padding:10px 0}.drawer-pf-action .btn-link:hover{color:#00659c}.drawer-pf-loading{color:#4d5258;font-size:14px;padding:20px 15px}.drawer-pf-notification{border-bottom:1px solid #d1d1d1;padding:15px}.drawer-pf-notification .date{border-right:1px solid #aaa;display:inline-block;line-height:1;margin-right:5px;padding-right:9px}.drawer-pf-notification .pficon{font-size:14px;margin-top:3px}.drawer-pf-notification:last-of-type{border-bottom:none}.drawer-pf-notification:hover{background-color:#def3ff}.drawer-pf-notification.unread .drawer-pf-notification-message{font-weight:700}.drawer-pf-notification.expanded-notification .date{border-right:none;padding-right:0}.drawer-pf-notification-info,.drawer-pf-notification-message{display:block;padding-left:27px;padding-right:19px}.expanded-notification .drawer-pf-notification-info,.expanded-notification .drawer-pf-notification-message{display:inline-block}.drawer-pf-notifications-non-clickable .drawer-pf-notification:hover{background-color:#fff}.drawer-pf-title{background-color:#fafafa;border-bottom:1px solid #d1d1d1;position:absolute;width:318px}.drawer-pf-title h3{font-size:12px;margin:0;padding:6px 15px}.navbar-pf-vertical .drawer-pf{height:calc(100vh - 80px);top:58px}.navbar-pf-vertical .nav .drawer-pf-trigger .drawer-pf-trigger-icon{border-left:1px solid #53565b;border-right:1px solid #53565b;padding-left:15px;padding-right:15px}.navbar-pf-vertical .nav .drawer-pf-trigger.open .drawer-pf-trigger-icon{background-color:#4a5053}.navbar-pf .drawer-pf{height:calc(100vh - 46px);top:26px}.navbar-pf .drawer-pf-trigger-icon{cursor:pointer}.pager li>a,.pager li>span{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;font-weight:600;line-height:22px;padding:2px 14px}.open .dropdown-toggle.pager li>a,.open .dropdown-toggle.pager li>span,.pager li>a.active,.pager li>a:active,.pager li>a:focus,.pager li>a:hover,.pager li>span.active,.pager li>span:active,.pager li>span:focus,.pager li>span:hover{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.open .dropdown-toggle.pager li>a,.open .dropdown-toggle.pager li>span,.pager li>a.active,.pager li>a:active,.pager li>span.active,.pager li>span:active{background-image:none}.open .dropdown-toggle.pager li>a.focus,.open .dropdown-toggle.pager li>a:focus,.open .dropdown-toggle.pager li>a:hover,.open .dropdown-toggle.pager li>span.focus,.open .dropdown-toggle.pager li>span:focus,.open .dropdown-toggle.pager li>span:hover,.pager li>a.active.focus,.pager li>a.active:focus,.pager li>a.active:hover,.pager li>a:active.focus,.pager li>a:active:focus,.pager li>a:active:hover,.pager li>span.active.focus,.pager li>span.active:focus,.pager li>span.active:hover,.pager li>span:active.focus,.pager li>span:active:focus,.pager li>span:active:hover{background-color:#e5e5e5;border-color:#a9a9a9}.pager li>a.disabled,.pager li>a.disabled.active,.pager li>a.disabled:active,.pager li>a.disabled:focus,.pager li>a.disabled:hover,.pager li>a[disabled],.pager li>a[disabled].active,.pager li>a[disabled]:active,.pager li>a[disabled]:focus,.pager li>a[disabled]:hover,.pager li>span.disabled,.pager li>span.disabled.active,.pager li>span.disabled:active,.pager li>span.disabled:focus,.pager li>span.disabled:hover,.pager li>span[disabled],.pager li>span[disabled].active,.pager li>span[disabled]:active,.pager li>span[disabled]:focus,.pager li>span[disabled]:hover,fieldset[disabled] .pager li>a,fieldset[disabled] .pager li>a.active,fieldset[disabled] .pager li>a:active,fieldset[disabled] .pager li>a:focus,fieldset[disabled] .pager li>a:hover,fieldset[disabled] .pager li>span,fieldset[disabled] .pager li>span.active,fieldset[disabled] .pager li>span:active,fieldset[disabled] .pager li>span:focus,fieldset[disabled] .pager li>span:hover{background-color:#f1f1f1;border-color:#bbb}.pager li>a>.i,.pager li>span>.i{font-size:18px;vertical-align:top;margin:2px 0}.pager li>a:hover>a:focus{color:#4d5258}.pager li a:active{background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(3,3,3,.125);box-shadow:inset 0 3px 5px rgba(3,3,3,.125);outline:0}.pager .disabled>a,.pager .disabled>a:active,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{background:#f5f5f5;-webkit-box-shadow:none;box-shadow:none;color:#8b8d8f;cursor:default}.pager .next>a>.i,.pager .next>span>.i{margin-left:5px}.pager .previous>a>.i,.pager .previous>span>.i{margin-right:5px}.pager-sm li>a,.pager-sm li>span{font-weight:400;line-height:16px;padding:1px 10px}.pager-sm li>a>.i,.pager-sm li>span>.i{font-size:12px}.pagination>li>a,.pagination>li>span{background-color:#f1f1f1;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);border-color:#bbb;color:#4d5258;cursor:default;font-weight:600;padding:2px 10px}.open .dropdown-toggle.pagination>li>a,.open .dropdown-toggle.pagination>li>span,.pagination>li>a.active,.pagination>li>a:active,.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span.active,.pagination>li>span:active,.pagination>li>span:focus,.pagination>li>span:hover{background-color:#f1f1f1;background-image:none;border-color:#bbb;color:#4d5258}.open .dropdown-toggle.pagination>li>a,.open .dropdown-toggle.pagination>li>span,.pagination>li>a.active,.pagination>li>a:active,.pagination>li>span.active,.pagination>li>span:active{background-image:none}.open .dropdown-toggle.pagination>li>a.focus,.open .dropdown-toggle.pagination>li>a:focus,.open .dropdown-toggle.pagination>li>a:hover,.open .dropdown-toggle.pagination>li>span.focus,.open .dropdown-toggle.pagination>li>span:focus,.open .dropdown-toggle.pagination>li>span:hover,.pagination>li>a.active.focus,.pagination>li>a.active:focus,.pagination>li>a.active:hover,.pagination>li>a:active.focus,.pagination>li>a:active:focus,.pagination>li>a:active:hover,.pagination>li>span.active.focus,.pagination>li>span.active:focus,.pagination>li>span.active:hover,.pagination>li>span:active.focus,.pagination>li>span:active:focus,.pagination>li>span:active:hover{background-color:#e5e5e5;border-color:#a9a9a9}.pagination>li>a.disabled,.pagination>li>a.disabled.active,.pagination>li>a.disabled:active,.pagination>li>a.disabled:focus,.pagination>li>a.disabled:hover,.pagination>li>a[disabled],.pagination>li>a[disabled].active,.pagination>li>a[disabled]:active,.pagination>li>a[disabled]:focus,.pagination>li>a[disabled]:hover,.pagination>li>span.disabled,.pagination>li>span.disabled.active,.pagination>li>span.disabled:active,.pagination>li>span.disabled:focus,.pagination>li>span.disabled:hover,.pagination>li>span[disabled],.pagination>li>span[disabled].active,.pagination>li>span[disabled]:active,.pagination>li>span[disabled]:focus,.pagination>li>span[disabled]:hover,fieldset[disabled] .pagination>li>a,fieldset[disabled] .pagination>li>a.active,fieldset[disabled] .pagination>li>a:active,fieldset[disabled] .pagination>li>a:focus,fieldset[disabled] .pagination>li>a:hover,fieldset[disabled] .pagination>li>span,fieldset[disabled] .pagination>li>span.active,fieldset[disabled] .pagination>li>span:active,fieldset[disabled] .pagination>li>span:focus,fieldset[disabled] .pagination>li>span:hover{background-color:#f1f1f1;border-color:#bbb}.pagination>li>a>.i,.pagination>li>span>.i{font-size:15px;vertical-align:top;margin:2px 0}.pagination>li>a:active,.pagination>li>span:active{-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2)}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{background-color:#f1f1f1;border-color:#bbb;-webkit-box-shadow:inset 0 2px 8px rgba(3,3,3,.2);box-shadow:inset 0 2px 8px rgba(3,3,3,.2);color:#4d5258;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{-webkit-box-shadow:none;box-shadow:none;cursor:default;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.pagination-sm>li>a,.pagination-sm>li>span{padding:2px 6px;font-size:11px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:1px;border-top-left-radius:1px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:1px;border-top-right-radius:1px}.pagination-sm>li>a,.pagination-sm>li>span{font-weight:400}.pagination-sm>li>a>.i,.pagination-sm>li>span>.i{font-size:12px;margin-top:2px}.panel-title{font-weight:700}.panel-group .panel{color:#4d5258}.panel-group .panel+.panel{margin-top:-1px}.panel-group .panel-default{border-color:#bbb;border-top-color:#bbb}.panel-group .panel-heading{background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #d1d1d1}.panel-group .panel-title{font-weight:500;line-height:1}.panel-group .panel-title>a{color:#4d5258;font-weight:600}.panel-group .panel-title>a:before{content:"\f107";display:inline-block;font-family:FontAwesome;font-size:13px;margin-right:5px;text-align:center;vertical-align:0;width:8px}.panel-group .panel-title>a:focus{outline:0;text-decoration:none}.panel-group .panel-title>a:hover{text-decoration:none}.panel-group .panel-title>a.collapsed:before{content:"\f105"}.popover{-webkit-box-shadow:0 2px 2px rgba(3,3,3,.08);box-shadow:0 2px 2px rgba(3,3,3,.08);padding:0}.popover-content{color:#4d5258;line-height:18px;padding:10px 14px}.popover-title{border-bottom:none;border-radius:0;color:#4d5258;font-size:13px;font-weight:700;min-height:34px}.popover-title .close{height:22px;position:absolute;right:8px;top:6px}.popover-title.closable{padding-right:30px}@-webkit-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}.progress{-webkit-box-shadow:inset 0 0 1px rgba(3,3,3,.25);box-shadow:inset 0 0 1px rgba(3,3,3,.25)}.progress.progress-label-left,.progress.progress-label-top-right{overflow:visible;position:relative}.progress.progress-label-left{margin-left:40px}.progress.progress-sm{height:14px;margin-bottom:14px}.progress.progress-xs{height:6px;margin-bottom:6px}td>.progress:first-child:last-child{margin-bottom:0;margin-top:3px}.progress-bar{box-shadow:none}.progress-label-left .progress-bar span,.progress-label-right .progress-bar span,.progress-label-top-right .progress-bar span{color:#363636;position:absolute;text-align:right}.progress-label-left .progress-bar span{font-size:14px;left:-40px;top:0;width:35px}.progress-label-right .progress-bar span,.progress-label-top-right .progress-bar span{font-size:11px;overflow:hidden;right:0;text-overflow:ellipsis;white-space:nowrap}.progress-label-right .progress-bar span strong,.progress-label-top-right .progress-bar span strong{font-weight:600}.progress-label-right .progress-bar span{max-width:85px;top:0}.progress-label-top-right .progress-bar span{max-width:47%;top:-30px}.progress-label-left.progress-sm .progress-bar span,.progress-label-top-right.progress-sm .progress-bar span{font-size:12px}.progress-sm .progress-bar{line-height:14px}.progress-xs .progress-bar{line-height:6px}.progress-bar-remaining{background:0 0}.progress-container{position:relative}.progress-container.progress-description-left{padding-left:90px}.progress-container.progress-label-right{padding-right:90px}.progress-description{margin-bottom:10px;max-width:52%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.progress-description .count{font-size:20px;font-weight:300;line-height:1;margin-right:5px}.progress-description .fa,.progress-description .pficon{font-size:14px;margin-right:3px}.progress-description-left .progress-description{left:0;margin-bottom:0;max-width:85px;position:absolute;top:0}.progress-description .tooltip{white-space:normal}.search-pf.has-button{border-collapse:separate;display:table}.search-pf.has-button .form-group{display:table-cell;width:100%}.search-pf.has-button .form-group .btn{-webkit-box-shadow:none;box-shadow:none;float:left;margin-left:-1px}.search-pf.has-button .form-group .btn.btn-lg{font-size:14.5px}.search-pf.has-button .form-group .btn.btn-sm{font-size:10.7px}.search-pf.has-button .form-group .form-control{float:left}.search-pf .has-clear .clear{background:0 0;background:rgba(255,255,255,0);border:0;height:25px;line-height:1;padding:0;position:absolute;right:1px;top:1px;width:28px}.search-pf .has-clear .clear:focus{outline:0}.search-pf .has-clear .form-control{padding-right:30px}.search-pf .has-clear .form-control::-ms-clear{display:none}.search-pf .has-clear .input-lg+.clear{height:31px;width:28px}.search-pf .has-clear .input-sm+.clear{height:20px;width:28px}.search-pf .has-clear .input-sm+.clear span{font-size:10px}.search-pf .has-clear .search-pf-input-group{position:relative}.sidebar-header{border-bottom:1px solid #ececec;padding-bottom:11px;margin:50px 0 20px}.sidebar-header .actions{margin-top:-2px}.sidebar-pf .sidebar-header+.list-group{border-top:0;margin-top:-10px}.sidebar-pf .sidebar-header+.list-group .list-group-item{background:0 0;border-color:#ececec;padding-left:0}.sidebar-pf .sidebar-header+.list-group .list-group-item-heading{font-size:12px}.sidebar-pf .nav-category h2{color:#9c9c9c;font-size:12px;font-weight:400;line-height:21px;margin:0;padding:8px 0}.sidebar-pf .nav-category+.nav-category{margin-top:10px}.sidebar-pf .nav-pills>li.active>a{background:#0088ce!important;border-color:#0088ce!important;color:#fff}@media (min-width:768px){.sidebar-pf .nav-pills>li.active>a:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:1px}}.sidebar-pf .nav-pills>li.active>a .fa{color:#fff}.sidebar-pf .nav-pills>li>a{border-bottom:1px solid transparent;border-radius:0;border-top:1px solid transparent;color:#363636;font-size:13px;line-height:21px;padding:1px 20px}.sidebar-pf .nav-pills>li>a:hover{background:#def3ff;border-color:#bee1f4}.sidebar-pf .nav-pills>li>a .fa{color:#6a7079;font-size:15px;margin-right:10px;text-align:center;vertical-align:middle;width:15px}.sidebar-pf .nav-stacked{margin-left:-20px;margin-right:-20px}.sidebar-pf .nav-stacked li+li{margin-top:0}.sidebar-pf .panel{background:0 0}.sidebar-pf .panel-body{padding:6px 20px}.sidebar-pf .panel-body .nav-pills>li>a{padding-left:37px}.sidebar-pf .panel-heading{padding:9px 20px}.sidebar-pf .panel-title{font-size:12px}.sidebar-pf .panel-title>a:before{display:inline-block;margin-left:1px;margin-right:4px;width:9px}.sidebar-pf .panel-title>a.collapsed:before{margin-left:3px;margin-right:2px}@media (min-width:767px){.sidebar-header-bleed-left{margin-left:-20px}.sidebar-header-bleed-left>h2{margin-left:20px}.sidebar-header-bleed-right{margin-right:-20px}.sidebar-header-bleed-right .actions{margin-right:20px}.sidebar-header-bleed-right>h2{margin-right:20px}.sidebar-header-bleed-right+.list-group{margin-right:-20px}.sidebar-pf .panel-group .panel-default,.sidebar-pf .treeview{border-left:0;border-right:0;margin-left:-20px;margin-right:-20px}.sidebar-pf .treeview{margin-top:5px}.sidebar-pf .treeview .list-group-item{padding-left:20px;padding-right:20px}.sidebar-pf .treeview .list-group-item.node-selected:after{content:"\f105";font-family:FontAwesome;display:block;position:absolute;right:10px;top:1px}}@media (min-width:768px){.sidebar-pf{background:#fafafa}.sidebar-pf.sidebar-pf-left{border-right:1px solid #d1d1d1}.sidebar-pf.sidebar-pf-right{border-left:1px solid #d1d1d1}.sidebar-pf>.nav-category,.sidebar-pf>.nav-stacked{margin-top:5px}}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(359deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}.spinner{-webkit-animation:rotation .6s infinite linear;animation:rotation .6s infinite linear;border-bottom:4px solid rgba(3,3,3,.25);border-left:4px solid rgba(3,3,3,.25);border-right:4px solid rgba(3,3,3,.25);border-radius:100%;border-top:4px solid rgba(3,3,3,.75);height:24px;margin:0 auto;position:relative;width:24px}.spinner.spinner-inline{display:inline-block;margin-right:3px}.spinner.spinner-lg{border-width:5px;height:30px;width:30px}.spinner.spinner-sm{border-width:3px;height:18px;width:18px}.spinner.spinner-xs{border-width:2px;height:12px;width:12px}.spinner.spinner-inverse{border-bottom-color:rgba(255,255,255,.25);border-left-color:rgba(255,255,255,.25);border-right-color:rgba(255,255,255,.25);border-top-color:rgba(255,255,255,.75)}.ie9 .spinner{background:url(../img/spinner.gif) no-repeat;border:0}.ie9 .spinner.spinner-inverse{background-image:url(../img/spinner-inverse.gif)}.ie9 .spinner.spinner-inverse-lg{background-image:url(../img/spinner-inverse-lg.gif)}.ie9 .spinner.spinner-inverse-sm{background-image:url(../img/spinner-inverse-sm.gif)}.ie9 .spinner.spinner-inverse-xs{background-image:url(../img/spinner-inverse-xs.gif)}.ie9 .spinner.spinner-lg{background-image:url(../img/spinner-lg.gif)}.ie9 .spinner.spinner-sm{background-image:url(../img/spinner-sm.gif)}.ie9 .spinner.spinner-xs{background-image:url(../img/spinner-xs.gif)}.prettyprint .atn,.prettyprint .com,.prettyprint .fun,.prettyprint .var{color:#3f9c35}.prettyprint .atv,.prettyprint .str{color:#a30000}.prettyprint .clo,.prettyprint .dec,.prettyprint .kwd,.prettyprint .opn,.prettyprint .pln,.prettyprint .pun{color:#363636}.prettyprint .lit,.prettyprint .tag,.prettyprint .typ{color:#00659c}.prettyprint ol.linenums{margin-bottom:0}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:2px 10px 3px}.table>tbody>tr>td>a:hover,.table>tbody>tr>th>a:hover,.table>tfoot>tr>td>a:hover,.table>tfoot>tr>th>a:hover,.table>thead>tr>td>a:hover,.table>thead>tr>th>a:hover{text-decoration:none}.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>th{font-family:'Open Sans';font-style:normal;font-weight:600}.table>thead{background-clip:padding-box;background-color:#f5f5f5;background-image:-webkit-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:-o-linear-gradient(top,#fafafa 0,#ededed 100%);background-image:linear-gradient(to bottom,#fafafa 0,#ededed 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0)}.table-bordered{border:1px solid #d1d1d1}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #d1d1d1}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:1px}.table-striped>tbody>tr:nth-of-type(even){background-color:#f5f5f5}.table-striped>tbody>tr:nth-of-type(odd){background-color:transparent}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#def3ff;border-bottom-color:#7dc3e8}.table-treegrid span.indent{margin-left:10px;margin-right:10px}.table-treegrid span.icon{display:inline-block;font-size:13px;margin-right:5px;min-width:10px;text-align:center}.table-treegrid span.collapse-icon,.table-treegrid span.expand-icon{cursor:pointer}.table-treegrid>tbody>tr.odd{background-color:#f5f5f5}.nav-tabs{font-size:14px}.nav-tabs>li>a{color:#4d5258;margin-right:-1px;padding-bottom:5px;padding-top:5px}.nav-tabs>li>a:active,.nav-tabs>li>a:focus,.nav-tabs>li>a:hover{background:0 0;border-color:#ededed;color:#252525}.nav-tabs>li>.dropdown-menu{border-top:0;border-color:#ededed}.nav-tabs>li>.dropdown-menu.pull-right{right:-1px}.nav-tabs+.nav-tabs-pf{font-size:12px}.nav-tabs+.nav-tabs-pf>li:first-child>a{padding-left:15px}.nav-tabs+.nav-tabs-pf>li:first-child>a:before{left:15px!important}.nav-tabs .open>a,.nav-tabs .open>a:focus,.nav-tabs .open>a:hover{background-color:transparent;border-color:#ededed}@media (min-width:768px){.nav-tabs-pf.nav-justified{border-bottom:1px solid #ededed}}.nav-tabs-pf.nav-justified>li:first-child>a{padding-left:15px}.nav-tabs-pf.nav-justified>li>a{border-bottom:0}.nav-tabs-pf.nav-justified>li>a:before{left:0!important;right:0!important}.nav-tabs-pf>li{margin-bottom:0}.nav-tabs-pf>li.active>a:before{background:#0088ce;bottom:-1px;content:'';display:block;height:2px;left:15px;position:absolute;right:15px}.nav-tabs-pf>li.active>a,.nav-tabs-pf>li.active>a:active,.nav-tabs-pf>li.active>a:focus,.nav-tabs-pf>li.active>a:hover{background-color:transparent;border:0!important;color:#0088ce}.nav-tabs-pf>li.active>a:active:before,.nav-tabs-pf>li.active>a:before,.nav-tabs-pf>li.active>a:focus:before,.nav-tabs-pf>li.active>a:hover:before{background:#0088ce}.nav-tabs-pf>li:first-child>a{padding-left:0}.nav-tabs-pf>li:first-child>a:before{left:0!important}.nav-tabs-pf>li>a{border:0;line-height:1;margin-right:0;padding-bottom:10px;padding-top:10px}.nav-tabs-pf>li>a:active:before,.nav-tabs-pf>li>a:focus:before,.nav-tabs-pf>li>a:hover:before{background:#bbb;bottom:-1px;content:'';display:block;height:2px;left:15px;position:absolute;right:15px}.nav-tabs-pf>li>.dropdown-menu{left:15px;margin-top:1px}.nav-tabs-pf>li>.dropdown-menu.pull-right{left:auto;right:15px}.nav-tabs-pf .open>a,.nav-tabs-pf .open>a:focus,.nav-tabs-pf .open>a:hover{background-color:transparent}.tooltip{font-size:12px;line-height:1.4}.tooltip-inner{padding:7px 12px;text-align:left}.h1,.h2,h1,h2{font-weight:300}.page-header .actions{margin-top:8px}.page-header .actions a>.pficon{margin-right:4px}@media (min-width:767px){.page-header-bleed-left{margin-left:-20px}.page-header-bleed-right{margin-right:-20px}.page-header-bleed-right .actions{margin-right:20px}}
+\ No newline at end of file
+-- 
+2.26.2
+
+
+From bcc1a38148401ba766d98647a5aba69a0905214e Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Sun, 7 Oct 2018 12:25:40 +0300
+Subject: [PATCH 2/3] install/ui/less/brand.less: Change branding to IPA and
+ Identity Management
+
+---
+ install/ui/less/brand.less | 103 ++++++++++++++++++-------------------
+ 1 file changed, 50 insertions(+), 53 deletions(-)
+
+diff --git a/install/ui/less/brand.less b/install/ui/less/brand.less
+index c9030bb..7488eaf 100644
+--- a/install/ui/less/brand.less
++++ b/install/ui/less/brand.less
+@@ -20,58 +20,55 @@
+ 
+ // this file should be overridden with brand/platform specific content
+ 
+-@login-details-border: #777777;
++@img-badge-ie8-height:                                              44px;
++@img-badge-ie8-width:                                               137px;
++// @img-bg-login:                                                      "bg-login.png";
++// @img-bg-login-2:                                                    "bg-login-2.png";
++@login-bg-color:                                                    #1a1a1a;
++@login-container-bg-color:                                          transparent;
++@login-container-bg-color-rgba:                                     transparent;
++@navbar-pf-bg-color:                                                #393F45;
++@navbar-pf-border-color:                                            #cc0000;
++@navbar-pf-active-color:                                            #fff;
++@navbar-pf-color:                                                   #dbdada;
++@navbar-pf-icon-bar-bg-color:                                       #fff;
++@navbar-pf-navbar-header-border-color:                              #53565b;
++@navbar-pf-navbar-nav-active-bg-color:                              #454C53;
++@navbar-pf-navbar-nav-active-active-bg-color:                       #3c434a;
++@navbar-pf-navbar-nav-active-active-open-bg-color:                  #424950;
++@navbar-pf-navbar-navbar-brand-min-width:                           300px;
++@navbar-pf-navbar-navbar-brand-padding:                             7px 0 8px;
++@navbar-pf-navbar-navbar-persistent-bg-color:                       #f6f6f6;
++@navbar-pf-navbar-navbar-persistent-border-color:                   #cecdcd;
++@navbar-pf-navbar-primary-active-bg-color-start:                    #72757a;
++@navbar-pf-navbar-primary-active-bg-color-stop:                     #64686c;
++@navbar-pf-navbar-primary-active-border-color:                      #949699;
++@navbar-pf-navbar-primary-bg-color-start:                           #474c50;
++@navbar-pf-navbar-primary-bg-color-stop:                            #383f43;
++@navbar-pf-navbar-primary-hover-bg-color-start:                     #5c6165;
++@navbar-pf-navbar-primary-hover-bg-color-stop:                      #4b5053;
++@navbar-pf-navbar-primary-hover-border-color:                       #949699;
++@navbar-pf-navbar-primary-context-active-bg-color-start:            #6b7175;
++@navbar-pf-navbar-primary-context-active-bg-color-stop:             #65696d;
++@navbar-pf-navbar-primary-context-active-border-color:              #6e7276;
++@navbar-pf-navbar-primary-context-active-border-right-color:        #777a7e;
++@navbar-pf-navbar-primary-context-active-border-top-color:          #767a7e;
++@navbar-pf-navbar-primary-context-bg-color-start:                   #585d61;
++@navbar-pf-navbar-primary-context-bg-color-stop:                    #505458;
++@navbar-pf-navbar-primary-context-border-color:                     #65696d;
++@navbar-pf-navbar-primary-context-border-top-color:                 #64696d;
++@navbar-pf-navbar-primary-context-hover-bg-color-start:             #62676b;
++@navbar-pf-navbar-primary-context-hover-bg-color-stop:              #5a5e62;
++@navbar-pf-navbar-primary-context-hover-border-color:               #6e7276;
++@navbar-pf-navbar-primary-context-hover-border-top-color:           #6c7276;
++@navbar-pf-navbar-utility-border-color:                             #53565b;
++@navbar-pf-navbar-utility-color:                                    #fff;
++@navbar-pf-navbar-utility-hover-bg-color:                           #4a5053;
++@navbar-pf-navbar-utility-hover-border-color:                       #636466;
++@navbar-pf-navbar-utility-open-bg-color:                            #5b6165;
++@navbar-pf-navbar-utility-open-border-color:                        #6c6e70;
+ 
+-.login-pf {
+-
+-    .login-pf-body {
+-        padding-top: 50px;
+-    }
+-
+-    #badge img {
+-        display: none;
+-    }
+-
+-    #brand {
+-        position: absolute;
+-        top: -135px;
+-        img {
+-            height: auto;
+-        }
+-    }
+-
+-    .container {
+-        padding-top: 40px;
+-
+-        .details p:first-child {
+-            border-top: 1px solid @login-details-border;
+-        }
+-    }
+-}
+-
+-// 768px == @screen-sm-min
+-@media (min-width: 768px) {
+-    .navbar-pf .navbar-brand {
+-        padding: 2px 0 3px;
+-    }
+-
+-    .login-pf .container .details {
+-        p:first-child {
+-            border-top: 0;
+-        }
+-        border-left: 1px solid @login-details-border;
+-    }
+-}
+-
+-@media (max-height: 520px) {
+-    .reset-login-pf-height;
+-    .login-pf {
+-        .login-pf-body {
+-            padding-top: 0;
+-        }
+-        #badge {
+-            margin-top: 0;
+-            height: 70px;
+-        }
+-    }
++.info-page .navbar-header {
++    line-height: inherit;
++    margin-left: 20px;
+ }
+\ No newline at end of file
+-- 
+2.26.2
+
+
+From b9506f826164e757c9b4a694c9ca1bfda921f931 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Sun, 7 Oct 2018 12:25:40 +0300
+Subject: [PATCH 3/3] install/ui/less/patternfly.less: Change branding to IPA
+ and Identity Management
+
+---
+ install/ui/less/patternfly.less | 48 +++++++++++++++++++++++++++++++++
+ 1 file changed, 48 insertions(+)
+
+diff --git a/install/ui/less/patternfly.less b/install/ui/less/patternfly.less
+index a2e30c8..97a8d5c 100644
+--- a/install/ui/less/patternfly.less
++++ b/install/ui/less/patternfly.less
+@@ -129,3 +129,51 @@
+ 
+ // our overrides
+ @fa-font-path:        "../fonts/fontawesome";
++
++@img-badge-ie8-height:                                              44px;
++@img-badge-ie8-width:                                               137px;
++// @img-bg-login:                                                      "bg-login.png";
++// @img-bg-login-2:                                                    "bg-login-2.png";
++@login-bg-color:                                                    #1a1a1a;
++@login-container-bg-color:                                          transparent;
++@login-container-bg-color-rgba:                                     transparent;
++@navbar-pf-bg-color:                                                #393F45;
++@navbar-pf-border-color:                                            #cc0000;
++@navbar-pf-active-color:                                            #fff;
++@navbar-pf-color:                                                   #dbdada;
++@navbar-pf-icon-bar-bg-color:                                       #fff;
++@navbar-pf-navbar-header-border-color:                              #53565b;
++@navbar-pf-navbar-nav-active-bg-color:                              #454C53;
++@navbar-pf-navbar-nav-active-active-bg-color:                       #3c434a;
++@navbar-pf-navbar-nav-active-active-open-bg-color:                  #424950;
++@navbar-pf-navbar-navbar-brand-min-width:                           300px;
++@navbar-pf-navbar-navbar-brand-padding:                             7px 0 8px;
++@navbar-pf-navbar-navbar-persistent-bg-color:                       #f6f6f6;
++@navbar-pf-navbar-navbar-persistent-border-color:                   #cecdcd;
++@navbar-pf-navbar-primary-active-bg-color-start:                    #72757a;
++@navbar-pf-navbar-primary-active-bg-color-stop:                     #64686c;
++@navbar-pf-navbar-primary-active-border-color:                      #949699;
++@navbar-pf-navbar-primary-bg-color-start:                           #474c50;
++@navbar-pf-navbar-primary-bg-color-stop:                            #383f43;
++@navbar-pf-navbar-primary-hover-bg-color-start:                     #5c6165;
++@navbar-pf-navbar-primary-hover-bg-color-stop:                      #4b5053;
++@navbar-pf-navbar-primary-hover-border-color:                       #949699;
++@navbar-pf-navbar-primary-context-active-bg-color-start:            #6b7175;
++@navbar-pf-navbar-primary-context-active-bg-color-stop:             #65696d;
++@navbar-pf-navbar-primary-context-active-border-color:              #6e7276;
++@navbar-pf-navbar-primary-context-active-border-right-color:        #777a7e;
++@navbar-pf-navbar-primary-context-active-border-top-color:          #767a7e;
++@navbar-pf-navbar-primary-context-bg-color-start:                   #585d61;
++@navbar-pf-navbar-primary-context-bg-color-stop:                    #505458;
++@navbar-pf-navbar-primary-context-border-color:                     #65696d;
++@navbar-pf-navbar-primary-context-border-top-color:                 #64696d;
++@navbar-pf-navbar-primary-context-hover-bg-color-start:             #62676b;
++@navbar-pf-navbar-primary-context-hover-bg-color-stop:              #5a5e62;
++@navbar-pf-navbar-primary-context-hover-border-color:               #6e7276;
++@navbar-pf-navbar-primary-context-hover-border-top-color:           #6c7276;
++@navbar-pf-navbar-utility-border-color:                             #53565b;
++@navbar-pf-navbar-utility-color:                                    #fff;
++@navbar-pf-navbar-utility-hover-bg-color:                           #4a5053;
++@navbar-pf-navbar-utility-hover-border-color:                       #636466;
++@navbar-pf-navbar-utility-open-bg-color:                            #5b6165;
++@navbar-pf-navbar-utility-open-border-color:                        #6c6e70;
+-- 
+2.26.2
+
diff --git a/SOURCES/freeipa-4.9.6.tar.gz.asc b/SOURCES/freeipa-4.9.6.tar.gz.asc
new file mode 100644
index 0000000..f71d351
--- /dev/null
+++ b/SOURCES/freeipa-4.9.6.tar.gz.asc
@@ -0,0 +1,16 @@
+-----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/SPECS/freeipa.spec b/SPECS/freeipa.spec
new file mode 100644
index 0000000..45b8446
--- /dev/null
+++ b/SPECS/freeipa.spec
@@ -0,0 +1,3119 @@
+# 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
+# On RHEL 8 we should use --with ipa_join_xml
+%bcond_with ipa_join_xml
+
+# Linting is disabled by default, needed for upstream testing
+%bcond_with lint
+
+# Build documentation with sphinx
+%bcond_with doc
+
+# Build Python wheels
+%bcond_with wheels
+
+# 389-ds-base 1.4 no longer supports i686 platform, build only client
+# packages, https://bugzilla.redhat.com/show_bug.cgi?id=1544386
+%ifarch %{ix86}
+    %{!?ONLY_CLIENT:%global ONLY_CLIENT 1}
+%endif
+
+# Define ONLY_CLIENT to only make the ipa-client and ipa-python
+# subpackages
+%{!?ONLY_CLIENT:%global ONLY_CLIENT 0}
+%if %{ONLY_CLIENT}
+    %global enable_server_option --disable-server
+%else
+    %global enable_server_option --enable-server
+%endif
+
+%if %{ONLY_CLIENT}
+    %global with_ipatests 0
+%endif
+
+# Whether to build ipatests
+%if %{with ipatests}
+    %global with_ipatests_option --with-ipatests
+%else
+    %global with_ipatests_option --without-ipatests
+%endif
+
+# Whether to use XML-RPC with ipa-join
+%if %{with ipa_join_xml}
+    %global with_ipa_join_xml_option --with-ipa-join-xml
+%else
+    %global with_ipa_join_xml_option --without-ipa-join-xml
+%endif
+
+# lint is not executed during rpmbuild
+# %%global with_lint 1
+%if %{with lint}
+    %global linter_options --enable-pylint --without-jslint --enable-rpmlint
+%else
+    %global linter_options --disable-pylint --without-jslint --disable-rpmlint
+%endif
+
+# Include SELinux subpackage
+%if 0%{?fedora} >= 30 || 0%{?rhel} >= 8
+    %global with_selinux 1
+    %global selinuxtype targeted
+    %global modulename ipa
+%endif
+
+%if 0%{?rhel}
+%global package_name ipa
+%global alt_name freeipa
+%global krb5_version 1.18.2-2
+%global krb5_kdb_version 8.0
+# 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 selinux_policy_version 3.14.3-52
+%global slapi_nis_version 0.56.4
+%global python_ldap_version 3.1.0-1
+%if 0%{?rhel} < 9
+# Bug 1929067 - PKI instance creation failed with new 389-ds-base build
+%global ds_version 1.4.3.16-12
+%else
+# DNA interval enabled
+%global ds_version 2.0.5-1
+%endif
+
+# Fix for TLS 1.3 PHA, RHBZ#1775158
+%global httpd_version 2.4.37-21
+%global bind_version 9.11.20-6
+
+%else
+# Fedora
+%global package_name freeipa
+%global alt_name ipa
+# Fix for CVE-2020-28196
+%global krb5_version 1.18.2-29
+# 0.7.16: https://github.com/drkjam/netaddr/issues/71
+%global python_netaddr_version 0.7.16
+# Require 4.7.0 which brings Python 3 bindings
+# Require 4.12 which has DsRGetForestTrustInformation access rights fixes
+%global samba_version 2:4.12.10
+
+# 3.14.5-45 or later includes a number of interfaces fixes for IPA interface
+%global selinux_policy_version 3.14.5-45
+%global slapi_nis_version 0.56.5
+
+%global krb5_kdb_version 8.0
+
+# fix for segfault in python3-ldap, https://pagure.io/freeipa/issue/7324
+%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 1.4.4.16-1
+%else
+%global ds_version 2.0.5-1
+%endif
+
+# Fix for TLS 1.3 PHA, RHBZ#1775146
+%global httpd_version 2.4.41-9
+
+%global bind_version 9.11.24-1
+# Don't use Fedora's Python dependency generator on Fedora 30/rawhide yet.
+# Some packages don't provide new dist aliases.
+# https://docs.fedoraproject.org/en-US/packaging-guidelines/Python/
+%{?python_disable_dependency_generator}
+# Fedora
+%endif
+
+# BIND employs 'pkcs11' OpenSSL engine instead of native PKCS11
+# Fedora 31+ uses OpenSSL engine, as well as Fedora ELN (RHEL9)
+%if 0%{?fedora} || 0%{?rhel} >= 9
+    %global openssl_pkcs11_version 0.4.10-6
+    %global softhsm_version 2.5.0-4
+%else
+    %global with_bind_pkcs11 1
+%endif
+
+%if 0%{?rhel} == 8
+# Make sure to use PKI versions that work with 389-ds fix for https://github.com/389ds/389-ds-base/issues/4609
+%global pki_version 10.10.5
+%else
+# Make sure to use PKI versions that work with 389-ds fix for https://github.com/389ds/389-ds-base/issues/4609
+%global pki_version 10.10.5
+%endif
+
+# RHEL 8.3+, F32+ has 0.79.13
+%global certmonger_version 0.79.7-3
+
+# RHEL 8.2+, F32+ has 3.58
+%global nss_version 3.44.0-4
+
+# RHEL 8.3+, F32+
+%global sssd_version 2.4.0
+
+%define krb5_base_version %(LC_ALL=C /usr/bin/pkgconf --modversion krb5 | grep -Eo '^[^.]+\.[^.]+' || echo %krb5_version)
+%global kdcproxy_version 0.4-3
+
+%if 0%{?fedora} >= 33 || 0%{?rhel} >= 9
+# systemd with resolved enabled
+# see https://pagure.io/freeipa/issue/8275
+%global systemd_version 246.6-3
+%else
+%global systemd_version 239
+%endif
+
+# augeas support for new chrony options
+# see https://pagure.io/freeipa/issue/8676
+# 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
+%global gettext_domain ipa
+
+%define _hardened_build 1
+
+# 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
+# Release candidate version -- uncomment with one percent for RC versions
+#%%global rc_version %%nil
+%define AT_SIGN @
+# redefine IPA_VERSION only if its value matches the Autoconf placeholder
+%if "%{IPA_VERSION}" == "%{AT_SIGN}VERSION%{AT_SIGN}"
+    %define IPA_VERSION nonsense.to.please.RPM.SPEC.parser
+%endif
+
+%define NON_DEVELOPER_BUILD ("%{lua: print(rpm.expand('%{suffix:%IPA_VERSION}'):find('^dev'))}" == "nil")
+
+Name:           %{package_name}
+Version:        %{IPA_VERSION}
+Release:        9%{?rc_version:.%rc_version}%{?dist}
+Summary:        The Identity, Policy and Audit system
+
+License:        GPLv3+
+URL:            http://www.freeipa.org/
+Source0:        https://releases.pagure.org/freeipa/freeipa-%{version}%{?rc_version}.tar.gz
+# Only use detached signature for the distribution builds. If it is a developer build, skip it
+%if %{NON_DEVELOPER_BUILD}
+Source1:        https://releases.pagure.org/freeipa/freeipa-%{version}%{?rc_version}.tar.gz.asc
+%endif
+
+# RHEL spec file only: START: Change branding to IPA and Identity Management
+# Moved branding logos and background to redhat-logos-ipa-80.4:
+# header-logo.png, login-screen-background.jpg, login-screen-logo.png,
+# product-name.png
+# RHEL spec file only: END: Change branding to IPA and Identity Management
+
+# 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
+Patch1001:      1001-Change-branding-to-IPA-and-Identity-Management.patch
+%endif
+%endif
+# RHEL spec file only: END
+
+# For the timestamp trick in patch application
+BuildRequires:  diffstat
+
+BuildRequires:  openldap-devel
+# For KDB DAL version, make explicit dependency so that increase of version
+# will cause the build to fail due to unsatisfied dependencies.
+# DAL version change may cause code crash or memory leaks, it is better to fail early.
+BuildRequires:  krb5-kdb-version = %{krb5_kdb_version}
+BuildRequires:  krb5-kdb-devel-version = %{krb5_kdb_version}
+BuildRequires:  krb5-devel >= %{krb5_version}
+BuildRequires:  pkgconfig(krb5)
+%if %{with ipa_join_xml}
+# 1.27.4: xmlrpc_curl_xportparms.gssapi_delegation
+BuildRequires:  xmlrpc-c-devel >= 1.27.4
+%else
+BuildRequires:  libcurl-devel
+BuildRequires:  jansson-devel
+%endif
+BuildRequires:  popt-devel
+BuildRequires:  gcc
+BuildRequires:  make
+BuildRequires:  pkgconfig
+BuildRequires:  pkgconf
+BuildRequires:  autoconf
+BuildRequires:  automake
+BuildRequires:  make
+BuildRequires:  libtool
+BuildRequires:  gettext
+BuildRequires:  gettext-devel
+BuildRequires:  python3-devel
+BuildRequires:  python3-setuptools
+BuildRequires:  systemd >= %{systemd_version}
+# systemd-tmpfiles which is executed from make install requires apache user
+BuildRequires:  httpd
+BuildRequires:  nspr-devel
+BuildRequires:  openssl-devel
+BuildRequires:  libini_config-devel
+BuildRequires:  cyrus-sasl-devel
+%if ! %{ONLY_CLIENT}
+BuildRequires:  389-ds-base-devel >= %{ds_version}
+BuildRequires:  samba-devel >= %{samba_version}
+BuildRequires:  libtalloc-devel
+BuildRequires:  libtevent-devel
+BuildRequires:  libuuid-devel
+BuildRequires:  libpwquality-devel
+BuildRequires:  libsss_idmap-devel
+BuildRequires:  libsss_certmap-devel
+BuildRequires:  libsss_nss_idmap-devel >= %{sssd_version}
+BuildRequires:  nodejs(abi)
+# use old dependency on RHEL 8 for now
+%if 0%{?fedora} >= 31 || 0%{?rhel} >= 9
+BuildRequires:  python3-rjsmin
+%else
+BuildRequires:  uglify-js
+%endif
+BuildRequires:  libverto-devel
+BuildRequires:  libunistring-devel
+# 0.13.0: https://bugzilla.redhat.com/show_bug.cgi?id=1584773
+# 0.13.0-2: fix for missing dependency on python-six
+BuildRequires:  python3-lesscpy >= 0.13.0-2
+BuildRequires:  cracklib-dicts
+# ONLY_CLIENT
+%endif
+
+#
+# Build dependencies for makeapi/makeaci
+#
+BuildRequires:  python3-cffi
+BuildRequires:  python3-dns
+BuildRequires:  python3-ldap >= %{python_ldap_version}
+BuildRequires:  python3-libsss_nss_idmap
+BuildRequires:  python3-netaddr >= %{python_netaddr_version}
+BuildRequires:  python3-pyasn1
+BuildRequires:  python3-pyasn1-modules
+BuildRequires:  python3-six
+BuildRequires:  python3-psutil
+
+#
+# Build dependencies for wheel packaging and PyPI upload
+#
+%if %{with wheels}
+BuildRequires:  dbus-glib-devel
+BuildRequires:  libffi-devel
+BuildRequires:  python3-tox
+%if 0%{?fedora} <= 28
+BuildRequires:  python3-twine
+%else
+BuildRequires:  twine
+%endif
+BuildRequires:  python3-wheel
+# with_wheels
+%endif
+
+%if %{with doc}
+BuildRequires: python3-sphinx
+BuildRequires: python3-m2r
+%endif
+
+#
+# Build dependencies for lint and fastcheck
+#
+%if %{with lint}
+
+# 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
+
+BuildRequires:  keyutils
+BuildRequires:  python3-augeas
+BuildRequires:  python3-cffi
+BuildRequires:  python3-cryptography >= 1.6
+BuildRequires:  python3-dateutil
+BuildRequires:  python3-dbus
+BuildRequires:  python3-dns >= 1.15
+BuildRequires:  python3-docker
+BuildRequires:  python3-gssapi >= 1.2.0
+BuildRequires:  python3-jinja2
+BuildRequires:  python3-jwcrypto >= 0.4.2
+BuildRequires:  python3-ldap >= %{python_ldap_version}
+BuildRequires:  python3-ldap >= %{python_ldap_version}
+BuildRequires:  python3-lib389 >= %{ds_version}
+BuildRequires:  python3-libipa_hbac
+BuildRequires:  python3-libsss_nss_idmap
+BuildRequires:  python3-lxml
+BuildRequires:  python3-netaddr >= %{python_netaddr_version}
+BuildRequires:  python3-netifaces
+BuildRequires:  python3-paste
+BuildRequires:  python3-pki >= %{pki_version}
+BuildRequires:  python3-polib
+BuildRequires:  python3-pyasn1
+BuildRequires:  python3-pyasn1-modules
+BuildRequires:  python3-pycodestyle
+# .wheelconstraints.in limits pylint version in Azure and tox tests
+BuildRequires:  python3-pylint
+BuildRequires:  python3-pytest-multihost
+BuildRequires:  python3-pytest-sourceorder
+BuildRequires:  python3-qrcode-core >= 5.0.0
+BuildRequires:  python3-samba
+BuildRequires:  python3-six
+BuildRequires:  python3-sss
+BuildRequires:  python3-sss-murmur
+BuildRequires:  python3-sssdconfig >= %{sssd_version}
+BuildRequires:  python3-systemd
+BuildRequires:  python3-yaml
+BuildRequires:  python3-yubico
+# with_lint
+%endif
+
+#
+# Build dependencies for unit tests
+#
+%if ! %{ONLY_CLIENT}
+BuildRequires:  libcmocka-devel
+# Required by ipa_kdb_tests
+BuildRequires:  krb5-server >= %{krb5_version}
+# ONLY_CLIENT
+%endif
+
+# Build dependencies for SELinux policy
+%if %{with selinux}
+BuildRequires:  selinux-policy-devel >= %{selinux_policy_version}
+%endif
+
+%description
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+
+
+%if ! %{ONLY_CLIENT}
+
+%package server
+Summary: The IPA authentication server
+Requires: %{name}-server-common = %{version}-%{release}
+Requires: %{name}-client = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+Requires: python3-ipaserver = %{version}-%{release}
+Requires: python3-ldap >= %{python_ldap_version}
+Requires: 389-ds-base >= %{ds_version}
+Requires: openldap-clients > 2.4.35-4
+Requires: nss-tools >= %{nss_version}
+Requires(post): krb5-server >= %{krb5_version}
+Requires(post): krb5-server >= %{krb5_base_version}
+Requires: krb5-kdb-version = %{krb5_kdb_version}
+Requires: krb5-pkinit-openssl >= %{krb5_version}
+Requires: cyrus-sasl-gssapi%{?_isa}
+Requires: chrony
+Requires: httpd >= %{httpd_version}
+Requires(preun): python3
+Requires(postun): python3
+Requires: python3-gssapi >= 1.2.0-5
+Requires: python3-systemd
+Requires: python3-mod_wsgi
+Requires: mod_auth_gssapi >= 1.5.0
+Requires: mod_ssl >= %{httpd_version}
+Requires: mod_session >= %{httpd_version}
+# 0.9.9: https://github.com/adelton/mod_lookup_identity/pull/3
+Requires: mod_lookup_identity >= 0.9.9
+Requires: acl
+Requires: systemd-units >= %{systemd_version}
+Requires(pre): systemd-units >= %{systemd_version}
+Requires(post): systemd-units >= %{systemd_version}
+Requires(preun): systemd-units >= %{systemd_version}
+Requires(postun): systemd-units >= %{systemd_version}
+Requires(pre): shadow-utils
+Requires: selinux-policy >= %{selinux_policy_version}
+Requires(post): selinux-policy-base >= %{selinux_policy_version}
+Requires: slapi-nis >= %{slapi_nis_version}
+Requires: pki-ca >= %{pki_version}
+Requires: pki-kra >= %{pki_version}
+# pki-acme package was split out in pki-10.10.0
+Requires: (pki-acme >= %{pki_version} if pki-ca >= 10.10.0)
+Requires: policycoreutils >= 2.1.12-5
+Requires: tar
+Requires(pre): certmonger >= %{certmonger_version}
+Requires(pre): 389-ds-base >= %{ds_version}
+Requires: fontawesome-fonts
+Requires: open-sans-fonts
+%if 0%{?fedora} >= 32 || 0%{?rhel} >= 9
+# https://pagure.io/freeipa/issue/8632
+Requires: openssl > 1.1.1i
+%else
+Requires: openssl
+%endif
+Requires: softhsm >= 2.0.0rc1-1
+Requires: p11-kit
+Requires: %{etc_systemd_dir}
+Requires: gzip
+Requires: oddjob
+# 0.7.0-2: https://pagure.io/gssproxy/pull-request/172
+Requires: gssproxy >= 0.7.0-2
+Requires: sssd-dbus >= %{sssd_version}
+Requires: libpwquality
+Requires: cracklib-dicts
+
+Provides: %{alt_name}-server = %{version}
+Conflicts: %{alt_name}-server
+Obsoletes: %{alt_name}-server < %{version}
+
+# With FreeIPA 3.3, package freeipa-server-selinux was obsoleted as the
+# entire SELinux policy is stored in the system policy
+Obsoletes: freeipa-server-selinux < 3.3.0
+
+# upgrade path from monolithic -server to -server + -server-dns
+Obsoletes: %{name}-server <= 4.2.0
+
+# Versions of nss-pam-ldapd < 0.8.4 require a mapping from uniqueMember to
+# member.
+Conflicts: nss-pam-ldapd < 0.8.4
+
+# RHEL spec file only: START: Do not build tests
+%if 0%{?rhel} == 8
+# ipa-tests subpackage was moved to separate srpm
+Conflicts: ipa-tests < 3.3.3-9
+%endif
+# RHEL spec file only: END: Do not build tests
+
+%description server
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If you are installing an IPA server, you need to install this package.
+
+
+%package -n python3-ipaserver
+Summary: Python libraries used by IPA server
+BuildArch: noarch
+%{?python_provide:%python_provide python3-ipaserver}
+Requires: %{name}-server-common = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+# we need pre-requires since earlier versions may break upgrade
+Requires(pre): python3-ldap >= %{python_ldap_version}
+Requires: python3-augeas
+Requires: augeas-libs >= %{augeas_version}
+Requires: python3-dbus
+Requires: python3-dns >= 1.15
+Requires: python3-gssapi >= 1.2.0
+Requires: python3-ipaclient = %{version}-%{release}
+Requires: python3-kdcproxy >= %{kdcproxy_version}
+Requires: python3-lxml
+Requires: python3-pki >= %{pki_version}
+Requires: python3-pyasn1 >= 0.3.2-2
+Requires: python3-sssdconfig >= %{sssd_version}
+Requires: python3-psutil
+Requires: rpm-libs
+# Indirect dependency: use newer urllib3 with TLS 1.3 PHA support
+%if 0%{?rhel}
+Requires: python3-urllib3 >= 1.24.2-3
+%else
+Requires: python3-urllib3 >= 1.25.7
+%endif
+
+%description -n python3-ipaserver
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If you are installing an IPA server, you need to install this package.
+
+
+%package server-common
+Summary: Common files used by IPA server
+BuildArch: noarch
+Requires: %{name}-client-common = %{version}-%{release}
+Requires: httpd >= %{httpd_version}
+Requires: systemd-units >= %{systemd_version}
+%if 0%{?rhel} >= 8 && ! 0%{?eln}
+Requires: system-logos-ipa >= 80.4
+%endif
+
+Provides: %{alt_name}-server-common = %{version}
+Conflicts: %{alt_name}-server-common
+Obsoletes: %{alt_name}-server-common < %{version}
+
+%description server-common
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If you are installing an IPA server, you need to install this package.
+
+
+%package server-dns
+Summary: IPA integrated DNS server with support for automatic DNSSEC signing
+BuildArch: noarch
+Requires: %{name}-server = %{version}-%{release}
+Requires: bind-dyndb-ldap >= 11.2-2
+Requires: bind >= %{bind_version}
+Requires: bind-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}
+%endif
+# See https://bugzilla.redhat.com/show_bug.cgi?id=1825812
+# RHEL 8.3+ and Fedora 32+ have 2.1
+Requires: opendnssec >= 2.1.6-5
+%{?systemd_requires}
+
+Provides: %{alt_name}-server-dns = %{version}
+Conflicts: %{alt_name}-server-dns
+Obsoletes: %{alt_name}-server-dns < %{version}
+
+# upgrade path from monolithic -server to -server + -server-dns
+Obsoletes: %{name}-server <= 4.2.0
+
+%description server-dns
+IPA integrated DNS server with support for automatic DNSSEC signing.
+Integrated DNS server is BIND 9. OpenDNSSEC provides key management.
+
+
+%package server-trust-ad
+Summary: Virtual package to install packages required for Active Directory trusts
+Requires: %{name}-server = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+
+Requires: samba >= %{samba_version}
+Requires: samba-winbind
+Requires: libsss_idmap
+Requires: sssd-winbind-idmap
+%if 0%{?rhel}
+Obsoletes: ipa-idoverride-memberof-plugin <= 0.1
+%endif
+Requires(post): python3
+Requires: python3-samba
+Requires: python3-libsss_nss_idmap
+Requires: python3-sss
+
+# We use alternatives to divert winbind_krb5_locator.so plugin to libkrb5
+# on the installes where server-trust-ad subpackage is installed because
+# IPA AD trusts cannot be used at the same time with the locator plugin
+# since Winbindd will be configured in a different mode
+Requires(post): %{_sbindir}/update-alternatives
+Requires(postun): %{_sbindir}/update-alternatives
+Requires(preun): %{_sbindir}/update-alternatives
+
+Provides: %{alt_name}-server-trust-ad = %{version}
+Conflicts: %{alt_name}-server-trust-ad
+Obsoletes: %{alt_name}-server-trust-ad < %{version}
+
+%description server-trust-ad
+Cross-realm trusts with Active Directory in IPA require working Samba 4
+installation. This package is provided for convenience to install all required
+dependencies at once.
+
+# ONLY_CLIENT
+%endif
+
+
+%package client
+Summary: IPA authentication for use on clients
+Requires: %{name}-client-common = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+Requires: python3-gssapi >= 1.2.0-5
+Requires: python3-ipaclient = %{version}-%{release}
+Requires: python3-ldap >= %{python_ldap_version}
+Requires: python3-sssdconfig >= %{sssd_version}
+Requires: cyrus-sasl-gssapi%{?_isa}
+Requires: chrony
+Requires: krb5-workstation >= %{krb5_version}
+Requires: authselect >= 0.4-2
+Requires: curl
+# NIS domain name config: /usr/lib/systemd/system/*-domainname.service
+# All Fedora 28+ and RHEL8+ contain the service in hostname package
+Requires: hostname
+Requires: libcurl >= 7.21.7-2
+%if %{with ipa_join_xml}
+Requires: xmlrpc-c >= 1.27.4
+%else
+Requires: jansson
+%endif
+Requires: sssd-ipa >= %{sssd_version}
+Requires: certmonger >= %{certmonger_version}
+Requires: nss-tools >= %{nss_version}
+Requires: bind-utils
+Requires: oddjob-mkhomedir
+Requires: libsss_autofs
+Requires: autofs
+Requires: libnfsidmap
+Requires: nfs-utils
+Requires: sssd-tools >= %{sssd_version}
+Requires(post): policycoreutils
+
+# https://pagure.io/freeipa/issue/8530
+Recommends: libsss_sudo
+Recommends: sudo
+Requires: (libsss_sudo if sudo)
+
+Provides: %{alt_name}-client = %{version}
+Conflicts: %{alt_name}-client
+Obsoletes: %{alt_name}-client < %{version}
+
+Provides: %{alt_name}-admintools = %{version}
+Conflicts: %{alt_name}-admintools
+Obsoletes: %{alt_name}-admintools < 4.4.1
+
+Obsoletes: %{name}-admintools < 4.4.1
+Provides: %{name}-admintools = %{version}-%{release}
+
+%if 0%{?rhel} == 8
+# Conflict with crypto-policies < 20200629-1 to get AD-SUPPORT policy module
+Conflicts: crypto-policies < 20200629-1
+%endif
+
+%description client
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If your network uses IPA for authentication, this package should be
+installed on every client machine.
+This package provides command-line tools for IPA administrators.
+
+%package client-samba
+Summary: Tools to configure Samba on IPA client
+Group: System Environment/Base
+Requires: %{name}-client = %{version}-%{release}
+Requires: python3-samba
+Requires: samba-client
+Requires: samba-winbind
+Requires: samba-common-tools
+Requires: samba
+Requires: sssd-winbind-idmap
+Requires: tdb-tools
+Requires: cifs-utils
+
+%description client-samba
+This package provides command-line tools to deploy Samba domain member
+on the machine enrolled into a FreeIPA environment
+
+%package client-epn
+Summary: Tools to configure Expiring Password Notification in IPA
+Group: System Environment/Base
+Requires: %{name}-client = %{version}-%{release}
+Requires: systemd-units >= %{systemd_version}
+Requires(post): systemd-units >= %{systemd_version}
+Requires(preun): systemd-units >= %{systemd_version}
+Requires(postun): systemd-units >= %{systemd_version}
+
+%description client-epn
+This package provides a service to collect and send expiring password
+notifications via email (SMTP).
+
+%package -n python3-ipaclient
+Summary: Python libraries used by IPA client
+BuildArch: noarch
+%{?python_provide:%python_provide python3-ipaclient}
+Requires: %{name}-client-common = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+Requires: python3-ipalib = %{version}-%{release}
+Requires: python3-augeas
+Requires: augeas-libs >= %{augeas_version}
+Requires: python3-dns >= 1.15
+Requires: python3-jinja2
+
+%description -n python3-ipaclient
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If your network uses IPA for authentication, this package should be
+installed on every client machine.
+
+%package client-common
+Summary: Common files used by IPA client
+BuildArch: noarch
+
+Provides: %{alt_name}-client-common = %{version}
+Conflicts: %{alt_name}-client-common
+Obsoletes: %{alt_name}-client-common < %{version}
+# python2-ipa* packages are no longer available in 4.8.
+Obsoletes: python2-ipaclient < 4.8.0-1
+Obsoletes: python2-ipalib < 4.8.0-1
+Obsoletes: python2-ipaserver < 4.8.0-1
+Obsoletes: python2-ipatests < 4.8.0-1
+
+
+%description client-common
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If your network uses IPA for authentication, this package should be
+installed on every client machine.
+
+
+%package python-compat
+Summary: Compatiblity package for Python libraries used by IPA
+BuildArch: noarch
+Obsoletes: %{name}-python < 4.2.91
+Provides: %{name}-python = %{version}-%{release}
+Requires: %{name}-common = %{version}-%{release}
+Requires: python3-ipalib = %{version}-%{release}
+
+Provides: %{alt_name}-python-compat = %{version}
+Conflicts: %{alt_name}-python-compat
+Obsoletes: %{alt_name}-python-compat < %{version}
+
+Obsoletes: %{alt_name}-python < 4.2.91
+Provides: %{alt_name}-python = %{version}
+
+%description python-compat
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+This is a compatibility package to accommodate %{name}-python split into
+python3-ipalib and %{name}-common. Packages still depending on
+%{name}-python should be fixed to depend on python2-ipaclient or
+%{name}-common instead.
+
+
+%package -n python3-ipalib
+Summary: Python3 libraries used by IPA
+BuildArch: noarch
+%{?python_provide:%python_provide python3-ipalib}
+Provides: python3-ipapython = %{version}-%{release}
+%{?python_provide:%python_provide python3-ipapython}
+Provides: python3-ipaplatform = %{version}-%{release}
+%{?python_provide:%python_provide python3-ipaplatform}
+Requires: %{name}-common = %{version}-%{release}
+# we need pre-requires since earlier versions may break upgrade
+Requires(pre): python3-ldap >= %{python_ldap_version}
+Requires: gnupg2
+Requires: keyutils
+Requires: python3-cffi
+Requires: python3-cryptography >= 1.6
+Requires: python3-dateutil
+Requires: python3-dbus
+Requires: python3-dns >= 1.15
+Requires: python3-gssapi >= 1.2.0
+Requires: python3-jwcrypto >= 0.4.2
+Requires: python3-libipa_hbac
+Requires: python3-netaddr >= %{python_netaddr_version}
+Requires: python3-netifaces >= 0.10.4
+Requires: python3-pyasn1 >= 0.3.2-2
+Requires: python3-pyasn1-modules >= 0.3.2-2
+Requires: python3-pyusb
+Requires: python3-qrcode-core >= 5.0.0
+Requires: python3-requests
+Requires: python3-six
+Requires: python3-sss-murmur
+Requires: python3-yubico >= 1.3.2-7
+%if 0%{?rhel} && 0%{?rhel} == 8
+Requires: platform-python-setuptools
+%else
+Requires: python3-setuptools
+%endif
+
+%description -n python3-ipalib
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If you are using IPA with Python 3, you need to install this package.
+
+
+%package common
+Summary: Common files used by IPA
+BuildArch: noarch
+Conflicts: %{name}-python < 4.2.91
+
+Provides: %{alt_name}-common = %{version}
+Conflicts: %{alt_name}-common
+Obsoletes: %{alt_name}-common < %{version}
+
+Conflicts: %{alt_name}-python < %{version}
+
+%if %{with selinux}
+# This ensures that the *-selinux package and all it’s dependencies are not
+# pulled into containers and other systems that do not use SELinux. The
+# policy defines types and file contexts for client and server.
+Requires:       (%{name}-selinux if selinux-policy-%{selinuxtype})
+%endif
+
+%description common
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+If you are using IPA, you need to install this package.
+
+
+%if %{with ipatests}
+
+%package -n python3-ipatests
+Summary: IPA tests and test tools
+BuildArch: noarch
+%{?python_provide:%python_provide python3-ipatests}
+Requires: python3-ipaclient = %{version}-%{release}
+Requires: python3-ipaserver = %{version}-%{release}
+Requires: iptables
+Requires: python3-cryptography >= 1.6
+%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
+Requires: python3-pytest >= 3.9.1
+Requires: python3-pytest-multihost >= 0.5
+Requires: python3-pytest-sourceorder
+Requires: sshpass
+%endif
+Requires: python3-sssdconfig >= %{sssd_version}
+Requires: tar
+Requires: xz
+Requires: openssh-clients
+%if 0%{?rhel}
+AutoReqProv: no
+%endif
+
+%description -n python3-ipatests
+IPA is an integrated solution to provide centrally managed Identity (users,
+hosts, services), Authentication (SSO, 2FA), and Authorization
+(host access control, SELinux user roles, services). The solution provides
+features for further integration with Linux based clients (SUDO, automount)
+and integration with Active Directory based infrastructures (Trusts).
+This package contains tests that verify IPA functionality under Python 3.
+
+# with ipatests
+%endif
+
+
+%if %{with selinux}
+# SELinux subpackage
+%package selinux
+Summary:             FreeIPA SELinux policy
+BuildArch:           noarch
+Requires:            selinux-policy-%{selinuxtype}
+Requires(post):      selinux-policy-%{selinuxtype}
+%{?selinux_requires}
+
+%description selinux
+Custom SELinux policy module for FreeIPA
+# with selinux
+%endif
+
+
+%prep
+# Update timestamps on the files touched by a patch, to avoid non-equal
+# .pyc/.pyo files across the multilib peers within a build, where "Level"
+# is the patch prefix option (e.g. -p1)
+# Taken from specfile for sssd and python-simplejson
+UpdateTimestamps() {
+  Level=$1
+  PatchFile=$2
+
+  # Locate the affected files:
+  for f in $(diffstat $Level -l $PatchFile); do
+    # Set the files to have the same timestamp as that of the patch:
+    touch -c -r $PatchFile $f
+  done
+}
+
+%setup -n freeipa-%{version}%{?rc_version} -q
+
+# To allow proper application patches to the stripped po files, strip originals
+pushd po
+for i in *.po ; do
+    msgattrib --translated --no-fuzzy --no-location -s $i > $i.tmp || exit 1
+    mv $i.tmp $i || exit 1
+done
+popd
+
+for p in %patches ; do
+    %__patch -p1 -i $p
+    UpdateTimestamps -p1 $p
+done
+
+%build
+# PATH is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1005235
+export PATH=/usr/bin:/usr/sbin:$PATH
+
+export PYTHON=%{__python3}
+autoreconf -ivf
+%configure --with-vendor-suffix=-%{release} \
+           %{enable_server_option} \
+           %{with_ipatests_option} \
+           %{with_ipa_join_xml_option} \
+           %{linter_options}
+
+# run build in default dir
+# -Onone is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1398405
+%make_build -Onone
+
+
+%check
+make %{?_smp_mflags} check VERBOSE=yes LIBDIR=%{_libdir}
+
+
+%install
+# Please put as much logic as possible into make install. It allows:
+# - easier porting to other distributions
+# - rapid devel & install cycle using make install
+#   (instead of full RPM build and installation each time)
+#
+# All files and directories created by spec install should be marked as ghost.
+# (These are typically configuration files created by IPA installer.)
+# All other artifacts should be created by make install.
+
+%make_install
+
+# don't package ipasphinx for now
+rm -rf %{buildroot}%{python3_sitelib}/ipasphinx*
+
+%if %{with ipatests}
+mv %{buildroot}%{_bindir}/ipa-run-tests %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version}
+mv %{buildroot}%{_bindir}/ipa-test-config %{buildroot}%{_bindir}/ipa-test-config-%{python3_version}
+mv %{buildroot}%{_bindir}/ipa-test-task %{buildroot}%{_bindir}/ipa-test-task-%{python3_version}
+ln -rs %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version} %{buildroot}%{_bindir}/ipa-run-tests-3
+ln -rs %{buildroot}%{_bindir}/ipa-test-config-%{python3_version} %{buildroot}%{_bindir}/ipa-test-config-3
+ln -rs %{buildroot}%{_bindir}/ipa-test-task-%{python3_version} %{buildroot}%{_bindir}/ipa-test-task-3
+ln -frs %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version} %{buildroot}%{_bindir}/ipa-run-tests
+ln -frs %{buildroot}%{_bindir}/ipa-test-config-%{python3_version} %{buildroot}%{_bindir}/ipa-test-config
+ln -frs %{buildroot}%{_bindir}/ipa-test-task-%{python3_version} %{buildroot}%{_bindir}/ipa-test-task
+# with_ipatests
+%endif
+
+# remove files which are useful only for make uninstall
+find %{buildroot} -wholename '*/site-packages/*/install_files.txt' -exec rm {} \;
+
+%if 0%{?rhel}
+# RHEL spec file only: START
+# Moved branding logos and background to redhat-logos-ipa-80.4:
+# header-logo.png, login-screen-background.jpg, login-screen-logo.png,
+# product-name.png
+rm -f %{buildroot}%{_usr}/share/ipa/ui/images/header-logo.png
+rm -f %{buildroot}%{_usr}/share/ipa/ui/images/login-screen-background.jpg
+rm -f %{buildroot}%{_usr}/share/ipa/ui/images/login-screen-logo.png
+rm -f %{buildroot}%{_usr}/share/ipa/ui/images/product-name.png
+%endif
+# RHEL spec file only: END
+
+%find_lang %{gettext_domain}
+
+%if ! %{ONLY_CLIENT}
+# Remove .la files from libtool - we don't want to package
+# these files
+rm %{buildroot}/%{plugin_dir}/libipa_pwd_extop.la
+rm %{buildroot}/%{plugin_dir}/libipa_enrollment_extop.la
+rm %{buildroot}/%{plugin_dir}/libipa_winsync.la
+rm %{buildroot}/%{plugin_dir}/libipa_repl_version.la
+rm %{buildroot}/%{plugin_dir}/libipa_uuid.la
+rm %{buildroot}/%{plugin_dir}/libipa_modrdn.la
+rm %{buildroot}/%{plugin_dir}/libipa_lockout.la
+rm %{buildroot}/%{plugin_dir}/libipa_cldap.la
+rm %{buildroot}/%{plugin_dir}/libipa_dns.la
+rm %{buildroot}/%{plugin_dir}/libipa_sidgen.la
+rm %{buildroot}/%{plugin_dir}/libipa_sidgen_task.la
+rm %{buildroot}/%{plugin_dir}/libipa_extdom_extop.la
+rm %{buildroot}/%{plugin_dir}/libipa_range_check.la
+rm %{buildroot}/%{plugin_dir}/libipa_otp_counter.la
+rm %{buildroot}/%{plugin_dir}/libipa_otp_lasttoken.la
+rm %{buildroot}/%{plugin_dir}/libtopology.la
+rm %{buildroot}/%{_libdir}/krb5/plugins/kdb/ipadb.la
+rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la
+
+# So we can own our Apache configuration
+mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d/
+/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa.conf
+/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-kdc-proxy.conf
+/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf
+/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf
+/bin/touch %{buildroot}%{_usr}/share/ipa/html/ca.crt
+/bin/touch %{buildroot}%{_usr}/share/ipa/html/krb.con
+/bin/touch %{buildroot}%{_usr}/share/ipa/html/krb5.ini
+/bin/touch %{buildroot}%{_usr}/share/ipa/html/krbrealm.con
+
+mkdir -p %{buildroot}%{_libdir}/krb5/plugins/libkrb5
+touch %{buildroot}%{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
+
+# ONLY_CLIENT
+%endif
+
+/bin/touch %{buildroot}%{_sysconfdir}/ipa/default.conf
+/bin/touch %{buildroot}%{_sysconfdir}/ipa/ca.crt
+
+%if ! %{ONLY_CLIENT}
+mkdir -p %{buildroot}%{_sysconfdir}/cron.d
+# ONLY_CLIENT
+%endif
+
+%if ! %{ONLY_CLIENT}
+
+%post server
+# NOTE: systemd specific section
+    /bin/systemctl --system daemon-reload 2>&1 || :
+# END
+if [ $1 -gt 1 ] ; then
+    /bin/systemctl condrestart certmonger.service 2>&1 || :
+fi
+/bin/systemctl reload-or-try-restart dbus
+/bin/systemctl reload-or-try-restart oddjobd
+
+%tmpfiles_create ipa.conf
+
+%posttrans server
+# don't execute upgrade and restart of IPA when server is not installed
+%{__python3} -c "import sys; from ipalib import facts; sys.exit(0 if facts.is_ipa_configured() else 1);" > /dev/null 2>&1
+
+if [  $? -eq 0 ]; then
+    # This is necessary for Fedora system upgrades which by default
+    # work with the network being offline
+    /bin/systemctl start network-online.target
+
+    # Restart IPA processes. This must be also run in postrans so that plugins
+    # and software is in consistent state. This will also perform the
+    # system upgrade.
+    # NOTE: systemd specific section
+
+    /bin/systemctl is-enabled ipa.service >/dev/null 2>&1
+    if [  $? -eq 0 ]; then
+        /bin/systemctl restart ipa.service >/dev/null
+    fi
+
+    /bin/systemctl is-enabled ipa-ccache-sweep.timer >/dev/null 2>&1
+    if [  $? -eq 1 ]; then
+        /bin/systemctl enable ipa-ccache-sweep.timer>/dev/null
+    fi
+fi
+# END
+
+
+%preun server
+if [ $1 = 0 ]; then
+# NOTE: systemd specific section
+    /bin/systemctl --quiet stop ipa.service || :
+    /bin/systemctl --quiet disable ipa.service || :
+    /bin/systemctl reload-or-try-restart dbus
+    /bin/systemctl reload-or-try-restart oddjobd
+# END
+fi
+
+
+%pre server
+# Stop ipa_kpasswd if it exists before upgrading so we don't have a
+# zombie process when we're done.
+if [ -e /usr/sbin/ipa_kpasswd ]; then
+# NOTE: systemd specific section
+    /bin/systemctl stop ipa_kpasswd.service >/dev/null 2>&1 || :
+# END
+fi
+
+
+%pre server-common
+# create users and groups
+# create kdcproxy group and user
+getent group kdcproxy >/dev/null || groupadd -f -r kdcproxy
+getent passwd kdcproxy >/dev/null || useradd -r -g kdcproxy -s /sbin/nologin -d / -c "IPA KDC Proxy User" kdcproxy
+# create ipaapi group and user
+getent group ipaapi >/dev/null || groupadd -f -r ipaapi
+getent passwd ipaapi >/dev/null || useradd -r -g ipaapi -s /sbin/nologin -d / -c "IPA Framework User" ipaapi
+# add apache to ipaaapi group
+id -Gn apache | grep '\bipaapi\b' >/dev/null || usermod apache -a -G ipaapi
+
+
+%post server-dns
+%systemd_post ipa-dnskeysyncd.service ipa-ods-exporter.socket ipa-ods-exporter.service
+
+%preun server-dns
+%systemd_preun ipa-dnskeysyncd.service ipa-ods-exporter.socket ipa-ods-exporter.service
+
+%postun server-dns
+%systemd_postun ipa-dnskeysyncd.service ipa-ods-exporter.socket ipa-ods-exporter.service
+
+
+%postun server-trust-ad
+if [ "$1" -ge "1" ]; then
+    if [ "`readlink %{_sysconfdir}/alternatives/winbind_krb5_locator.so`" == "/dev/null" ]; then
+        %{_sbindir}/alternatives --set winbind_krb5_locator.so /dev/null
+    fi
+fi
+
+
+%post server-trust-ad
+%{_sbindir}/update-alternatives --install %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \
+        winbind_krb5_locator.so /dev/null 90
+/bin/systemctl reload-or-try-restart dbus
+/bin/systemctl reload-or-try-restart oddjobd
+
+
+%posttrans server-trust-ad
+%{__python3} -c "import sys; from ipalib import facts; sys.exit(0 if facts.is_ipa_configured() else 1);" > /dev/null 2>&1
+if [  $? -eq 0 ]; then
+# NOTE: systemd specific section
+    /bin/systemctl try-restart httpd.service >/dev/null 2>&1 || :
+# END
+fi
+
+
+%preun server-trust-ad
+if [ $1 -eq 0 ]; then
+    %{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
+    /bin/systemctl reload-or-try-restart dbus
+    /bin/systemctl reload-or-try-restart oddjobd
+fi
+
+# ONLY_CLIENT
+%endif
+
+%preun client-epn
+%systemd_preun ipa-epn.service
+%systemd_preun ipa-epn.timer
+
+%postun client-epn
+%systemd_postun ipa-epn.service
+%systemd_postun ipa-epn.timer
+
+%post client-epn
+%systemd_post ipa-epn.service
+%systemd_post ipa-epn.timer
+
+%post client
+if [ $1 -gt 1 ] ; then
+    # Has the client been configured?
+    restore=0
+    test -f '/var/lib/ipa-client/sysrestore/sysrestore.index' && restore=$(wc -l '/var/lib/ipa-client/sysrestore/sysrestore.index' | awk '{print $1}')
+
+    if [ -f '/etc/sssd/sssd.conf' -a $restore -ge 2 ]; then
+        if ! grep -E -q '/var/lib/sss/pubconf/krb5.include.d/' /etc/krb5.conf  2>/dev/null ; then
+            echo "includedir /var/lib/sss/pubconf/krb5.include.d/" > /etc/krb5.conf.ipanew
+            cat /etc/krb5.conf >> /etc/krb5.conf.ipanew
+            mv -Z /etc/krb5.conf.ipanew /etc/krb5.conf
+        fi
+    fi
+
+    if [ $restore -ge 2 ]; then
+        if grep -E -q '\s*pkinit_anchors = FILE:/etc/ipa/ca.crt$' /etc/krb5.conf 2>/dev/null; then
+            sed -E 's|(\s*)pkinit_anchors = FILE:/etc/ipa/ca.crt$|\1pkinit_anchors = FILE:/var/lib/ipa-client/pki/kdc-ca-bundle.pem\n\1pkinit_pool = FILE:/var/lib/ipa-client/pki/ca-bundle.pem|' /etc/krb5.conf >/etc/krb5.conf.ipanew
+            mv -Z /etc/krb5.conf.ipanew /etc/krb5.conf
+            cp /etc/ipa/ca.crt /var/lib/ipa-client/pki/kdc-ca-bundle.pem
+            cp /etc/ipa/ca.crt /var/lib/ipa-client/pki/ca-bundle.pem
+        fi
+
+        %{__python3} -c 'from ipaclient.install.client import configure_krb5_snippet; configure_krb5_snippet()' >>/var/log/ipaupgrade.log 2>&1
+        %{__python3} -c 'from ipaclient.install.client import update_ipa_nssdb; update_ipa_nssdb()' >>/var/log/ipaupgrade.log 2>&1
+        SSH_CLIENT_SYSTEM_CONF="/etc/ssh/ssh_config"
+        if [ -f "$SSH_CLIENT_SYSTEM_CONF" ]; then
+            sed -E --in-place=.orig 's/^(HostKeyAlgorithms ssh-rsa,ssh-dss)$/# disabled by ipa-client update\n# \1/' "$SSH_CLIENT_SYSTEM_CONF"
+        fi
+    fi
+fi
+
+
+%if %{with selinux}
+# SELinux contexts are saved so that only affected files can be
+# relabeled after the policy module installation
+%pre selinux
+%selinux_relabel_pre -s %{selinuxtype}
+
+%post selinux
+semodule -d ipa_custodia &> /dev/null || true;
+%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
+
+%postun selinux
+if [ $1 -eq 0 ]; then
+    %selinux_modules_uninstall -s %{selinuxtype} %{modulename}
+    semodule -e ipa_custodia &> /dev/null || true;
+fi
+
+%posttrans selinux
+%selinux_relabel_post -s %{selinuxtype}
+# with_selinux
+%endif
+
+
+%triggerin client -- openssh-server < 8.2
+# Has the client been configured?
+restore=0
+test -f '/var/lib/ipa-client/sysrestore/sysrestore.index' && restore=$(wc -l '/var/lib/ipa-client/sysrestore/sysrestore.index' | awk '{print $1}')
+
+if [ -f '/etc/ssh/sshd_config' -a $restore -ge 2 ]; then
+    if grep -E -q '^(AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys|PubKeyAgent /usr/bin/sss_ssh_authorizedkeys %u)$' /etc/ssh/sshd_config 2>/dev/null; then
+        sed -r '
+            /^(AuthorizedKeysCommand(User|RunAs)|PubKeyAgentRunAs)[ \t]/ d
+        ' /etc/ssh/sshd_config >/etc/ssh/sshd_config.ipanew
+
+        if /usr/sbin/sshd -t -f /dev/null -o 'AuthorizedKeysCommand=/usr/bin/sss_ssh_authorizedkeys' -o 'AuthorizedKeysCommandUser=nobody' 2>/dev/null; then
+            sed -ri '
+                s/^PubKeyAgent (.+) %u$/AuthorizedKeysCommand \1/
+                s/^AuthorizedKeysCommand .*$/\0\nAuthorizedKeysCommandUser nobody/
+            ' /etc/ssh/sshd_config.ipanew
+        elif /usr/sbin/sshd -t -f /dev/null -o 'AuthorizedKeysCommand=/usr/bin/sss_ssh_authorizedkeys' -o 'AuthorizedKeysCommandRunAs=nobody' 2>/dev/null; then
+            sed -ri '
+                s/^PubKeyAgent (.+) %u$/AuthorizedKeysCommand \1/
+                s/^AuthorizedKeysCommand .*$/\0\nAuthorizedKeysCommandRunAs nobody/
+            ' /etc/ssh/sshd_config.ipanew
+        elif /usr/sbin/sshd -t -f /dev/null -o 'PubKeyAgent=/usr/bin/sss_ssh_authorizedkeys %u' -o 'PubKeyAgentRunAs=nobody' 2>/dev/null; then
+            sed -ri '
+                s/^AuthorizedKeysCommand (.+)$/PubKeyAgent \1 %u/
+                s/^PubKeyAgent .*$/\0\nPubKeyAgentRunAs nobody/
+            ' /etc/ssh/sshd_config.ipanew
+        fi
+
+        mv -Z /etc/ssh/sshd_config.ipanew /etc/ssh/sshd_config
+        chmod 600 /etc/ssh/sshd_config
+
+        /bin/systemctl condrestart sshd.service 2>&1 || :
+    fi
+fi
+
+
+%triggerin client -- openssh-server >= 8.2
+# Has the client been configured?
+restore=0
+test -f '/var/lib/ipa-client/sysrestore/sysrestore.index' && restore=$(wc -l '/var/lib/ipa-client/sysrestore/sysrestore.index' | awk '{print $1}')
+
+if [ -f '/etc/ssh/sshd_config' -a $restore -ge 2 ]; then
+    # If the snippet already exists, skip
+    if [ ! -f '/etc/ssh/sshd_config.d/04-ipa.conf' ]; then
+        # Take the values from /etc/ssh/sshd_config and put them in 04-ipa.conf
+        grep -E '^(PubkeyAuthentication|KerberosAuthentication|GSSAPIAuthentication|UsePAM|ChallengeResponseAuthentication|AuthorizedKeysCommand|AuthorizedKeysCommandUser)' /etc/ssh/sshd_config 2>/dev/null > /etc/ssh/sshd_config.d/04-ipa.conf
+        # Remove the values from sshd_conf
+        sed -ri '
+            /^(PubkeyAuthentication|KerberosAuthentication|GSSAPIAuthentication|UsePAM|ChallengeResponseAuthentication|AuthorizedKeysCommand|AuthorizedKeysCommandUser)[ \t]/ d
+        ' /etc/ssh/sshd_config
+
+        /bin/systemctl condrestart sshd.service 2>&1 || :
+    fi
+    # If the snippet has been created, ensure that it is included
+    # either by /etc/ssh/sshd_config.d/*.conf or directly
+    if [ -f '/etc/ssh/sshd_config.d/04-ipa.conf' ]; then
+        if ! grep -E -q  '^\s*Include\s*/etc/ssh/sshd_config.d/\*\.conf' /etc/ssh/sshd_config 2> /dev/null ; then
+            if ! grep -E -q '^\s*Include\s*/etc/ssh/sshd_config.d/04-ipa\.conf' /etc/ssh/sshd_config 2> /dev/null ; then
+                # Include the snippet
+                echo "Include /etc/ssh/sshd_config.d/04-ipa.conf" > /etc/ssh/sshd_config.ipanew
+                cat /etc/ssh/sshd_config >> /etc/ssh/sshd_config.ipanew
+                mv -fZ --backup=existing --suffix .ipaold /etc/ssh/sshd_config.ipanew /etc/ssh/sshd_config
+            fi
+        fi
+    fi
+fi
+
+
+%if ! %{ONLY_CLIENT}
+
+%files server
+%doc README.md Contributors.txt
+%license COPYING
+%{_sbindir}/ipa-backup
+%{_sbindir}/ipa-restore
+%{_sbindir}/ipa-ca-install
+%{_sbindir}/ipa-kra-install
+%{_sbindir}/ipa-server-install
+%{_sbindir}/ipa-replica-conncheck
+%{_sbindir}/ipa-replica-install
+%{_sbindir}/ipa-replica-manage
+%{_sbindir}/ipa-csreplica-manage
+%{_sbindir}/ipa-server-certinstall
+%{_sbindir}/ipa-server-upgrade
+%{_sbindir}/ipa-ldap-updater
+%{_sbindir}/ipa-otptoken-import
+%{_sbindir}/ipa-compat-manage
+%{_sbindir}/ipa-nis-manage
+%{_sbindir}/ipa-managed-entries
+%{_sbindir}/ipactl
+%{_sbindir}/ipa-advise
+%{_sbindir}/ipa-cacert-manage
+%{_sbindir}/ipa-winsync-migrate
+%{_sbindir}/ipa-pkinit-manage
+%{_sbindir}/ipa-crlgen-manage
+%{_sbindir}/ipa-cert-fix
+%{_sbindir}/ipa-acme-manage
+%{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
+%{_libexecdir}/certmonger/ipa-server-guard
+%dir %{_libexecdir}/ipa
+%{_libexecdir}/ipa/ipa-ccache-sweeper
+%{_libexecdir}/ipa/ipa-custodia
+%{_libexecdir}/ipa/ipa-custodia-check
+%{_libexecdir}/ipa/ipa-httpd-kdcproxy
+%{_libexecdir}/ipa/ipa-httpd-pwdreader
+%{_libexecdir}/ipa/ipa-pki-retrieve-key
+%{_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
+%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat-wrapped
+%attr(755,root,root) %{_libexecdir}/ipa/custodia/ipa-custodia-ra-agent
+%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
+%config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freeipa.server.conf
+%config(noreplace) %{_sysconfdir}/oddjobd.conf.d/ipa-server.conf
+%dir %{_libexecdir}/ipa/certmonger
+%attr(755,root,root) %{_libexecdir}/ipa/certmonger/*
+# NOTE: systemd specific section
+%attr(644,root,root) %{_unitdir}/ipa.service
+%attr(644,root,root) %{_unitdir}/ipa-otpd.socket
+%attr(644,root,root) %{_unitdir}/ipa-otpd@.service
+%attr(644,root,root) %{_unitdir}/ipa-ccache-sweep.service
+%attr(644,root,root) %{_unitdir}/ipa-ccache-sweep.timer
+# END
+%attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
+%attr(755,root,root) %{plugin_dir}/libipa_enrollment_extop.so
+%attr(755,root,root) %{plugin_dir}/libipa_winsync.so
+%attr(755,root,root) %{plugin_dir}/libipa_repl_version.so
+%attr(755,root,root) %{plugin_dir}/libipa_uuid.so
+%attr(755,root,root) %{plugin_dir}/libipa_modrdn.so
+%attr(755,root,root) %{plugin_dir}/libipa_lockout.so
+%attr(755,root,root) %{plugin_dir}/libipa_dns.so
+%attr(755,root,root) %{plugin_dir}/libipa_range_check.so
+%attr(755,root,root) %{plugin_dir}/libipa_otp_counter.so
+%attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so
+%attr(755,root,root) %{plugin_dir}/libtopology.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
+%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
+%attr(755,root,root) %{_libdir}/krb5/plugins/kdb/ipadb.so
+%{_mandir}/man1/ipa-replica-conncheck.1*
+%{_mandir}/man1/ipa-replica-install.1*
+%{_mandir}/man1/ipa-replica-manage.1*
+%{_mandir}/man1/ipa-csreplica-manage.1*
+%{_mandir}/man1/ipa-server-certinstall.1*
+%{_mandir}/man1/ipa-server-install.1*
+%{_mandir}/man1/ipa-server-upgrade.1*
+%{_mandir}/man1/ipa-ca-install.1*
+%{_mandir}/man1/ipa-kra-install.1*
+%{_mandir}/man1/ipa-compat-manage.1*
+%{_mandir}/man1/ipa-nis-manage.1*
+%{_mandir}/man1/ipa-managed-entries.1*
+%{_mandir}/man1/ipa-ldap-updater.1*
+%{_mandir}/man8/ipactl.8*
+%{_mandir}/man1/ipa-backup.1*
+%{_mandir}/man1/ipa-restore.1*
+%{_mandir}/man1/ipa-advise.1*
+%{_mandir}/man1/ipa-otptoken-import.1*
+%{_mandir}/man1/ipa-cacert-manage.1*
+%{_mandir}/man1/ipa-winsync-migrate.1*
+%{_mandir}/man1/ipa-pkinit-manage.1*
+%{_mandir}/man1/ipa-crlgen-manage.1*
+%{_mandir}/man1/ipa-cert-fix.1*
+%{_mandir}/man1/ipa-acme-manage.1*
+
+
+%files -n python3-ipaserver
+%doc README.md Contributors.txt
+%license COPYING
+%{python3_sitelib}/ipaserver
+%{python3_sitelib}/ipaserver-*.egg-info
+
+
+%files server-common
+%doc README.md Contributors.txt
+%license COPYING
+%ghost %verify(not owner group) %dir %{_sharedstatedir}/kdcproxy
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/kdcproxy
+%config(noreplace) %{_sysconfdir}/ipa/kdcproxy/kdcproxy.conf
+# NOTE: systemd specific section
+%{_tmpfilesdir}/ipa.conf
+%attr(644,root,root) %{_unitdir}/ipa-custodia.service
+%ghost %attr(644,root,root) %{etc_systemd_dir}/httpd.d/ipa.conf
+# END
+%{_usr}/share/ipa/wsgi.py*
+%{_usr}/share/ipa/kdcproxy.wsgi
+%{_usr}/share/ipa/ipaca*.ini
+%{_usr}/share/ipa/*.ldif
+%exclude %{_datadir}/ipa/ipa-cldap-conf.ldif
+%{_usr}/share/ipa/*.uldif
+%{_usr}/share/ipa/*.template
+%dir %{_usr}/share/ipa/advise
+%dir %{_usr}/share/ipa/advise/legacy
+%{_usr}/share/ipa/advise/legacy/*.template
+%dir %{_usr}/share/ipa/profiles
+%{_usr}/share/ipa/profiles/README
+%{_usr}/share/ipa/profiles/*.cfg
+%dir %{_usr}/share/ipa/html
+%{_usr}/share/ipa/html/ssbrowser.html
+%{_usr}/share/ipa/html/unauthorized.html
+%dir %{_usr}/share/ipa/migration
+%{_usr}/share/ipa/migration/index.html
+%{_usr}/share/ipa/migration/migration.py*
+%dir %{_usr}/share/ipa/ui
+%{_usr}/share/ipa/ui/index.html
+%{_usr}/share/ipa/ui/reset_password.html
+%{_usr}/share/ipa/ui/sync_otp.html
+%{_usr}/share/ipa/ui/*.ico
+%{_usr}/share/ipa/ui/*.css
+%dir %{_usr}/share/ipa/ui/css
+%{_usr}/share/ipa/ui/css/*.css
+%dir %{_usr}/share/ipa/ui/js
+%dir %{_usr}/share/ipa/ui/js/dojo
+%{_usr}/share/ipa/ui/js/dojo/dojo.js
+%dir %{_usr}/share/ipa/ui/js/libs
+%{_usr}/share/ipa/ui/js/libs/*.js
+%dir %{_usr}/share/ipa/ui/js/freeipa
+%{_usr}/share/ipa/ui/js/freeipa/app.js
+%{_usr}/share/ipa/ui/js/freeipa/core.js
+%dir %{_usr}/share/ipa/ui/js/plugins
+%dir %{_usr}/share/ipa/ui/images
+%if 0%{?rhel}
+%{_usr}/share/ipa/ui/images/facet-*.png
+# Moved branding logos and background to redhat-logos-ipa-80.4:
+# header-logo.png, login-screen-background.jpg, login-screen-logo.png,
+# product-name.png
+%else
+%{_usr}/share/ipa/ui/images/*.jpg
+%{_usr}/share/ipa/ui/images/*.png
+%endif
+%dir %{_usr}/share/ipa/wsgi
+%{_usr}/share/ipa/wsgi/plugins.py*
+%dir %{_sysconfdir}/ipa
+%dir %{_sysconfdir}/ipa/html
+%config(noreplace) %{_sysconfdir}/ipa/html/ssbrowser.html
+%config(noreplace) %{_sysconfdir}/ipa/html/unauthorized.html
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa.conf
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-kdc-proxy.conf
+%ghost %attr(0640,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/kdcproxy/ipa-kdc-proxy.conf
+%ghost %attr(0644,root,root) %config(noreplace) %{_usr}/share/ipa/html/ca.crt
+%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-ext.conf
+%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-options-ext.conf
+%ghost %attr(0644,root,root) %{_usr}/share/ipa/html/krb.con
+%ghost %attr(0644,root,root) %{_usr}/share/ipa/html/krb5.ini
+%ghost %attr(0644,root,root) %{_usr}/share/ipa/html/krbrealm.con
+%dir %{_usr}/share/ipa/updates/
+%{_usr}/share/ipa/updates/*
+%dir %{_localstatedir}/lib/ipa
+%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup
+%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/gssproxy
+%attr(711,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore
+%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysupgrade
+%attr(755,root,root) %dir %{_localstatedir}/lib/ipa/pki-ca
+%attr(755,root,root) %dir %{_localstatedir}/lib/ipa/certs
+%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/private
+%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/passwds
+%ghost %attr(775,root,pkiuser) %{_localstatedir}/lib/ipa/pki-ca/publish
+%ghost %attr(770,named,named) %{_localstatedir}/named/dyndb-ldap/ipa
+%dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia
+%dir %{_usr}/share/ipa/schema.d
+%attr(0644,root,root) %{_usr}/share/ipa/schema.d/README
+%attr(0644,root,root) %{_usr}/share/ipa/gssapi.login
+%{_usr}/share/ipa/ipakrb5.aug
+
+%files server-dns
+%doc README.md Contributors.txt
+%license COPYING
+%config(noreplace) %{_sysconfdir}/sysconfig/ipa-dnskeysyncd
+%config(noreplace) %{_sysconfdir}/sysconfig/ipa-ods-exporter
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/dnssec
+%{_libexecdir}/ipa/ipa-dnskeysyncd
+%{_libexecdir}/ipa/ipa-dnskeysync-replica
+%{_libexecdir}/ipa/ipa-ods-exporter
+%{_sbindir}/ipa-dns-install
+%{_mandir}/man1/ipa-dns-install.1*
+%attr(644,root,root) %{_unitdir}/ipa-dnskeysyncd.service
+%attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket
+%attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service
+
+%files server-trust-ad
+%doc README.md Contributors.txt
+%license COPYING
+%{_sbindir}/ipa-adtrust-install
+%{_usr}/share/ipa/smb.conf.empty
+%attr(755,root,root) %{_libdir}/samba/pdb/ipasam.so
+%attr(755,root,root) %{plugin_dir}/libipa_cldap.so
+%{_datadir}/ipa/ipa-cldap-conf.ldif
+%{_mandir}/man1/ipa-adtrust-install.1*
+%ghost %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
+%{_sysconfdir}/dbus-1/system.d/oddjob-ipa-trust.conf
+%{_sysconfdir}/oddjobd.conf.d/oddjobd-ipa-trust.conf
+%attr(755,root,root) %{_libexecdir}/ipa/oddjob/com.redhat.idm.trust-fetch-domains
+
+# ONLY_CLIENT
+%endif
+
+
+%files client
+%doc README.md Contributors.txt
+%license COPYING
+%{_sbindir}/ipa-client-install
+%{_sbindir}/ipa-client-automount
+%{_sbindir}/ipa-certupdate
+%{_sbindir}/ipa-getkeytab
+%{_sbindir}/ipa-rmkeytab
+%{_sbindir}/ipa-join
+%{_bindir}/ipa
+%config %{_sysconfdir}/bash_completion.d
+%config %{_sysconfdir}/sysconfig/certmonger
+%{_mandir}/man1/ipa.1*
+%{_mandir}/man1/ipa-getkeytab.1*
+%{_mandir}/man1/ipa-rmkeytab.1*
+%{_mandir}/man1/ipa-client-install.1*
+%{_mandir}/man1/ipa-client-automount.1*
+%{_mandir}/man1/ipa-certupdate.1*
+%{_mandir}/man1/ipa-join.1*
+%dir %{_libexecdir}/ipa/acme
+%{_libexecdir}/ipa/acme/certbot-dns-ipa
+
+%files client-samba
+%doc README.md Contributors.txt
+%license COPYING
+%{_sbindir}/ipa-client-samba
+%{_mandir}/man1/ipa-client-samba.1*
+
+
+%files client-epn
+%doc README.md Contributors.txt
+%dir %{_sysconfdir}/ipa/epn
+%license COPYING
+%{_sbindir}/ipa-epn
+%{_mandir}/man1/ipa-epn.1*
+%{_mandir}/man5/epn.conf.5*
+%attr(644,root,root) %{_unitdir}/ipa-epn.service
+%attr(644,root,root) %{_unitdir}/ipa-epn.timer
+%attr(600,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn.conf
+%attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn/expire_msg.template
+
+%files -n python3-ipaclient
+%doc README.md Contributors.txt
+%license COPYING
+%dir %{python3_sitelib}/ipaclient
+%{python3_sitelib}/ipaclient/*.py
+%{python3_sitelib}/ipaclient/__pycache__/*.py*
+%dir %{python3_sitelib}/ipaclient/install
+%{python3_sitelib}/ipaclient/install/*.py
+%{python3_sitelib}/ipaclient/install/__pycache__/*.py*
+%dir %{python3_sitelib}/ipaclient/plugins
+%{python3_sitelib}/ipaclient/plugins/*.py
+%{python3_sitelib}/ipaclient/plugins/__pycache__/*.py*
+%dir %{python3_sitelib}/ipaclient/remote_plugins
+%{python3_sitelib}/ipaclient/remote_plugins/*.py
+%{python3_sitelib}/ipaclient/remote_plugins/__pycache__/*.py*
+%dir %{python3_sitelib}/ipaclient/remote_plugins/2_*
+%{python3_sitelib}/ipaclient/remote_plugins/2_*/*.py
+%{python3_sitelib}/ipaclient/remote_plugins/2_*/__pycache__/*.py*
+%{python3_sitelib}/ipaclient-*.egg-info
+
+
+%files client-common
+%doc README.md Contributors.txt
+%license COPYING
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/default.conf
+%ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/nssdb
+# old dbm format
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/cert8.db
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/key3.db
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/secmod.db
+# new sql format
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/cert9.db
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/key4.db
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/pkcs11.txt
+%ghost %attr(600,root,root) %config(noreplace) %{_sysconfdir}/ipa/nssdb/pwdfile.txt
+%ghost %attr(644,root,root) %config(noreplace) %{_sysconfdir}/pki/ca-trust/source/ipa.p11-kit
+%dir %{_localstatedir}/lib/ipa-client
+%dir %{_localstatedir}/lib/ipa-client/pki
+%dir %{_localstatedir}/lib/ipa-client/sysrestore
+%{_mandir}/man5/default.conf.5*
+%dir %{_usr}/share/ipa/client
+%{_usr}/share/ipa/client/*.template
+
+
+%files python-compat
+%doc README.md Contributors.txt
+%license COPYING
+
+
+%files common -f %{gettext_domain}.lang
+%doc README.md Contributors.txt
+%license COPYING
+%dir %{_usr}/share/ipa
+%dir %{_libexecdir}/ipa
+
+%files -n python3-ipalib
+%doc README.md Contributors.txt
+%license COPYING
+
+%{python3_sitelib}/ipapython/
+%{python3_sitelib}/ipalib/
+%{python3_sitelib}/ipaplatform/
+%{python3_sitelib}/ipapython-*.egg-info
+%{python3_sitelib}/ipalib-*.egg-info
+%{python3_sitelib}/ipaplatform-*.egg-info
+
+
+%if %{with ipatests}
+
+
+%files -n python3-ipatests
+%doc README.md Contributors.txt
+%license COPYING
+%{python3_sitelib}/ipatests
+%{python3_sitelib}/ipatests-*.egg-info
+%{_bindir}/ipa-run-tests-3
+%{_bindir}/ipa-test-config-3
+%{_bindir}/ipa-test-task-3
+%{_bindir}/ipa-run-tests-%{python3_version}
+%{_bindir}/ipa-test-config-%{python3_version}
+%{_bindir}/ipa-test-task-%{python3_version}
+%{_bindir}/ipa-run-tests
+%{_bindir}/ipa-test-config
+%{_bindir}/ipa-test-task
+%{_mandir}/man1/ipa-run-tests.1*
+%{_mandir}/man1/ipa-test-config.1*
+%{_mandir}/man1/ipa-test-task.1*
+
+# with ipatests
+%endif
+
+
+%if %{with selinux}
+%files selinux
+%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.*
+%ghost %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename}
+# with selinux
+%endif
+
+%changelog
+* Tue Oct 5 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-9
+- Resolves: rhbz#2010195 ipa-server-install fails while 'configuring certificate server instance'
+  - Parse getStatus as JSON not XML
+  - Parse cert chain as JSON not XML
+  - Specify PKI installation log paths
+  - Make Dogtag return XML for ipa cert-find
+
+* Fri Sep 17 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-8
+- Resolves: rhbz#2004922  ipa cert-request replaces user certificate instead of adding
+ - Don't store entries with a usercertificate in the LDAP cache
+ - ipatests: Test that a user can be issued multiple certificates
+
+* Fri Sep 10 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-7
+- Resolves: rhbz#2000629 AVC denied { read } comm="ipa-custodia" on aarch64 during installation of ipa-server
+  - selinux policy: allow custodia to access /proc/cpuinfo
+- Resolves: rhbz#2000269 extdom: LDAP_INVALID_SYNTAX returned instead of LDAP_NO_SUCH_OBJECT
+  - extdom: return LDAP_NO_SUCH_OBJECT if domains differ
+- Resolves: rhbz#2000947 subid: subid-match displays the DN of the owner, not its UID.
+  - subid: subid-match: display the owner's ID not DN
+- Resolves: rhbz #2002285 ipa migrate-ds command fails to warn when compat plugin is enabled
+  - migrate-ds: workaround to detect compat tree
+
+* Thu Aug 26 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-6
+- Resolves: rhbz#1998098 - Backport latest test fixes in python3-ipatests
+  - ipatests: Test unsecure nsupdate.
+  - ipatests: Fix TestAJPSecretUpgrade tests on systems without pkiuser
+  - ipatests: test_ipahealthcheck: Verify permissions for /var/log/ files
+  - ipatests: test to renew certs on replica using ipa-cert-fix
+  - ipatests: wait while http/ldap/pkinit cert get renew on replica
+  - ipatests: refactor test_ipa_cert_fix with tasks
+  - ipatests: use whole date for journalctl --since
+* Tue Aug 17 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-5
+- Resolves: rhbz#1988383 Do SRV discovery in ipa-getkeytab if -s and -H aren't provided 
+  - ipa-getkeytab: add option to discover servers using DNS SRV
+  - ipa-getkeytab: fix compiler warnings
+  - ipatests: test ipa-getkeytab server option
+- Resolves: rhbz#1986329 ipa-server install failure without DNS
+  - Fix ldapupdate.get_sub_dict() for missing named user
+- Resolves: rhbz#1980734 Remove python3-pexpect as dependency for ipatests pkg
+  - freeipa.spec.in: remove python3-pexpect from Requires
+- Resolves: rhbz#1992538 Backport recent test fixes in python3-ipatests
+  - ipatests: use whole date when calling journalctl --since
+  - ipatests: Fix for test_source_ipahealthcheck_ipa_host_check_ipahostkeytab
+  - ipatests: test_ipahealthcheck: print a message if a system is healthy
+  - ipatests: test_installation: move tracking_reqs dependency to ipalib constants ipaserver: krainstance: utilize moved tracking_reqs dependency
+  - webui tests: close notification when revoking cert
+  - ipatests: Test ipa-cert-fix warns when startup directive is missing from CS.cfg
+  - webui tests: fix algo for finding available idrange
+  - ipatests: smbclient "-k" => "--use-kerberos=desired"
+  - test_acme: refactor with tasks
+  - test_acme: make password renewal more robust
+  - tasks.py: fix flake8-reported issues
+  - ipatests: Test for OTP when the LDAP connection timed out.
+  - ipatests: verify that getcert output includes the issued date
+  - ipatests: Look for warning into stderr instead of stdout
+  - ipatests: use krb5_trace in TestIpaAdTrustInstall
+  - ipatests: Test ldapsearch with base scope works with compat tree.
+  - ipatests: skip test_basesearch_compat_tree on fedora.
+  - ipatests: Refactor test_check_otpd_after_idle_timeout
+
+* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 4.9.6-4.1
+- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
+  Related: rhbz#1991688
+
+* Fri Jul 23 2021 Rob Crittenden <rcritten@redhat.com> - 4.9.6-4
+- Use new method in check to prevent removal of last KRA (#1985072)
+- ipatests: NAMED_CRYPTO_POLICY_FILE not defined for RHEL (#1982952)
+- Fix index definition for memberOf (#1952028)
+
+* Thu Jul 15 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-3
+- Resolves: rhbz#1979629 Add checks to prevent assigning authentication indicators to internal IPA services
+- Resolves: rhbz#1982212 ipa-trust-add fails with "not enough quota"
+- Resolves: rhbz#1952028 [RFE] Add support for managing subuids and subgids in FreeIPA
+- Resolves: rhbz#1981789 [man page] contradiction in ipa-server-upgrade command's man page and usage
+
+* Fri Jul 9 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-2
+- Resolves: rhbz#1955440 ipa installation fails to configure chrony
+- Resolves: rhbz#1976761 Package python3-ipatests (from CRB repo) Requires python3-coverage
+- Resolves: rhbz#1979609 Unable to set ipaUserAuthType with stageuser-add
+- Resolves: rhbz#1979629 Add checks to prevent assigning authentication indicators to internal IPA services
+
+* Wed Jun 30 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.6-1
+- Resolves: rhbz#1969351 Rebase IPA to latest 4.9.x version
+- Resolves: rhbz#1976288 ansible-freeipa automember test fails with `automember_add_condition: testgroup: 'objectclass'` due to ldap cache
+- Resolves: rhbz#1975139 Upgrade error: Add failure missing required attribute "objectclass"
+- Resolves: rhbz#1973024 CA_less ipa-server-install fails if CA cert subject contains non ascii chars
+- Resolves: rhbz#1966101 [RFE] - IDM - Allow specifying permanent logging settings for BIND
+- Resolves: rhbz#1962570 IPA in c9s should not require redhat-logos-ipa as a runtime package 
+- Resolves: rhbz#1957736 [RFE] IPA to allow configuring auto-private-groups at idrange level 
+
+* Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 4.9.3-2.1
+- Rebuilt for RHEL 9 BETA for openssl 3.0
+  Related: rhbz#1971065
+
+* Tue Apr 20 2021 Florence Blanc-Renaud <frenaud@redhat.com> - 4.9.3-2
+- RHEL 9 Beta mass rebuild. Resolves: rhbz#1951304
+
+* Wed Mar 31 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.3-1
+- Upstream release FreeIPA 4.9.3
+
+* Fri Feb 26 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.2-4
+- Rebuild against 389-ds and PKI to fix https://github.com/389ds/389-ds-base/issues/4609
+
+* Tue Feb 23 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.2-3
+- Only use python-platform on RHEL 8
+
+* Mon Feb 15 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.2-2
+- Fix ipatests dependency to python3-pexpect
+
+* Mon Feb 15 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.2-1
+- Upstream release FreeIPA 4.9.2
+
+* Wed Jan 27 2021 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.1-1
+- Upstream release FreeIPA 4.9.1
+
+* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 4.9.0-2.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
+
+* Wed Jan 20 2021 Rob Crittenden <rcritten@redhat.com> - 4.9.0-2
+- Set client keytab location for 389ds (RHBZ#1918075)
+
+* Wed Dec 23 17:05:00 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-1
+- FreeIPA 4.9.0 final release
+
+* Wed Dec 16 07:52:00 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.6.rc3
+- Refactor DNSSEC paths creation code (upstream PR#5340)
+
+* Thu Dec 10 20:06:03 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.5.rc3
+- FreeIPA 4.9.0 release candidate 3
+- Enforce C.UTF-8 locale in systemd service units
+- Fold up fixes from Rawhide and RHEL 8.4 testing
+
+* Wed Dec  9 20:06:03 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.4.rc2
+- Fix upgrade script for CA rule rewrites
+- Fix permissions for /run/ipa/ccaches
+
+* Fri Dec  4 22:17:00 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.3.rc2
+- Correct SELinux policy requirements
+
+* Fri Dec  4 13:41:28 EET 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.2.rc2
+- FreeIPA 4.9.0 release candidate 2
+
+* Thu Nov 19 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.1.rc1
+- Use correct bind PKCS11 engine dependencies
+- Fix SELinux build requirement
+- Fix linting requirements
+
+* Wed Nov 18 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.9.0-0.rc1
+- FreeIPA 4.9.0 release candidate 1
+- Synchronize spec file with upstream and RHEL
+
+* Wed Oct 28 2020 Adam Williamson <awilliam@redhat.com> - 4.8.10-7
+- Backport #5212 for deployment failures with 389-ds-base 1.4.4.6+
+
+* Tue Oct 13 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-6
+- Handle sshd_config upgrade properly
+  Fixes: rhbz#1887928
+
+* Tue Sep 29 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-5
+- Properly handle upgrade case when systemd-resolved is enabled
+
+* Mon Sep 28 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-4
+- Fix permissions for /etc/systemd/resolved.conf.d/zzz-ipa.conf
+- Add NetworkManager and systemd-resolved configuration files to backup
+
+* Sun Sep 27 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-3
+- Fix dependency between freeipa-selinux and freeipa-common
+- Resolves: rhbz#1883005
+
+* Sat Sep 26 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-2
+- Support upgrade F32 -> F33 with systemd-resolved
+
+* Sat Sep 26 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.10-1
+- Upstream release FreeIPA 4.8.10
+
+* Fri Aug 21 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.9-2
+- Backport fix for detecting older installations on upgrade
+
+* Thu Aug 20 2020 François Cami <fcami@redhat.com> - 4.8.9-1
+- Upstream release FreeIPA 4.8.9
+
+* Mon Aug 03 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.7-5
+- Make use of unshare+chroot in ipa-extdom-extop unittests to work against glibc 2.32
+
+* Sat Aug 01 2020 Fedora Release Engineering <releng@fedoraproject.org> - 4.8.7-4
+- Second attempt - Rebuilt for
+  https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
+
+* Thu Jul 30 2020 Merlin Mathesius <mmathesi@redhat.com> - 4.8.7-3
+- Conditional fixes for ELN to set krb5-kdb version appropriately
+
+* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 4.8.7-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
+
+* Wed Jun 10 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.7-1
+- Upstream release FreeIPA 4.8.7
+
+* Tue May 26 2020 Miro Hrončok <mhroncok@redhat.com> - 4.8.6-2
+- Rebuilt for Python 3.9
+
+* Fri Mar 27 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.6-1
+- Upstream release FreeIPA 4.8.6
+
+* Sat Mar 21 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.5-2
+- Roll up post-release fixes from upstream
+- Move freeipa-selinux to be a dependency of freeipa-common
+
+* Wed Mar 18 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.5-1
+- Upstream release FreeIPA 4.8.5
+- Depend on selinux-policy-devel 3.14.6-9 for build due to a makefile issue in
+  SELinux external policy support
+
+* Tue Mar 03 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.4-8
+- Support opendnssec 2.1
+- Resolves: #1809492
+
+* Mon Feb 17 2020 François Cami <fcami@redhat.com> - 4.8.4-7
+- Fix audit_as_req() callback usage
+- Resolves: #1803786
+
+* Sat Feb 01 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.4-6
+- Fix constraint delegation for krb5 1.18 update
+- Resolves: #1797096
+
+* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 4.8.4-5
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
+
+* Tue Jan 28 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.4-4
+- Rebuild against krb5 1.18 beta
+
+* Sun Jan 26 2020 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.4-3
+- Rebuild against Samba 4.12RC1
+
+* Mon Dec 16 2019 Adam Williamson <awilliam@redhat.com> - 4.8.4-2
+- Backport PR #4045 to fix overlapping DNS zone check bugs
+
+* Sat Dec 14 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.4-1
+- New upstream release 4.8.4
+
+* Tue Nov 26 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.3-1
+- New upstream release 4.8.3
+- CVE-2019-14867: Denial of service in IPA server due to wrong use of ber_scanf()
+- CVE-2019-10195: Don't log passwords embedded in commands in calls using batch
+
+* Tue Nov 12 2019 Rob Crittenden <rcritten@redhat.com> - 4.8.2-1
+- New upstream release 4.8.2
+- Replace %%{_libdir} macro in BuildRequires (#1746882)
+- Restore user-nsswitch.conf before calling authselect (#1746557)
+- ipa service-find does not list cifs service created by
+  ipa-client-samba (#1731433)
+- Occasional 'whoami.data is undefined' error in FreeIPA web UI
+  (#1699109)
+- ipa-kra-install fails due to fs.protected_regular=1 (#1698384)
+
+* Sun Oct 20 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.1-5
+- Don't create log files from helper scripts
+- Fixes: rhbz#1754189
+
+* Tue Oct 08 2019 Christian Heimes <cheimes@redhat.com> - 4.8.1-4
+- Fix compatibility issue with preexec_fn in Python 3.8
+- Fixes: rhbz#1759290
+
+* Tue Oct  1 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.1-3
+- Fix ipasam for compatibility with Samba 4.11
+- Fixes: rhbz#1757089
+
+* Mon Aug 19 2019 Miro Hrončok <mhroncok@redhat.com> - 4.8.1-2
+- Rebuilt for Python 3.8
+
+* Wed Aug 14 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.1-1
+- New upstream release 4.8.1
+- Fixes: rhbz#1732528
+- Fixes: rhbz#1732524
+
+* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 4.8.0-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
+
+* Wed Jul 03 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.8.0-1
+- New upstream release 4.8.0
+- New subpackage: freeipa-client-samba
+
+* Sat May 11 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.90.pre1-6
+- Upgrade: handle situation when trusts were configured but not established yet
+  Fixed: rhbz#1708808
+
+* Fri May  3 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.90.pre1-5
+- Add krb5-kdb-server dependency provided by krb5-server >= 1.17-17
+
+* Fri May  3 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.90.pre1-4
+- Rebuild to drop upper limit for Kerberos package
+  After krb5-server will provide krb5-kdb-version, we'll switch to it
+
+* Wed May  1 2019 Adam Williamson <awilliam@redhat.com> - 4.7.90.pre1-3
+- Backport PR #3104 to fix a font path error
+
+* Wed May  1 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.90.pre1-2
+- Revert MINSSF defaults because realmd cannot join FreeIPA right now
+  as it uses anonymous LDAP connection for the discovery and validation
+
+* Mon Apr 29 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.90.pre1-1
+- First release candidate for FreeIPA 4.8.0
+
+* Sat Apr 06 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-8
+- Fixed: rhbz#1696963 (Failed to install replica)
+  
+* Sat Apr 06 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-7
+- Support Samba 4.10
+- Support 389-ds 1.4.1.2-2.fc30 or later
+
+* Thu Feb 28 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-6
+- Support new nfs-utils behavior (#1668836)
+- ipa-client-automount now works without /etc/sysconfig/nfs
+
+* Tue Feb 19 2019 François Cami <fcami@redhat.com> - 4.7.2-5
+- Fix FTBS due to Samba having removed talloc_strackframe.h
+  and memory.h (#1678670)
+- Fix CA setup when fs.protected_regular=1 (#1677027)
+
+* Mon Feb 11 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-4
+- Disable python dependency generator in Rawhide as not all required packages support it yet
+- Require python-kdcproxy 0.4.1 or later on Rawhide
+
+* Fri Feb 8 2019 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-3
+- Fix compile issues after a mass rebuild using upstream patches
+
+* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 4.7.2-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
+
+* Mon Dec 03 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.2-1
+- Upstream release FreeIPA 4.7.2
+
+* Wed Nov 28 2018 Adam Williamson <awilliam@redhat.com> - 4.7.1-4
+- Update PR #2610 patch to tiran's modified version
+
+* Tue Nov 27 2018 Adam Williamson <awilliam@redhat.com> - 4.7.1-3
+- Backport PR #2610 to fix for authselect 1.0.2+ (see #1645708)
+
+* Sun Nov 11 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.7.1-2
+- Rebuild for krb5-1.17 (#1648673)
+- Bump required SSSD version to 2.0.0-4 to get back pysss.getgrouplist() API
+
+* Fri Oct  5 2018 Rob Crittenden <rcritten@redhat.com> - 4.7.1-1
+- Update to upstream 4.7.1
+
+* Tue Sep 25 2018 Christian Heimes <cheimes@redhat.com> - 4.7.0-5
+- Remove Python 2 support from Fedora 30
+- https://fedoraproject.org/wiki/Changes/FreeIPA_Python_2_Removal
+
+* Tue Sep  4 2018 Thomas Woerner <twoerner@redhat.com> - 4.7.0-4
+- Enable python2 client packages for f30 for now again
+
+* Tue Sep  4 2018 Thomas Woerner <twoerner@redhat.com> - 4.7.0-3
+- Force generation of aclocal.m4 and configuration scripts
+- Fix only client build for Fedora>=28 and RHEL>7
+- Bring back special patch handling for Fedora
+
+* Mon Sep  3 2018 Thomas Woerner <twoerner@redhat.com> - 4.7.0-2
+- Restore SELinux context of session_dir /etc/httpd/alias (pagure#7662)
+- Restore SELinux context of template_dir /var/log/dirsrv/slapd-X (pagure#7662)
+- Add "389-ds-base-legacy-tools" to requires
+- Refactor os-release and platform information (#1609475)
+- Don't check for systemd service (#1609475)
+- Switched to upstream spec file with small adaptions
+
+* Thu Jul 26 2018 Thomas Woerner <twoerner@redhat.com> - 4.7.0-1
+- Update to upstream 4.7.0
+- New BuildRequires for nodejs and uglify-js
+- New Requires for 389-ds-base-legacy-tools in server (RHBZ#1606541)
+- Do not build python2-ipaserver and python2-ipatests for Fedora 29 and up
+- Do not build any python2 packages for Fedora 30
+- Added ipatest man pages to python3-ipatests packages also
+- Added ipatest bindir links to python3-ipatests for Fedora up to 28
+- Dropped explicit copy of freeipa.template, install is doing this now
+- Added upstream fix: (f3faecb) Fix $-style format string in ipa_ldap_init
+- Added upstream fix: (4b592fe,1a7baa2) Added reason to raise of errors.NotFound
+
+* Mon Jul 16 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre2-11
+- Use version-aware macros for Python
+
+* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 4.6.90.pre2-10
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
+
+* Mon Jul 02 2018 Miro Hrončok <mhroncok@redhat.com> - 4.6.90.pre2-9
+- Rebuilt for Python 3.7
+
+* Wed Jun 27 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-8
+- Build UI using py3-lesscpy
+
+* Tue Jun 19 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-7
+- *-domainname.service moved to the hostname package in F29 (#1592355)
+
+* Tue Jun 19 2018 Miro Hrončok <mhroncok@redhat.com> - 4.6.90.pre2-6
+- Rebuilt for Python 3.7
+
+* Fri Jun 15 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-5
+- Change BuildRequires from python-lesscpy to python3-lesscpy
+
+* Fri Jun 15 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-4.1
+- Rename service fedora-domainname.service to nis-domainname.service
+  (#1588192)
+- Fix bad date in changelog
+
+* Wed May 16 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre2-3
+- Fine tune packaging of server templates so that it doesn't include
+  freeipa.template which always go to freeipa-client-common
+
+* Tue May 15 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-2
+- Exclude /usr/share from client-only builds
+
+* Tue May 15 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre2-1
+- Update to upstream 4.6.90.pre2
+
+* Wed May 02 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre1-7
+- Fix upgrade when named.conf does not exist
+- Resolves rhbz#1573671
+- Requires newer slapi-nis to avoid hitting rhbz#1573636
+
+* Wed Mar 21 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre1-6.1
+- Change upgrade code to use DIR-based ccache and no kinit (#1558818)
+- Require pki-symkey until pki-core has proper dependencies
+
+* Wed Mar 21 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre1-6
+- Change upgrade code to use DIR-based ccache and no kinit (#1558818)
+
+* Tue Mar 20 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre1-5
+- Apply upstream fix for #1558354
+- Run upgrade under file-based ccache (#1558818)
+- Fix OTP token issuance due to regression in https://pagure.io/389-ds-base/issue/49617
+
+* Tue Mar 20 2018 Adam Williamson <awilliam@redhat.com> - 4.6.90.pre1-4
+- Fix upgrades harder (extension of -3 patch) (#1558354)
+
+* Tue Mar 20 2018 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.90.pre1-3
+- Fix upgrade from F27 to F28 (#1558354)
+
+* Mon Mar 19 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre1-2
+- Patch to fix GUI login for non-admin users (#1557609)
+
+* Fri Mar 16 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.90.pre1-1
+- Update to upstream 4.6.90.pre1
+
+* Tue Feb 20 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.3-5
+- Disable i686 server builds because 389-ds no longer provides
+  builds on that arch. (#1544386)
+
+* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 4.6.3-4
+- Escape macros in %%changelog
+
+* Thu Feb  8 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.3-3
+- Don't fail on upgrades if KRA is not installed
+- Remove Conflicts between mod_wsgi and python3-mod_wsgi
+
+* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 4.6.3-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
+
+* Wed Jan 31 2018 Rob Crittenden <rcritten@redhat.com> - 4.6.3-1
+- Update to upstream 4.6.3
+
+* Wed Jan 03 2018 Lumír Balhar <lbalhar@redhat.com> - 4.6.1-5
+- Fix directory ownership in python3 subpackage
+
+* Tue Oct 17 2017 Rob Crittenden <rcritten@redhat.com> - 4.6.1-4
+- Update workaround patch to prevent SELinux execmem AVC (#1491508)
+
+* Mon Oct 16 2017 Alexander Bokovoy <abokovoy@redhat.com> - 4.6.1-3
+- Another attempt at fix for bug #1491053
+
+* Fri Oct 06 2017 Tomas Krizek <tkrizek@redhat.com> - 4.6.1-2
+- Rebuild against krb5-1.16
+
+* Fri Sep 22 2017 Tomas Krizek <tkrizek@redhat.com> - 4.6.1-1
+- Fixes #1491053  Firefox reports insecure TLS configuration when visiting
+  FreeIPA web UI after standard server deployment
+
+* Wed Sep 13 2017 Adam Williamson <awilliam@redhat.com> - 4.6.0-3
+- Fixes #1490762 Ipa-server-install update dse.ldif with wrong SELinux context
+- Fixes #1491056 FreeIPA enrolment via kickstart fails
+
+* Wed Sep 06 2017 Adam Williamson <awilliam@redhat.com> - 4.6.0-2
+- Fixes #1488640 "unknown command 'undefined'" error when changing password in web UI
+- BuildRequires diffstat (for the use in patch application)
+
+* Mon Sep 04 2017 Tomas Krizek <tkrizek@redhat.com> - 4.6.0-1
+- Rebase to upstream 4.6.0
+
+* Wed Aug 02 2017 Fedora Release Engineering <releng@fedoraproject.org> - 4.5.3-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
+
+* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 4.5.3-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
+
+* Fri Jul 21 2017 Tomas Krizek <tkrizek@redhat.com> - 4.5.3-1
+- Update to upstream 4.5.3 - see https://www.freeipa.org/page/Releases/4.5.3
+
+* Thu Jul 13 2017 Alexander Bokovoy <abokovoy@redhat.com> - 4.5.2-4
+- Make sure tmpfiles.d snippet for replica is in place after install
+
+* Mon Jul 10 2017 Alexander Bokovoy <abokovoy@redhat.com> - 4.5.2-3
+- Fix build with Samba 4.7.0-RC1
+- Increase java stack for rhino calls to get around crashes on ppc64-le
+
+* Tue Jun 20 2017 Tomas Krizek <tkrizek@redhat.com> - 4.5.2-2
+- Patch: Fix IP address checks
+- Patch: python-netifaces fix
+
+* Sun Jun 18 2017 Tomas Krizek <tkrizek@redhat.com> - 4.5.2-1
+- Update to upstream 4.5.2 - see https://www.freeipa.org/page/Releases/4.5.2
+
+* Thu May 25 2017 Tomas Krizek <tkrizek@redhat.com> - 4.5.1-1
+- Update to upstream 4.5.1 - see https://www.freeipa.org/page/Releases/4.5.1
+- Fixes #1168266 UI drops "Enknown Error" when the ipa record in /etc/hosts changes
+
+* Tue May 23 2017 Tomas Krizek <tkrizek@redhat.com> - 4.4.4-2
+- Fixes #1448049 Subpackage freeipa-server-common has unmet dependencies on Rawhide
+- Fixes #1430247 FreeIPA server deployment runs ipa-custodia on Python 3, should use Python 2
+- Fixes #1446744 python2-ipaclient subpackage does not own %%{python_sitelib}/ipaclient/plugins
+- Fixes #1440525 surplus 'the' in output of `ipa-adtrust-install`
+- Fixes #1411810 ipa-replica-install fails with 406 Client Error
+- Fixes #1405814 ipa plugins: ERROR an internal error occured
+
+* Fri Mar 24 2017 Tomas Krizek <tkrizek@redhat.com> - 4.4.4-1
+- Update to upstream 4.4.4 - see https://www.freeipa.org/page/Releases/4.4.4
+- Add upstream signature file for tarball
+
+* Wed Mar  1 2017 Alexander Bokovoy <abokovoy@redhat.com> - 4.4.3-8
+- Use different method to keep /usr/bin/ipa on Python 2
+- Fixes #1426847
+
+* Mon Feb 27 2017 Tomas Krizek <tkrizek@redhat.com> - 4.4.3-7
+- Fixes #1413137 CVE-2017-2590 ipa: Insufficient permission check for
+  ca-del, ca-disable and ca-enable commands
+
+* Mon Feb 27 2017 Alexander Bokovoy <abokovoy@redhat.com> - 4.4.3-6
+- Rebuild to pick up system-python dependency change
+- Fixes #1426847 - Cannot upgrade freeipa-client on rawhide
+
+* Wed Feb 15 2017 Tomas Krizek <tkrizek@redhat.com> - 4.4.3-5
+- Fixes #1403352 - bind-dyndb-ldap: support new named.conf API in BIND 9.11
+- Fixes #1412739 - ipa-kdb: support DAL version 6.1
+
+* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 4.4.3-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Sat Jan 21 2017 Igor Gnatenko <ignatenko@redhat.com> - 4.4.3-3
+- Rebuild for xmlrpc-c
+
+* Thu Dec 22 2016 Miro Hrončok <mhroncok@redhat.com> - 4.4.3-2
+- Rebuild for Python 3.6
+
+* Fri Dec 16 2016 Pavel Vomacka <pvomacka@redhat.com> - 4.4.3-1
+- Update to upstream 4.4.3 - see http://www.freeipa.org/page/Releases/4.4.3
+
+* Wed Dec 14 2016 Pavel Vomacka <pvomacka@redhat.com> - 4.4.2-4
+- Fixes 1395311 - CVE-2016-9575 ipa: Insufficient permission check in certprofile-mod
+- Fixes 1370493 - CVE-2016-7030 ipa: DoS attack against kerberized services
+  by abusing password policy
+
+* Tue Nov 29 2016 Petr Vobornik <pvoborni@redhat.com> - 4.4.2-3
+- Fixes 1389866  krb5-server: ipadb_change_pwd(): kdb5_util killed by SIGSEGV
+
+* Fri Oct 21 2016 Petr Vobornik <pvoborni@redhat.com> - 4.4.2-2
+- Rebuild against krb5-1.15
+
+* Thu Oct 13 2016 Petr Vobornik <pvoborni@redhat.com> - 4.4.2-1
+- Update to upstream 4.4.2 - see http://www.freeipa.org/page/Releases/4.4.2
+
+* Thu Sep 01 2016 Alexander Bokovoy <abokovoy@redhat.com> - 4.4.1-1
+- Update to upstream 4.4.1 - see http://www.freeipa.org/page/Releases/4.4.1
+
+* Fri Aug 19 2016 Petr Vobornik <pvoborni@redhat.com> - 4.3.2-2
+- Fixes 1365669 - The ipa-server-upgrade command failed when named-pkcs11 does
+  not happen to run during dnf upgrade
+- Fixes 1367883 - CVE-2016-5404 freeipa: ipa: Insufficient privileges check
+  in certificate revocation
+- Fixes 1364338 - Freeipa cannot be build on fedora 25
+
+* Fri Jul 22 2016 Petr Vobornik <pvoborni@redhat.com> - 4.3.2-1
+- Update to upstream 4.3.2 - see http://www.freeipa.org/page/Releases/4.3.2
+
+* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 4.3.1-2
+- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages
+
+* Thu Mar 24 2016 Petr Vobornik <pvoborni@redhat.com> - 4.3.1-1
+- Update to upstream 4.3.1 - see http://www.freeipa.org/page/Releases/4.3.1
+
+* Thu Feb 04 2016 Petr Vobornik <pvoborni@redhat.com> - 4.3.0-3
+- Fix build with Samba 4.4
+- Update SELinux requires to fix connection check during installation
+
+* Wed Feb 03 2016 Fedora Release Engineering <releng@fedoraproject.org> - 4.3.0-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
+
+* Fri Dec 18 2015 Petr Vobornik <pvoborni@redhat.com> - 4.3.0-1
+- Update to upstream 4.3.0 - see http://www.freeipa.org/page/Releases/4.3.0
+
+* Mon Dec 07 2015 Petr Vobornik <pvoborni@redhat.com> - 4.2.3-2
+- Workarounds for SELinux execmem violations in cryptography
+
+* Mon Nov 02 2015 Petr Vobornik <pvoborni@redhat.com> - 4.2.3-1
+- Update to upstream 4.2.3 - see http://www.freeipa.org/page/Releases/4.2.3
+- fix #1274905
+
+* Wed Oct 21 2015 Alexander Bokovoy <abokovoy@redhat.com> - 4.2.2-2
+- Depend on samba-common-tools for the trust-ad subpackage after
+  samba package split
+- Rebuild against krb5 1.14 to fix bug #1273957
+
+* Thu Oct 8 2015 Petr Vobornik <pvoborni@redhat.com> - 4.2.2-1
+- Update to upstream 4.2.2 - see http://www.freeipa.org/page/Releases/4.2.2
+
+* Mon Sep 7 2015 Petr Vobornik <pvoborni@redhat.com> - 4.2.1-1
+- Update to upstream 4.2.1 - see http://www.freeipa.org/page/Releases/4.2.1
+
+* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 4.1.4-5
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
+
+* Tue May 12 2015 Alexander Bokovoy <abokovoy@redhat.com> - 4.1.4-4
+- Fix typo in the patch to fix bug #1219834
+
+* Mon May 11 2015 Alexander Bokovoy <abokovoy@redhat.com> - 4.1.4-3
+- Fix FreeIPA trusts to AD feature with Samba 4.2 (#1219834)
+
+* Mon Mar 30 2015 Petr Vobornik <pvoborni@redhat.com> - 4.1.4-2
+- Replace mod_auth_kerb usage with mod_auth_gssapi
+
+* Thu Mar 26 2015 Alexander Bokovoy <abokovoy@redhat.com> - 4.1.4-1
+- Update to upstream 4.1.4 - see http://www.freeipa.org/page/Releases/4.1.4
+- fix CVE-2015-1827 (#1206047)
+- Require slapi-nis 0.54.2 and newer for CVE-2015-0283 fixes
+
+* Tue Mar 17 2015 Petr Vobornik <pvoborni@redhat.com> - 4.1.3-3
+- Timeout ipa-client install if ntp server is unreachable #4842
+- Skip time sync during client install when using --no-ntp #4842
+
+* Wed Mar 04 2015 Petr Vobornik <pvoborni@redhat.com> - 4.1.3-2
+- Add missing sssd python dependencies
+- https://bugzilla.redhat.com/show_bug.cgi?id=1197218
+
+* Wed Feb 18 2015 Petr Vobornik <pvoborni@redhat.com> - 4.1.3-1
+- Update to upstream 4.1.3 - see http://www.freeipa.org/page/Releases/4.1.3
+
+* Mon Jan 19 2015 Alexander Bokovoy <abokovoy@redhat.com> - 4.1.2-2
+- Fix broken build after Samba ABI change and rename of libpdb to libsamba-passdb
+- Use python-dateutil15 until we validate python-dateutil 2.x
+
+* Tue Nov 25 2014 Petr Vobornik <pvoborni@redhat.com> - 4.1.2-1
+- Update to upstream 4.1.2 - see http://www.freeipa.org/page/Releases/4.1.2
+- fix CVE-2014-7850
+
+* Thu Nov 20 2014 Simo Sorce <simo@redhat.com> - 4.1.1-2
+- Patch blokers and feature freze exceptions
+- Resolves: bz1165674
+- Resolves: bz1165856 (CVE-2014-7850)
+- Fixes DNS install issue that prevents the server from working
+
+* Thu Nov 06 2014 Petr Vobornik <pvoborni@redhat.com> - 4.1.1-1
+- Update to upstream 4.1.1 - see http://www.freeipa.org/page/Releases/4.1.1
+- fix CVE-2014-7828
+
+* Wed Oct 22 2014 Petr Vobornik <pvoborni@redhat.com> - 4.1.0-2
+- fix armv7hl stack oversize build failure
+- fix https://fedorahosted.org/freeipa/ticket/4660
+
+* Tue Oct 21 2014 Petr Vobornik <pvoborni@redhat.com> - 4.1.0-1
+- Update to upstream 4.1.0 - see http://www.freeipa.org/page/Releases/4.1.0
+
+* Fri Sep 12 2014 Petr Viktorin <pviktori@redhat.com> - 4.0.3-1
+- Update to upstream 4.0.3 - see http://www.freeipa.org/page/Releases/4.0.3
+
+* Fri Sep 05 2014 Petr Viktorin <pviktori@redhat.com> - 4.0.2-1
+- Update to upstream 4.0.1 - see http://www.freeipa.org/page/Releases/4.0.2
+
+* Tue Sep 02 2014 Pádraig Brady <pbrady@redhat.com> - 4.0.1-3
+- rebuild for libunistring soname bump
+
+* Sat Aug 16 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 4.0.1-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
+
+* Fri Jul 25 2014 Martin Kosek <mkosek@redhat.com> 4.0.1-1
+- Update to upstream 4.0.1
+
+* Mon Jul 07 2014 Petr Viktorin <pviktori@redhat.com> 4.0.0-1
+- Update to upstream 4.0.0
+- Remove the server-strict package
+
+* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.3.5-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
+
+* Wed May 21 2014 Petr Vobornik <pvoborni@redhat.com> 3.3.5-3
+- Increase Java stack size for Web UI build on aarch64
+
+* Wed Apr 16 2014 Peter Robinson <pbrobinson@fedoraproject.org> 3.3.5-2
+- Add rhino as dependency to fix FTBFS
+
+* Fri Mar 28 2014 Martin Kosek <mkosek@redhat.com> - 3.3.5-1
+- Update to upstream 3.3.5
+
+* Tue Feb 11 2014 Martin Kosek <mkosek@redhat.com> - 3.3.4-3
+- Move ipa-otpd socket directory to /var/run/krb5kdc
+- Require krb5-server 1.11.5-3 supporting the new directory
+- ipa_lockout plugin did not work with users's without krbPwdPolicyReference
+
+* Wed Jan 29 2014 Martin Kosek <mkosek@redhat.com> - 3.3.4-2
+- Fix hardened build
+
+* Tue Jan 28 2014 Martin Kosek <mkosek@redhat.com> - 3.3.4-1
+- Update to upstream 3.3.4
+- Install CA anchor into standard location (#928478)
+- ipa-client-install part of ipa-server-install fails on reinstall (#1044994)
+- Remove mod_ssl workaround (RHEL bug #1029046)
+- Enable syncrepl plugin to support bind-dyndb-ldap 4.0
+
+* Fri Jan 3 2014 Martin Kosek <mkosek@redhat.com> - 3.3.3-5
+- Build crashed with rhino exception on s390 architectures (#1040576)
+
+* Thu Dec 12 2013 Martin Kosek <mkosek@redhat.com> - 3.3.3-4
+- Build crashed with rhino exception on PPC architectures (#1040576)
+
+* Tue Dec 3 2013 Martin Kosek <mkosek@redhat.com> - 3.3.3-3
+- Fix -Werror=format-security errors (#1037070)
+
+* Mon Nov 4 2013 Martin Kosek <mkosek@redhat.com> - 3.3.3-2
+- ipa-server-install crashed when freeipa-server-trust-ad subpackage was not
+  installed
+
+* Fri Nov 1 2013 Martin Kosek <mkosek@redhat.com> - 3.3.3-1
+- Update to upstream 3.3.3
+
+* Fri Oct 4 2013 Martin Kosek <mkosek@redhat.com> - 3.3.2-1
+- Update to upstream 3.3.2
+
+* Thu Aug 29 2013 Petr Viktorin <pviktori@redhat.com> - 3.3.1-1
+- Bring back Fedora-only changes
+
+* Thu Aug 29 2013 Petr Viktorin <pviktori@redhat.com> - 3.3.1-0
+- Update to upstream 3.3.1
+
+* Wed Aug 14 2013 Alexander Bokovoy <abokovoy@redhat.com> - 3.3.0-2
+- Remove freeipa-systemd-upgrade as non-systemd installs are not supported
+  anymore by Fedora project
+
+* Wed Aug 7 2013 Martin Kosek <mkosek@redhat.com> - 3.3.0-1
+- Update to upstream 3.3.0
+
+* Sat Aug 03 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.2.2-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild
+
+* Wed Jul 17 2013 Martin Kosek <mkosek@redhat.com> - 3.2.2-1
+- Update to upstream 3.2.2
+- Drop freeipa-server-selinux subpackage
+- Drop redundant directory /var/cache/ipa/sessions
+- Do not create /var/lib/ipa/pki-ca/publish, retain reference as ghost
+- Run ipa-upgradeconfig and server restart in posttrans to avoid inconsistency
+  issues when there are still old parts of software (like entitlements plugin)
+
+* Fri Jun  7 2013 Martin Kosek <mkosek@redhat.com> - 3.2.1-1
+- Update to upstream 3.2.1
+
+* Tue May 14 2013 Rob Crittenden <rcritten@redhat.com> - 3.2.0-2
+- Add OTP patches
+- Add patch to set KRB5CCNAME for 389-ds-base
+
+* Fri May 10 2013 Rob Crittenden <rcritten@redhat.com> - 3.2.0-1
+- Update to upstream 3.2.0 GA
+- ipa-client-install fails if /etc/ipa does not exist (#961483)
+- Certificate status is not visible in Service and Host page (#956718)
+- ipa-client-install removes needed options from ldap.conf (#953991)
+- Handle socket.gethostbyaddr() exceptions when verifying hostnames (#953957)
+- Add triggerin scriptlet to support OpenSSH 6.2 (#953617)
+- Require nss 3.14.3-12.0 to address certutil certificate import
+  errors (#953485)
+- Require pki-ca 10.0.2-3 to pull in fix for sslget and mixed IPv4/6
+  environments. (#953464)
+- ipa-client-install removes 'sss' from /etc/nsswitch.conf (#953453)
+- ipa-server-install --uninstall doesn't stop dirsrv instances (#953432)
+- Add requires for openldap-2.4.35-4 to pickup fixed SASL_NOCANON behavior for
+  socket based connections (#960222)
+- Require libsss_nss_idmap-python
+- Add Conflicts on nss-pam-ldapd < 0.8.4. The mapping from uniqueMember to
+  member is now done automatically and having it in the config file raises
+  an error.
+- Add backup and restore tools, directory.
+- require at least systemd 38 which provides the journal (we no longer
+  need to require syslog.target)
+- Update Requires on policycoreutils to 2.1.14-37
+- Update Requires on selinux-policy to 3.12.1-42
+- Update Requires on 389-ds-base to 1.3.1.0
+- Remove a Requires for java-atk-wrapper
+
+* Tue Apr 23 2013 Rob Crittenden <rcritten@redhat.com> - 3.2.0-0.4.beta1
+- Remove release from krb5-server in strict sub-package to allow for rebuilds.
+
+* Mon Apr 22 2013 Rob Crittenden <rcritten@redhat.com> - 3.2.0-0.3.beta1
+- Add a Requires for java-atk-wrapper until we can determine which package
+  should be pulling it in, dogtag or tomcat.
+
+* Tue Apr 16 2013 Rob Crittenden <rcritten@redhat.com> - 3.2.0-0.2.beta1
+- Update to upstream 3.2.0 Beta 1
+
+* Tue Apr  2 2013 Martin Kosek <mkosek@redhat.com> - 3.2.0-0.1.pre1
+- Update to upstream 3.2.0 Prerelease 1
+- Use upstream reference spec file as a base for Fedora spec file
+
+* Sat Mar 30 2013 Kevin Fenzi <kevin@scrye.com> 3.1.2-4
+- Rebuild for broken deps
+- Fix 389-ds-base strict dep to be 1.3.0.5 and krb5-server 1.11.1
+
+* Sat Feb 23 2013 Kevin Fenzi <kevin@scrye.com> - 3.1.2-3
+- Rebuild for broken deps in rawhide
+- Fix 389-ds-base strict dep to be 1.3.0.3
+
+* Wed Feb 13 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 3.1.2-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
+* Wed Jan 23 2013 Rob Crittenden <rcritten@redhat.com> - 3.1.2-1
+- Update to upstream 3.1.2
+- CVE-2012-4546: Incorrect CRLs publishing
+- CVE-2012-5484: MITM Attack during Join process
+- CVE-2013-0199: Cross-Realm Trust key leak
+- Updated strict dependencies to 389-ds-base = 1.3.0.2 and
+  pki-ca = 10.0.1
+
+* Thu Dec 20 2012 Martin Kosek <mkosek@redhat.com> - 3.1.0-2
+- Remove redundat Requires versions that are already in Fedora 17
+- Replace python-crypto Requires with m2crypto
+- Add missing Requires(post) for client and server-trust-ad subpackages
+- Restart httpd service when server-trust-ad subpackage is installed
+- Bump selinux-policy Requires to pick up PKI/LDAP port labeling fixes
+
+* Mon Dec 10 2012 Rob Crittenden <rcritten@redhat.com> - 3.1.0-1
+- Updated to upstream 3.1.0 GA
+- Set minimum for sssd to 1.9.2
+- Set minimum for pki-ca to 10.0.0-1
+- Set minimum for 389-ds-base to 1.3.0
+- Set minimum for selinux-policy to 3.11.1-60
+- Remove unneeded dogtag package requires
+
+* Tue Oct 23 2012 Martin Kosek <mkosek@redhat.com> - 3.0.0-3
+- Update Requires on krb5-server to 1.11
+
+* Fri Oct 12 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-2
+- Configure CA replication to use TLS instead of SSL
+
+* Fri Oct 12 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-1
+- Updated to upstream 3.0.0 GA
+- Set minimum for samba to 4.0.0-153.
+- Make sure server-trust-ad subpackage alternates winbind_krb5_locator.so
+  plugin to /dev/null since they cannot be used when trusts are configured
+- Restrict krb5-server to 1.10.
+- Update BR for 389-ds-base to 1.3.0
+- Add directory /var/lib/ipa/pki-ca/publish for CRL published by pki-ca
+- Add Requires on zip for generating FF browser extension
+
+* Fri Oct  5 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.10
+- Updated to upstream 3.0.0 rc 2
+- Include new FF configuration extension
+- Set minimum Requires of selinux-policy to 3.11.1-33
+- Set minimum Requires dogtag to 10.0.0-0.43.b1
+- Add new optional strict sub-package to allow users to limit other
+  package upgrades.
+
+* Tue Oct  2 2012 Martin Kosek <mkosek@redhat.com> - 3.0.0-0.9
+- Require samba packages instead of obsoleted samba4 packages
+
+* Fri Sep 21 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.8
+- Updated to upstream 3.0.0 rc 1
+- Update BR for 389-ds-base to 1.2.11.14
+- Update BR for krb5 to 1.10
+- Update BR for samba4-devel to 4.0.0-139 (rc1)
+- Add BR for python-polib
+- Update BR and Requires on sssd to 1.9.0
+- Update Requires on policycoreutils to 2.1.12-5
+- Update Requires on 389-ds-base to 1.2.11.14
+- Update Requires on selinux-policy to 3.11.1-21
+- Update Requires on dogtag to 10.0.0-0.33.a1
+- Update Requires on certmonger to 0.60
+- Update Requires on tomcat to 7.0.29
+- Update minimum version of bind to 9.9.1-10.P3
+- Update minimum version of bind-dyndb-ldap to 1.1.0-0.16.rc1
+- Remove Requires on authconfig from python sub-package
+
+* Wed Sep  5 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.7
+- Rebuild against samba4 beta8
+
+* Fri Aug 31 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.6
+- Rebuild against samba4 beta7
+
+* Wed Aug 22 2012 Alexander Bokovoy <abokovoy@redhat.com> - 3.0.0-0.5
+- Adopt to samba4 beta6 (libsecurity -> libsamba-security)
+- Add dependency to samba4-winbind
+
+* Fri Aug 17 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.4
+- Updated to upstream 3.0.0 beta 2
+
+* Mon Aug  6 2012 Martin Kosek <mkosek@redhat.com> - 3.0.0-0.3
+- Updated to current upstream state of 3.0.0 beta 2 development
+
+* Mon Jul 23 2012 Alexander Bokovoy <abokovy@redhat.com> - 3.0.0-0.2
+- Rebuild against samba4 beta4
+
+* Mon Jul  2 2012 Rob Crittenden <rcritten@redhat.com> - 3.0.0-0.1
+- Updated to upstream 3.0.0 beta 1
+
+* Thu May  3 2012 Rob Crittenden <rcritten@redhat.com> - 2.2.0-1
+- Updated to upstream 2.2.0 GA
+- Update minimum n-v-r of certmonger to 0.53
+- Update minimum n-v-r of slapi-nis to 0.40
+- Add Requires in client to oddjob-mkhomedir and python-krbV
+- Update minimum selinux-policy to 3.10.0-110
+
+* Mon Mar 19 2012 Rob Crittenden <rcritten@redhat.com> - 2.1.90-0.2
+- Update to upstream 2.2.0 beta 1 (2.1.90.rc1)
+- Set minimum n-v-r for pki-ca and pki-silent to 9.0.18.
+- Add Conflicts on mod_ssl
+- Update minimum n-v-r of 389-ds-base to 1.2.10.4
+- Update minimum n-v-r of sssd to 1.8.0
+- Update minimum n-v-r of slapi-nis to 0.38
+- Update minimum n-v-r of pki-* to 9.0.18
+- Update conflicts on bind-dyndb-ldap to < 1.1.0-0.9.b1
+- Update conflicts on bind to < 9.9.0-1
+- Drop requires on krb5-server-ldap
+- Add patch to remove escaping arguments to pkisilent
+
+* Mon Feb 06 2012 Rob Crittenden <rcritten@redhat.com> - 2.1.90-0.1
+- Update to upstream 2.2.0 alpha 1 (2.1.90.pre1)
+
+* Wed Feb 01 2012 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.4-5
+- Force to use 389-ds 1.2.10-0.8.a7 or above
+- Improve upgrade script to handle systemd 389-ds change
+- Fix freeipa to work with python-ldap 2.4.6
+
+* Wed Jan 11 2012 Martin Kosek <mkosek@redhat.com> - 2.1.4-4
+- Fix ipa-replica-install crashes
+- Fix ipa-server-install and ipa-dns-install logging
+- Set minimum version of pki-ca to 9.0.17 to fix sslget problem
+  caused by FEDORA-2011-17400 update (#771357)
+
+* Wed Dec 21 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.4-3
+- Allow Web-based migration to work with tightened SE Linux policy (#769440)
+- Rebuild slapi plugins against re-enterant version of libldap
+
+* Sun Dec 11 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.4-2
+- Allow longer dirsrv startup with systemd:
+  - IPAdmin class will wait until dirsrv instance is available up to 10 seconds
+  - Helps with restarts during upgrade for ipa-ldap-updater
+- Fix pylint warnings from F16 and Rawhide
+
+* Tue Dec  6 2011 Rob Crittenden <rcritten@redhat.com> - 2.1.4-1
+- Update to upstream 2.1.4 (CVE-2011-3636)
+
+* Mon Dec  5 2011 Rob Crittenden <rcritten@redhat.com> - 2.1.3-8
+- Update SELinux policy to allow ipa_kpasswd to connect ldap and
+  read /dev/urandom. (#759679)
+
+* Wed Nov 30 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-7
+- Fix wrong path in packaging freeipa-systemd-upgrade
+
+* Wed Nov 30 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-6
+- Introduce upgrade script to recover existing configuration after systemd migration
+  as user has no means to recover FreeIPA from systemd migration
+- Upgrade script:
+  - recovers symlinks in Dogtag instance install
+  - recovers systemd configuration for FreeIPA's directory server instances
+  - recovers freeipa.service
+  - migrates directory server and KDC configs to use proper keytabs for systemd services
+
+* Wed Oct 26 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.3-5
+- Rebuilt for glibc bug#747377
+
+* Wed Oct 19 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-4
+- clean up spec
+- Depend on sssd >= 1.6.2 for better user experience
+
+* Tue Oct 18 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-3
+- Fix Fedora package changelog after merging systemd changes
+
+* Tue Oct 18 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-2
+- Fix postin scriplet for F-15/F-16
+
+* Tue Oct 18 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.3-1
+- 2.1.3
+
+* Mon Oct 17 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.1.2-1
+- Default to systemd for Fedora 16 and onwards
+
+* Tue Aug 16 2011 Rob Crittenden <rcritten@redhat.com> - 2.1.0-1
+- Update to upstream 2.1.0
+
+* Fri May  6 2011 Simo Sorce <ssorce@redhat.com> - 2.0.1-2
+- Fix bug #702633
+
+* Mon May  2 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.1-1
+- Update minimum selinux-policy to 3.9.16-18
+- Update minimum pki-ca and pki-selinux to 9.0.7
+- Update minimum 389-ds-base to 1.2.8.0-1
+- Update to upstream 2.0.1
+
+* Thu Mar 24 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-1
+- Update to upstream GA release
+- Automatically apply updates when the package is upgraded
+
+* Fri Feb 25 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.4.rc2
+- Update to upstream freeipa-2.0.0.rc2
+- Set minimum version of python-nss to 0.11 to make sure IPv6 support is in
+- Set minimum version of sssd to 1.5.1
+- Patch to include SuiteSpotGroup when setting up 389-ds instances
+- Move a lot of BuildRequires so this will build with ONLY_CLIENT enabled
+
+* Tue Feb 15 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.3.rc1
+- Set the N-V-R so rc1 is an update to beta2.
+
+* Mon Feb 14 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.1.rc1
+- Set minimum version of sssd to 1.5.1
+- Update to upstream freeipa-2.0.0.rc1
+- Move server-only binaries from admintools subpackage to server
+
+* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.0.0-0.2.beta2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
+
+* Thu Feb  3 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.1.beta2
+- Set min version of 389-ds-base to 1.2.8
+- Set min version of mod_nss 1.0.8-10
+- Set min version of selinux-policy to 3.9.7-27
+- Add dogtag themes to Requires
+- Update to upstream freeipa-2.0.0.pre2
+
+* Thu Jan 27 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.2.beta.git80e87e7
+- Remove unnecessary moving of v1 CA serial number file in post script
+- Add Obsoletes for server-selinxu subpackage
+- Using git snapshot 442d6ad30ce1156914e6245aa7502499e50ec0da
+
+* Wed Jan 26 2011 Rob Crittenden <rcritten@redhat.com> - 2.0.0-0.1.beta.git80e87e7
+- Prepare spec file for release
+- Using git snapshot 80e87e75bd6ab56e3e20c49ece55bd4d52f1a503
+
+* Tue Jan 25 2011 Rob Crittenden <rcritten@redhat.com> - 1.99-41
+- Re-arrange doc and defattr to clean up rpmlint warnings
+- Remove conditionals on older releases
+- Move some man pages into admintools subpackage
+- Remove some explicit Requires in client that aren't needed
+- Consistent use of buildroot vs RPM_BUILD_ROOT
+
+* Wed Jan 19 2011 Adam Young <ayoung@redhat.com> - 1.99-40
+- Moved directory install/static to install/ui
+
+* Thu Jan 13 2011 Simo Sorce <ssorce@redhat.com> - 1.99-39
+- Remove dependency on nss_ldap/nss-pam-ldapd
+- The official client is sssd and that's what we use by default.
+
+* Thu Jan 13 2011 Simo Sorce <ssorce@redhat.com> - 1.99-38
+- Remove radius subpackages
+
+* Thu Jan 13 2011 Rob Crittenden <rcritten@redhat.com> - 1.99-37
+- Set minimum pki-ca and pki-silent versions to 9.0.0
+
+* Wed Jan 12 2011 Rob Crittenden <rcritten@redhat.com> - 1.99-36
+- Drop BuildRequires on mozldap-devel
+
+* Mon Dec 13 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-35
+- Add Requires on krb5-pkinit-openssl
+
+* Fri Dec 10 2010 Jr Aquino <jr.aquino@citrix.com> - 1.99-34
+- Add ipa-host-net-manage script
+
+* Tue Dec  7 2010 Simo Sorce <ssorce@redhat.com> - 1.99-33
+- Add ipa init script
+
+* Fri Nov 19 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-32
+- Set minimum level of 389-ds-base to 1.2.7 for enhanced memberof plugin
+
+* Wed Nov  3 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-31
+- remove ipa-fix-CVE-2008-3274
+
+* Wed Oct  6 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-30
+- Remove duplicate %%files entries on share/ipa/static
+- Add python default encoding shared library
+
+* Mon Sep 20 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-29
+- Drop requires on python-configobj (not used any more)
+- Drop ipa-ldap-updater message, upgrades are done differently now
+
+* Wed Sep  8 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-28
+- Drop conflicts on mod_nss
+- Require nss-pam-ldapd on F-14 or higher instead of nss_ldap (#606847)
+- Drop a slew of conditionals on older Fedora releases (< 12)
+- Add a few conditionals against RHEL 6
+- Add Requires of nss-tools on ipa-client
+
+* Fri Aug 13 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-27
+- Set minimum version of certmonger to 0.26 (to pck up #621670)
+- Set minimum version of pki-silent to 1.3.4 (adds -key_algorithm)
+- Set minimum version of pki-ca to 1.3.6
+- Set minimum version of sssd to 1.2.1
+
+* Tue Aug 10 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-26
+- Add BuildRequires for authconfig
+
+* Mon Jul 19 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-25
+- Bump up minimum version of python-nss to pick up nss_is_initialize() API
+
+* Thu Jun 24 2010 Adam Young <ayoung@redhat.com> - 1.99-24
+- Removed python-asset based webui
+
+* Thu Jun 24 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-23
+- Change Requires from fedora-ds-base to 389-ds-base
+- Set minimum level of 389-ds-base to 1.2.6 for the replication
+  version plugin.
+
+* Tue Jun  1 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-22
+- Drop Requires of python-krbV on ipa-client
+
+* Mon May 17 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-21
+- Load ipa_dogtag.pp in post install
+
+* Mon Apr 26 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-20
+- Set minimum level of sssd to 1.1.1 to pull in required hbac fixes.
+
+* Thu Mar  4 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-19
+- No need to create /var/log/ipa_error.log since we aren't using
+  TurboGears any more.
+
+* Mon Mar 1 2010 Jason Gerard DeRose <jderose@redhat.com> - 1.99-18
+- Fixed share/ipa/wsgi.py so .pyc, .pyo files are included
+
+* Wed Feb 24 2010 Jason Gerard DeRose <jderose@redhat.com> - 1.99-17
+- Added Require mod_wsgi, added share/ipa/wsgi.py
+
+* Thu Feb 11 2010 Jason Gerard DeRose <jderose@redhat.com> - 1.99-16
+- Require python-wehjit >= 0.2.2
+
+* Wed Feb  3 2010 Rob Crittenden <rcritten@redhat.com> - 1.99-15
+- Add sssd and certmonger as a Requires on ipa-client
+
+* Wed Jan 27 2010 Jason Gerard DeRose <jderose@redhat.com> - 1.99-14
+- Require python-wehjit >= 0.2.0
+
+* Fri Dec  4 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-13
+- Add ipa-rmkeytab tool
+
+* Tue Dec  1 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-12
+- Set minimum of python-pyasn1 to 0.0.9a so we have support for the ASN.1
+  Any type
+
+* Wed Nov 25 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-11
+- Remove v1-style /etc/ipa/ipa.conf, replacing with /etc/ipa/default.conf
+
+* Fri Nov 13 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-10
+- Add bash completion script and own /etc/bash_completion.d in case it
+  doesn't already exist
+
+* Tue Nov  3 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-9
+- Remove ipa_webgui, its functions rolled into ipa_httpd
+
+* Mon Oct 12 2009 Jason Gerard DeRose <jderose@redhat.com> - 1.99-8
+- Removed python-cherrypy from BuildRequires and Requires
+- Added Requires python-assets, python-wehjit
+
+* Mon Aug 24 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-7
+- Added httpd SELinux policy so CRLs can be read
+
+* Thu May 21 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-6
+- Move ipalib to ipa-python subpackage
+- Bump minimum version of slapi-nis to 0.15
+
+* Wed May  6 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-5
+- Set 0.14 as minimum version for slapi-nis
+
+* Wed Apr 22 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-4
+- Add Requires: python-nss to ipa-python sub-package
+
+* Thu Mar  5 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-3
+- Remove the IPA DNA plugin, use the DS one
+
+* Wed Mar  4 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-2
+- Build radius separately
+- Fix a few minor issues
+
+* Tue Feb  3 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-1
+- Replace TurboGears requirement with python-cherrypy
+
+* Sat Jan 17 2009 Tomas Mraz <tmraz@redhat.com> - 1.2.1-3
+- rebuild with new openssl
+
+* Fri Dec 19 2008 Dan Walsh <dwalsh@redhat.com> - 1.2.1-2
+- Fix SELinux code
+
+* Mon Dec 15 2008 Simo Sorce <ssorce@redhat.com> - 1.2.1-1
+- Fix breakage caused by python-kerberos update to 1.1
+
+* Fri Dec 5 2008 Simo Sorce <ssorce@redhat.com> - 1.2.1-0
+- New upstream release 1.2.1
+
+* Sat Nov 29 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm@gmail.com> - 1.2.0-4
+- Rebuild for Python 2.6
+
+* Fri Nov 14 2008 Simo Sorce <ssorce@redhat.com> - 1.2.0-3
+- Respin after the tarball has been re-released upstream
+  New hash is 506c9c92dcaf9f227cba5030e999f177
+
+* Thu Nov 13 2008 Simo Sorce <ssorce@redhat.com> - 1.2.0-2
+- Conditionally restart also dirsrv and httpd when upgrading
+
+* Wed Oct 29 2008 Rob Crittenden <rcritten@redhat.com> - 1.2.0-1
+- Update to upstream version 1.2.0
+- Set fedora-ds-base minimum version to 1.1.3 for winsync header
+- Set the minimum version for SELinux policy
+- Remove references to Fedora 7
+
+* Wed Jul 23 2008 Simo Sorce <ssorce@redhat.com> - 1.1.0-3
+- Fix for CVE-2008-3274
+- Fix segfault in ipa-kpasswd in case getifaddrs returns a NULL interface
+- Add fix for bug #453185
+- Rebuild against openldap libraries, mozldap ones do not work properly
+- TurboGears is currently broken in rawhide. Added patch to not build
+  the UI locales and removed them from the ipa-server files section.
+
+* Wed Jun 18 2008 Rob Crittenden <rcritten@redhat.com> - 1.1.0-2
+- Add call to /usr/sbin/upgradeconfig to post install
+
+* Wed Jun 11 2008 Rob Crittenden <rcritten@redhat.com> - 1.1.0-1
+- Update to upstream version 1.1.0
+- Patch for indexing memberof attribute
+- Patch for indexing uidnumber and gidnumber
+- Patch to change DNA default values for replicas
+- Patch to fix uninitialized variable in ipa-getkeytab
+
+* Fri May 16 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-5
+- Set fedora-ds-base minimum version to 1.1.0.1-4 and mod_nss minimum
+  version to 1.0.7-4 so we pick up the NSS fixes.
+- Add selinux-policy-base(post) to Requires (446496)
+
+* Tue Apr 29 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-4
+- Add missing entry for /var/cache/ipa/kpasswd (444624)
+- Added patch to fix permissions problems with the Apache NSS database.
+- Added patch to fix problem with DNS querying where the query could be
+  returned as the answer.
+- Fix spec error where patch1 was in the wrong section
+
+* Fri Apr 25 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-3
+- Added patch to fix problem reported by ldapmodify
+
+* Fri Apr 25 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-2
+- Fix Requires for krb5-server that was missing for Fedora versions > 9
+- Remove quotes around test for fedora version to package egg-info
+
+* Fri Apr 18 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-1
+- Update to upstream version 1.0.0
+
+* Tue Mar 18 2008 Rob Crittenden <rcritten@redhat.com> 0.99-12
+- Pull upstream changelog 722
+- Add Conflicts mod_ssl (435360)
+
+* Fri Feb 29 2008 Rob Crittenden <rcritten@redhat.com> 0.99-11
+- Pull upstream changelog 698
+- Fix ownership of /var/log/ipa_error.log during install (435119)
+- Add pwpolicy command and man page
+
+* Thu Feb 21 2008 Rob Crittenden <rcritten@redhat.com> 0.99-10
+- Pull upstream changelog 678
+- Add new subpackage, ipa-server-selinux
+- Add Requires: authconfig to ipa-python (bz #433747)
+- Package i18n files
+
+* Mon Feb 18 2008 Rob Crittenden <rcritten@redhat.com> 0.99-9
+- Pull upstream changelog 641
+- Require minimum version of krb5-server on F-7 and F-8
+- Package some new files
+
+* Thu Jan 31 2008 Rob Crittenden <rcritten@redhat.com> 0.99-8
+- Marked with wrong license. IPA is GPLv2.
+
+* Tue Jan 29 2008 Rob Crittenden <rcritten@redhat.com> 0.99-7
+- Ensure that /etc/ipa exists before moving user-modifiable html files there
+- Put html files into /etc/ipa/html instead of /etc/ipa
+
+* Tue Jan 29 2008 Rob Crittenden <rcritten@redhat.com> 0.99-6
+- Pull upstream changelog 608 which renamed several files
+
+* Thu Jan 24 2008 Rob Crittenden <rcritten@redhat.com> 0.99-5
+- package the sessions dir /var/cache/ipa/sessions
+- Pull upstream changelog 597
+
+* Thu Jan 24 2008 Rob Crittenden <rcritten@redhat.com> 0.99-4
+- Updated upstream pull (596) to fix bug in ipa_webgui that was causing the
+  UI to not start.
+
+* Thu Jan 24 2008 Rob Crittenden <rcritten@redhat.com> 0.99-3
+- Included LICENSE and README in all packages for documentation
+- Move user-modifiable content to /etc/ipa and linked back to
+  /usr/share/ipa/html
+- Changed some references to /usr to the {_usr} macro and /etc
+  to {_sysconfdir}
+- Added popt-devel to BuildRequires for Fedora 8 and higher and
+  popt for Fedora 7
+- Package the egg-info for Fedora 9 and higher for ipa-python
+
+* Tue Jan 22 2008 Rob Crittenden <rcritten@redhat.com> 0.99-2
+- Added auto* BuildRequires
+
+* Mon Jan 21 2008 Rob Crittenden <rcritten@redhat.com> 0.99-1
+- Unified spec file
+
+* Thu Jan 17 2008 Rob Crittenden <rcritten@redhat.com> - 0.6.0-2
+- Fixed License in specfile
+- Include files from /usr/lib/python*/site-packages/ipaserver
+
+* Fri Dec 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.6.0-1
+- Version bump for release
+
+* Wed Nov 21 2007 Karl MacMillan <kmacmill@mentalrootkit.com> - 0.5.0-1
+- Preverse mode on ipa-keytab-util
+- Version bump for relase and rpm name change
+
+* Thu Nov 15 2007 Rob Crittenden <rcritten@redhat.com> - 0.4.1-2
+- Broke invididual Requires and BuildRequires onto separate lines and
+  reordered them
+- Added python-tgexpandingformwidget as a dependency
+- Require at least fedora-ds-base 1.1
+
+* Thu Nov  1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
+- Version bump for release
+
+* Wed Oct 31 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.0-6
+- Add dep for freeipa-admintools and acl
+
+* Wed Oct 24 2007 Rob Crittenden <rcritten@redhat.com> - 0.4.0-5
+- Add dependency for python-krbV
+
+* Fri Oct 19 2007 Rob Crittenden <rcritten@redhat.com> - 0.4.0-4
+- Require mod_nss-1.0.7-2 for mod_proxy fixes
+
+* Thu Oct 18 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.0-3
+- Convert to autotools-based build
+
+* Tue Sep 25 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.0-2
+
+* Fri Sep 7 2007 Karl MacMillan <kmacmill@redhat.com> - 0.3.0-1
+- Added support for libipa-dna-plugin
+
+* Fri Aug 10 2007 Karl MacMillan <kmacmill@redhat.com> - 0.2.0-1
+- Added support for ipa_kpasswd and ipa_pwd_extop
+
+* Sun Aug  5 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-3
+- Abstracted client class to work directly or over RPC
+
+* Wed Aug  1 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-2
+- Add mod_auth_kerb and cyrus-sasl-gssapi to Requires
+- Remove references to admin server in ipa-server-setupssl
+- Generate a client certificate for the XML-RPC server to connect to LDAP with
+- Create a keytab for Apache
+- Create an ldif with a test user
+- Provide a certmap.conf for doing SSL client authentication
+
+* Fri Jul 27 2007 Karl MacMillan <kmacmill@redhat.com> - 0.1.0-1
+- Initial rpm version