From 3a117a41e7cbd21932df9021c6b995082daf3971 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 5 Apr 2022 06:31:02 -0400 Subject: [PATCH] import ipa-4.9.8-7.el9_0 --- ...ipatests-Tests-for-Autoprivate-group.patch | 326 ++++++++++ ...est_idoverride_with_auto_private_gro.patch | 43 ++ ...gidnumber_not_corresponding_existing.patch | 38 ++ ...e-provision-to-work-with-crypto-poli.patch | 108 ++++ ...nsure-AD-SUPPORT-subpolicy-is-active.patch | 58 ++ ...extend-AES-keyset-to-SHA2-based-ones.patch | 46 ++ ...p-crypto-policies-dependency-for-Cen.patch | 35 ++ ...e-default-to-AES256-SHA2-for-master-.patch | 56 ++ ...use-paramiko-unless-it-is-really-nee.patch | 44 ++ ...skip-SPAKE-related-tests-in-FIPS-mod.patch | 44 ++ ...upport-AES-for-KRA-archival-wrapping.patch | 555 ++++++++++++++++++ ...as-default-for-KRA-archival-wrapping.patch | 86 +++ SPECS/freeipa.spec | 40 +- 13 files changed, 1477 insertions(+), 2 deletions(-) create mode 100644 SOURCES/0024-ipatests-Tests-for-Autoprivate-group.patch create mode 100644 SOURCES/0025-mark-xfail-for-test_idoverride_with_auto_private_gro.patch create mode 100644 SOURCES/0026-Mark-xfail-test_gidnumber_not_corresponding_existing.patch create mode 100644 SOURCES/0027-KRB-instance-make-provision-to-work-with-crypto-poli.patch create mode 100644 SOURCES/0028-tests-ensure-AD-SUPPORT-subpolicy-is-active.patch create mode 100644 SOURCES/0029-ipatests-extend-AES-keyset-to-SHA2-based-ones.patch create mode 100644 SOURCES/0030-freeipa.spec-bump-crypto-policies-dependency-for-Cen.patch create mode 100644 SOURCES/0031-Kerberos-instance-default-to-AES256-SHA2-for-master-.patch create mode 100644 SOURCES/0032-test_otp-do-not-use-paramiko-unless-it-is-really-nee.patch create mode 100644 SOURCES/0033-test_krbtpolicy-skip-SPAKE-related-tests-in-FIPS-mod.patch create mode 100644 SOURCES/0034-Support-AES-for-KRA-archival-wrapping.patch create mode 100644 SOURCES/0035-Set-AES-as-default-for-KRA-archival-wrapping.patch diff --git a/SOURCES/0024-ipatests-Tests-for-Autoprivate-group.patch b/SOURCES/0024-ipatests-Tests-for-Autoprivate-group.patch new file mode 100644 index 0000000..f66b4cc --- /dev/null +++ b/SOURCES/0024-ipatests-Tests-for-Autoprivate-group.patch @@ -0,0 +1,326 @@ +From 6b70e3c49acc55b5553101cf850fc40978861979 Mon Sep 17 00:00:00 2001 +From: Anuja More +Date: Mon, 17 Jan 2022 16:57:52 +0530 +Subject: [PATCH] ipatests: Tests for Autoprivate group. + +Added tests using posix AD trust and non posix AD trust. +For option --auto-private-groups=[hybrid/true/false] + +Related : https://pagure.io/freeipa/issue/8807 + +Signed-off-by: Anuja More +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Anuja More +--- + .../nightly_ipa-4-9_latest.yaml | 2 +- + .../nightly_ipa-4-9_latest_selinux.yaml | 2 +- + .../nightly_ipa-4-9_previous.yaml | 2 +- + ipatests/test_integration/test_trust.py | 242 +++++++++++++++++- + 4 files changed, 240 insertions(+), 8 deletions(-) + +diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml +index 6817421b278999c52c32b3e28dd06587e30d874f..8b1f58c4d99e744e319e6c758050a62a8d35c9ee 100644 +--- a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml ++++ b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml +@@ -1627,7 +1627,7 @@ jobs: + build_url: '{fedora-latest-ipa-4-9/build_url}' + test_suite: test_integration/test_trust.py + template: *ci-ipa-4-9-latest +- timeout: 9000 ++ timeout: 10000 + topology: *adroot_adchild_adtree_master_1client + + fedora-latest-ipa-4-9/test_backup_and_restore_TestBackupAndRestoreTrust: +diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml +index 817329756dc145fa5e6bc7aa0477e5df2a6ece5b..a11376ab836e7ed2f942c29753707e5b8e88a00f 100644 +--- a/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml ++++ b/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml +@@ -1743,7 +1743,7 @@ jobs: + selinux_enforcing: True + test_suite: test_integration/test_trust.py + template: *ci-ipa-4-9-latest +- timeout: 9000 ++ timeout: 10000 + topology: *adroot_adchild_adtree_master_1client + + fedora-latest-ipa-4-9/test_backup_and_restore_TestBackupAndRestoreTrust: +diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml +index 4196265c772ec393ebb8f8bbdc4af845cd6d2d24..3f8ce8b7641fdfdc27278651cbf83c2b152e1a16 100644 +--- a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml ++++ b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml +@@ -1627,7 +1627,7 @@ jobs: + build_url: '{fedora-previous-ipa-4-9/build_url}' + test_suite: test_integration/test_trust.py + template: *ci-ipa-4-9-previous +- timeout: 9000 ++ timeout: 10000 + topology: *adroot_adchild_adtree_master_1client + + fedora-previous-ipa-4-9/test_backup_and_restore_TestBackupAndRestoreTrust: +diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py +index 0634badbb6a9aa148db2e3062e866215e61e89e7..ff2dd9cc819e1c5620ce449384957a633ae6d1f0 100644 +--- a/ipatests/test_integration/test_trust.py ++++ b/ipatests/test_integration/test_trust.py +@@ -62,11 +62,12 @@ class BaseTestTrust(IntegrationTest): + cls.check_sid_generation() + tasks.sync_time(cls.master, cls.ad) + +- cls.child_ad = cls.ad_subdomains[0] +- cls.ad_subdomain = cls.child_ad.domain.name +- cls.tree_ad = cls.ad_treedomains[0] +- cls.ad_treedomain = cls.tree_ad.domain.name +- ++ if cls.num_ad_subdomains > 0: ++ cls.child_ad = cls.ad_subdomains[0] ++ cls.ad_subdomain = cls.child_ad.domain.name ++ if cls.num_ad_treedomains > 0: ++ cls.tree_ad = cls.ad_treedomains[0] ++ cls.ad_treedomain = cls.tree_ad.domain.name + # values used in workaround for + # https://bugzilla.redhat.com/show_bug.cgi?id=1711958 + cls.srv_gc_record_name = \ +@@ -106,6 +107,63 @@ class BaseTestTrust(IntegrationTest): + expected_text = 'iparangetype: %s\n' % expected_type + assert expected_text in result.stdout_text + ++ def mod_idrange_auto_private_group( ++ self, option='false' ++ ): ++ """ ++ Set the auto-private-group option of the default trusted ++ AD domain range. ++ """ ++ tasks.kinit_admin(self.master) ++ rangename = self.ad_domain.upper() + '_id_range' ++ error_msg = "ipa: ERROR: no modifications to be performed" ++ cmd = ["ipa", "idrange-mod", rangename, ++ "--auto-private-groups", option] ++ result = self.master.run_command(cmd, raiseonerr=False) ++ if result.returncode != 0: ++ tasks.assert_error(result, error_msg) ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.clients[0]) ++ test = self.master.run_command(["ipa", "idrange-show", rangename]) ++ assert "Auto private groups: {0}".format(option) in test.stdout_text ++ ++ def get_user_id(self, host, username): ++ """ ++ User uid gid is parsed from the output of id user command. ++ """ ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.clients[0]) ++ self.master.run_command(["id", username]) ++ test_id = host.run_command(["id", username]) ++ regex = r"^uid=(?P\d+).*gid=(?P\d+).*groups=(?P\d+)" ++ match = re.match(regex, test_id.stdout_text) ++ uid = match.group('uid') ++ gid = match.group('gid') ++ return uid, gid ++ ++ @contextmanager ++ def set_idoverrideuser(self, user, uid, gid): ++ """ ++ Fixture to add/remove idoverrideuser for default idview, ++ also creates idm group with the provided gid because ++ gid overrides requires an existing group. ++ """ ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.clients[0]) ++ tasks.kinit_admin(self.master) ++ try: ++ args = ["ipa", "idoverrideuser-add", "Default Trust View", ++ "--gid", gid, "--uid", uid, user] ++ self.master.run_command(args) ++ tasks.group_add(self.master, "idgroup", ++ extra_args=["--gid", gid]) ++ yield ++ finally: ++ self.master.run_command([ ++ "ipa", "idoverrideuser-del", "Default Trust View", user] ++ ) ++ self.master.run_command(["ipa", "group-del", "idgroup"]) ++ + def remove_trust(self, ad): + tasks.remove_trust_with_ad(self.master, + ad.domain.name, ad.hostname) +@@ -993,3 +1051,177 @@ class TestTrust(BaseTestTrust): + self.master.run_command(['rm', '-f', ad_zone_file]) + tasks.configure_dns_for_trust(self.master, self.ad) + self.remove_trust(self.ad) ++ ++ ++class TestNonPosixAutoPrivateGroup(BaseTestTrust): ++ """ ++ Tests for auto-private-groups option with non posix AD trust ++ Related : https://pagure.io/freeipa/issue/8807 ++ """ ++ topology = 'line' ++ num_ad_domains = 1 ++ num_clients = 1 ++ num_ad_subdomains = 0 ++ num_ad_treedomains = 0 ++ uid_override = "99999999" ++ gid_override = "78878787" ++ ++ def test_add_nonposix_trust(self): ++ tasks.configure_dns_for_trust(self.master, self.ad) ++ tasks.establish_trust_with_ad( ++ self.master, self.ad_domain, ++ extra_args=['--range-type', 'ipa-ad-trust']) ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_auto_private_groups_default_trusted_range(self, type): ++ """ ++ Modify existing range for default trusted AD domain range ++ with auto-private-groups set as true/hybrid/false and test ++ user with no posix attributes. ++ """ ++ self.mod_idrange_auto_private_group(type) ++ nonposixuser = "nonposixuser@%s" % self.ad_domain ++ (uid, gid) = self.get_user_id(self.clients[0], nonposixuser) ++ if type == "true": ++ assert uid == gid ++ else: ++ test_group = self.clients[0].run_command(["id", nonposixuser]) ++ gid_str = "gid={0}(domain users@{1})".format(gid, self.ad_domain) ++ grp_str = "groups={0}(domain users@{1})".format(gid, ++ self.ad_domain) ++ assert gid_str in test_group.stdout_text ++ assert grp_str in test_group.stdout_text ++ assert uid != gid ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_idoverride_with_auto_private_group(self, type): ++ """ ++ Override ad trusted user in default trust view ++ and set auto-private-groups=[hybrid,true,false] ++ and ensure that overridden values takes effect. ++ """ ++ nonposixuser = "nonposixuser@%s" % self.ad_domain ++ with self.set_idoverrideuser(nonposixuser, ++ self.uid_override, ++ self.gid_override ++ ): ++ self.mod_idrange_auto_private_group(type) ++ (uid, gid) = self.get_user_id(self.clients[0], nonposixuser) ++ assert (uid == self.uid_override and gid == self.gid_override) ++ test_group = self.clients[0].run_command( ++ ["id", nonposixuser]).stdout_text ++ assert "domain users@{0}".format(self.ad_domain) in test_group ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_nonposixuser_nondefault_primary_group(self, type): ++ """ ++ Test for non default primary group. ++ For hybrid/false gid corresponds to the group testgroup1. ++ """ ++ nonposixuser1 = "nonposixuser1@%s" % self.ad_domain ++ self.mod_idrange_auto_private_group(type) ++ (uid, gid) = self.get_user_id(self.clients[0], nonposixuser1) ++ if type == "true": ++ assert uid == gid ++ else: ++ test_group = self.clients[0].run_command(["id", nonposixuser1]) ++ gid_str = "gid={0}(testgroup1@{1})".format(gid, self.ad_domain) ++ group = "groups={0}(testgroup1@{1})".format(gid, self.ad_domain) ++ assert (gid_str in test_group.stdout_text ++ and group in test_group.stdout_text) ++ ++ ++class TestPosixAutoPrivateGroup(BaseTestTrust): ++ """ ++ Tests for auto-private-groups option with posix AD trust ++ Related : https://pagure.io/freeipa/issue/8807 ++ """ ++ topology = 'line' ++ num_ad_domains = 1 ++ num_clients = 1 ++ num_ad_subdomains = 0 ++ num_ad_treedomains = 0 ++ uid_override = "99999999" ++ gid_override = "78878787" ++ ++ def test_add_posix_trust(self): ++ tasks.configure_dns_for_trust(self.master, self.ad) ++ tasks.establish_trust_with_ad( ++ self.master, self.ad_domain, ++ extra_args=['--range-type', 'ipa-ad-trust-posix']) ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_gidnumber_not_corresponding_existing_group(self, type): ++ """ ++ Test checks that sssd can resolve AD users which ++ contain posix attributes (uidNumber and gidNumber) ++ but there is no group with the corresponding gidNumber. ++ """ ++ posixuser = "testuser2@%s" % self.ad_domain ++ self.mod_idrange_auto_private_group(type) ++ if type != "true": ++ result = self.clients[0].run_command(['id', posixuser], ++ raiseonerr=False) ++ tasks.assert_error(result, "no such user") ++ else: ++ (uid, gid) = self.get_user_id(self.clients[0], posixuser) ++ assert uid == gid ++ assert uid == '10060' ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_only_uid_number_auto_private_group_default(self, type): ++ """ ++ Test checks that posix user with only uidNumber defined ++ and gidNumber not set, auto-private-group ++ is set to false/true/hybrid ++ """ ++ posixuser = "testuser1@%s" % self.ad_domain ++ self.mod_idrange_auto_private_group(type) ++ if type == "true": ++ (uid, gid) = self.get_user_id(self.clients[0], posixuser) ++ assert uid == gid ++ else: ++ for host in [self.master, self.clients[0]]: ++ result = host.run_command(['id', posixuser], raiseonerr=False) ++ tasks.assert_error(result, "no such user") ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_auto_private_group_primary_group(self, type): ++ """ ++ Test checks that AD users which contain posix attributes ++ (uidNumber and gidNumber) and there is primary group ++ with gid number defined. ++ """ ++ posixuser = "testuser@%s" % self.ad_domain ++ self.mod_idrange_auto_private_group(type) ++ (uid, gid) = self.get_user_id(self.clients[0], posixuser) ++ test_grp = self.clients[0].run_command(["id", posixuser]) ++ assert uid == '10042' ++ if type == "true": ++ assert uid == gid ++ groups = "groups=10042(testuser@{0}),10047(testgroup@{1})".format( ++ self.ad_domain, self.ad_domain) ++ assert groups in test_grp.stdout_text ++ else: ++ assert gid == '10047' ++ groups = "10047(testgroup@{0})".format(self.ad_domain) ++ assert groups in test_grp.stdout_text ++ ++ @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) ++ def test_idoverride_with_auto_private_group(self, type): ++ """ ++ Override ad trusted user in default trust view ++ and set auto-private-groups=[hybrid,true,false] ++ and ensure that overridden values takes effect. ++ """ ++ posixuser = "testuser@%s" % self.ad_domain ++ with self.set_idoverrideuser(posixuser, ++ self.uid_override, ++ self.gid_override): ++ self.mod_idrange_auto_private_group(type) ++ (uid, gid) = self.get_user_id(self.clients[0], posixuser) ++ assert(uid == self.uid_override ++ and gid == self.gid_override) ++ result = self.clients[0].run_command(['id', posixuser]) ++ assert "10047(testgroup@{0})".format( ++ self.ad_domain) in result.stdout_text +-- +2.34.1 + diff --git a/SOURCES/0025-mark-xfail-for-test_idoverride_with_auto_private_gro.patch b/SOURCES/0025-mark-xfail-for-test_idoverride_with_auto_private_gro.patch new file mode 100644 index 0000000..2b84d99 --- /dev/null +++ b/SOURCES/0025-mark-xfail-for-test_idoverride_with_auto_private_gro.patch @@ -0,0 +1,43 @@ +From 84381001d2e114b1f29fe89e16155c040b56b80f Mon Sep 17 00:00:00 2001 +From: Anuja More +Date: Thu, 10 Feb 2022 17:07:45 +0530 +Subject: [PATCH] mark xfail for + test_idoverride_with_auto_private_group[hybrid] + +Related : https://github.com/SSSD/sssd/issues/5989 + +Signed-off-by: Anuja More +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Anuja More +--- + ipatests/test_integration/test_trust.py | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py +index ff2dd9cc819e1c5620ce449384957a633ae6d1f0..54bd154628cb8fb063d9839d7928acd37647e2a4 100644 +--- a/ipatests/test_integration/test_trust.py ++++ b/ipatests/test_integration/test_trust.py +@@ -15,6 +15,7 @@ from ipaplatform.paths import paths + from ipatests.test_integration.base import IntegrationTest + from ipatests.pytest_ipa.integration import tasks + from ipatests.pytest_ipa.integration import fips ++from ipatests.util import xfail_context + from ipapython.dn import DN + from collections import namedtuple + from contextlib import contextmanager +@@ -1110,7 +1111,11 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust): + assert (uid == self.uid_override and gid == self.gid_override) + test_group = self.clients[0].run_command( + ["id", nonposixuser]).stdout_text +- assert "domain users@{0}".format(self.ad_domain) in test_group ++ version = tasks.get_sssd_version(self.clients[0]) ++ with xfail_context(version <= tasks.parse_version('2.6.3') ++ and type == "hybrid", ++ 'https://github.com/SSSD/sssd/issues/5989'): ++ assert "domain users@{0}".format(self.ad_domain) in test_group + + @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) + def test_nonposixuser_nondefault_primary_group(self, type): +-- +2.34.1 + diff --git a/SOURCES/0026-Mark-xfail-test_gidnumber_not_corresponding_existing.patch b/SOURCES/0026-Mark-xfail-test_gidnumber_not_corresponding_existing.patch new file mode 100644 index 0000000..a45d917 --- /dev/null +++ b/SOURCES/0026-Mark-xfail-test_gidnumber_not_corresponding_existing.patch @@ -0,0 +1,38 @@ +From 7ad500e5d3f7d9af81e8a3137158672c6fafb0b4 Mon Sep 17 00:00:00 2001 +From: Anuja More +Date: Thu, 10 Feb 2022 17:29:45 +0530 +Subject: [PATCH] Mark xfail + test_gidnumber_not_corresponding_existing_group[true,hybrid] + +Related : https://github.com/SSSD/sssd/issues/5988 + +Signed-off-by: Anuja More +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Anuja More +--- + ipatests/test_integration/test_trust.py | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py +index 54bd154628cb8fb063d9839d7928acd37647e2a4..c128378151ec4c0fb295823d75f2a04df2f7ffa0 100644 +--- a/ipatests/test_integration/test_trust.py ++++ b/ipatests/test_integration/test_trust.py +@@ -1169,9 +1169,12 @@ class TestPosixAutoPrivateGroup(BaseTestTrust): + raiseonerr=False) + tasks.assert_error(result, "no such user") + else: +- (uid, gid) = self.get_user_id(self.clients[0], posixuser) +- assert uid == gid +- assert uid == '10060' ++ sssd_version = tasks.get_sssd_version(self.clients[0]) ++ with xfail_context(sssd_version <= tasks.parse_version('2.6.3'), ++ 'https://github.com/SSSD/sssd/issues/5988'): ++ (uid, gid) = self.get_user_id(self.clients[0], posixuser) ++ assert uid == gid ++ assert uid == '10060' + + @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) + def test_only_uid_number_auto_private_group_default(self, type): +-- +2.34.1 + diff --git a/SOURCES/0027-KRB-instance-make-provision-to-work-with-crypto-poli.patch b/SOURCES/0027-KRB-instance-make-provision-to-work-with-crypto-poli.patch new file mode 100644 index 0000000..fb75eeb --- /dev/null +++ b/SOURCES/0027-KRB-instance-make-provision-to-work-with-crypto-poli.patch @@ -0,0 +1,108 @@ +From a51900819bd5332bc05ec9d513f062844b3a7763 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Fri, 25 Feb 2022 08:58:24 +0200 +Subject: [PATCH] KRB instance: make provision to work with crypto policy + without SHA-1 HMAC types + +RHEL 9 system-wide crypto policies aim at eventual removal of SHA-1 use. + +Due to bootstrapping process, force explicitly supported encryption +types in kdc.conf or we may end up with AES128-SHA1 and AES256-SHA2 only +in FIPS mode at bootstrap time which then fails to initialize kadmin +principals requiring use of AES256-SHA2 and AES128-SHA2. + +Camellia ciphers must be filtered out in FIPS mode, we do that already +in the kerberos.ldif. + +At this point we are not changing the master key encryption type to +AES256-SHA2 because upgrading existing deployments is complicated and +at the time when a replica configuration is deployed, we don't know what +is the encryption type of the master key of the original server as well. + +Fixes: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Julien Rische +Reviewed-By: Francisco Trivino +--- + install/share/kdc.conf.template | 3 ++- + install/share/kerberos.ldif | 2 ++ + ipaserver/install/krbinstance.py | 21 ++++++++++++++++++++- + 3 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template +index 232fedc445f660c30a88d8844d9f1b6042db41a7..685d42f3b7fb263e86b7a6db98be8bcc53e7bbe6 100644 +--- a/install/share/kdc.conf.template ++++ b/install/share/kdc.conf.template +@@ -6,7 +6,8 @@ + + [realms] + $REALM = { +- master_key_type = aes256-cts ++ master_key_type = $MASTER_KEY_TYPE ++ supported_enctypes = $SUPPORTED_ENCTYPES + max_life = 7d + max_renewable_life = 14d + acl_file = $KRB5KDC_KADM5_ACL +diff --git a/install/share/kerberos.ldif b/install/share/kerberos.ldif +index 3b75b445641fd86e2029ceb51e479c6ccb17856c..51e5cf9bca4b0b2cf2e1fe3ec85777deb61b76b0 100644 +--- a/install/share/kerberos.ldif ++++ b/install/share/kerberos.ldif +@@ -28,6 +28,8 @@ ${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:normal + ${FIPS}krbSupportedEncSaltTypes: camellia256-cts-cmac:special + krbMaxTicketLife: 86400 + krbMaxRenewableAge: 604800 ++krbDefaultEncSaltTypes: aes256-sha2:special ++krbDefaultEncSaltTypes: aes128-sha2:special + krbDefaultEncSaltTypes: aes256-cts:special + krbDefaultEncSaltTypes: aes128-cts:special + +diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py +index 216c1032d8abd9fc119d98d8f9976ce17d246ea4..852edcd9978f4a47d355e206fbb4a513ea699865 100644 +--- a/ipaserver/install/krbinstance.py ++++ b/ipaserver/install/krbinstance.py +@@ -51,6 +51,14 @@ logger = logging.getLogger(__name__) + + PKINIT_ENABLED = 'pkinitEnabled' + ++MASTER_KEY_TYPE = 'aes256-sha1' ++SUPPORTED_ENCTYPES = ('aes256-sha2:special', 'aes128-sha2:special', ++ 'aes256-sha2:normal', 'aes128-sha2:normal', ++ 'aes256-cts:special', 'aes128-cts:special', ++ 'aes256-cts:normal', 'aes128-cts:normal', ++ 'camellia256-cts:special', 'camellia128-cts:special', ++ 'camellia256-cts:normal', 'camellia128-cts:normal') ++ + + def get_pkinit_request_ca(): + """ +@@ -252,6 +260,7 @@ class KrbInstance(service.Service): + else: + includes = '' + ++ fips_enabled = tasks.is_fips_enabled() + self.sub_dict = dict(FQDN=self.fqdn, + IP=self.ip, + PASSWORD=self.kdc_password, +@@ -269,7 +278,17 @@ class KrbInstance(service.Service): + KDC_CA_BUNDLE_PEM=paths.KDC_CA_BUNDLE_PEM, + CA_BUNDLE_PEM=paths.CA_BUNDLE_PEM, + INCLUDES=includes, +- FIPS='#' if tasks.is_fips_enabled() else '') ++ FIPS='#' if fips_enabled else '') ++ ++ if fips_enabled: ++ supported_enctypes = list( ++ filter(lambda e: not e.startswith('camelia'), ++ SUPPORTED_ENCTYPES)) ++ else: ++ supported_enctypes = SUPPORTED_ENCTYPES ++ self.sub_dict['SUPPORTED_ENCTYPES'] = ' '.join(supported_enctypes) ++ ++ self.sub_dict['MASTER_KEY_TYPE'] = MASTER_KEY_TYPE + + # IPA server/KDC is not a subdomain of default domain + # Proper domain-realm mapping needs to be specified +-- +2.34.1 + diff --git a/SOURCES/0028-tests-ensure-AD-SUPPORT-subpolicy-is-active.patch b/SOURCES/0028-tests-ensure-AD-SUPPORT-subpolicy-is-active.patch new file mode 100644 index 0000000..562bba7 --- /dev/null +++ b/SOURCES/0028-tests-ensure-AD-SUPPORT-subpolicy-is-active.patch @@ -0,0 +1,58 @@ +From b016683552a58f9cc2a05cf628cc467234eaf599 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 28 Feb 2022 11:10:49 +0200 +Subject: [PATCH] tests: ensure AD-SUPPORT subpolicy is active + +Use AD-SUPPORT subpolicy when testing trust to Active Directory in FIPS +mode. This is required in FIPS mode due to AD not supporting Kerberos +AES-bases encryption types using FIPS-compliant PBKDF2 and KDF, as +defined in RFC 8009. + +Fixes: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Julien Rische +Reviewed-By: Francisco Trivino +--- + ipatests/pytest_ipa/integration/fips.py | 6 ++++++ + ipatests/pytest_ipa/integration/tasks.py | 3 +++ + 2 files changed, 9 insertions(+) + +diff --git a/ipatests/pytest_ipa/integration/fips.py b/ipatests/pytest_ipa/integration/fips.py +index 694ec8a9927da917fe99482094f68540a1032c14..b33aa91b14552d6f47191c913db4f974a5a5948c 100644 +--- a/ipatests/pytest_ipa/integration/fips.py ++++ b/ipatests/pytest_ipa/integration/fips.py +@@ -68,3 +68,9 @@ def disable_userspace_fips(host): + # sanity check + assert not is_fips_enabled(host) + host.run_command(["openssl", "md5", "/dev/null"]) ++ ++ ++def enable_crypto_subpolicy(host, subpolicy): ++ result = host.run_command(["update-crypto-policies", "--show"]) ++ policy = result.stdin_text.strip() + ":" + subpolicy ++ host.run_command(["update-crypto-policies", "--set", policy]) +diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py +index 7e1b7c24dab00986ff6e75430bf55e55dd1a6b8e..13d84e23fa7dc8a5e562e8498c9142e2bcad696a 100755 +--- a/ipatests/pytest_ipa/integration/tasks.py ++++ b/ipatests/pytest_ipa/integration/tasks.py +@@ -66,6 +66,7 @@ from .env_config import env_to_script + from .host import Host + from .firewall import Firewall + from .resolver import ResolvedResolver ++from .fips import is_fips_enabled, enable_crypto_subpolicy + + logger = logging.getLogger(__name__) + +@@ -362,6 +363,8 @@ def install_master(host, setup_dns=True, setup_kra=False, setup_adtrust=False, + if setup_adtrust: + args.append('--setup-adtrust') + fw_services.append("freeipa-trust") ++ if is_fips_enabled(host): ++ enable_crypto_subpolicy(host, "AD-SUPPORT") + if external_ca: + args.append('--external-ca') + +-- +2.34.1 + diff --git a/SOURCES/0029-ipatests-extend-AES-keyset-to-SHA2-based-ones.patch b/SOURCES/0029-ipatests-extend-AES-keyset-to-SHA2-based-ones.patch new file mode 100644 index 0000000..b20a590 --- /dev/null +++ b/SOURCES/0029-ipatests-extend-AES-keyset-to-SHA2-based-ones.patch @@ -0,0 +1,46 @@ +From 49d9147e38c5b50c52a1ebc7283753c779c2f81f Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Thu, 3 Mar 2022 14:38:57 +0200 +Subject: [PATCH] ipatests: extend AES keyset to SHA2-based ones + +Fixes: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Julien Rische +Reviewed-By: Francisco Trivino +--- + ipaserver/install/plugins/adtrust.py | 3 ++- + ipatests/pytest_ipa/integration/tasks.py | 3 ++- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py +index 5b87ac47c6919de287b07c9ceef7ae22e1e79398..67e372bdb40a0b1f6815f107fc567f0ae056dad8 100644 +--- a/ipaserver/install/plugins/adtrust.py ++++ b/ipaserver/install/plugins/adtrust.py +@@ -754,7 +754,8 @@ class update_host_cifs_keytabs(Updater): + """ + + host_princ_template = "host/{master}@{realm}" +- valid_etypes = ['aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96'] ++ valid_etypes = ['aes256-cts-hmac-sha384-192', 'aes128-cts-hmac-sha256-128', ++ 'aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96'] + + def extract_key_refs(self, keytab): + host_princ = self.host_princ_template.format( +diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py +index 13d84e23fa7dc8a5e562e8498c9142e2bcad696a..d06f8eb2cf6c36956ec200a1abb7c488d1dad9aa 100755 +--- a/ipatests/pytest_ipa/integration/tasks.py ++++ b/ipatests/pytest_ipa/integration/tasks.py +@@ -2261,7 +2261,8 @@ class KerberosKeyCopier: + copier.copy_keys('/etc/krb5.keytab', tmpname, replacement=replacement) + """ + host_princ_template = "host/{master}@{realm}" +- valid_etypes = ['aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96'] ++ valid_etypes = ['aes256-cts-hmac-sha384-192', 'aes128-cts-hmac-sha256-128', ++ 'aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96'] + + def __init__(self, host): + self.host = host +-- +2.34.1 + diff --git a/SOURCES/0030-freeipa.spec-bump-crypto-policies-dependency-for-Cen.patch b/SOURCES/0030-freeipa.spec-bump-crypto-policies-dependency-for-Cen.patch new file mode 100644 index 0000000..4c61492 --- /dev/null +++ b/SOURCES/0030-freeipa.spec-bump-crypto-policies-dependency-for-Cen.patch @@ -0,0 +1,35 @@ +From ee39de46a1c1ea96bbe524f159ae435319b2d072 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Thu, 3 Mar 2022 14:43:11 +0200 +Subject: [PATCH] freeipa.spec: bump crypto-policies dependency for CentOS 9 + Stream + +Fixes: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Julien Rische +Reviewed-By: Francisco Trivino +--- + freeipa.spec.in | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/freeipa.spec.in b/freeipa.spec.in +index 0b24febc0baff6f60fd2b4cb254971bd3e3aa3b8..c1d81605068c6fc3e6c765ad01c4967fa9f03c95 100755 +--- a/freeipa.spec.in ++++ b/freeipa.spec.in +@@ -695,6 +695,12 @@ Provides: %{name}-admintools = %{version}-%{release} + Conflicts: crypto-policies < 20200629-1 + %endif + ++%if 0%{?rhel} == 9 ++# Conflict with crypto-policies < 20220223-1 to get upgraded AD-SUPPORT and ++# AD-SUPPORT-LEGACY policy modules ++Conflicts: crypto-policies < 20220223-1 ++%endif ++ + %description client + IPA is an integrated solution to provide centrally managed Identity (users, + hosts, services), Authentication (SSO, 2FA), and Authorization +-- +2.34.1 + diff --git a/SOURCES/0031-Kerberos-instance-default-to-AES256-SHA2-for-master-.patch b/SOURCES/0031-Kerberos-instance-default-to-AES256-SHA2-for-master-.patch new file mode 100644 index 0000000..4cb65b1 --- /dev/null +++ b/SOURCES/0031-Kerberos-instance-default-to-AES256-SHA2-for-master-.patch @@ -0,0 +1,56 @@ +From 3e54c4362490b4da1b6cb3e141bb6e08fecc58c0 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 14 Mar 2022 13:23:04 +0200 +Subject: [PATCH] Kerberos instance: default to AES256-SHA2 for master key + encryption + +KDC configuration in /var/kerberos/krb5kdc/kdc.conf is generated from +the template in install/share/kdc.conf.template. Master key encryption +type specified there is used to bootstrap the master key in LDAP +database. Once it is done, actual deployment does not rely on the +master_key_type value anymore. The actual master key(s) get loaded from +LDAP database where they stored in a BER-encoded format, preserving all +parameters, including encryption type. + +This means we can safely migrate to AES256-SHA2 as the default master +key encryption type for new installations. Replicas will get their +master key encryption type details from the server they were provisioned +from. + +MIT Kerberos supports AES256-SHA2 since 1.15 (2015), meaning RHEL 7.4 is +the earliest supported version as it provides krb5 1.15.1. Current +supported RHEL 7 version is RHEL 7.9. Since RHEL 6 already cannot be +used as a replica to IPA 4.5+ due to a domain level 1 upgrade, this +change does not affect old releases. + +Migration from the previously deployed master key encryption type is +described by MIT Kerberos upstream in +http://web.mit.edu/kerberos/krb5-latest/doc/admin/advanced/retiring-des.html#the-database-master-key + +One would need to use '-x ipa-setup-override-restrictions' to allow +the `kdb5_util` utility to modify the data over IPA KDB driver. + +Fixes: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Francisco Trivino +--- + ipaserver/install/krbinstance.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py +index 01b3309d50c0e8025e3381eac577225b1ef0be9d..a5eaa7b17133498f08e84d01c90764236e8ebe84 100644 +--- a/ipaserver/install/krbinstance.py ++++ b/ipaserver/install/krbinstance.py +@@ -51,7 +51,7 @@ logger = logging.getLogger(__name__) + + PKINIT_ENABLED = 'pkinitEnabled' + +-MASTER_KEY_TYPE = 'aes256-sha1' ++MASTER_KEY_TYPE = 'aes256-sha2' + SUPPORTED_ENCTYPES = ('aes256-sha2:special', 'aes128-sha2:special', + 'aes256-sha2:normal', 'aes128-sha2:normal', + 'aes256-cts:special', 'aes128-cts:special', +-- +2.34.1 + diff --git a/SOURCES/0032-test_otp-do-not-use-paramiko-unless-it-is-really-nee.patch b/SOURCES/0032-test_otp-do-not-use-paramiko-unless-it-is-really-nee.patch new file mode 100644 index 0000000..368346f --- /dev/null +++ b/SOURCES/0032-test_otp-do-not-use-paramiko-unless-it-is-really-nee.patch @@ -0,0 +1,44 @@ +From 3baae8d1bd0a0c4c707314524289e86e6ecbc0df Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Mon, 14 Mar 2022 21:09:36 +0200 +Subject: [PATCH] test_otp: do not use paramiko unless it is really needed + +paramiko cannot be used in FIPS mode. We have few tests that import +generic methods from test_otp (add_token/del_token) and those tests fail +in FIPS mode due to unconditional 'import paramiko'. + +Instead, move 'import paramiko' to the ssh_2f() helper which is not used +in FIPS mode (the whole SSH 2FA test is skipped then). + +Related: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Francisco Trivino +--- + ipatests/test_integration/test_otp.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py +index bec76d205bf37699483b65ebbc5613cbbb466bb4..04bef4626077e727654898b07a76acab4f1d5971 100644 +--- a/ipatests/test_integration/test_otp.py ++++ b/ipatests/test_integration/test_otp.py +@@ -5,7 +5,6 @@ + """ + import base64 + import logging +-import paramiko + import pytest + import re + import time +@@ -102,6 +101,8 @@ def ssh_2f(hostname, username, answers_dict, port=22): + logger.info( + "Answer to ssh prompt is: '%s'", answers_dict[prmpt_str]) + return resp ++ ++ import paramiko + trans = paramiko.Transport((hostname, port)) + trans.connect() + trans.auth_interactive(username, answer_handler) +-- +2.34.1 + diff --git a/SOURCES/0033-test_krbtpolicy-skip-SPAKE-related-tests-in-FIPS-mod.patch b/SOURCES/0033-test_krbtpolicy-skip-SPAKE-related-tests-in-FIPS-mod.patch new file mode 100644 index 0000000..de21062 --- /dev/null +++ b/SOURCES/0033-test_krbtpolicy-skip-SPAKE-related-tests-in-FIPS-mod.patch @@ -0,0 +1,44 @@ +From 2e70535f74e7d9dd76e728eca1119ce522fd138a Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Tue, 15 Mar 2022 11:39:46 +0200 +Subject: [PATCH] test_krbtpolicy: skip SPAKE-related tests in FIPS mode + +SPAKE is based on the crypto primitives which are not FIPS compliant +yet. This means that in FIPS mode use of 'hardened' authentication +indicator is not possible. Skip corresponding tests in FIPS mode. + +Related: https://pagure.io/freeipa/issue/9119 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Francisco Trivino +--- + ipatests/test_integration/test_krbtpolicy.py | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/ipatests/test_integration/test_krbtpolicy.py b/ipatests/test_integration/test_krbtpolicy.py +index 9489fbc97b7836aecf491b57627f254d4849eb56..eae16247bdfb195c1d91209cf2d11eac4c25018f 100644 +--- a/ipatests/test_integration/test_krbtpolicy.py ++++ b/ipatests/test_integration/test_krbtpolicy.py +@@ -105,6 +105,9 @@ class TestPWPolicy(IntegrationTest): + + def test_krbtpolicy_password_and_hardended(self): + """Test a pwd and hardened kerberos ticket policy with 10min tickets""" ++ if self.master.is_fips_mode: ++ pytest.skip("SPAKE pre-auth is not compatible with FIPS mode") ++ + master = self.master + master.run_command(['ipa', 'user-mod', USER1, + '--user-auth-type', 'password', +@@ -133,6 +136,9 @@ class TestPWPolicy(IntegrationTest): + + def test_krbtpolicy_hardended(self): + """Test a hardened kerberos ticket policy with 30min tickets""" ++ if self.master.is_fips_mode: ++ pytest.skip("SPAKE pre-auth is not compatible with FIPS mode") ++ + master = self.master + master.run_command(['ipa', 'user-mod', USER1, + '--user-auth-type', 'hardened']) +-- +2.34.1 + diff --git a/SOURCES/0034-Support-AES-for-KRA-archival-wrapping.patch b/SOURCES/0034-Support-AES-for-KRA-archival-wrapping.patch new file mode 100644 index 0000000..8d4ec8a --- /dev/null +++ b/SOURCES/0034-Support-AES-for-KRA-archival-wrapping.patch @@ -0,0 +1,555 @@ +From 895e99b6843c2fa2274acab824607c33c1a560a4 Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Mon, 7 Oct 2019 14:13:03 +0200 +Subject: [PATCH] Support AES for KRA archival wrapping + +The vault plugin has used TripleDES (des-ede3-cbc) as default wrapping +algorithm since the plugin was introduced. Allow use of AES-128-CBC as +alternative wrapping algorithm for transport of secrets. + +Fixes: https://pagure.io/freeipa/issue/6524 + +Signed-off-by: Christian Heimes +Reviewed-By: Christian Heimes +Reviewed-By: Alexander Bokovoy +--- + API.txt | 7 +- + VERSION.m4 | 5 +- + ipaclient/plugins/vault.py | 155 +++++++++++++++++++++++++------------ + ipalib/capabilities.py | 4 + + ipalib/constants.py | 12 +++ + ipaserver/plugins/vault.py | 61 ++++++++++++--- + 6 files changed, 180 insertions(+), 64 deletions(-) + +diff --git a/API.txt b/API.txt +index 576fa7c51e31886b257ccf176aaf232c0f2ea5ee..f95f2c8457e39f2268386a8a2336952d3285e008 100644 +--- a/API.txt ++++ b/API.txt +@@ -6548,7 +6548,7 @@ output: Output('completed', type=[]) + output: Output('failed', type=[]) + output: Entry('result') + command: vault_archive_internal/1 +-args: 1,9,3 ++args: 1,10,3 + arg: Str('cn', cli_name='name') + option: Flag('all', autofill=True, cli_name='all', default=False) + option: Bytes('nonce') +@@ -6559,6 +6559,7 @@ option: Flag('shared?', autofill=True, default=False) + option: Str('username?', cli_name='user') + option: Bytes('vault_data') + option: Str('version?') ++option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'des-ede3-cbc', u'aes-128-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +@@ -6649,7 +6650,7 @@ output: Output('completed', type=[]) + output: Output('failed', type=[]) + output: Entry('result') + command: vault_retrieve_internal/1 +-args: 1,7,3 ++args: 1,8,3 + arg: Str('cn', cli_name='name') + option: Flag('all', autofill=True, cli_name='all', default=False) + option: Flag('raw', autofill=True, cli_name='raw', default=False) +@@ -6658,6 +6659,7 @@ option: Bytes('session_key') + option: Flag('shared?', autofill=True, default=False) + option: Str('username?', cli_name='user') + option: Str('version?') ++option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'des-ede3-cbc', u'aes-128-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +@@ -7327,6 +7329,7 @@ default: vaultcontainer_del/1 + default: vaultcontainer_remove_owner/1 + default: vaultcontainer_show/1 + default: whoami/1 ++capability: vault_aes_keywrap 2.246 + capability: messages 2.52 + capability: optional_uid_params 2.54 + capability: permissions2 2.69 +diff --git a/VERSION.m4 b/VERSION.m4 +index 70aaff4c9b9514a5937eae60074376e1a592464e..997ac35e74fa6f2a96da027ed3ce93cf809b62a7 100644 +--- a/VERSION.m4 ++++ b/VERSION.m4 +@@ -86,9 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) + # # + ######################################################## + define(IPA_API_VERSION_MAJOR, 2) +-# Last change: add enable_sid to config +-define(IPA_API_VERSION_MINOR, 245) +- ++# Last change: Add wrapping algorithm to vault archive/retrieve ++define(IPA_API_VERSION_MINOR, 246) + + ######################################################## + # Following values are auto-generated from values above +diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py +index d3a1d370efaccc7e5b0088bd3df341d76884d509..115171c7768d44251c17d0bcdac9c37b3a25db99 100644 +--- a/ipaclient/plugins/vault.py ++++ b/ipaclient/plugins/vault.py +@@ -25,11 +25,12 @@ import io + import json + import logging + import os ++import ssl + import tempfile + + from cryptography.fernet import Fernet, InvalidToken + from cryptography.hazmat.backends import default_backend +-from cryptography.hazmat.primitives import hashes, serialization ++from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +@@ -39,7 +40,7 @@ from cryptography.hazmat.primitives.serialization import ( + + from ipaclient.frontend import MethodOverride + from ipalib import x509 +-from ipalib.constants import USER_CACHE_PATH ++from ipalib import constants + from ipalib.frontend import Local, Method, Object + from ipalib.util import classproperty + from ipalib import api, errors +@@ -546,42 +547,49 @@ class vault_mod(Local): + return response + + +-class _TransportCertCache: ++class _KraConfigCache: ++ """The KRA config cache stores vaultconfig-show result. ++ """ + def __init__(self): + self._dirname = os.path.join( +- USER_CACHE_PATH, 'ipa', 'kra-transport-certs' ++ constants.USER_CACHE_PATH, 'ipa', 'kra-config' + ) + + def _get_filename(self, domain): +- basename = DNSName(domain).ToASCII() + '.pem' ++ basename = DNSName(domain).ToASCII() + '.json' + return os.path.join(self._dirname, basename) + +- def load_cert(self, domain): +- """Load cert from cache ++ def load(self, domain): ++ """Load config from cache + + :param domain: IPA domain +- :return: cryptography.x509.Certificate or None ++ :return: dict or None + """ + filename = self._get_filename(domain) + try: + try: +- return x509.load_certificate_from_file(filename) +- except EnvironmentError as e: ++ with open(filename) as f: ++ return json.load(f) ++ except OSError as e: + if e.errno != errno.ENOENT: + raise + except Exception: + logger.warning("Failed to load %s", filename, exc_info=True) + return None + +- def store_cert(self, domain, transport_cert): +- """Store a new cert or override existing cert ++ def store(self, domain, response): ++ """Store config in cache + + :param domain: IPA domain +- :param transport_cert: cryptography.x509.Certificate +- :return: True if cert was stored successfully ++ :param config: ipa vaultconfig-show response ++ :return: True if config was stored successfully + """ ++ config = response['result'].copy() ++ # store certificate as PEM-encoded ASCII ++ config['transport_cert'] = ssl.DER_cert_to_PEM_cert( ++ config['transport_cert'] ++ ) + filename = self._get_filename(domain) +- pem = transport_cert.public_bytes(serialization.Encoding.PEM) + try: + try: + os.makedirs(self._dirname) +@@ -589,9 +597,9 @@ class _TransportCertCache: + if e.errno != errno.EEXIST: + raise + with tempfile.NamedTemporaryFile(dir=self._dirname, delete=False, +- mode='wb') as f: ++ mode='w') as f: + try: +- f.write(pem) ++ json.dump(config, f) + ipautil.flush_sync(f) + f.close() + os.rename(f.name, filename) +@@ -604,8 +612,8 @@ class _TransportCertCache: + else: + return True + +- def remove_cert(self, domain): +- """Remove a cert from cache, ignores errors ++ def remove(self, domain): ++ """Remove a config from cache, ignores errors + + :param domain: IPA domain + :return: True if cert was found and removed +@@ -621,7 +629,7 @@ class _TransportCertCache: + return True + + +-_transport_cert_cache = _TransportCertCache() ++_kra_config_cache = _KraConfigCache() + + + @register(override=True, no_fail=True) +@@ -636,13 +644,8 @@ class vaultconfig_show(MethodOverride): + + response = super(vaultconfig_show, self).forward(*args, **options) + +- # cache transport certificate +- transport_cert = x509.load_der_x509_certificate( +- response['result']['transport_cert']) +- +- _transport_cert_cache.store_cert( +- self.api.env.domain, transport_cert +- ) ++ # cache config ++ _kra_config_cache.store(self.api.env.domain, response) + + if file: + with open(file, 'wb') as f: +@@ -652,10 +655,54 @@ class vaultconfig_show(MethodOverride): + + + class ModVaultData(Local): +- def _generate_session_key(self): +- key_length = max(algorithms.TripleDES.key_sizes) +- algo = algorithms.TripleDES(os.urandom(key_length // 8)) +- return algo ++ def _generate_session_key(self, name): ++ if name not in constants.VAULT_WRAPPING_SUPPORTED_ALGOS: ++ msg = _("{algo} is not a supported vault wrapping algorithm") ++ raise errors.ValidationError(msg.format(algo=repr(name))) ++ if name == constants.VAULT_WRAPPING_AES128_CBC: ++ return algorithms.AES(os.urandom(128 // 8)) ++ elif name == constants.VAULT_WRAPPING_3DES: ++ return algorithms.TripleDES(os.urandom(196 // 8)) ++ else: ++ # unreachable ++ raise ValueError(name) ++ ++ def _get_vaultconfig(self, force_refresh=False): ++ config = None ++ if not force_refresh: ++ config = _kra_config_cache.load(self.api.env.domain) ++ if config is None: ++ # vaultconfig_show also caches data ++ response = self.api.Command.vaultconfig_show() ++ config = response['result'] ++ transport_cert = x509.load_der_x509_certificate( ++ config['transport_cert'] ++ ) ++ else: ++ # cached JSON uses PEM-encoded ASCII string ++ transport_cert = x509.load_pem_x509_certificate( ++ config['transport_cert'].encode('ascii') ++ ) ++ ++ default_algo = config.get('wrapping_default_algorithm') ++ if default_algo is None: ++ # old server ++ wrapping_algo = constants.VAULT_WRAPPING_AES128_CBC ++ elif default_algo in constants.VAULT_WRAPPING_SUPPORTED_ALGOS: ++ # try to use server default ++ wrapping_algo = default_algo ++ else: ++ # prefer server's sorting order ++ for algo in config['wrapping_supported_algorithms']: ++ if algo in constants.VAULT_WRAPPING_SUPPORTED_ALGOS: ++ wrapping_algo = algo ++ break ++ else: ++ raise errors.ValidationError( ++ "No overlapping wrapping algorithm between server and " ++ "client." ++ ) ++ return transport_cert, wrapping_algo + + def _do_internal(self, algo, transport_cert, raise_unexpected, + *args, **options): +@@ -675,29 +722,23 @@ class ModVaultData(Local): + except (errors.InternalError, + errors.ExecutionError, + errors.GenericError): +- _transport_cert_cache.remove_cert(self.api.env.domain) ++ _kra_config_cache.remove(self.api.env.domain) + if raise_unexpected: + raise + return None + +- def internal(self, algo, *args, **options): ++ def internal(self, algo, transport_cert, *args, **options): + """ + Calls the internal counterpart of the command. + """ +- domain = self.api.env.domain +- + # try call with cached transport certificate +- transport_cert = _transport_cert_cache.load_cert(domain) +- if transport_cert is not None: +- result = self._do_internal(algo, transport_cert, False, ++ result = self._do_internal(algo, transport_cert, False, + *args, **options) +- if result is not None: +- return result ++ if result is not None: ++ return result + + # retrieve transport certificate (cached by vaultconfig_show) +- response = self.api.Command.vaultconfig_show() +- transport_cert = x509.load_der_x509_certificate( +- response['result']['transport_cert']) ++ transport_cert = self._get_vaultconfig(force_refresh=True)[0] + # call with the retrieved transport certificate + return self._do_internal(algo, transport_cert, True, + *args, **options) +@@ -777,7 +818,7 @@ class vault_archive(ModVaultData): + def _wrap_data(self, algo, json_vault_data): + """Encrypt data with wrapped session key and transport cert + +- :param bytes algo: wrapping algorithm instance ++ :param algo: wrapping algorithm instance + :param bytes json_vault_data: dumped vault data + :return: + """ +@@ -929,15 +970,24 @@ class vault_archive(ModVaultData): + + json_vault_data = json.dumps(vault_data).encode('utf-8') + ++ # get config ++ transport_cert, wrapping_algo = self._get_vaultconfig() ++ # let options override wrapping algo ++ # For backwards compatibility do not send old legacy wrapping algo ++ # to server. Only send the option when non-3DES is used. ++ wrapping_algo = options.pop('wrapping_algo', wrapping_algo) ++ if wrapping_algo != constants.VAULT_WRAPPING_3DES: ++ options['wrapping_algo'] = wrapping_algo ++ + # generate session key +- algo = self._generate_session_key() ++ algo = self._generate_session_key(wrapping_algo) + # wrap vault data + nonce, wrapped_vault_data = self._wrap_data(algo, json_vault_data) + options.update( + nonce=nonce, + vault_data=wrapped_vault_data + ) +- return self.internal(algo, *args, **options) ++ return self.internal(algo, transport_cert, *args, **options) + + + @register(no_fail=True) +@@ -1061,10 +1111,19 @@ class vault_retrieve(ModVaultData): + vault = self.api.Command.vault_show(*args, **options)['result'] + vault_type = vault['ipavaulttype'][0] + ++ # get config ++ transport_cert, wrapping_algo = self._get_vaultconfig() ++ # let options override wrapping algo ++ # For backwards compatibility do not send old legacy wrapping algo ++ # to server. Only send the option when non-3DES is used. ++ wrapping_algo = options.pop('wrapping_algo', wrapping_algo) ++ if wrapping_algo != constants.VAULT_WRAPPING_3DES: ++ options['wrapping_algo'] = wrapping_algo ++ + # generate session key +- algo = self._generate_session_key() ++ algo = self._generate_session_key(wrapping_algo) + # send retrieval request to server +- response = self.internal(algo, *args, **options) ++ response = self.internal(algo, transport_cert, *args, **options) + # unwrap data with session key + vault_data = self._unwrap_response( + algo, +diff --git a/ipalib/capabilities.py b/ipalib/capabilities.py +index 55b84aa6bc73d583e7bd5d03d2f4f1cc5c8e7c0b..4d8ae408bf67c280d27ce494baa9db9aaff0cd69 100644 +--- a/ipalib/capabilities.py ++++ b/ipalib/capabilities.py +@@ -54,6 +54,10 @@ capabilities = dict( + + # dns_name_values: dnsnames as objects + dns_name_values=u'2.88', ++ ++ # vault supports aes key wrapping ++ vault_aes_keywrap='2.246' ++ + ) + + +diff --git a/ipalib/constants.py b/ipalib/constants.py +index 9f19b0f9941ba5068f1e6c218092e3b76fdc7599..11171b2e8aeb6f7306299b2bd7db3a3f39d29d4a 100644 +--- a/ipalib/constants.py ++++ b/ipalib/constants.py +@@ -374,3 +374,15 @@ KRA_TRACKING_REQS = { + } + + ALLOWED_NETBIOS_CHARS = string.ascii_uppercase + string.digits + '-' ++ ++# vault data wrapping algorithms ++VAULT_WRAPPING_3DES = 'des-ede3-cbc' ++VAULT_WRAPPING_AES128_CBC = 'aes-128-cbc' ++VAULT_WRAPPING_SUPPORTED_ALGOS = ( ++ # old default was 3DES ++ VAULT_WRAPPING_3DES, ++ # supported since pki-kra >= 10.4 ++ VAULT_WRAPPING_AES128_CBC, ++) ++# 3DES for backwards compatibility ++VAULT_WRAPPING_DEFAULT_ALGO = VAULT_WRAPPING_3DES +diff --git a/ipaserver/plugins/vault.py b/ipaserver/plugins/vault.py +index aebac7dff7bb9d183c6012cc685577d476e18c4e..4d40f66c6a793a831e91c5fe25c8b5277cbd1972 100644 +--- a/ipaserver/plugins/vault.py ++++ b/ipaserver/plugins/vault.py +@@ -23,6 +23,10 @@ from ipalib.frontend import Command, Object + from ipalib import api, errors + from ipalib import Bytes, Flag, Str, StrEnum + from ipalib import output ++from ipalib.constants import ( ++ VAULT_WRAPPING_SUPPORTED_ALGOS, VAULT_WRAPPING_DEFAULT_ALGO, ++ VAULT_WRAPPING_3DES, VAULT_WRAPPING_AES128_CBC, ++) + from ipalib.crud import PKQuery, Retrieve + from ipalib.parameters import Principal + from ipalib.plugable import Registry +@@ -39,14 +43,8 @@ from ipaserver.masters import is_service_enabled + if api.env.in_server: + import pki.account + import pki.key +- # pylint: disable=no-member +- try: +- # pki >= 10.4.0 +- from pki.crypto import DES_EDE3_CBC_OID +- except ImportError: +- DES_EDE3_CBC_OID = pki.key.KeyClient.DES_EDE3_CBC_OID +- # pylint: enable=no-member +- ++ from pki.crypto import DES_EDE3_CBC_OID ++ from pki.crypto import AES_128_CBC_OID + + if six.PY3: + unicode = str +@@ -652,6 +652,20 @@ class vault(LDAPObject): + ), + ) + ++ def _translate_algorithm(self, name): ++ if name is None: ++ name = VAULT_WRAPPING_DEFAULT_ALGO ++ if name not in VAULT_WRAPPING_SUPPORTED_ALGOS: ++ msg = _("{algo} is not a supported vault wrapping algorithm") ++ raise errors.ValidationError(msg.format(algo=name)) ++ if name == VAULT_WRAPPING_3DES: ++ return DES_EDE3_CBC_OID ++ elif name == VAULT_WRAPPING_AES128_CBC: ++ return AES_128_CBC_OID ++ else: ++ # unreachable ++ raise ValueError(name) ++ + def get_dn(self, *keys, **options): + """ + Generates vault DN from parameters. +@@ -992,14 +1006,18 @@ class vaultconfig_show(Retrieve): + ) + + def execute(self, *args, **options): +- + if not self.api.Command.kra_is_enabled()['result']: + raise errors.InvocationError( + format=_('KRA service is not enabled')) + ++ config = dict( ++ wrapping_supported_algorithms=VAULT_WRAPPING_SUPPORTED_ALGOS, ++ wrapping_default_algorithm=VAULT_WRAPPING_DEFAULT_ALGO, ++ ) ++ + with self.api.Backend.kra.get_client() as kra_client: + transport_cert = kra_client.system_certs.get_transport_cert() +- config = {'transport_cert': transport_cert.binary} ++ config['transport_cert'] = transport_cert.binary + + self.api.Object.config.show_servroles_attributes( + config, "KRA server", **options) +@@ -1029,6 +1047,13 @@ class vault_archive_internal(PKQuery): + 'nonce', + doc=_('Nonce'), + ), ++ StrEnum( ++ 'wrapping_algo?', ++ doc=_('Key wrapping algorithm'), ++ values=VAULT_WRAPPING_SUPPORTED_ALGOS, ++ default=VAULT_WRAPPING_DEFAULT_ALGO, ++ autofill=True, ++ ), + ) + + has_output = output.standard_entry +@@ -1045,6 +1070,9 @@ class vault_archive_internal(PKQuery): + nonce = options.pop('nonce') + wrapped_session_key = options.pop('session_key') + ++ wrapping_algo = options.pop('wrapping_algo', None) ++ algorithm_oid = self.obj._translate_algorithm(wrapping_algo) ++ + # retrieve vault info + vault = self.api.Command.vault_show(*args, **options)['result'] + +@@ -1071,7 +1099,7 @@ class vault_archive_internal(PKQuery): + pki.key.KeyClient.PASS_PHRASE_TYPE, + wrapped_vault_data, + wrapped_session_key, +- algorithm_oid=DES_EDE3_CBC_OID, ++ algorithm_oid=algorithm_oid, + nonce_iv=nonce, + ) + +@@ -1098,6 +1126,13 @@ class vault_retrieve_internal(PKQuery): + 'session_key', + doc=_('Session key wrapped with transport certificate'), + ), ++ StrEnum( ++ 'wrapping_algo?', ++ doc=_('Key wrapping algorithm'), ++ values=VAULT_WRAPPING_SUPPORTED_ALGOS, ++ default=VAULT_WRAPPING_DEFAULT_ALGO, ++ autofill=True, ++ ), + ) + + has_output = output.standard_entry +@@ -1112,6 +1147,9 @@ class vault_retrieve_internal(PKQuery): + + wrapped_session_key = options.pop('session_key') + ++ wrapping_algo = options.pop('wrapping_algo', None) ++ algorithm_oid = self.obj._translate_algorithm(wrapping_algo) ++ + # retrieve vault info + vault = self.api.Command.vault_show(*args, **options)['result'] + +@@ -1132,6 +1170,9 @@ class vault_retrieve_internal(PKQuery): + + key_info = response.key_infos[0] + ++ # XXX hack ++ kra_client.keys.encrypt_alg_oid = algorithm_oid ++ + # retrieve encrypted data from KRA + key = kra_client.keys.retrieve_key( + key_info.get_key_id(), +-- +2.34.1 + diff --git a/SOURCES/0035-Set-AES-as-default-for-KRA-archival-wrapping.patch b/SOURCES/0035-Set-AES-as-default-for-KRA-archival-wrapping.patch new file mode 100644 index 0000000..f762229 --- /dev/null +++ b/SOURCES/0035-Set-AES-as-default-for-KRA-archival-wrapping.patch @@ -0,0 +1,86 @@ +From 984190eea01ac42cd1f97567a67dd9446e5b0bf9 Mon Sep 17 00:00:00 2001 +From: Francisco Trivino +Date: Fri, 11 Mar 2022 17:47:38 +0100 +Subject: [PATCH] Set AES as default for KRA archival wrapping + +This commit sets AES-128-CBC as default wrapping algorithm as +TripleDES (des-ede3-cbc) is not supported anymore in C9S. + +Fixes: https://pagure.io/freeipa/issue/6524 + +Signed-off-by: Francisco Trivino +Reviewed-By: Christian Heimes +Reviewed-By: Alexander Bokovoy +--- + API.txt | 6 +++--- + ipalib/constants.py | 14 +++++++++----- + 2 files changed, 12 insertions(+), 8 deletions(-) + +diff --git a/API.txt b/API.txt +index f95f2c8457e39f2268386a8a2336952d3285e008..1f27dcc616a6395c56ef91f3453e7620625c7645 100644 +--- a/API.txt ++++ b/API.txt +@@ -6559,7 +6559,7 @@ option: Flag('shared?', autofill=True, default=False) + option: Str('username?', cli_name='user') + option: Bytes('vault_data') + option: Str('version?') +-option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'des-ede3-cbc', u'aes-128-cbc']) ++option: StrEnum('wrapping_algo?', autofill=True, default=u'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +@@ -6659,7 +6659,7 @@ option: Bytes('session_key') + option: Flag('shared?', autofill=True, default=False) + option: Str('username?', cli_name='user') + option: Str('version?') +-option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'des-ede3-cbc', u'aes-128-cbc']) ++option: StrEnum('wrapping_algo?', autofill=True, default=u'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +@@ -7329,10 +7329,10 @@ default: vaultcontainer_del/1 + default: vaultcontainer_remove_owner/1 + default: vaultcontainer_show/1 + default: whoami/1 +-capability: vault_aes_keywrap 2.246 + capability: messages 2.52 + capability: optional_uid_params 2.54 + capability: permissions2 2.69 + capability: primary_key_types 2.83 + capability: datetime_values 2.84 + capability: dns_name_values 2.88 ++capability: vault_aes_keywrap 2.246 +diff --git a/ipalib/constants.py b/ipalib/constants.py +index 11171b2e8aeb6f7306299b2bd7db3a3f39d29d4a..68178004181bebcc8c093dac55e18d5afe0251e5 100644 +--- a/ipalib/constants.py ++++ b/ipalib/constants.py +@@ -29,6 +29,8 @@ from ipaplatform.constants import constants as _constants + from ipapython.dn import DN + from ipapython.fqdn import gethostfqdn + from ipapython.version import VERSION, API_VERSION ++from cryptography.hazmat.primitives.ciphers import algorithms, modes ++from cryptography.hazmat.backends.openssl.backend import backend + + + FQDN = gethostfqdn() +@@ -379,10 +381,12 @@ ALLOWED_NETBIOS_CHARS = string.ascii_uppercase + string.digits + '-' + VAULT_WRAPPING_3DES = 'des-ede3-cbc' + VAULT_WRAPPING_AES128_CBC = 'aes-128-cbc' + VAULT_WRAPPING_SUPPORTED_ALGOS = ( +- # old default was 3DES +- VAULT_WRAPPING_3DES, +- # supported since pki-kra >= 10.4 ++ # new default and supported since pki-kra >= 10.4 + VAULT_WRAPPING_AES128_CBC, + ) +-# 3DES for backwards compatibility +-VAULT_WRAPPING_DEFAULT_ALGO = VAULT_WRAPPING_3DES ++VAULT_WRAPPING_DEFAULT_ALGO = VAULT_WRAPPING_AES128_CBC ++ ++# Add 3DES for backwards compatibility if supported ++if backend.cipher_supported(algorithms.TripleDES(b"\x00" * 8), ++ modes.CBC(b"\x00" * 8)): ++ VAULT_WRAPPING_SUPPORTED_ALGOS += (VAULT_WRAPPING_3DES,) +-- +2.34.1 + diff --git a/SPECS/freeipa.spec b/SPECS/freeipa.spec index c145291..12c75a3 100644 --- a/SPECS/freeipa.spec +++ b/SPECS/freeipa.spec @@ -66,7 +66,7 @@ %if 0%{?rhel} %global package_name ipa %global alt_name freeipa -%global krb5_version 1.18.2-2 +%global krb5_version 1.19.1-15 %global krb5_kdb_version 8.0 # 0.7.16: https://github.com/drkjam/netaddr/issues/71 %global python_netaddr_version 0.7.19 @@ -198,7 +198,7 @@ Name: %{package_name} Version: %{IPA_VERSION} -Release: 5%{?rc_version:.%rc_version}%{?dist} +Release: 7%{?rc_version:.%rc_version}%{?dist} Summary: The Identity, Policy and Audit system License: GPLv3+ @@ -241,6 +241,18 @@ Patch0020: 0020-Test-ipa-ccache-sweep.timer-enabled-by-default-durin.patch Patch0021: 0021-ipa_cldap-fix-memory-leak.patch Patch0022: 0022-ipatests-remove-additional-check-for-failed-units_rhbz#2053025.patch Patch0023: 0023-ipatests-fix-TestOTPToken-rhbz#2053025.patch +Patch0024: 0024-ipatests-Tests-for-Autoprivate-group.patch +Patch0025: 0025-mark-xfail-for-test_idoverride_with_auto_private_gro.patch +Patch0026: 0026-Mark-xfail-test_gidnumber_not_corresponding_existing.patch +Patch0027: 0027-KRB-instance-make-provision-to-work-with-crypto-poli.patch +Patch0028: 0028-tests-ensure-AD-SUPPORT-subpolicy-is-active.patch +Patch0029: 0029-ipatests-extend-AES-keyset-to-SHA2-based-ones.patch +Patch0030: 0030-freeipa.spec-bump-crypto-policies-dependency-for-Cen.patch +Patch0031: 0031-Kerberos-instance-default-to-AES256-SHA2-for-master-.patch +Patch0032: 0032-test_otp-do-not-use-paramiko-unless-it-is-really-nee.patch +Patch0033: 0033-test_krbtpolicy-skip-SPAKE-related-tests-in-FIPS-mod.patch +Patch0034: 0034-Support-AES-for-KRA-archival-wrapping.patch +Patch0035: 0035-Set-AES-as-default-for-KRA-archival-wrapping.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch %endif %endif @@ -713,6 +725,12 @@ Provides: %{name}-admintools = %{version}-%{release} Conflicts: crypto-policies < 20200629-1 %endif +%if 0%{?rhel} == 9 +# Conflict with crypto-policies < 20220223-1 to get upgraded AD-SUPPORT and +# AD-SUPPORT-LEGACY policy modules +Conflicts: crypto-policies < 20220223-1 +%endif + %description client IPA is an integrated solution to provide centrally managed Identity (users, hosts, services), Authentication (SSO, 2FA), and Authorization @@ -1732,6 +1750,24 @@ fi %endif %changelog +* Mon Mar 21 2022 Florence Blanc-Renaud - 4.9.8-7 +- Resolves: rhbz#2057471 Consequences of FIPS crypto policy tightening in RHEL 9 + - KRB instance: make provision to work with crypto policy without SHA-1 HMAC types + - tests: ensure AD-SUPPORT subpolicy is active + - ipatests: extend AES keyset to SHA2-based ones + - freeipa.spec: bump crypto-policies dependency for CentOS 9 Stream + - Kerberos instance: default to AES256-SHA2 for master key encryption + - test_otp: do not use paramiko unless it is really needed + - test_krbtpolicy: skip SPAKE-related tests in FIPS mode + - Support AES for KRA archival wrapping + - Set AES as default for KRA archival wrapping + +* Thu Feb 24 2022 Florence Blanc-Renaud - 4.9.8-6 +- Resolves: rhbz#2057467 Backport latest test fixes in python3-ipatests + - ipatests: Tests for Autoprivate group. + - mark xfail for test_idoverride_with_auto_private_group[hybrid] + - Mark xfail test_gidnumber_not_corresponding_existing_group[true,hybrid] + * Mon Feb 14 2022 Alexander Bokovoy - 4.9.8-5 - Resolves: rhbz#2053025 - add IPA test suite fixes