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 +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 +Reviewed-By: Francois Cami +--- + 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 +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 +--- + 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 +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 +--- + .../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 +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 +Reviewed-By: Alexander Bokovoy +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +--- + 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 +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 +Reviewed-By: Anuja More +--- + 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 +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 +--- + 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 +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 +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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=[, ]) + 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=[]) + output: Output('summary', type=[, ]) + 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=[, ]) + output: Output('truncated', type=[]) + 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=[, ]) + 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=[, ]) + 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=[, ]) ++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=[]) + output: Output('summary', type=[, ]) + 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=[]) + output: ListOfEntries('result') + output: Output('summary', type=[, ]) + output: Output('truncated', type=[]) ++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=[]) ++output: ListOfEntries('result') ++output: Output('summary', type=[, ]) ++output: Output('truncated', type=[]) + 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 . + ++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 +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 +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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=[, ]) + 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=[, ]) + output: Output('truncated', type=[]) + 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=[, ]) + 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=[, ]) ++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=[]) ++output: Output('summary', type=[, ]) ++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=[]) ++output: ListOfEntries('result') ++output: Output('summary', type=[, ]) ++output: Output('truncated', type=[]) ++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=[, ]) ++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=[]) ++output: ListOfEntries('result') ++output: Output('summary', type=[, ]) ++output: Output('truncated', type=[]) ++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=[, ]) ++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=[, ]) ++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=[, ]) + 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=[, ]) + 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=[, ]) + 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=[, ]) +-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=[]) + output: ListOfEntries('result') + output: Output('summary', type=[, ]) + output: Output('truncated', type=[]) +-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=[]) +-output: ListOfEntries('result') +-output: Output('summary', type=[, ]) +-output: Output('truncated', type=[]) + 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: ;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 . + +-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 +Date: Fri, 21 May 2021 09:26:33 +0200 +Subject: [PATCH] Use 389-DS' dnaInterval setting to assign intervals + +Signed-off-by: Christian Heimes +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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 +Date: Fri, 21 May 2021 14:56:32 +0200 +Subject: [PATCH] Fix ipa-server-upgrade + +Signed-off-by: Christian Heimes +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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 +Date: Tue, 15 Jun 2021 13:25:18 +0200 +Subject: [PATCH] Fix oid of ipaUserDefaultSubordinateId + +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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 +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 +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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 +Date: Fri, 18 Jun 2021 10:51:54 +0200 +Subject: [PATCH] Test DNA plugin configuration + +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +--- + 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 +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 +Reviewed-By: Francois Cami +--- + 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 +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 +Reviewed-By: Francois Cami +--- + 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 +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 +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Anuja More +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +--- + 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 +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 +Reviewed-By: Anuja More +--- + 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 +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 +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Rob Crittenden +--- + 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 +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 +Reviewed-By: Michal Polovka +Reviewed-By: Francois Cami +Reviewed-By: Tibor Dudlak +Reviewed-By: Rob Crittenden +Reviewed-By: Christian Heimes +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Mohammad Rizwan +--- + 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 +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 +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Florence Blanc-Renaud +--- + .../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 +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 +--- + 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?= +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 +Reviewed-By: Michal Polovka +Reviewed-By: Michal Polovka +--- + 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?= +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 +Reviewed-By: Michal Polovka +--- + 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?= +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 +Reviewed-By: Michal Polovka +--- + 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?= +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 +Reviewed-By: Michal Polovka +--- + 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 +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 +Co-authored-by: François Cami +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +Reviewed-By: Francois Cami +Reviewed-By: Rob Crittenden +--- + 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?= +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 +Reviewed-By: Rob Crittenden +Reviewed-By: Antonio Torres +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +Reviewed-By: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud +--- + 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 + #include + #include ++#include + #include + #include + #include ++#include + + #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 +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 +Reviewed-By: Alexander Bokovoy +Reviewed-By: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Alexander Bokovoy +Reviewed-By: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Mohammad Rizwan +Reviewed-By: Rob Crittenden +--- + 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 +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 +Reviewed-By: Mohammad Rizwan +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Florence Blanc-Renaud +--- + 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?= +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 +Reviewed-By: Anuja More +--- + 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 +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 +Reviewed-By: Mohammad Rizwan +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Mohammad Rizwan +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Rob Crittenden +--- + 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 +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 +Reviewed-By: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + .../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 +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 +Reviewed-By: Rob Crittenden +--- + .../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 +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 +Reviewed-By: Michal Polovka +Reviewed-By: Florence Blanc-Renaud +--- + .../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 +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 +Reviewed-By: Florence Blanc-Renaud +--- + .../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 +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 +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Sergey Orlov +Reviewed-By: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + .../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?= +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 +Reviewed-By: Michal Polovka +Reviewed-By: Armando Neto +Reviewed-By: Mohammad Rizwan +Reviewed-By: Florence Blanc-Renaud +--- + 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 +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 +Reviewed-By: Francois Cami +--- + 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 +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 +Reviewed-By: Christian Heimes +--- + 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 +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 +--- + .../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?= +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 +Reviewed-By: Rob Crittenden +--- + 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 +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 +--- + 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 +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=. 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 +Reviewed-By: Francois Cami +Reviewed-By: Fraser Tweedale +--- + 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 +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 +Reviewed-By: Francois Cami +Reviewed-By: Fraser Tweedale +--- + 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 +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 +Reviewed-By: Rob Crittenden +--- + 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 +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 +--- + 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" +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 +--- + 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 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 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 +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 +Reviewed-By: Francois Cami +--- + 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 +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 +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 +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 - 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 - 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 - 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 - 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 - 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 - 4.9.6-4.1 +- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags + Related: rhbz#1991688 + +* Fri Jul 23 2021 Rob Crittenden - 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 - 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 - 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 - 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 - 4.9.3-2.1 +- Rebuilt for RHEL 9 BETA for openssl 3.0 + Related: rhbz#1971065 + +* Tue Apr 20 2021 Florence Blanc-Renaud - 4.9.3-2 +- RHEL 9 Beta mass rebuild. Resolves: rhbz#1951304 + +* Wed Mar 31 2021 Alexander Bokovoy - 4.9.3-1 +- Upstream release FreeIPA 4.9.3 + +* Fri Feb 26 2021 Alexander Bokovoy - 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 - 4.9.2-3 +- Only use python-platform on RHEL 8 + +* Mon Feb 15 2021 Alexander Bokovoy - 4.9.2-2 +- Fix ipatests dependency to python3-pexpect + +* Mon Feb 15 2021 Alexander Bokovoy - 4.9.2-1 +- Upstream release FreeIPA 4.9.2 + +* Wed Jan 27 2021 Alexander Bokovoy - 4.9.1-1 +- Upstream release FreeIPA 4.9.1 + +* Tue Jan 26 2021 Fedora Release Engineering - 4.9.0-2.1 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Wed Jan 20 2021 Rob Crittenden - 4.9.0-2 +- Set client keytab location for 389ds (RHBZ#1918075) + +* Wed Dec 23 17:05:00 EET 2020 Alexander Bokovoy - 4.9.0-1 +- FreeIPA 4.9.0 final release + +* Wed Dec 16 07:52:00 EET 2020 Alexander Bokovoy - 4.9.0-0.6.rc3 +- Refactor DNSSEC paths creation code (upstream PR#5340) + +* Thu Dec 10 20:06:03 EET 2020 Alexander Bokovoy - 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 - 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 - 4.9.0-0.3.rc2 +- Correct SELinux policy requirements + +* Fri Dec 4 13:41:28 EET 2020 Alexander Bokovoy - 4.9.0-0.2.rc2 +- FreeIPA 4.9.0 release candidate 2 + +* Thu Nov 19 2020 Alexander Bokovoy - 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 - 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 - 4.8.10-7 +- Backport #5212 for deployment failures with 389-ds-base 1.4.4.6+ + +* Tue Oct 13 2020 Alexander Bokovoy - 4.8.10-6 +- Handle sshd_config upgrade properly + Fixes: rhbz#1887928 + +* Tue Sep 29 2020 Alexander Bokovoy - 4.8.10-5 +- Properly handle upgrade case when systemd-resolved is enabled + +* Mon Sep 28 2020 Alexander Bokovoy - 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 - 4.8.10-3 +- Fix dependency between freeipa-selinux and freeipa-common +- Resolves: rhbz#1883005 + +* Sat Sep 26 2020 Alexander Bokovoy - 4.8.10-2 +- Support upgrade F32 -> F33 with systemd-resolved + +* Sat Sep 26 2020 Alexander Bokovoy - 4.8.10-1 +- Upstream release FreeIPA 4.8.10 + +* Fri Aug 21 2020 Alexander Bokovoy - 4.8.9-2 +- Backport fix for detecting older installations on upgrade + +* Thu Aug 20 2020 François Cami - 4.8.9-1 +- Upstream release FreeIPA 4.8.9 + +* Mon Aug 03 2020 Alexander Bokovoy - 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 - 4.8.7-4 +- Second attempt - Rebuilt for + https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Thu Jul 30 2020 Merlin Mathesius - 4.8.7-3 +- Conditional fixes for ELN to set krb5-kdb version appropriately + +* Mon Jul 27 2020 Fedora Release Engineering - 4.8.7-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Wed Jun 10 2020 Alexander Bokovoy - 4.8.7-1 +- Upstream release FreeIPA 4.8.7 + +* Tue May 26 2020 Miro Hrončok - 4.8.6-2 +- Rebuilt for Python 3.9 + +* Fri Mar 27 2020 Alexander Bokovoy - 4.8.6-1 +- Upstream release FreeIPA 4.8.6 + +* Sat Mar 21 2020 Alexander Bokovoy - 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 - 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 - 4.8.4-8 +- Support opendnssec 2.1 +- Resolves: #1809492 + +* Mon Feb 17 2020 François Cami - 4.8.4-7 +- Fix audit_as_req() callback usage +- Resolves: #1803786 + +* Sat Feb 01 2020 Alexander Bokovoy - 4.8.4-6 +- Fix constraint delegation for krb5 1.18 update +- Resolves: #1797096 + +* Tue Jan 28 2020 Fedora Release Engineering - 4.8.4-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Tue Jan 28 2020 Alexander Bokovoy - 4.8.4-4 +- Rebuild against krb5 1.18 beta + +* Sun Jan 26 2020 Alexander Bokovoy - 4.8.4-3 +- Rebuild against Samba 4.12RC1 + +* Mon Dec 16 2019 Adam Williamson - 4.8.4-2 +- Backport PR #4045 to fix overlapping DNS zone check bugs + +* Sat Dec 14 2019 Alexander Bokovoy - 4.8.4-1 +- New upstream release 4.8.4 + +* Tue Nov 26 2019 Alexander Bokovoy - 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 - 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 - 4.8.1-5 +- Don't create log files from helper scripts +- Fixes: rhbz#1754189 + +* Tue Oct 08 2019 Christian Heimes - 4.8.1-4 +- Fix compatibility issue with preexec_fn in Python 3.8 +- Fixes: rhbz#1759290 + +* Tue Oct 1 2019 Alexander Bokovoy - 4.8.1-3 +- Fix ipasam for compatibility with Samba 4.11 +- Fixes: rhbz#1757089 + +* Mon Aug 19 2019 Miro Hrončok - 4.8.1-2 +- Rebuilt for Python 3.8 + +* Wed Aug 14 2019 Alexander Bokovoy - 4.8.1-1 +- New upstream release 4.8.1 +- Fixes: rhbz#1732528 +- Fixes: rhbz#1732524 + +* Thu Jul 25 2019 Fedora Release Engineering - 4.8.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Wed Jul 03 2019 Alexander Bokovoy - 4.8.0-1 +- New upstream release 4.8.0 +- New subpackage: freeipa-client-samba + +* Sat May 11 2019 Alexander Bokovoy - 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 - 4.7.90.pre1-5 +- Add krb5-kdb-server dependency provided by krb5-server >= 1.17-17 + +* Fri May 3 2019 Alexander Bokovoy - 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 - 4.7.90.pre1-3 +- Backport PR #3104 to fix a font path error + +* Wed May 1 2019 Alexander Bokovoy - 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 - 4.7.90.pre1-1 +- First release candidate for FreeIPA 4.8.0 + +* Sat Apr 06 2019 Alexander Bokovoy - 4.7.2-8 +- Fixed: rhbz#1696963 (Failed to install replica) + +* Sat Apr 06 2019 Alexander Bokovoy - 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 - 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 - 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 - 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 - 4.7.2-3 +- Fix compile issues after a mass rebuild using upstream patches + +* Thu Jan 31 2019 Fedora Release Engineering - 4.7.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Mon Dec 03 2018 Alexander Bokovoy - 4.7.2-1 +- Upstream release FreeIPA 4.7.2 + +* Wed Nov 28 2018 Adam Williamson - 4.7.1-4 +- Update PR #2610 patch to tiran's modified version + +* Tue Nov 27 2018 Adam Williamson - 4.7.1-3 +- Backport PR #2610 to fix for authselect 1.0.2+ (see #1645708) + +* Sun Nov 11 2018 Alexander Bokovoy - 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 - 4.7.1-1 +- Update to upstream 4.7.1 + +* Tue Sep 25 2018 Christian Heimes - 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 - 4.7.0-4 +- Enable python2 client packages for f30 for now again + +* Tue Sep 4 2018 Thomas Woerner - 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 - 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 - 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 - 4.6.90.pre2-11 +- Use version-aware macros for Python + +* Fri Jul 13 2018 Fedora Release Engineering - 4.6.90.pre2-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Mon Jul 02 2018 Miro Hrončok - 4.6.90.pre2-9 +- Rebuilt for Python 3.7 + +* Wed Jun 27 2018 Rob Crittenden - 4.6.90.pre2-8 +- Build UI using py3-lesscpy + +* Tue Jun 19 2018 Rob Crittenden - 4.6.90.pre2-7 +- *-domainname.service moved to the hostname package in F29 (#1592355) + +* Tue Jun 19 2018 Miro Hrončok - 4.6.90.pre2-6 +- Rebuilt for Python 3.7 + +* Fri Jun 15 2018 Rob Crittenden - 4.6.90.pre2-5 +- Change BuildRequires from python-lesscpy to python3-lesscpy + +* Fri Jun 15 2018 Rob Crittenden - 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 - 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 - 4.6.90.pre2-2 +- Exclude /usr/share from client-only builds + +* Tue May 15 2018 Rob Crittenden - 4.6.90.pre2-1 +- Update to upstream 4.6.90.pre2 + +* Wed May 02 2018 Alexander Bokovoy - 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 - 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 - 4.6.90.pre1-6 +- Change upgrade code to use DIR-based ccache and no kinit (#1558818) + +* Tue Mar 20 2018 Alexander Bokovoy - 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 - 4.6.90.pre1-4 +- Fix upgrades harder (extension of -3 patch) (#1558354) + +* Tue Mar 20 2018 Alexander Bokovoy - 4.6.90.pre1-3 +- Fix upgrade from F27 to F28 (#1558354) + +* Mon Mar 19 2018 Rob Crittenden - 4.6.90.pre1-2 +- Patch to fix GUI login for non-admin users (#1557609) + +* Fri Mar 16 2018 Rob Crittenden - 4.6.90.pre1-1 +- Update to upstream 4.6.90.pre1 + +* Tue Feb 20 2018 Rob Crittenden - 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 - 4.6.3-4 +- Escape macros in %%changelog + +* Thu Feb 8 2018 Rob Crittenden - 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 - 4.6.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Wed Jan 31 2018 Rob Crittenden - 4.6.3-1 +- Update to upstream 4.6.3 + +* Wed Jan 03 2018 Lumír Balhar - 4.6.1-5 +- Fix directory ownership in python3 subpackage + +* Tue Oct 17 2017 Rob Crittenden - 4.6.1-4 +- Update workaround patch to prevent SELinux execmem AVC (#1491508) + +* Mon Oct 16 2017 Alexander Bokovoy - 4.6.1-3 +- Another attempt at fix for bug #1491053 + +* Fri Oct 06 2017 Tomas Krizek - 4.6.1-2 +- Rebuild against krb5-1.16 + +* Fri Sep 22 2017 Tomas Krizek - 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 - 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 - 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 - 4.6.0-1 +- Rebase to upstream 4.6.0 + +* Wed Aug 02 2017 Fedora Release Engineering - 4.5.3-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 4.5.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Fri Jul 21 2017 Tomas Krizek - 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 - 4.5.2-4 +- Make sure tmpfiles.d snippet for replica is in place after install + +* Mon Jul 10 2017 Alexander Bokovoy - 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 - 4.5.2-2 +- Patch: Fix IP address checks +- Patch: python-netifaces fix + +* Sun Jun 18 2017 Tomas Krizek - 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 - 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 - 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 - 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 - 4.4.3-8 +- Use different method to keep /usr/bin/ipa on Python 2 +- Fixes #1426847 + +* Mon Feb 27 2017 Tomas Krizek - 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 - 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 - 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 - 4.4.3-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Sat Jan 21 2017 Igor Gnatenko - 4.4.3-3 +- Rebuild for xmlrpc-c + +* Thu Dec 22 2016 Miro Hrončok - 4.4.3-2 +- Rebuild for Python 3.6 + +* Fri Dec 16 2016 Pavel Vomacka - 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 - 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 - 4.4.2-3 +- Fixes 1389866 krb5-server: ipadb_change_pwd(): kdb5_util killed by SIGSEGV + +* Fri Oct 21 2016 Petr Vobornik - 4.4.2-2 +- Rebuild against krb5-1.15 + +* Thu Oct 13 2016 Petr Vobornik - 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 - 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 - 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 - 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 - 4.3.1-2 +- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages + +* Thu Mar 24 2016 Petr Vobornik - 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 - 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 - 4.3.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Fri Dec 18 2015 Petr Vobornik - 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 - 4.2.3-2 +- Workarounds for SELinux execmem violations in cryptography + +* Mon Nov 02 2015 Petr Vobornik - 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 - 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 - 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 - 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 - 4.1.4-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Tue May 12 2015 Alexander Bokovoy - 4.1.4-4 +- Fix typo in the patch to fix bug #1219834 + +* Mon May 11 2015 Alexander Bokovoy - 4.1.4-3 +- Fix FreeIPA trusts to AD feature with Samba 4.2 (#1219834) + +* Mon Mar 30 2015 Petr Vobornik - 4.1.4-2 +- Replace mod_auth_kerb usage with mod_auth_gssapi + +* Thu Mar 26 2015 Alexander Bokovoy - 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 - 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 - 4.1.3-2 +- Add missing sssd python dependencies +- https://bugzilla.redhat.com/show_bug.cgi?id=1197218 + +* Wed Feb 18 2015 Petr Vobornik - 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 - 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 - 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 - 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 - 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 - 4.1.0-2 +- fix armv7hl stack oversize build failure +- fix https://fedorahosted.org/freeipa/ticket/4660 + +* Tue Oct 21 2014 Petr Vobornik - 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 - 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 - 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 - 4.0.1-3 +- rebuild for libunistring soname bump + +* Sat Aug 16 2014 Fedora Release Engineering - 4.0.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Fri Jul 25 2014 Martin Kosek 4.0.1-1 +- Update to upstream 4.0.1 + +* Mon Jul 07 2014 Petr Viktorin 4.0.0-1 +- Update to upstream 4.0.0 +- Remove the server-strict package + +* Sat Jun 07 2014 Fedora Release Engineering - 3.3.5-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Wed May 21 2014 Petr Vobornik 3.3.5-3 +- Increase Java stack size for Web UI build on aarch64 + +* Wed Apr 16 2014 Peter Robinson 3.3.5-2 +- Add rhino as dependency to fix FTBFS + +* Fri Mar 28 2014 Martin Kosek - 3.3.5-1 +- Update to upstream 3.3.5 + +* Tue Feb 11 2014 Martin Kosek - 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 - 3.3.4-2 +- Fix hardened build + +* Tue Jan 28 2014 Martin Kosek - 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 - 3.3.3-5 +- Build crashed with rhino exception on s390 architectures (#1040576) + +* Thu Dec 12 2013 Martin Kosek - 3.3.3-4 +- Build crashed with rhino exception on PPC architectures (#1040576) + +* Tue Dec 3 2013 Martin Kosek - 3.3.3-3 +- Fix -Werror=format-security errors (#1037070) + +* Mon Nov 4 2013 Martin Kosek - 3.3.3-2 +- ipa-server-install crashed when freeipa-server-trust-ad subpackage was not + installed + +* Fri Nov 1 2013 Martin Kosek - 3.3.3-1 +- Update to upstream 3.3.3 + +* Fri Oct 4 2013 Martin Kosek - 3.3.2-1 +- Update to upstream 3.3.2 + +* Thu Aug 29 2013 Petr Viktorin - 3.3.1-1 +- Bring back Fedora-only changes + +* Thu Aug 29 2013 Petr Viktorin - 3.3.1-0 +- Update to upstream 3.3.1 + +* Wed Aug 14 2013 Alexander Bokovoy - 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 - 3.3.0-1 +- Update to upstream 3.3.0 + +* Sat Aug 03 2013 Fedora Release Engineering - 3.2.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Wed Jul 17 2013 Martin Kosek - 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 - 3.2.1-1 +- Update to upstream 3.2.1 + +* Tue May 14 2013 Rob Crittenden - 3.2.0-2 +- Add OTP patches +- Add patch to set KRB5CCNAME for 389-ds-base + +* Fri May 10 2013 Rob Crittenden - 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 - 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 - 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 - 3.2.0-0.2.beta1 +- Update to upstream 3.2.0 Beta 1 + +* Tue Apr 2 2013 Martin Kosek - 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 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 - 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 - 3.1.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Wed Jan 23 2013 Rob Crittenden - 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 - 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 - 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 - 3.0.0-3 +- Update Requires on krb5-server to 1.11 + +* Fri Oct 12 2012 Rob Crittenden - 3.0.0-2 +- Configure CA replication to use TLS instead of SSL + +* Fri Oct 12 2012 Rob Crittenden - 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 - 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 - 3.0.0-0.9 +- Require samba packages instead of obsoleted samba4 packages + +* Fri Sep 21 2012 Rob Crittenden - 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 - 3.0.0-0.7 +- Rebuild against samba4 beta8 + +* Fri Aug 31 2012 Rob Crittenden - 3.0.0-0.6 +- Rebuild against samba4 beta7 + +* Wed Aug 22 2012 Alexander Bokovoy - 3.0.0-0.5 +- Adopt to samba4 beta6 (libsecurity -> libsamba-security) +- Add dependency to samba4-winbind + +* Fri Aug 17 2012 Rob Crittenden - 3.0.0-0.4 +- Updated to upstream 3.0.0 beta 2 + +* Mon Aug 6 2012 Martin Kosek - 3.0.0-0.3 +- Updated to current upstream state of 3.0.0 beta 2 development + +* Mon Jul 23 2012 Alexander Bokovoy - 3.0.0-0.2 +- Rebuild against samba4 beta4 + +* Mon Jul 2 2012 Rob Crittenden - 3.0.0-0.1 +- Updated to upstream 3.0.0 beta 1 + +* Thu May 3 2012 Rob Crittenden - 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 - 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 - 2.1.90-0.1 +- Update to upstream 2.2.0 alpha 1 (2.1.90.pre1) + +* Wed Feb 01 2012 Alexander Bokovoy - 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 - 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 - 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 - 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 - 2.1.4-1 +- Update to upstream 2.1.4 (CVE-2011-3636) + +* Mon Dec 5 2011 Rob Crittenden - 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 - 2.1.3-7 +- Fix wrong path in packaging freeipa-systemd-upgrade + +* Wed Nov 30 2011 Alexander Bokovoy - 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 - 2.1.3-5 +- Rebuilt for glibc bug#747377 + +* Wed Oct 19 2011 Alexander Bokovoy - 2.1.3-4 +- clean up spec +- Depend on sssd >= 1.6.2 for better user experience + +* Tue Oct 18 2011 Alexander Bokovoy - 2.1.3-3 +- Fix Fedora package changelog after merging systemd changes + +* Tue Oct 18 2011 Alexander Bokovoy - 2.1.3-2 +- Fix postin scriplet for F-15/F-16 + +* Tue Oct 18 2011 Alexander Bokovoy - 2.1.3-1 +- 2.1.3 + +* Mon Oct 17 2011 Alexander Bokovoy - 2.1.2-1 +- Default to systemd for Fedora 16 and onwards + +* Tue Aug 16 2011 Rob Crittenden - 2.1.0-1 +- Update to upstream 2.1.0 + +* Fri May 6 2011 Simo Sorce - 2.0.1-2 +- Fix bug #702633 + +* Mon May 2 2011 Rob Crittenden - 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 - 2.0.0-1 +- Update to upstream GA release +- Automatically apply updates when the package is upgraded + +* Fri Feb 25 2011 Rob Crittenden - 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 - 2.0.0-0.3.rc1 +- Set the N-V-R so rc1 is an update to beta2. + +* Mon Feb 14 2011 Rob Crittenden - 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 - 2.0.0-0.2.beta2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Thu Feb 3 2011 Rob Crittenden - 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 - 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 - 2.0.0-0.1.beta.git80e87e7 +- Prepare spec file for release +- Using git snapshot 80e87e75bd6ab56e3e20c49ece55bd4d52f1a503 + +* Tue Jan 25 2011 Rob Crittenden - 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 - 1.99-40 +- Moved directory install/static to install/ui + +* Thu Jan 13 2011 Simo Sorce - 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 - 1.99-38 +- Remove radius subpackages + +* Thu Jan 13 2011 Rob Crittenden - 1.99-37 +- Set minimum pki-ca and pki-silent versions to 9.0.0 + +* Wed Jan 12 2011 Rob Crittenden - 1.99-36 +- Drop BuildRequires on mozldap-devel + +* Mon Dec 13 2010 Rob Crittenden - 1.99-35 +- Add Requires on krb5-pkinit-openssl + +* Fri Dec 10 2010 Jr Aquino - 1.99-34 +- Add ipa-host-net-manage script + +* Tue Dec 7 2010 Simo Sorce - 1.99-33 +- Add ipa init script + +* Fri Nov 19 2010 Rob Crittenden - 1.99-32 +- Set minimum level of 389-ds-base to 1.2.7 for enhanced memberof plugin + +* Wed Nov 3 2010 Rob Crittenden - 1.99-31 +- remove ipa-fix-CVE-2008-3274 + +* Wed Oct 6 2010 Rob Crittenden - 1.99-30 +- Remove duplicate %%files entries on share/ipa/static +- Add python default encoding shared library + +* Mon Sep 20 2010 Rob Crittenden - 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 - 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 - 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 - 1.99-26 +- Add BuildRequires for authconfig + +* Mon Jul 19 2010 Rob Crittenden - 1.99-25 +- Bump up minimum version of python-nss to pick up nss_is_initialize() API + +* Thu Jun 24 2010 Adam Young - 1.99-24 +- Removed python-asset based webui + +* Thu Jun 24 2010 Rob Crittenden - 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 - 1.99-22 +- Drop Requires of python-krbV on ipa-client + +* Mon May 17 2010 Rob Crittenden - 1.99-21 +- Load ipa_dogtag.pp in post install + +* Mon Apr 26 2010 Rob Crittenden - 1.99-20 +- Set minimum level of sssd to 1.1.1 to pull in required hbac fixes. + +* Thu Mar 4 2010 Rob Crittenden - 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 - 1.99-18 +- Fixed share/ipa/wsgi.py so .pyc, .pyo files are included + +* Wed Feb 24 2010 Jason Gerard DeRose - 1.99-17 +- Added Require mod_wsgi, added share/ipa/wsgi.py + +* Thu Feb 11 2010 Jason Gerard DeRose - 1.99-16 +- Require python-wehjit >= 0.2.2 + +* Wed Feb 3 2010 Rob Crittenden - 1.99-15 +- Add sssd and certmonger as a Requires on ipa-client + +* Wed Jan 27 2010 Jason Gerard DeRose - 1.99-14 +- Require python-wehjit >= 0.2.0 + +* Fri Dec 4 2009 Rob Crittenden - 1.99-13 +- Add ipa-rmkeytab tool + +* Tue Dec 1 2009 Rob Crittenden - 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 - 1.99-11 +- Remove v1-style /etc/ipa/ipa.conf, replacing with /etc/ipa/default.conf + +* Fri Nov 13 2009 Rob Crittenden - 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 - 1.99-9 +- Remove ipa_webgui, its functions rolled into ipa_httpd + +* Mon Oct 12 2009 Jason Gerard DeRose - 1.99-8 +- Removed python-cherrypy from BuildRequires and Requires +- Added Requires python-assets, python-wehjit + +* Mon Aug 24 2009 Rob Crittenden - 1.99-7 +- Added httpd SELinux policy so CRLs can be read + +* Thu May 21 2009 Rob Crittenden - 1.99-6 +- Move ipalib to ipa-python subpackage +- Bump minimum version of slapi-nis to 0.15 + +* Wed May 6 2009 Rob Crittenden - 1.99-5 +- Set 0.14 as minimum version for slapi-nis + +* Wed Apr 22 2009 Rob Crittenden - 1.99-4 +- Add Requires: python-nss to ipa-python sub-package + +* Thu Mar 5 2009 Rob Crittenden - 1.99-3 +- Remove the IPA DNA plugin, use the DS one + +* Wed Mar 4 2009 Rob Crittenden - 1.99-2 +- Build radius separately +- Fix a few minor issues + +* Tue Feb 3 2009 Rob Crittenden - 1.99-1 +- Replace TurboGears requirement with python-cherrypy + +* Sat Jan 17 2009 Tomas Mraz - 1.2.1-3 +- rebuild with new openssl + +* Fri Dec 19 2008 Dan Walsh - 1.2.1-2 +- Fix SELinux code + +* Mon Dec 15 2008 Simo Sorce - 1.2.1-1 +- Fix breakage caused by python-kerberos update to 1.1 + +* Fri Dec 5 2008 Simo Sorce - 1.2.1-0 +- New upstream release 1.2.1 + +* Sat Nov 29 2008 Ignacio Vazquez-Abrams - 1.2.0-4 +- Rebuild for Python 2.6 + +* Fri Nov 14 2008 Simo Sorce - 1.2.0-3 +- Respin after the tarball has been re-released upstream + New hash is 506c9c92dcaf9f227cba5030e999f177 + +* Thu Nov 13 2008 Simo Sorce - 1.2.0-2 +- Conditionally restart also dirsrv and httpd when upgrading + +* Wed Oct 29 2008 Rob Crittenden - 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 - 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 - 1.1.0-2 +- Add call to /usr/sbin/upgradeconfig to post install + +* Wed Jun 11 2008 Rob Crittenden - 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 - 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 - 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 - 1.0.0-3 +- Added patch to fix problem reported by ldapmodify + +* Fri Apr 25 2008 Rob Crittenden - 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 - 1.0.0-1 +- Update to upstream version 1.0.0 + +* Tue Mar 18 2008 Rob Crittenden 0.99-12 +- Pull upstream changelog 722 +- Add Conflicts mod_ssl (435360) + +* Fri Feb 29 2008 Rob Crittenden 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 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 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 0.99-8 +- Marked with wrong license. IPA is GPLv2. + +* Tue Jan 29 2008 Rob Crittenden 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 0.99-6 +- Pull upstream changelog 608 which renamed several files + +* Thu Jan 24 2008 Rob Crittenden 0.99-5 +- package the sessions dir /var/cache/ipa/sessions +- Pull upstream changelog 597 + +* Thu Jan 24 2008 Rob Crittenden 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 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 0.99-2 +- Added auto* BuildRequires + +* Mon Jan 21 2008 Rob Crittenden 0.99-1 +- Unified spec file + +* Thu Jan 17 2008 Rob Crittenden - 0.6.0-2 +- Fixed License in specfile +- Include files from /usr/lib/python*/site-packages/ipaserver + +* Fri Dec 21 2007 Karl MacMillan - 0.6.0-1 +- Version bump for release + +* Wed Nov 21 2007 Karl MacMillan - 0.5.0-1 +- Preverse mode on ipa-keytab-util +- Version bump for relase and rpm name change + +* Thu Nov 15 2007 Rob Crittenden - 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 - 0.4.1-1 +- Version bump for release + +* Wed Oct 31 2007 Karl MacMillan - 0.4.0-6 +- Add dep for freeipa-admintools and acl + +* Wed Oct 24 2007 Rob Crittenden - 0.4.0-5 +- Add dependency for python-krbV + +* Fri Oct 19 2007 Rob Crittenden - 0.4.0-4 +- Require mod_nss-1.0.7-2 for mod_proxy fixes + +* Thu Oct 18 2007 Karl MacMillan - 0.4.0-3 +- Convert to autotools-based build + +* Tue Sep 25 2007 Karl MacMillan - 0.4.0-2 + +* Fri Sep 7 2007 Karl MacMillan - 0.3.0-1 +- Added support for libipa-dna-plugin + +* Fri Aug 10 2007 Karl MacMillan - 0.2.0-1 +- Added support for ipa_kpasswd and ipa_pwd_extop + +* Sun Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 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 - 0.1.0-1 +- Initial rpm version