From 66cc1eaeec5b7da5f03c21684d4d2a5f52ab8e0a Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Mon, 21 Oct 2024 17:39:50 +0200 Subject: [PATCH] ipa-4.12.2-4 - Related: RHEL-59777 Rebase Samba to the latest 4.21.x release - Resolves: RHEL-59659 ipa dns-zone --allow-query '!198.18.2.0/24;any;' fails with Unrecognized IPAddress flags - Resolves: RHEL-61636 Uninstall ACME separately during PKI uninstallation - Resolves: RHEL-61723 Include latest fixes in python3-ipatests packages - Resolves: RHEL-63325 Last expired OTP token would be considered as still assigned to the user Signed-off-by: Florence Blanc-Renaud --- ...feIPAddress-pass-flag-0-to-IPNetwork.patch | 34 +++ ...-a-ccache-to-rpcclient-deletetrustdo.patch | 67 +++++ ...tall-add-use-krb5-ccache-to-smbclien.patch | 60 ++++ ...moving-the-CA-to-uninstall-the-ACME-.patch | 209 +++++++++++++ ...-Fixes-for-ipa-idrange-fix-testsuite.patch | 35 +++ ...spec-Use-nodejs22-on-RHEL-10-and-ELN.patch | 34 +++ ...with-an-expired-OTP-token-to-log-in-.patch | 265 +++++++++++++++++ 0020-ipatests-Activate-ssh-in-sssd.conf.patch | 41 +++ ...igrate-man-page-fix-typos-and-errors.patch | 131 +++++++++ ...s-Test-for-ipa-hbac-rule-duplication.patch | 61 ++++ ...r-password-file-handling-in-TestHSMI.patch | 116 ++++++++ 0024-ipatests-2FA-test-cases.patch | 276 ++++++++++++++++++ freeipa.spec | 27 +- 13 files changed, 1352 insertions(+), 4 deletions(-) create mode 100644 0013-UnsafeIPAddress-pass-flag-0-to-IPNetwork.patch create mode 100644 0014-ipatests-provide-a-ccache-to-rpcclient-deletetrustdo.patch create mode 100644 0015-test_adtrust_install-add-use-krb5-ccache-to-smbclien.patch create mode 100644 0016-Don-t-rely-on-removing-the-CA-to-uninstall-the-ACME-.patch create mode 100644 0017-ipatests-Fixes-for-ipa-idrange-fix-testsuite.patch create mode 100644 0018-spec-Use-nodejs22-on-RHEL-10-and-ELN.patch create mode 100644 0019-Do-not-let-user-with-an-expired-OTP-token-to-log-in-.patch create mode 100644 0020-ipatests-Activate-ssh-in-sssd.conf.patch create mode 100644 0021-ipa-migrate-man-page-fix-typos-and-errors.patch create mode 100644 0022-ipatests-Test-for-ipa-hbac-rule-duplication.patch create mode 100644 0023-ipatests-refactor-password-file-handling-in-TestHSMI.patch create mode 100644 0024-ipatests-2FA-test-cases.patch diff --git a/0013-UnsafeIPAddress-pass-flag-0-to-IPNetwork.patch b/0013-UnsafeIPAddress-pass-flag-0-to-IPNetwork.patch new file mode 100644 index 0000000..1d6fab3 --- /dev/null +++ b/0013-UnsafeIPAddress-pass-flag-0-to-IPNetwork.patch @@ -0,0 +1,34 @@ +From a9e653ca36a0829ae59cd204e7388d7a6c91e082 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Fri, 13 Sep 2024 09:58:36 +0200 +Subject: [PATCH] UnsafeIPAddress: pass flag=0 to IPNetwork + +When parsing a string, the constructor tries to parse the value +as an IP Address first, or falls back to an IPNetwork with the +flags INET_PTON. + +Use the flag 0 instead for an IPNetwork. + +Fixes: https://pagure.io/freeipa/issue/9645 +Signed-off-by: Florence Blanc-Renaud +Reviewed-By: Alexander Bokovoy +--- + ipapython/ipautil.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py +index 3e98bfd6a66f24933e7e4de8efb79f4f5bf8bd0e..c237d59fb4b8be4187fb0efb04b097ff4df6c182 100644 +--- a/ipapython/ipautil.py ++++ b/ipapython/ipautil.py +@@ -119,7 +119,7 @@ class UnsafeIPAddress(netaddr.IPAddress): + if addr.version != 6: + raise + except ValueError: +- self._net = netaddr.IPNetwork(addr, flags=self.netaddr_ip_flags) ++ self._net = netaddr.IPNetwork(addr, flags=0) + addr = self._net.ip + super(UnsafeIPAddress, self).__init__(addr, + flags=self.netaddr_ip_flags) +-- +2.46.2 + diff --git a/0014-ipatests-provide-a-ccache-to-rpcclient-deletetrustdo.patch b/0014-ipatests-provide-a-ccache-to-rpcclient-deletetrustdo.patch new file mode 100644 index 0000000..127b1f7 --- /dev/null +++ b/0014-ipatests-provide-a-ccache-to-rpcclient-deletetrustdo.patch @@ -0,0 +1,67 @@ +From a343c149838a3058794f33c75c58b75bc1748f7f Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Tue, 17 Sep 2024 17:00:49 +0200 +Subject: [PATCH] ipatests: provide a ccache to rpcclient deletetrustdom + +With samba update to samba-4.20.4, rpcclient now needs a +ccache otherwise it prompts for a password. + +Fixes: https://pagure.io/freeipa/issue/9667 + +Signed-off-by: Florence Blanc-Renaud +Reviewed-By: Rob Crittenden +Reviewed-By: Alexander Bokovoy +--- + ipatests/pytest_ipa/integration/tasks.py | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py +index 9d6b5f67a311a28c335801d59e0ff0f0c7faccdd..677fb7534256a65940fb5280fa6412789dcba54f 100755 +--- a/ipatests/pytest_ipa/integration/tasks.py ++++ b/ipatests/pytest_ipa/integration/tasks.py +@@ -795,15 +795,22 @@ def remove_trust_info_from_ad(master, ad_domain, ad_hostname): + kinit_as_user(master, + 'Administrator@{}'.format(ad_domain.upper()), + master.config.ad_admin_password) ++ # Find cache for the user ++ cache_args = [] ++ cache = get_credential_cache(master) ++ if cache: ++ cache_args = ["--use-krb5-ccache", cache] ++ + # Detect whether rpcclient supports -k or --use-kerberos option + res = master.run_command(['rpcclient', '-h'], raiseonerr=False) + if "--use-kerberos" in res.stderr_text: + rpcclient_krb5_knob = "--use-kerberos=desired" + else: + rpcclient_krb5_knob = "-k" +- master.run_command(['rpcclient', rpcclient_krb5_knob, ad_hostname, +- '-c', 'deletetrustdom {}'.format(master.domain.name)], +- raiseonerr=False) ++ cmd_args = ['rpcclient', rpcclient_krb5_knob, ad_hostname] ++ cmd_args.extend(cache_args) ++ cmd_args.extend(['-c', 'deletetrustdom {}'.format(master.domain.name)]) ++ master.run_command(cmd_args, raiseonerr=False) + + + def configure_auth_to_local_rule(master, ad): +@@ -1086,6 +1093,16 @@ def kinit_admin(host, raiseonerr=True): + raiseonerr=raiseonerr) + + ++def get_credential_cache(host): ++ # Return the credential cache currently in use on host or None ++ result = host.run_command(["klist"]).stdout_text ++ pattern = re.compile(r'Ticket cache: (?P.*)\n') ++ res = pattern.search(result) ++ if res: ++ return res['cache'] ++ return None ++ ++ + def uninstall_master(host, ignore_topology_disconnect=True, + ignore_last_of_role=True, clean=True, verbose=False): + uninstall_cmd = ['ipa-server-install', '--uninstall', '-U'] +-- +2.46.2 + diff --git a/0015-test_adtrust_install-add-use-krb5-ccache-to-smbclien.patch b/0015-test_adtrust_install-add-use-krb5-ccache-to-smbclien.patch new file mode 100644 index 0000000..79600fd --- /dev/null +++ b/0015-test_adtrust_install-add-use-krb5-ccache-to-smbclien.patch @@ -0,0 +1,60 @@ +From 743c7b46e463bef666dc84e9f513eb7dee7f59f6 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Tue, 17 Sep 2024 14:48:58 +0200 +Subject: [PATCH] test_adtrust_install: add --use-krb5-ccache to smbclient + command + +With samba 4.20.4 the smbclient commands needs a ccache otherwise it +prompts for a password. + +Fixes: https://pagure.io/freeipa/issue/9666 +Signed-off-by: Florence Blanc-Renaud +Reviewed-By: Rob Crittenden +Reviewed-By: Alexander Bokovoy +--- + .../test_integration/test_adtrust_install.py | 28 ++++++++++++------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py +index de252db1705ad940c3b5ee4df967d7c17a4203a7..79a91dfaa61276de74b10777c6d44b5942ed1be0 100644 +--- a/ipatests/test_integration/test_adtrust_install.py ++++ b/ipatests/test_integration/test_adtrust_install.py +@@ -873,17 +873,25 @@ class TestIpaAdTrustInstall(IntegrationTest): + "path", "/freeipa4234"]) + self.master.run_command(["touch", "before"]) + self.master.run_command(["touch", "after"]) +- self.master.run_command( +- ["smbclient", "--use-kerberos=desired", +- "-c=put before", "//{}/share".format( +- self.master.hostname)] +- ) ++ # Find cache for the admin user ++ cache_args = [] ++ cache = tasks.get_credential_cache(self.master) ++ if cache: ++ cache_args = ["--use-krb5-ccache", cache] ++ ++ cmd_args = ["smbclient", "--use-kerberos=desired"] ++ cmd_args.extend(cache_args) ++ cmd_args.extend([ ++ "-c=put before", "//{}/share".format(self.master.hostname) ++ ]) ++ self.master.run_command(cmd_args) + self.master.run_command( + ["net", "conf", "setparm", "share", + "valid users", "@admins"]) +- result = self.master.run_command( +- ["smbclient", "--use-kerberos=desired", +- "-c=put after", "//{}/share".format( +- self.master.hostname)] +- ) ++ cmd_args = ["smbclient", "--use-kerberos=desired"] ++ cmd_args.extend(cache_args) ++ cmd_args.extend([ ++ "-c=put after", "//{}/share".format(self.master.hostname) ++ ]) ++ result = self.master.run_command(cmd_args) + assert msg not in result.stdout_text +-- +2.46.2 + diff --git a/0016-Don-t-rely-on-removing-the-CA-to-uninstall-the-ACME-.patch b/0016-Don-t-rely-on-removing-the-CA-to-uninstall-the-ACME-.patch new file mode 100644 index 0000000..19498d0 --- /dev/null +++ b/0016-Don-t-rely-on-removing-the-CA-to-uninstall-the-ACME-.patch @@ -0,0 +1,209 @@ +From a785d0c561b8e22bd9d56739481095e07e0a7eb7 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Mon, 30 Sep 2024 13:30:46 -0400 +Subject: [PATCH] Don't rely on removing the CA to uninstall the ACME depoyment + +There has always been a pki-server commnd acme-remove. We were +not aware that it should be called prior to removing a CA. In +11.5.0 this is strongly encouraged by the PKI team. In 11.6.0 +ACME is treated as a full subsystem so will be removed in the +future using pkidestroy -s ACME + +The new class acmeinstance.ACMEInstance is introduced so its +uninstallation can be handled in a similar way as the other +PKI services via DogtagInstance. It is, right now, a pretty +thin wrapper. + +We can discuss moving the ACME installation routines here at +some point. It would be ok as long as we don't have to introduce +another PKI restart as part of it. + +In PKI 11.6.0 pkidestroy has new options to ensure a clean +uninstall: --remove-conf --remove-logs. Pass those options +into pkidestroy calls for 11.6.0+. + +Clean up an additional IPA-generated file that needs to be +cleaned up during uninstall: /root/kracert.p12. 11.6.0 is +more sensitive to leftover files than previous versions. + +Fixes: https://pagure.io/freeipa/issue/9673 +Fixes: https://pagure.io/freeipa/issue/9674 + +Signed-off-by: Rob Crittenden +Reviewed-By: Alexander Bokovoy +--- + ipaserver/install/acmeinstance.py | 31 +++++++++++++ + ipaserver/install/ca.py | 5 ++- + ipaserver/install/cainstance.py | 1 + + ipaserver/install/dogtaginstance.py | 44 +++++++++++++------ + .../test_integration/test_uninstallation.py | 21 +++++++++ + 5 files changed, 87 insertions(+), 15 deletions(-) + create mode 100644 ipaserver/install/acmeinstance.py + +diff --git a/ipaserver/install/acmeinstance.py b/ipaserver/install/acmeinstance.py +new file mode 100644 +index 0000000000000000000000000000000000000000..0027c314545f384d9b6ee24b279479e5360d8bef +--- /dev/null ++++ b/ipaserver/install/acmeinstance.py +@@ -0,0 +1,31 @@ ++# ++# Copyright (C) 2024 FreeIPA Contributors see COPYING for license ++# ++ ++import logging ++ ++from ipaserver.install.dogtaginstance import DogtagInstance ++ ++logger = logging.getLogger(__name__) ++ ++ ++class ACMEInstance(DogtagInstance): ++ """ ++ ACME is deployed automatically with a CA subsystem but it is the ++ responsibility of IPA to uninstall the service. ++ ++ This is mostly a placeholder for the uninstaller. We can ++ eventually move the ACME installation routines into this class ++ if we want but it might result in an extra PKI restart which ++ would be slow. ++ """ ++ def __init__(self, realm=None, host_name=None): ++ super(ACMEInstance, self).__init__( ++ realm=realm, ++ subsystem="ACME", ++ service_desc="ACME server", ++ host_name=host_name ++ ) ++ ++ def uninstall(self): ++ DogtagInstance.uninstall(self) +diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py +index ffcb5268399ce71128fc8de5f54d433d35e99dd2..520e3fc5de1084e7c22c0cf7eaa86e1d3c421373 100644 +--- a/ipaserver/install/ca.py ++++ b/ipaserver/install/ca.py +@@ -22,7 +22,7 @@ from ipaplatform.constants import constants + from ipaserver.install import sysupgrade + from ipapython.install import typing + from ipapython.install.core import group, knob, extend_knob +-from ipaserver.install import cainstance, bindinstance, dsinstance ++from ipaserver.install import acmeinstance, cainstance, bindinstance, dsinstance + from ipapython import ipautil, certdb + from ipapython import ipaldap + from ipapython.admintool import ScriptError +@@ -715,6 +715,9 @@ def install_step_1(standalone, replica_config, options, custodia): + + + def uninstall(): ++ acme = acmeinstance.ACMEInstance(api.env.realm) ++ acme.uninstall() ++ + ca_instance = cainstance.CAInstance(api.env.realm) + ca_instance.stop_tracking_certificates() + ipautil.remove_file(paths.RA_AGENT_PEM) +diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py +index 5dac2c0441752e7bb569cde1fc93bc17c3128cdf..5c2c9f8b981cf5d587865f7680e2b231eae655e2 100644 +--- a/ipaserver/install/cainstance.py ++++ b/ipaserver/install/cainstance.py +@@ -1118,6 +1118,7 @@ class CAInstance(DogtagInstance): + + ipautil.remove_file(paths.DOGTAG_ADMIN_P12) + ipautil.remove_file(paths.CACERT_P12) ++ ipautil.remove_file(paths.ADMIN_CERT_PATH) + + def unconfigure_certmonger_renewal_guard(self): + if not self.is_configured(): +diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py +index e89492312deb8ca20668a62fd7a2a20e2866a3fb..4b0f4d274b0c33140ed6f939f1a3fd8b75930ff9 100644 +--- a/ipaserver/install/dogtaginstance.py ++++ b/ipaserver/install/dogtaginstance.py +@@ -304,21 +304,37 @@ class DogtagInstance(service.Service): + if self.is_installed(): + self.print_msg("Unconfiguring %s" % self.subsystem) + +- args = [paths.PKIDESTROY, +- "-i", "pki-tomcat", "--force", +- "-s", self.subsystem] +- +- # specify --log-file on PKI 11.0.0 or later +- ++ args = [] + 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]) ++ if self.subsystem == "ACME": ++ if pki_version < pki.util.Version("11.0.0"): ++ return ++ elif ( ++ pki.util.Version("11.0.0") <= pki_version ++ <= pki.util.Version("11.5.0") ++ ): ++ args = ['pki-server', 'acme-remove'] ++ else: ++ # fall through for PKI >= 11.6.0 ++ pass ++ if not args: ++ args = [paths.PKIDESTROY, ++ "-i", "pki-tomcat", "--force", ++ "-s", self.subsystem] ++ ++ # specify --log-file on PKI 11.0.0 or later ++ ++ 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]) ++ if pki_version >= pki.util.Version("11.6.0"): ++ args.extend(["--remove-conf", "--remove-logs"]) + + try: + ipautil.run(args) +diff --git a/ipatests/test_integration/test_uninstallation.py b/ipatests/test_integration/test_uninstallation.py +index 4f8f17ce3ad8d5376ecba11442f379e5691de7f7..049c50db536ae1070f5f958e76b12a1518da0aba 100644 +--- a/ipatests/test_integration/test_uninstallation.py ++++ b/ipatests/test_integration/test_uninstallation.py +@@ -197,6 +197,7 @@ class TestUninstallCleanup(IntegrationTest): + '/var/lib/sss/pubconf/krb5.include.d/localauth_plugin', + '/var/named/dynamic/managed-keys.bind', + '/var/named/dynamic/managed-keys.bind.jnl', ++ '/var/lib/systemd/coredump/', + ] + + leftovers = [] +@@ -217,3 +218,23 @@ class TestUninstallCleanup(IntegrationTest): + leftovers.append(line) + + assert len(leftovers) == 0 ++ ++ ++class TestUninstallReinstall(IntegrationTest): ++ """Test install, uninstall, re-install. ++ ++ Reinstall with PKI 11.6.0 was failing ++ https://pagure.io/freeipa/issue/9673 ++ """ ++ ++ num_replicas = 0 ++ ++ @classmethod ++ def install(cls, mh): ++ tasks.install_master(cls.master, setup_dns=False) ++ ++ def test_uninstall_server(self): ++ tasks.uninstall_master(self.master) ++ ++ def test_reinstall_server(self): ++ tasks.install_master(self.master, setup_dns=False) +-- +2.46.2 + diff --git a/0017-ipatests-Fixes-for-ipa-idrange-fix-testsuite.patch b/0017-ipatests-Fixes-for-ipa-idrange-fix-testsuite.patch new file mode 100644 index 0000000..910e631 --- /dev/null +++ b/0017-ipatests-Fixes-for-ipa-idrange-fix-testsuite.patch @@ -0,0 +1,35 @@ +From ae4c2ad6cd966d48c063814f494dcc16cf0ccd4c Mon Sep 17 00:00:00 2001 +From: Sudhir Menon +Date: Tue, 24 Sep 2024 13:46:48 +0530 +Subject: [PATCH] ipatests: Fixes for ipa-idrange-fix testsuite + +This patch adds the line tasks.install_master(cls.master). +The kinit admin command fails with the below error as the +IPA is not configured on the test system + +'ipa: ERROR: stderr: kinit: Configuration file does not specify default +realm when parsing name admin' + +Signed-off-by: Sudhir Menon +Reviewed-By: Rob Crittenden +--- + ipatests/test_integration/test_ipa_idrange_fix.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/ipatests/test_integration/test_ipa_idrange_fix.py b/ipatests/test_integration/test_ipa_idrange_fix.py +index ff8fbdac9d028d26fc55f5e357f89af879a61723..0c915bd0931ed11a3aa86c533ee8748aa8a7ec07 100644 +--- a/ipatests/test_integration/test_ipa_idrange_fix.py ++++ b/ipatests/test_integration/test_ipa_idrange_fix.py +@@ -17,6 +17,9 @@ logger = logging.getLogger(__name__) + + + class TestIpaIdrangeFix(IntegrationTest): ++ ++ topology = 'line' ++ + @classmethod + def install(cls, mh): + super(TestIpaIdrangeFix, cls).install(mh) +-- +2.46.2 + diff --git a/0018-spec-Use-nodejs22-on-RHEL-10-and-ELN.patch b/0018-spec-Use-nodejs22-on-RHEL-10-and-ELN.patch new file mode 100644 index 0000000..9586e86 --- /dev/null +++ b/0018-spec-Use-nodejs22-on-RHEL-10-and-ELN.patch @@ -0,0 +1,34 @@ +From 642af014d9e7de8e53934af4ca3970977957cba7 Mon Sep 17 00:00:00 2001 +From: Yaakov Selkowitz +Date: Tue, 8 Oct 2024 02:13:00 -0400 +Subject: [PATCH] spec: Use nodejs22 on RHEL 10 and ELN + +nodejs22 is now the default nodejs version in RHEL 10 as well as ELN. + +Signed-off-by: Yaakov Selkowitz +Reviewed-By: Alexander Bokovoy +Reviewed-By: Florence Blanc-Renaud +--- + freeipa.spec.in | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/freeipa.spec.in b/freeipa.spec.in +index 171b6ad27b57553fdd46c7d041715949bb00b163..9b3d916f34c10a00070855578a593e299187bc44 100755 +--- a/freeipa.spec.in ++++ b/freeipa.spec.in +@@ -308,10 +308,10 @@ BuildRequires: libpwquality-devel + BuildRequires: libsss_idmap-devel + BuildRequires: libsss_certmap-devel + BuildRequires: libsss_nss_idmap-devel >= %{sssd_version} +-%if 0%{?fedora} >= 41 ++%if 0%{?fedora} >= 41 || 0%{?rhel} >= 10 + # Do not use nodejs22 on fedora < 41, https://pagure.io/freeipa/issue/9643 + BuildRequires: nodejs(abi) +-%elif 0%{?fedora} >= 39 || 0%{?rhel} >= 10 ++%elif 0%{?fedora} >= 39 + # Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374 + BuildRequires: nodejs(abi) < 127 + %else +-- +2.46.2 + diff --git a/0019-Do-not-let-user-with-an-expired-OTP-token-to-log-in-.patch b/0019-Do-not-let-user-with-an-expired-OTP-token-to-log-in-.patch new file mode 100644 index 0000000..ca6e6da --- /dev/null +++ b/0019-Do-not-let-user-with-an-expired-OTP-token-to-log-in-.patch @@ -0,0 +1,265 @@ +From 18303b94bea4e08a0c889fc357df6ba2f308fa0d Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 2 Oct 2024 21:26:34 -0400 +Subject: [PATCH] Do not let user with an expired OTP token to log in if only + OTP is allowed + +If only OTP authentication is allowed, and a user tries to login with an +expired token, do not let them log in with their password. Forcing the +admin to intervene. If the user does not have an OTP token then allow +them to log in with a password until an OTP token is configured + +Fixes: https://pagure.io/freeipa/issue/9387 + +Signed-off-by: Mark Reynolds +Reviewed-By: Rob Crittenden +Reviewed-By: Alexander Bokovoy +Reviewed-By: Julien Rische +--- + daemons/ipa-kdb/ipa_kdb_principals.c | 63 +++++++++++-- + .../ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 3 +- + ipatests/test_integration/test_otp.py | 94 ++++++++++++++++++- + 3 files changed, 151 insertions(+), 9 deletions(-) + +diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c +index 14603e528b43acb29234c425e97ad297ac6724a7..114957b884786dd3ca3b01c47f6bb82e8a040beb 100644 +--- a/daemons/ipa-kdb/ipa_kdb_principals.c ++++ b/daemons/ipa-kdb/ipa_kdb_principals.c +@@ -107,7 +107,6 @@ static char *std_principal_obj_classes[] = { + "krbprincipal", + "krbprincipalaux", + "krbTicketPolicyAux", +- + NULL + }; + +@@ -338,14 +337,16 @@ static void ipadb_validate_otp(struct ipadb_context *ipactx, + if (dn == NULL) + return; + count = asprintf(&filter, ftmpl, dn, datetime, datetime); +- ldap_memfree(dn); +- if (count < 0) ++ if (count < 0) { ++ ldap_memfree(dn); + return; ++ } + + /* Fetch the active token list. */ + kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE, + filter, (char**) attrs, &res); + free(filter); ++ filter = NULL; + if (kerr != 0 || res == NULL) + return; + +@@ -353,10 +354,60 @@ static void ipadb_validate_otp(struct ipadb_context *ipactx, + count = ldap_count_entries(ipactx->lcontext, res); + ldap_msgfree(res); + +- /* If the user is configured for OTP, but has no active tokens, remove +- * OTP from the list since the user obviously can't log in this way. */ +- if (count == 0) ++ /* ++ * If there are no valid tokens then we need to remove the OTP flag, ++ * unless OTP is the only auth type allowed... ++ */ ++ if (count == 0) { ++ /* Remove the OTP flag for now */ + *ua &= ~IPADB_USER_AUTH_OTP; ++ ++ if (*ua == 0) { ++ /* ++ * Ok, we "only" allow OTP, so if there is an expired/disabled ++ * token then add back the OTP flag as the server will double ++ * check the validity and reject the entire bind. Otherwise, this ++ * is the first time the user is authenticating and the user ++ * should be allowed to bind using its password ++ */ ++ static const char *expired_ftmpl = "(&" ++ "(objectClass=ipaToken)(ipatokenOwner=%s)" ++ "(|(ipatokenNotAfter<=%s)(!(ipatokenNotAfter=*))" ++ "(ipatokenDisabled=True))" ++ ")"; ++ if (asprintf(&filter, expired_ftmpl, dn, datetime) < 0) { ++ ldap_memfree(dn); ++ return; ++ } ++ ++ krb5_klog_syslog(LOG_INFO, ++ "Entry (%s) does not have a valid token and only OTP " ++ "authentication is supported, checking for expired tokens...", ++ dn); ++ ++ kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE, ++ filter, (char**) attrs, &res); ++ free(filter); ++ if (kerr != 0 || res == NULL) { ++ ldap_memfree(dn); ++ return; ++ } ++ ++ if (ldap_count_entries(ipactx->lcontext, res) > 0) { ++ /* ++ * Ok we only allow OTP, and there are expired/disabled tokens ++ * so add the OTP flag back, and the server will reject the ++ * bind ++ */ ++ krb5_klog_syslog(LOG_INFO, ++ "Entry (%s) does have an expired/disabled token so this " ++ "user can not fall through to password auth", dn); ++ *ua |= IPADB_USER_AUTH_OTP; ++ } ++ ldap_msgfree(res); ++ } ++ } ++ ldap_memfree(dn); + } + + static void ipadb_validate_radius(struct ipadb_context *ipactx, +diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +index c967e2cfffbd920280639f3188783ec150523b47..1c1340e31ac30cb01412a7065ea339cb5461e839 100644 +--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c ++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +@@ -1528,7 +1528,8 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb) + if (!syncreq && (otpreq == OTP_IS_NOT_REQUIRED)) { + ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_ONLY_CONFIG); + if (ret != 0) { +- LOG_FATAL("ipapwd_gen_checks failed!?\n"); ++ LOG_FATAL("ipapwd_gen_checks failed for '%s': %s\n", ++ slapi_sdn_get_dn(sdn), errMesg); + slapi_entry_free(entry); + slapi_sdn_free(&sdn); + return 0; +diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py +index 350371bfe1e4c1cc6dcc89f6584f813fcb0d32a0..878b4fb560ba8d7768ead54b065656462545babd 100644 +--- a/ipatests/test_integration/test_otp.py ++++ b/ipatests/test_integration/test_otp.py +@@ -10,6 +10,7 @@ import re + import time + import textwrap + from urllib.parse import urlparse, parse_qs ++from paramiko import AuthenticationException + + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes +@@ -83,7 +84,7 @@ def kinit_otp(host, user, *, password, otp, success=True): + ) + + +-def ssh_2f(hostname, username, answers_dict, port=22): ++def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""): + """ + :param hostname: hostname + :param username: username +@@ -103,6 +104,10 @@ def ssh_2f(hostname, username, answers_dict, port=22): + logger.info("Prompt is: '%s'", prmpt_str) + logger.info( + "Answer to ssh prompt is: '%s'", answers_dict[prmpt_str]) ++ if unwanted_prompt and prmpt_str == unwanted_prompt: ++ # We should not see this prompt ++ raise ValueError("We got an unwanted prompt: " ++ + answers_dict[prmpt_str]) + return resp + + import paramiko +@@ -193,7 +198,8 @@ class TestOTPToken(IntegrationTest): + + # skipping too many OTP fails + otp1 = hotp.generate(10).decode("ascii") +- kinit_otp(self.master, USER, password=PASSWORD, otp=otp1, success=False) ++ kinit_otp(self.master, USER, password=PASSWORD, otp=otp1, ++ success=False) + # Now the token is desynchronized + yield (otpuid, hotp) + +@@ -536,3 +542,87 @@ class TestOTPToken(IntegrationTest): + finally: + master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '1']) + master.run_command(['ipa', 'user-del', USER1]) ++ ++ def test_totp_expired_ldap(self): ++ master = self.master ++ basedn = master.domain.basedn ++ USER1 = 'user-expired-otp' ++ TMP_PASSWORD = 'Secret1234509' ++ binddn = DN(f"uid={USER1},cn=users,cn=accounts,{basedn}") ++ controls = [ ++ BooleanControl( ++ controlType="2.16.840.1.113730.3.8.10.7", ++ booleanValue=True) ++ ] ++ ++ tasks.kinit_admin(master) ++ master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '0']) ++ tasks.user_add(master, USER1, password=TMP_PASSWORD) ++ # Enforce use of OTP token for this user ++ master.run_command(['ipa', 'user-mod', USER1, ++ '--user-auth-type=otp']) ++ try: ++ # Change initial password through the IPA endpoint ++ url = f'https://{master.hostname}/ipa/session/change_password' ++ master.run_command(['curl', '-d', f'user={USER1}', ++ '-d', f'old_password={TMP_PASSWORD}', ++ '-d', f'new_password={PASSWORD}', ++ '--referer', f'https://{master.hostname}/ipa', ++ url]) ++ conn = master.ldap_connect() ++ # First, attempt authenticating with a password but without LDAP ++ # control to enforce OTP presence and without server-side ++ # enforcement of the OTP presence check. ++ conn.simple_bind(binddn, f"{PASSWORD}") ++ ++ # Add an OTP token and then modify it to be expired ++ otpuid, totp = add_otptoken(master, USER1, otptype="totp") ++ ++ # Make sure OTP auth is working ++ otpvalue = totp.generate(int(time.time())).decode("ascii") ++ conn = master.ldap_connect() ++ conn.simple_bind(binddn, f"{PASSWORD}{otpvalue}", ++ client_controls=controls) ++ conn.unbind() ++ ++ # Modfy token so that is now expired ++ args = [ ++ "ipa", ++ "otptoken-mod", ++ otpuid, ++ "--not-after", ++ "20241001010000Z", ++ ] ++ master.run_command(args) ++ ++ # Next, authenticate with Password+OTP again and with the LDAP ++ # control this operation should now fail ++ time.sleep(45) ++ otpvalue = totp.generate(int(time.time())).decode("ascii") ++ ++ conn = master.ldap_connect() ++ with pytest.raises(errors.ACIError): ++ conn.simple_bind(binddn, f"{PASSWORD}{otpvalue}", ++ client_controls=controls) ++ ++ # Sleep to make sure we are going to use a different token value ++ time.sleep(45) ++ ++ # Use OTP token again but authenticate over ssh and make sure it ++ # doesn't fallthrough to asking for a password ++ otpvalue = totp.generate(int(time.time())).decode("ascii") ++ answers = { ++ 'Enter first factor:': PASSWORD, ++ 'Enter second factor:': otpvalue ++ } ++ with pytest.raises(AuthenticationException): ++ # ssh should fail and NOT ask for a password ++ ssh_2f(master.hostname, USER1, answers, ++ unwanted_prompt="Password:") ++ ++ # Remove token ++ del_otptoken(self.master, otpuid) ++ ++ finally: ++ master.run_command(['ipa', 'pwpolicy-mod', '--minlife', '1']) ++ master.run_command(['ipa', 'user-del', USER1]) +-- +2.46.2 + diff --git a/0020-ipatests-Activate-ssh-in-sssd.conf.patch b/0020-ipatests-Activate-ssh-in-sssd.conf.patch new file mode 100644 index 0000000..9e24025 --- /dev/null +++ b/0020-ipatests-Activate-ssh-in-sssd.conf.patch @@ -0,0 +1,41 @@ +From 761647f842567713032709753b6d63467d9871a6 Mon Sep 17 00:00:00 2001 +From: Sudhir Menon +Date: Mon, 23 Sep 2024 14:05:43 +0530 +Subject: [PATCH] ipatests: Activate ssh in sssd.conf + +This testcase checks that services: ssh +is included in the sssd.conf file when +ipa-client-install is successful. + +Ref: https://pagure.io/freeipa/issue/9649 + +Signed-off-by: Sudhir Menon +Reviewed-By: Rob Crittenden +--- + ipatests/test_integration/test_installation_client.py | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/ipatests/test_integration/test_installation_client.py b/ipatests/test_integration/test_installation_client.py +index f8567b39eead4dffd522aad504fa72a086969257..884bff2f2f3e49f66da391424e128d8e31b82a8a 100644 +--- a/ipatests/test_integration/test_installation_client.py ++++ b/ipatests/test_integration/test_installation_client.py +@@ -94,6 +94,16 @@ class TestInstallClient(IntegrationTest): + ).encode() not in krb5_cfg + tasks.uninstall_client(self.clients[0]) + ++ def test_check_ssh_service_is_activated(self): ++ """ ++ This test checks all default services are activated ++ in sssd.conf including ssh ++ """ ++ tasks.install_client(self.master, self.clients[0]) ++ sssd_cfg = self.clients[0].get_file_contents(paths.SSSD_CONF) ++ assert 'services = nss, pam, ssh, sudo' in sssd_cfg.decode() ++ tasks.uninstall_client(self.clients[0]) ++ + def test_install_with_automount(self): + """Test that installation with automount is successful""" + tasks.install_client(self.master, self.clients[0], +-- +2.46.2 + diff --git a/0021-ipa-migrate-man-page-fix-typos-and-errors.patch b/0021-ipa-migrate-man-page-fix-typos-and-errors.patch new file mode 100644 index 0000000..a48b5d4 --- /dev/null +++ b/0021-ipa-migrate-man-page-fix-typos-and-errors.patch @@ -0,0 +1,131 @@ +From f978fa05e3ed9d4ad9d20493c05c77fb9b4976a7 Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Tue, 15 Oct 2024 17:04:55 +0200 +Subject: [PATCH] ipa-migrate man page: fix typos and errors + +ipa-migrate man page mentions non-existing option --hostname. +Fix the SYNOPSIS and various typos. + +Fixes: https://pagure.io/freeipa/issue/9681 + +Signed-off-by: Florence Blanc-Renaud +Reviewed-By: Mark Reynolds +--- + install/tools/man/ipa-migrate.1 | 38 +++++++++++++++------------------ + 1 file changed, 17 insertions(+), 21 deletions(-) + +diff --git a/install/tools/man/ipa-migrate.1 b/install/tools/man/ipa-migrate.1 +index 47ae47ea4afa3a5a6fe25dd9bbd14c27ab5f1fdb..5106f4f0f5b5928909ccd5abcef3bb6d1586f5df 100644 +--- a/install/tools/man/ipa-migrate.1 ++++ b/install/tools/man/ipa-migrate.1 +@@ -5,11 +5,11 @@ + .SH "NAME" + ipa\-migrate \- Migrate an IPA server from one machine to another + .SH "SYNOPSIS" +-ipa\-migrate ++\fBipa\-migrate\fR [OPTIONS] \fBprod\-mode\fR|\fBstage\-mode\fR \fIhostname\fR + .SH "DESCRIPTION" + + Use the \fIipa-migrate\fR command to migrate one +-IPA server to an existing local IPA server installation. ++IPA server \fIhostname\fR to an existing local IPA server installation. + + Migrate IPA schema, configuration, and database to a local IPA server. This + migration can be done online, where the tool will query the remote server. Or, +@@ -19,7 +19,6 @@ and then use an exported LDIF file for the database migration portion (this + might be more useful for very large databases as you don't need to worry about + network interruptions) + +-.SH POSITIONAL ARGUMENTS + .TP + \fBprod\-mode\fR + In this mode everything will be migrated including the current user SIDs and +@@ -28,13 +27,10 @@ DNA ranges + \fBstage\-mode\fR + In this mode, SIDs & DNA ranges are not migrated, and DNA attributes are reset + +-.SH "COMMANDS" ++.SH "OPTIONS" + .TP + \fB\-v\fR, \fB\-\-verbose\fR +-Use verbose output while running the migration tool. +-.TP +-\fB\-e\fR, \fB\-\-hostname=HOSTNAME\fR +-The host name of the remote IPA server that is being migrated from. ++Use verbose output while running the migration tool + .TP + \fB\-D\fR, \fB\-\-bind\-dn=BIND_DN\fR + The Bind DN (Distinguished Name) or an LDAP entry to bind to the remote IPA server with. +@@ -43,10 +39,10 @@ access to read the userPassword attribute. If ommitted the default is "cn=direc + .TP + \fB\-w\fR, \fB\-\-bind\-pw=PASSWORD\fR + The password for the Bind DN that is authenticating against the remote IPA server. If +-a password is not provided then the tool with prompt for the password if needed. ++a password is not provided then the tool with prompt for the password if needed + .TP +-\fB\-Just\fR, \fB\-\-bind\-pw\-file=FILE_PATH\fR +-Path to a file containing the password for the Bind DN. ++\fB\-j\fR, \fB\-\-bind\-pw\-file=FILE_PATH\fR ++Path to a file containing the password for the Bind DN + .TP + \fB\-Z\fR, \fB\-\-cacertfile=FILE_PATH\fR + Path to a file containing a CA Certificate that the remote server trusts +@@ -55,23 +51,23 @@ Path to a file containing a CA Certificate that the remote server trusts + Path to a file containing the migration log. By default the tool will use \fI/var/log/ipa-migrate.log\fR + .TP + \fB\-x\fR, \fB\-\-dryrun\fR +-Go through the migration process but do not write and data to the new IPA server. ++Go through the migration process but do not write any data to the new IPA server + .TP + \fB\-o\fR, \fB\-\-dryrun\-record=FILE_PATH\fR + Go through the migration process but do not write any data to the new IPA server. However, write the +-migration operations to an LDIF file which can be applied later or reused for multiple migrations. ++migration operations to an LDIF file which can be applied later or reused for multiple migrations + .TP + \fB\-r\fR, \fB\-\-reset\-range\fR + Reset the ID range for migrated users/groups. In "stage-mode" this is done automatically + .TP + \fB\-F\fR, \fB\-\-force\fR +-Ignore any errors and continue to proceed with migration effort. ++Ignore any errors and continue to proceed with migration effort + .TP + \fB\-q\fR, \fB\-\-quiet\fR +-Only log errors during the migration process. ++Only log errors during the migration process + .TP + \fB\-B\fR, \fB\-\-migrate\-dns\fR +-Migrate thr DNS records ++Migrate the DNS records + .TP + \fB\-S\fR, \fB\-\-skip\-schema\fR + Do not migrate the database schema +@@ -80,21 +76,21 @@ Do not migrate the database schema + Do not migrate the database configuration (dse.ldif/cn=config) + .TP + \fB\-O\fR, \fB\-\-schema\-overwrite\fR +-Overwrite existing schema definitions. By default duplicate schema is skipped. ++Overwrite existing schema definitions. By default duplicate schema is skipped + .TP + \fB\-s\fR, \fB\-\-subtree=DN\fR + Specifies a custom database subtree that should be included in the migration. + This is only needed if non-default subtrees/branches were added to the database +-outside of IPA. ++outside of IPA + .TP + \fB\-f\fR, \fB\-\-db\-ldif=FILE_PATH\fR +-LDIF file containing the entire backend. If omitted the tool will query the remote IPA server. ++LDIF file containing the entire backend. If omitted the tool will query the remote IPA server + .TP + \fB\-m\fR, \fB\-\-schema\-ldif=FILE_PATH\fR +-LDIF file containing the schema. If omitted the tool will query the remote IPA server. ++LDIF file containing the schema. If omitted the tool will query the remote IPA server + .TP + \fB\-g\fR, \fB\-\-config\-ldif=FILE_PATH\fR +-LDIF file containing the entire "cn=config" DIT. If omitted the tool will query the remote IPA server. ++LDIF file containing the entire "cn=config" DIT. If omitted the tool will query the remote IPA server + .TP + \fB\-n\fR, \fB\-\-no\-prompt\fR + Do not prompt for confirmation before starting migration. Use at your own risk! +-- +2.46.2 + diff --git a/0022-ipatests-Test-for-ipa-hbac-rule-duplication.patch b/0022-ipatests-Test-for-ipa-hbac-rule-duplication.patch new file mode 100644 index 0000000..7660ce0 --- /dev/null +++ b/0022-ipatests-Test-for-ipa-hbac-rule-duplication.patch @@ -0,0 +1,61 @@ +From 7f4e7e1d6a2ae9d05a2dfcf620f4df07d09d9d2b Mon Sep 17 00:00:00 2001 +From: Sudhir Menon +Date: Thu, 3 Oct 2024 18:45:31 +0530 +Subject: [PATCH] ipatests: Test for ipa hbac rule duplication + +This test checks that ipa-migrate is not creating duplicate default hbac rules +for allow_all and allow_systemd-user rules. + +Related: https://pagure.io/freeipa/issue/9640 + +Signed-off-by: Sudhir Menon +Reviewed-By: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + .../test_ipa_ipa_migration.py | 26 +++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py +index 288165e8a83a96e6f6bd4e52866f98617f497c56..70c268951a0d7e40806742b16e62b764b2bae37b 100644 +--- a/ipatests/test_integration/test_ipa_ipa_migration.py ++++ b/ipatests/test_integration/test_ipa_ipa_migration.py +@@ -9,6 +9,7 @@ from __future__ import absolute_import + from ipatests.test_integration.base import IntegrationTest + from ipatests.pytest_ipa.integration import tasks + from ipaplatform.paths import paths ++from collections import Counter + + import pytest + import textwrap +@@ -920,3 +921,28 @@ class TestIPAMigrateScenario1(IntegrationTest): + ) + assert result.returncode == 1 + assert ERR_MSG in result.stderr_text ++ ++ def test_ipa_hbac_rule_duplication(self): ++ """ ++ This testcase checks that default hbac rules ++ are not duplicated on the local server when ++ ipa-migrate command is run. ++ """ ++ run_migrate( ++ self.replicas[0], ++ "prod-mode", ++ self.master.hostname, ++ "cn=Directory Manager", ++ self.master.config.admin_password, ++ extra_args=['-n'] ++ ) ++ result = self.replicas[0].run_command( ++ ['ipa', 'hbacrule-find'] ++ ) ++ lines = result.stdout_text.splitlines() ++ line = [] ++ for i in lines: ++ line.append(i.strip()) ++ count = Counter(line) ++ assert count.get('Rule name: allow_all') < 2 ++ assert count.get('Rule name: allow_systemd-user') < 2 +-- +2.46.2 + diff --git a/0023-ipatests-refactor-password-file-handling-in-TestHSMI.patch b/0023-ipatests-refactor-password-file-handling-in-TestHSMI.patch new file mode 100644 index 0000000..0d8eacd --- /dev/null +++ b/0023-ipatests-refactor-password-file-handling-in-TestHSMI.patch @@ -0,0 +1,116 @@ +From 142f52fc981fe9f1d693b79a7b49506af2e98829 Mon Sep 17 00:00:00 2001 +From: Mohammad Rizwan +Date: Mon, 19 Aug 2024 16:08:53 +0530 +Subject: [PATCH] ipatests: refactor password file handling in TestHSMInstall + +When token and associated certs are not being cleaned +up properly, the subsequent installation fails. Hence +Password file related scenarios moved out to new test class +so that it have fresh installation. + +Signed-off-by: Mohammad Rizwan +Reviewed-By: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + .../nightly_ipa-4-12_latest.yaml | 12 ++++++++ + .../nightly_ipa-4-12_latest_selinux.yaml | 13 ++++++++ + ipatests/test_integration/test_hsm.py | 30 ++++++++++--------- + 3 files changed, 41 insertions(+), 14 deletions(-) + +diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml +index 6d18e708fb0512ce21d8db68d4f1ab26849f40b7..07e2a8399ae4cc953adb415b975101ed20c67fd2 100644 +--- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml ++++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml +@@ -1950,6 +1950,18 @@ jobs: + timeout: 6300 + topology: *master_3repl_1client + ++ fedora-latest-ipa-4-12/test_hsm_TestHSMInstallPasswordFile: ++ requires: [fedora-latest-ipa-4-12/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-latest-ipa-4-12/build_url}' ++ test_suite: test_integration/test_hsm.py::TestHSMInstallPasswordFile ++ template: *ci-ipa-4-12-latest ++ timeout: 6300 ++ topology: *master_1repl ++ + fedora-latest-ipa-4-12/test_hsm_TestHSMInstallADTrustBase: + requires: [fedora-latest-ipa-4-12/build] + priority: 50 +diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml +index 52686df9713975c9590b8a99edb7c3442531fecc..11046be13fca1e7403d0fd74329a66ded3927a6c 100644 +--- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml ++++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml +@@ -2105,6 +2105,19 @@ jobs: + timeout: 6300 + topology: *master_3repl_1client + ++ fedora-latest-ipa-4-12/test_hsm_TestHSMInstallPasswordFile: ++ requires: [fedora-latest-ipa-4-12/build] ++ priority: 50 ++ job: ++ class: RunPytest ++ args: ++ build_url: '{fedora-latest-ipa-4-12/build_url}' ++ selinux_enforcing: True ++ test_suite: test_integration/test_hsm.py::TestHSMInstallPasswordFile ++ template: *ci-ipa-4-12-latest ++ timeout: 6300 ++ topology: *master_1repl ++ + fedora-latest-ipa-4-12/test_hsm_TestHSMInstallADTrustBase: + requires: [fedora-latest-ipa-4-12/build] + priority: 50 +diff --git a/ipatests/test_integration/test_hsm.py b/ipatests/test_integration/test_hsm.py +index 374f5c25fd3453cd45a15d2b0f20cee424282595..42895fcd60a7c02d3b6103c2f6751a367da30b2f 100644 +--- a/ipatests/test_integration/test_hsm.py ++++ b/ipatests/test_integration/test_hsm.py +@@ -312,24 +312,26 @@ class TestHSMInstall(BaseHSMTest): + assert returncode == 0 + assert output == "No issues found." + +- def test_hsm_install_server_password_file(self): +- check_version(self.master) +- # cleanup before fresh install with password file +- for client in self.clients: +- tasks.uninstall_client(client) + +- for replica in self.replicas: +- tasks.uninstall_master(replica) ++class TestHSMInstallPasswordFile(BaseHSMTest): + +- tasks.uninstall_master(self.master) ++ num_replicas = 1 + +- delete_hsm_token([self.master] + self.replicas, self.token_name) +- self.token_name, self.token_password = get_hsm_token(self.master) +- self.master.put_file_contents(self.token_password_file, +- self.token_password) +- self.replicas[0].put_file_contents(self.token_password_file, +- self.token_password) ++ @classmethod ++ def install(cls, mh): ++ check_version(cls.master) ++ # Enable pkiuser to read softhsm tokens ++ cls.master.run_command(['usermod', 'pkiuser', '-a', '-G', 'ods']) ++ cls.token_name, cls.token_password = get_hsm_token(cls.master) ++ cls.master.put_file_contents( ++ cls.token_password_file, cls.token_password ++ ) ++ cls.replicas[0].put_file_contents( ++ cls.token_password_file, cls.token_password ++ ) + ++ def test_hsm_install_server_password_file(self): ++ check_version(self.master) + tasks.install_master( + self.master, setup_dns=self.master_with_dns, + setup_kra=self.master_with_kra, +-- +2.46.2 + diff --git a/0024-ipatests-2FA-test-cases.patch b/0024-ipatests-2FA-test-cases.patch new file mode 100644 index 0000000..398202f --- /dev/null +++ b/0024-ipatests-2FA-test-cases.patch @@ -0,0 +1,276 @@ +From 6ac11ae003740faf19f3c75bf542ec44f717114f Mon Sep 17 00:00:00 2001 +From: Madhuri Upadhye +Date: Tue, 23 Jul 2024 18:14:36 +0530 +Subject: [PATCH] ipatests: 2FA test cases + +Added following: + +Added 'ssh_2fa_with_cmd' method for authentication, +as for '\n' with paramiko did not work. In a test case +need to just press `Enter` for `second factor`. +Advantage of above function is no having paramiko +dependancy. +We can run the any command in same session after +authentication of user. + +Test cases: +1. Authenticate the user only with password, +just press enter at `Second factor` and check tgt after auth. +when User authentication types: otp, password +2. Authenticate the user with password and otpvalues and +check tgt of user after auth when +User authentication types: otp, password + +related: https://github.com/SSSD/sssd/pull/7500 + +Signed-off-by: Madhuri Upadhye +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/test_integration/test_otp.py | 192 ++++++++++++++++++++++++-- + 1 file changed, 181 insertions(+), 11 deletions(-) + +diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py +index 878b4fb560ba8d7768ead54b065656462545babd..0babb45897c6107bf354477dbb0d3a805a3116f5 100644 +--- a/ipatests/test_integration/test_otp.py ++++ b/ipatests/test_integration/test_otp.py +@@ -5,26 +5,27 @@ + """ + import base64 + import logging +-import pytest + import re +-import time ++import tempfile + import textwrap +-from urllib.parse import urlparse, parse_qs +-from paramiko import AuthenticationException ++import time ++from urllib.parse import parse_qs, urlparse + ++import pytest + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.twofactor.hotp import HOTP + 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 +- + from ldap.controls.simple import BooleanControl ++from paramiko import AuthenticationException + + from ipalib import errors ++from ipaplatform.osinfo import osinfo ++from ipaplatform.paths import paths ++from ipapython.dn import DN ++from ipatests.pytest_ipa.integration import tasks ++from ipatests.test_integration.base import IntegrationTest ++from ipatests.util import xfail_context + + PASSWORD = "DummyPassword123" + USER = "opttestuser" +@@ -84,6 +85,65 @@ def kinit_otp(host, user, *, password, otp, success=True): + ) + + ++def ssh_2fa_with_cmd(host, hostname, username, password, otpvalue, ++ command="exit 0"): ++ """ ssh to user and in same session pass the command to check tgt of user ++ :param host: host to ssh ++ :param hostname: hostname to ssh ++ :param str username: The name of user ++ :param str password: password, usually the first factor ++ :param str otpvalue: generated pin of user ++ :param str command: command to execute in same session, ++ by deafult set to "exit 0" ++ :return: object class of expect command run ++ """ ++ temp_conf = tempfile.NamedTemporaryFile(suffix='.exp', delete=False) ++ with open(temp_conf.name, 'w') as tfile: ++ tfile.write('proc exitmsg { msg code } {\n') ++ tfile.write('\t# Close spawned program, if we are in the prompt\n') ++ tfile.write('\tcatch close\n\n') ++ tfile.write('\t# Wait for the exit code\n') ++ tfile.write('\tlassign [wait] pid spawnid os_error_flag rc\n\n') ++ tfile.write('\tputs ""\n') ++ tfile.write('\tputs "expect result: $msg"\n') ++ tfile.write('\tputs "expect exit code: $code"\n') ++ tfile.write('\tputs "expect spawn exit code: $rc"\n') ++ tfile.write('\texit $code\n') ++ tfile.write('}\n') ++ tfile.write('set timeout 60\n') ++ tfile.write('set prompt ".*\\[#\\$>\\] $"\n') ++ tfile.write(f'set password "{password}"\n') ++ tfile.write(f'set otpvalue "{otpvalue}"\n') ++ tfile.write(f'spawn ssh -o NumberOfPasswordPrompts=1 -o ' ++ f'StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' ++ f' -l {username} {hostname} {command}\n') ++ tfile.write('expect {\n') ++ tfile.write('"Enter first factor:*" {send -- "$password\r"}\n') ++ tfile.write('timeout {exitmsg "Unexpected output" 201}\n') ++ tfile.write('eof {exitmsg "Unexpected end of file" 202}\n') ++ tfile.write('}\n') ++ tfile.write('expect {\n') ++ tfile.write('"Enter second factor:*" {send -- "$otpvalue\r"}\n') ++ tfile.write('timeout {exitmsg "Unexpected output" 201}\n') ++ tfile.write('eof {exitmsg "Unexpected end of file" 202}\n') ++ tfile.write('}\n') ++ tfile.write('expect {\n') ++ tfile.write('"Authentication failure" ' ++ '{exitmsg "Authentication failure" 1}\n') ++ tfile.write('eof {exitmsg "Authentication successful" 0}\n') ++ tfile.write('timeout {exitmsg "Unexpected output" 201}\n') ++ tfile.write('}\n') ++ tfile.write('expect {\n') ++ tfile.write('exitmsg "Unexpected code path" 203\n') ++ tfile.write('EOF\n') ++ tfile.write('}') ++ host.transport.put_file(temp_conf.name, '/tmp/ssh.exp') ++ tasks.clear_sssd_cache(host) ++ expect_cmd = 'expect -f /tmp/ssh.exp' ++ cmd = host.run_command(expect_cmd, raiseonerr=False) ++ return cmd ++ ++ + def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""): + """ + :param hostname: hostname +@@ -91,6 +151,7 @@ def ssh_2f(hostname, username, answers_dict, port=22, unwanted_prompt=""): + :param answers_dict: dictionary of options with prompt_message and value. + :param port: port for ssh + """ ++ + # Handler for server questions + def answer_handler(title, instructions, prompt_list): + resp = [] +@@ -131,8 +192,9 @@ class TestOTPToken(IntegrationTest): + + @classmethod + def install(cls, mh): +- super(TestOTPToken, cls).install(mh) + master = cls.master ++ tasks.install_packages(master, ['expect']) ++ super(TestOTPToken, cls).install(mh) + + tasks.kinit_admin(master) + # create service with OTP auth indicator +@@ -398,6 +460,114 @@ class TestOTPToken(IntegrationTest): + self.master.run_command(['semanage', 'login', '-D']) + sssd_conf_backup.restore() + ++ def test_2fa_only_with_password(self): ++ """Test ssh with 2FA only with the password(first factor) when ++ user-auth-type is opt and password. ++ ++ Test for : https://github.com/SSSD/sssd/pull/7500 ++ ++ Add the IPA user and user-auth-type set to opt and password. ++ Authenticate the user only with password, just press enter ++ at `Second factor` ++ """ ++ master = self.master ++ USER3 = 'sshuser3' ++ sssd_conf_backup = tasks.FileBackup(master, paths.SSSD_CONF) ++ first_prompt = 'Enter first factor:' ++ second_prompt = 'Enter second factor:' ++ add_contents = textwrap.dedent(''' ++ [prompting/2fa/sshd] ++ single_prompt = False ++ first_prompt = {0} ++ second_prompt = {1} ++ ''').format(first_prompt, second_prompt) ++ set_sssd_conf(master, add_contents) ++ tasks.create_active_user(master, USER3, PASSWORD) ++ tasks.kinit_admin(master) ++ master.run_command(['ipa', 'user-mod', USER3, '--user-auth-type=otp', ++ '--user-auth-type=password']) ++ try: ++ otpuid, totp = add_otptoken(master, USER3, otptype='totp') ++ master.run_command(['ipa', 'otptoken-show', otpuid]) ++ totp.generate(int(time.time())).decode('ascii') ++ otpvalue = "\n" ++ tasks.clear_sssd_cache(self.master) ++ github_ticket = "https://github.com/SSSD/sssd/pull/7500" ++ sssd_version = tasks.get_sssd_version(master) ++ rhel_fail = ( ++ osinfo.id == 'rhel' ++ and sssd_version < tasks.parse_version("2.9.5") ++ ) ++ fedora_fail = ( ++ osinfo.id == 'fedora' ++ and sssd_version == tasks.parse_version("2.9.5") ++ ) ++ with xfail_context(rhel_fail or fedora_fail, reason=github_ticket): ++ result = ssh_2fa_with_cmd(master, ++ self.master.external_hostname, ++ USER3, PASSWORD, otpvalue=otpvalue, ++ command="klist") ++ print(result.stdout_text) ++ assert ('Authentication successful') in result.stdout_text ++ assert USER3 in result.stdout_text ++ assert (f'Default principal: ' ++ f'{USER3}@{self.master.domain.realm}' in ++ result.stdout_text) ++ cmd = self.master.run_command(['semanage', 'login', '-l']) ++ assert USER3 in cmd.stdout_text ++ finally: ++ master.run_command(['ipa', 'user-del', USER3]) ++ self.master.run_command(['semanage', 'login', '-D']) ++ sssd_conf_backup.restore() ++ ++ def test_2fa_with_otp_password(self): ++ """Test ssh with 2FA only with password and otpvalue when ++ user-auth-type is opt and password. ++ ++ Test for : https://github.com/SSSD/sssd/pull/7500 ++ ++ Add the IPA user and user-auth-type set to opt and password. ++ Authenticate the user only with password and otpvalue. ++ """ ++ master = self.master ++ USER4 = 'sshuser4' ++ sssd_conf_backup = tasks.FileBackup(master, paths.SSSD_CONF) ++ first_prompt = 'Enter first factor:' ++ second_prompt = 'Enter second factor:' ++ add_contents = textwrap.dedent(''' ++ [prompting/2fa/sshd] ++ single_prompt = False ++ first_prompt = {0} ++ second_prompt = {1} ++ ''').format(first_prompt, second_prompt) ++ set_sssd_conf(master, add_contents) ++ tasks.create_active_user(master, USER4, PASSWORD) ++ tasks.kinit_admin(master) ++ ++ master.run_command(['ipa', 'user-mod', USER4, '--user-auth-type=otp', ++ '--user-auth-type=password']) ++ try: ++ otpuid, totp = add_otptoken(master, USER4, otptype='totp') ++ master.run_command(['ipa', 'otptoken-show', otpuid]) ++ otpvalue = totp.generate(int(time.time())).decode('ascii') ++ tasks.clear_sssd_cache(self.master) ++ result = ssh_2fa_with_cmd(master, ++ self.master.external_hostname, ++ USER4, PASSWORD, otpvalue=otpvalue, ++ command="klist") ++ print(result.stdout_text) ++ cmd = self.master.run_command(['semanage', 'login', '-l']) ++ # check the output ++ assert ('Authentication successful') in result.stdout_text ++ assert USER4 in result.stdout_text ++ assert (f'Default principal: {USER4}@' ++ f'{self.master.domain.realm}' in result.stdout_text) ++ assert USER4 in cmd.stdout_text ++ finally: ++ master.run_command(['ipa', 'user-del', USER4]) ++ self.master.run_command(['semanage', 'login', '-D']) ++ sssd_conf_backup.restore() ++ + @pytest.fixture + def setup_otp_nsslapd(self): + check_services = self.master.run_command( +-- +2.46.2 + diff --git a/freeipa.spec b/freeipa.spec index 7e00820..7feceb0 100644 --- a/freeipa.spec +++ b/freeipa.spec @@ -70,7 +70,7 @@ %global krb5_kdb_version 9.0 # 0.7.16: https://github.com/drkjam/netaddr/issues/71 %global python_netaddr_version 0.7.19 -%global samba_version 4.20.0 +%global samba_version 4.21.1 %global slapi_nis_version 0.70.0 %global python_ldap_version 3.1.0-1 %if 0%{?rhel} < 9 @@ -88,7 +88,7 @@ %global bind_version 9.11.20-6 # support for passkey -%global sssd_version 2.9.0 +%global sssd_version 2.10.0 %else # Fedora @@ -248,6 +248,18 @@ Patch0009: 0009-ipa-migrate-fix-migration-issues-with-entries-using-.patch Patch0010: 0010-ipa-migrate-fix-alternate-entry-search-filter.patch Patch0011: 0011-Custodia-in-fips-mode-add-nomac-or-nomacver-to-opens.patch Patch0012: 0012-ipatests-make-TestDuplicates-teardowns-order-agnosti.patch +Patch0013: 0013-UnsafeIPAddress-pass-flag-0-to-IPNetwork.patch +Patch0014: 0014-ipatests-provide-a-ccache-to-rpcclient-deletetrustdo.patch +Patch0015: 0015-test_adtrust_install-add-use-krb5-ccache-to-smbclien.patch +Patch0016: 0016-Don-t-rely-on-removing-the-CA-to-uninstall-the-ACME-.patch +Patch0017: 0017-ipatests-Fixes-for-ipa-idrange-fix-testsuite.patch +Patch0018: 0018-spec-Use-nodejs22-on-RHEL-10-and-ELN.patch +Patch0019: 0019-Do-not-let-user-with-an-expired-OTP-token-to-log-in-.patch +Patch0020: 0020-ipatests-Activate-ssh-in-sssd.conf.patch +Patch0021: 0021-ipa-migrate-man-page-fix-typos-and-errors.patch +Patch0022: 0022-ipatests-Test-for-ipa-hbac-rule-duplication.patch +Patch0023: 0023-ipatests-refactor-password-file-handling-in-TestHSMI.patch +Patch0024: 0024-ipatests-2FA-test-cases.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch %endif %endif @@ -300,10 +312,10 @@ BuildRequires: libpwquality-devel BuildRequires: libsss_idmap-devel BuildRequires: libsss_certmap-devel BuildRequires: libsss_nss_idmap-devel >= %{sssd_version} -%if 0%{?fedora} >= 41 +%if 0%{?fedora} >= 41 || 0%{?rhel} >= 10 # Do not use nodejs22 on fedora < 41, https://pagure.io/freeipa/issue/9643 BuildRequires: nodejs(abi) -%elif 0%{?fedora} >= 39 || 0%{?rhel} >= 10 +%elif 0%{?fedora} >= 39 # Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374 BuildRequires: nodejs(abi) < 127 %else @@ -1870,6 +1882,13 @@ fi %endif %changelog +* Mon Oct 21 2024 Florence Blanc-Renaud - 4.12.2-4 +- Related: RHEL-59777 Rebase Samba to the latest 4.21.x release +- Resolves: RHEL-59659 ipa dns-zone --allow-query '!198.18.2.0/24;any;' fails with Unrecognized IPAddress flags +- Resolves: RHEL-61636 Uninstall ACME separately during PKI uninstallation +- Resolves: RHEL-61723 Include latest fixes in python3-ipatests packages +- Resolves: RHEL-63325 Last expired OTP token would be considered as still assigned to the user + * Tue Sep 24 2024 Rafael Guteres Jeffman - 4.12.2-3 - Resolves: RHEL-33818 Remove python3-ipalib's dependency on python3-netifaces