From b69616ed48224b48b5615044314787f69af13c13 Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Wed, 12 Mar 2025 13:50:46 +0100 Subject: [PATCH] Add test for master key upgrade Since commit 4ed7da378940198cf4415f86d4eb013de6ac6455 in MIT krb5, kdb5_util sets the required bits on the modification mask of updated principal entries to enable ipa-kdb to switch the active master key. This commit creates a "test_fedora_legacy" ipaplatform where the AES HMAC-SHA2 encryption types are not enabled. "test_mkey_upgrade" uses this platform to initialize a domain with an aes256-cts-hmac-sha1-96 master key, and test its upgrade to aes256-cts-hmac-sha384-192. Some parts of the test infrastructure had to be made aware of this new platform (e.g. firewall integration). Reviewed-By: Rob Crittenden --- ipaplatform/setup.py | 1 + ipaplatform/test_fedora_legacy/__init__.py | 7 + ipaplatform/test_fedora_legacy/constants.py | 16 +++ ipaplatform/test_fedora_legacy/paths.py | 13 ++ ipaplatform/test_fedora_legacy/services.py | 27 ++++ ipaplatform/test_fedora_legacy/tasks.py | 35 +++++ ipatests/pytest_ipa/integration/firewall.py | 1 + .../test_integration/test_mkey_upgrade.py | 135 ++++++++++++++++++ 8 files changed, 235 insertions(+) create mode 100644 ipaplatform/test_fedora_legacy/__init__.py create mode 100644 ipaplatform/test_fedora_legacy/constants.py create mode 100644 ipaplatform/test_fedora_legacy/paths.py create mode 100644 ipaplatform/test_fedora_legacy/services.py create mode 100644 ipaplatform/test_fedora_legacy/tasks.py create mode 100644 ipatests/test_integration/test_mkey_upgrade.py diff --git a/ipaplatform/setup.py b/ipaplatform/setup.py index 086a99d1fa602551d2ff3270113608aa0fbc026c..1d6df1eff62b434162a7ce815e8273241d424c78 100644 --- a/ipaplatform/setup.py +++ b/ipaplatform/setup.py @@ -42,6 +42,7 @@ if __name__ == '__main__': "ipaplatform.rhel", "ipaplatform.rhel_container", "ipaplatform.suse", + "ipaplatform.test_fedora_legacy", "ipaplatform.opencloudos", "ipaplatform.tencentos" ], diff --git a/ipaplatform/test_fedora_legacy/__init__.py b/ipaplatform/test_fedora_legacy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a4f4cff5a7084d5d1697aefbe2bebcfb89e0a694 --- /dev/null +++ b/ipaplatform/test_fedora_legacy/__init__.py @@ -0,0 +1,7 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# +""" +This module contains Fedora AES HMAC-SHA1 master key specific platform files. +""" +NAME = 'test_fedora_legacy' diff --git a/ipaplatform/test_fedora_legacy/constants.py b/ipaplatform/test_fedora_legacy/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..0cebac86a9eea45803af669d8237dffd34675d74 --- /dev/null +++ b/ipaplatform/test_fedora_legacy/constants.py @@ -0,0 +1,16 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# +"""Fedora AES HMAC-SHA1 master key constants +""" +from ipaplatform.fedora.constants import FedoraConstantsNamespace, User, Group + + +__all__ = ("constants", "User", "Group") + + +class TestFedoraLegacyConstantsNamespace(FedoraConstantsNamespace): + pass + + +constants = TestFedoraLegacyConstantsNamespace() diff --git a/ipaplatform/test_fedora_legacy/paths.py b/ipaplatform/test_fedora_legacy/paths.py new file mode 100644 index 0000000000000000000000000000000000000000..71fb8acdc9143a4daa14a0b0dc7cf46fe5fdc2ce --- /dev/null +++ b/ipaplatform/test_fedora_legacy/paths.py @@ -0,0 +1,13 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# +"""Fedora AES HMAC-SHA1 master key paths +""" +from ipaplatform.fedora.paths import FedoraPathNamespace + + +class TestFedoraLegacyPathNamespace(FedoraPathNamespace): + pass + + +paths = TestFedoraLegacyPathNamespace() diff --git a/ipaplatform/test_fedora_legacy/services.py b/ipaplatform/test_fedora_legacy/services.py new file mode 100644 index 0000000000000000000000000000000000000000..adbe21f82e215349e54b586cdd5201374dd1378e --- /dev/null +++ b/ipaplatform/test_fedora_legacy/services.py @@ -0,0 +1,27 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# +"""Fedora AES HMAC-SHA1 master key services +""" +from ipaplatform.fedora import services as fedora_services + + +test_fedora_legacy_system_units = fedora_services.fedora_system_units.copy() + + +class TestFedoraLegacyService(fedora_services.FedoraService): + system_units = test_fedora_legacy_system_units + + +def test_fedora_legacy_service_class_factory(name, api=None): + return fedora_services.fedora_service_class_factory(name, api) + + +class TestFedoraLegacyServices(fedora_services.FedoraServices): + def service_class_factory(self, name, api=None): + return test_fedora_legacy_service_class_factory(name, api) + + +timedate_services = fedora_services.timedate_services +service = test_fedora_legacy_service_class_factory +knownservices = TestFedoraLegacyServices() diff --git a/ipaplatform/test_fedora_legacy/tasks.py b/ipaplatform/test_fedora_legacy/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..e6e72d7237cbc7d2a14b92cbb746bf8d66b197f2 --- /dev/null +++ b/ipaplatform/test_fedora_legacy/tasks.py @@ -0,0 +1,35 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# +"""Fedora AES HMAC-SHA1 master key tasks +""" +from ipaplatform.fedora.tasks import FedoraTaskNamespace + +from re import compile + + +def add_aes_sha1(enctypes): + return tuple({*enctypes, + 'aes256-cts:special', 'aes128-cts:special', + 'aes256-cts:normal', 'aes128-cts:normal'}) + + +class TestFedoraLegacyTaskNamespace(FedoraTaskNamespace): + + def get_masterkey_enctype(self): + return 'aes256-cts' + + def get_supported_enctypes(self): + aes_sha2_pattern = compile('^aes[0-9]+-sha2:') + + return tuple(e for e in super().get_supported_enctypes() + if not aes_sha2_pattern.match(e)) + + def get_removed_supported_enctypes(self): + return add_aes_sha1(super().get_removed_supported_enctypes()) + + def get_removed_default_enctypes(self): + return add_aes_sha1(super().get_removed_default_enctypes()) + + +tasks = TestFedoraLegacyTaskNamespace() diff --git a/ipatests/pytest_ipa/integration/firewall.py b/ipatests/pytest_ipa/integration/firewall.py index 1a5af5c5e71f71da4c939cddede7cab4817dd3a3..01661b6c6ab84b5ad0f13ebd537b25a2adf0ec1c 100644 --- a/ipatests/pytest_ipa/integration/firewall.py +++ b/ipatests/pytest_ipa/integration/firewall.py @@ -239,6 +239,7 @@ class Firewall(FirewallBase): firewalls = { 'rhel': FirewallD, 'fedora': FirewallD, + 'test_fedora_legacy': FirewallD, 'debian': FirewallD, 'ubuntu': FirewallD, 'altlinux': NoOpFirewall, diff --git a/ipatests/test_integration/test_mkey_upgrade.py b/ipatests/test_integration/test_mkey_upgrade.py new file mode 100644 index 0000000000000000000000000000000000000000..933623fbfc913ed6efe5409b3a35af1524305c8e --- /dev/null +++ b/ipatests/test_integration/test_mkey_upgrade.py @@ -0,0 +1,135 @@ +# +# Copyright (C) 2015 FreeIPA Contributors see COPYING for license +# + +import re +import textwrap + +from ipatests.pytest_ipa.integration import tasks +from ipatests.test_integration.base import IntegrationTest + + +class TestMkeyUpgrade(IntegrationTest): + + num_replicas = 1 + topology = 'line' + + @classmethod + def install(cls, mh): + cls.master.put_file_contents( + '/etc/profile.d/ipaplatform.sh', + 'export IPAPLATFORM_OVERRIDE=test_fedora_legacy') + cls.master.run_command(['mkdir', '/etc/systemd/system/ipa.service.d']) + cls.master.put_file_contents( + '/etc/systemd/system/ipa.service.d/platform.conf', + '[Service]\n' + 'Environment="IPAPLATFORM_OVERRIDE=test_fedora_legacy"') + cls.master.run_command(['systemctl', 'daemon-reload']) + tasks.install_master(cls.master, setup_dns=False) + tasks.install_replica(cls.master, cls.replicas[0], setup_dns=False) + + def test_old_active_mkey(self): + p = re.compile('^KVNO: 1, Enctype: aes256-cts-hmac-sha1-96, .+ \\*$', + flags=re.MULTILINE) + + result = self.master.run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + + def test_enable_new_entypes(self): + base_dn = "dc=%s" % (",dc=".join(self.master.domain.name.split("."))) + realm = self.master.domain.name.upper() + + entry_ldif = textwrap.dedent(""" + dn: cn={realm},cn=kerberos,{base_dn} + changetype: modify + replace: krbSupportedEncSaltTypes + krbSupportedEncSaltTypes: aes128-sha2:normal + krbSupportedEncSaltTypes: aes128-sha2:special + krbSupportedEncSaltTypes: aes256-sha2:normal + krbSupportedEncSaltTypes: aes256-sha2:special + krbSupportedEncSaltTypes: aes256-cts:normal + krbSupportedEncSaltTypes: aes256-cts:special + krbSupportedEncSaltTypes: aes128-cts:normal + krbSupportedEncSaltTypes: aes128-cts:special + krbSupportedEncSaltTypes: camellia128-cts-cmac:normal + krbSupportedEncSaltTypes: camellia128-cts-cmac:special + krbSupportedEncSaltTypes: camellia256-cts-cmac:normal + krbSupportedEncSaltTypes: camellia256-cts-cmac:special + - + replace: krbDefaultEncSaltTypes + krbDefaultEncSaltTypes: aes256-sha2:special + krbDefaultEncSaltTypes: aes128-sha2:special + krbDefaultEncSaltTypes: aes256-cts:special + krbDefaultEncSaltTypes: aes128-cts:special""").format( + base_dn=base_dn, realm=realm) + tasks.ldapmodify_dm(self.master, entry_ldif) + + def test_add_new_mkey(self): + self.master.run_command('kdb5_util add_mkey -e aes256-sha2 -s', + stdin_text='Secret123\nSecret123') + + def test_new_inactive_mkey(self): + p = re.compile('^KVNO: 2, Enctype: aes256-cts-hmac-sha384-192, ', + flags=re.MULTILINE) + + result = self.master.run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + + def test_switch_mkey(self): + self.master.run_command(['kdb5_util', 'use_mkey', '2']) + + def test_new_active_mkey(self): + p = re.compile('^KVNO: 2, Enctype: aes256-cts-hmac-sha384-192, .+ \\*$', + flags=re.MULTILINE) + + result = self.master.run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys']) + assert p.search(result.stdout_text) + + def test_used_old_mkey(self): + p = re.compile('^MKey: vno 1$', flags=re.MULTILINE) + + result = self.master.run_command(['kadmin.local', 'getprinc', + f'ldap/{self.master.hostname}']) + assert p.search(result.stdout_text) + result = self.replicas[0].run_command(['kadmin.local', 'getprinc', + f'ldap/{self.master.hostname}']) + assert p.search(result.stdout_text) + + def test_reencrypt_with_new_mkey(self): + self.master.run_command(['kdb5_util', '-x', 'unlockiter', + 'update_princ_encryption', '-vf']) + + def test_used_new_mkey(self): + p = re.compile('^MKey: vno 2$', flags=re.MULTILINE) + + result = self.master.run_command(['kadmin.local', 'getprinc', + f'ldap/{self.master.hostname}']) + assert p.search(result.stdout_text) + result = self.replicas[0].run_command(['kadmin.local', 'getprinc', + f'ldap/{self.master.hostname}']) + assert p.search(result.stdout_text) + + def test_purge_old_mkey(self): + self.master.run_command(['kdb5_util', 'purge_mkeys', '-vf']) + + def test_only_new_mkey(self): + p = re.compile('^KVNO: 1,', flags=re.MULTILINE) + + result = self.master.run_command(['kdb5_util', 'list_mkeys']) + assert not p.search(result.stdout_text) + result = self.replicas[0].run_command(['kdb5_util', 'list_mkeys']) + assert not p.search(result.stdout_text) + + @classmethod + def uninstall(cls, mh): + cls.master.run_command([ + 'rm', '/etc/profile.d/ipaplatform.sh', + '/etc/systemd/system/ipa.service.d/platform.conf']) + cls.master.run_command(['rmdir', '/etc/systemd/system/ipa.service.d']) + super().uninstall(mh) -- 2.50.0