From ad479c1a26771d7d9c4f03ac21085dde92ee4f25 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Thu, 12 Jan 2023 03:27:02 -0500 Subject: [PATCH] import ipa-4.9.10-9.module+el8.7.0+17438+aa488955 --- ...d-integration-tests-for-external-IdP.patch | 346 +++++++++++++ ...w-empty-pagination-size_rhbz#2094672.patch | 67 +++ ...Allow-grace-login-limit_rhbz#2109243.patch | 56 +++ ...n-progress-is-a-boolean_rhbz#2117303.patch | 35 ++ ...-not-prevent-LDAP-binds_rhbz#2109236.patch | 125 +++++ ...cy-on-group-pw-policies_rhbz#2115475.patch | 230 +++++++++ ...ization-issue-in-Web-UI_rhbz#2133050.patch | 62 +++ ...al-krb5-conf-on-clients_rhbz#2150246.patch | 473 ++++++++++++++++++ ...with-older-RHEL-systems_rhbz#2148255.patch | 123 +++++ SPECS/ipa.spec | 45 +- 10 files changed, 1560 insertions(+), 2 deletions(-) create mode 100644 SOURCES/0005-Add-end-to-end-integration-tests-for-external-IdP.patch create mode 100644 SOURCES/0006-webui-Do-not-allow-empty-pagination-size_rhbz#2094672.patch create mode 100644 SOURCES/0007-webui-Allow-grace-login-limit_rhbz#2109243.patch create mode 100644 SOURCES/0008-check_repl_update-in-progress-is-a-boolean_rhbz#2117303.patch create mode 100644 SOURCES/0009-Disabling-gracelimit-does-not-prevent-LDAP-binds_rhbz#2109236.patch create mode 100644 SOURCES/0010-Set-passwordgracelimit-to-match-global-policy-on-group-pw-policies_rhbz#2115475.patch create mode 100644 SOURCES/0011-fix-canonicalization-issue-in-Web-UI_rhbz#2133050.patch create mode 100644 SOURCES/0012-Defer-creating-the-final-krb5-conf-on-clients_rhbz#2150246.patch create mode 100644 SOURCES/0013-Vault-fix-interoperability-issues-with-older-RHEL-systems_rhbz#2148255.patch diff --git a/SOURCES/0005-Add-end-to-end-integration-tests-for-external-IdP.patch b/SOURCES/0005-Add-end-to-end-integration-tests-for-external-IdP.patch new file mode 100644 index 0000000..700df13 --- /dev/null +++ b/SOURCES/0005-Add-end-to-end-integration-tests-for-external-IdP.patch @@ -0,0 +1,346 @@ +From 857713c5a9c8e0b62c06dd92e69c09eeb34b2e99 Mon Sep 17 00:00:00 2001 +From: Anuja More +Date: Mon, 23 May 2022 12:26:34 +0530 +Subject: [PATCH] Add end to end integration tests for external IdP + +Added tests for HBAC and SUDO rule and other +test scenarios. + +Related : https://pagure.io/freeipa/issue/8805 +Related: https://pagure.io/freeipa/issue/8803 +Related: https://pagure.io/freeipa/issue/8804 + +Signed-off-by: Anuja More +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Florence Blanc-Renaud +--- + ipatests/test_integration/test_idp.py | 260 ++++++++++++++++++++++---- + 1 file changed, 226 insertions(+), 34 deletions(-) + +diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py +index 8f9e92e6a..2ffe6a208 100644 +--- a/ipatests/test_integration/test_idp.py ++++ b/ipatests/test_integration/test_idp.py +@@ -1,6 +1,8 @@ + from __future__ import absolute_import + + import time ++import pytest ++import re + + import textwrap + from ipaplatform.paths import paths +@@ -22,12 +24,12 @@ driver.get(verification_uri) + try: + element = WebDriverWait(driver, 90).until( + EC.presence_of_element_located((By.ID, "username"))) +- driver.find_element_by_id("username").send_keys("testuser1") +- driver.find_element_by_id("password").send_keys("{passwd}") +- driver.find_element_by_id("kc-login").click() ++ driver.find_element(By.ID, "username").send_keys("testuser1") ++ driver.find_element(By.ID, "password").send_keys("{passwd}") ++ driver.find_element(By.ID, "kc-login").click() + element = WebDriverWait(driver, 90).until( + EC.presence_of_element_located((By.ID, "kc-login"))) +- driver.find_element_by_id("kc-login").click() ++ driver.find_element(By.ID, "kc-login").click() + assert "Device Login Successful" in driver.page_source + finally: + now = datetime.now().strftime("%M-%S") +@@ -39,18 +41,12 @@ finally: + def add_user_code(host, verification_uri): + contents = user_code_script.format(uri=verification_uri, + passwd=host.config.admin_password) +- host.put_file_contents("/tmp/add_user_code.py", contents) +- tasks.run_repeatedly( +- host, ['python3', '/tmp/add_user_code.py']) +- +- +-def get_verification_uri(host, since, keycloak_server_name): +- command = textwrap.dedent(""" +- journalctl -u ipa-otpd\\* --since="%s" | grep "user_code:" | awk '{ print substr($7,2,9) }'""" % since) # noqa: E501 +- user_code = host.run_command(command).stdout_text.rstrip("\r\n") +- uri = ("https://{0}:8443/auth/realms/master/device?user_code={1}".format( +- keycloak_server_name, user_code)) +- return uri ++ try: ++ host.put_file_contents("/tmp/add_user_code.py", contents) ++ tasks.run_repeatedly( ++ host, ['python3', '/tmp/add_user_code.py']) ++ finally: ++ host.run_command(["rm", "-f", "/tmp/add_user_code.py"]) + + + def kinit_idp(host, user, keycloak_server): +@@ -58,11 +54,14 @@ def kinit_idp(host, user, keycloak_server): + tasks.kdestroy_all(host) + # create armor for FAST + host.run_command(["kinit", "-n", "-c", ARMOR]) +- since = time.strftime('%Y-%m-%d %H:%M:%S') + cmd = ["kinit", "-T", ARMOR, user] ++ + with host.spawn_expect(cmd, default_timeout=100) as e: +- e.expect('Authenticate at .+: ') +- uri = get_verification_uri(host, since, keycloak_server.hostname) ++ e.expect('Authenticate at (.+) and press ENTER.:') ++ prompt = e.get_last_output() ++ uri = re.search(r'Authenticate at (.*?) and press ENTER.:', prompt ++ ).group(1) ++ time.sleep(15) + if uri: + add_user_code(keycloak_server, uri) + e.sendline('\n') +@@ -74,21 +73,27 @@ def kinit_idp(host, user, keycloak_server): + + class TestIDPKeycloak(IntegrationTest): + +- num_replicas = 1 ++ num_replicas = 2 + topology = 'line' + + @classmethod + def install(cls, mh): +- tasks.install_master(cls.master, setup_dns=True) +- tasks.install_client(cls.master, cls.replicas[0]) +- content = cls.master.get_file_contents(paths.IPA_DEFAULT_CONF, +- encoding='utf-8') +- new_content = content + "\noidc_child_debug_level = 10" +- cls.master.put_file_contents(paths.IPA_DEFAULT_CONF, new_content) ++ cls.client = cls.replicas[0] ++ cls.replica = cls.replicas[1] ++ tasks.install_master(cls.master) ++ tasks.install_client(cls.master, cls.replicas[0], ++ extra_args=["--mkhomedir"]) ++ tasks.install_replica(cls.master, cls.replicas[1]) ++ for host in [cls.master, cls.replicas[0], cls.replicas[1]]: ++ content = host.get_file_contents(paths.IPA_DEFAULT_CONF, ++ encoding='utf-8') ++ new_content = content + "\noidc_child_debug_level = 10" ++ host.put_file_contents(paths.IPA_DEFAULT_CONF, new_content) + with tasks.remote_sssd_config(cls.master) as sssd_config: + sssd_config.edit_domain( + cls.master.domain, 'krb5_auth_timeout', 1100) + tasks.clear_sssd_cache(cls.master) ++ tasks.clear_sssd_cache(cls.replicas[0]) + tasks.kinit_admin(cls.master) + cls.master.run_command(["ipa", "config-mod", "--user-auth-type=idp", + "--user-auth-type=password"]) +@@ -97,20 +102,207 @@ class TestIDPKeycloak(IntegrationTest): + cls.replicas[0].run_command(xvfb) + + def test_auth_keycloak_idp(self): +- keycloak_srv = self.replicas[0] +- create_quarkus.setup_keycloakserver(keycloak_srv) ++ """ ++ Test case to check that OAuth 2.0 Device ++ Authorization Grant is working as ++ expected for user configured with external idp. ++ """ ++ create_quarkus.setup_keycloakserver(self.client) + time.sleep(60) +- create_quarkus.setup_keycloak_client(keycloak_srv) ++ create_quarkus.setup_keycloak_client(self.client) + tasks.kinit_admin(self.master) +- cmd = ["ipa", "idp-add", "keycloak", "--provider=keycloak", ++ cmd = ["ipa", "idp-add", "keycloakidp", "--provider=keycloak", + "--client-id=ipa_oidc_client", "--org=master", +- "--base-url={0}:8443/auth".format(keycloak_srv.hostname)] ++ "--base-url={0}:8443/auth".format(self.client.hostname)] + self.master.run_command(cmd, stdin_text="{0}\n{0}".format( +- keycloak_srv.config.admin_password)) ++ self.client.config.admin_password)) + tasks.user_add(self.master, 'keycloakuser', + extra_args=["--user-auth-type=idp", + "--idp-user-id=testuser1@ipa.test", +- "--idp=keycloak"] ++ "--idp=keycloakidp"] + ) ++ list_user = self.master.run_command( ++ ["ipa", "user-find", "--idp-user-id=testuser1@ipa.test"] ++ ) ++ assert "keycloakuser" in list_user.stdout_text ++ list_by_idp = self.master.run_command(["ipa", "user-find", ++ "--idp=keycloakidp"] ++ ) ++ assert "keycloakuser" in list_by_idp.stdout_text ++ list_by_user = self.master.run_command( ++ ["ipa", "user-find", "--idp-user-id=testuser1@ipa.test", "--all"] ++ ) ++ assert "keycloakidp" in list_by_user.stdout_text ++ tasks.clear_sssd_cache(self.master) ++ kinit_idp(self.master, 'keycloakuser', keycloak_server=self.client) ++ ++ @pytest.fixture ++ def hbac_setup_teardown(self): ++ # allow sshd only on given host ++ tasks.kinit_admin(self.master) ++ self.master.run_command(["ipa", "hbacrule-disable", "allow_all"]) ++ self.master.run_command(["ipa", "hbacrule-add", "rule1"]) ++ self.master.run_command(["ipa", "hbacrule-add-user", "rule1", ++ "--users=keycloakuser"] ++ ) ++ self.master.run_command(["ipa", "hbacrule-add-host", "rule1", ++ "--hosts", self.replica.hostname]) ++ self.master.run_command(["ipa", "hbacrule-add-service", "rule1", ++ "--hbacsvcs=sshd"] ++ ) ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.replica) ++ yield ++ ++ # cleanup ++ tasks.kinit_admin(self.master) ++ self.master.run_command(["ipa", "hbacrule-enable", "allow_all"]) ++ self.master.run_command(["ipa", "hbacrule-del", "rule1"]) ++ ++ def test_auth_hbac(self, hbac_setup_teardown): ++ """ ++ Test case to check that hbacrule is working as ++ expected for user configured with external idp. ++ """ ++ kinit_idp(self.master, 'keycloakuser', keycloak_server=self.client) ++ ssh_cmd = "ssh -q -K -l keycloakuser {0} whoami" ++ valid_ssh = self.master.run_command( ++ ssh_cmd.format(self.replica.hostname)) ++ assert "keycloakuser" in valid_ssh.stdout_text ++ negative_ssh = self.master.run_command( ++ ssh_cmd.format(self.master.hostname), raiseonerr=False ++ ) ++ assert negative_ssh.returncode == 255 ++ ++ def test_auth_sudo_idp(self): ++ """ ++ Test case to check that sudorule is working as ++ expected for user configured with external idp. ++ """ ++ tasks.kdestroy_all(self.master) ++ tasks.kinit_admin(self.master) ++ # rule: keycloakuser are allowed to execute yum on ++ # the client machine as root. ++ cmdlist = [ ++ ["ipa", "sudocmd-add", "/usr/bin/yum"], ++ ["ipa", "sudorule-add", "sudorule"], ++ ['ipa', 'sudorule-add-user', '--users=keycloakuser', ++ 'sudorule'], ++ ['ipa', 'sudorule-add-host', '--hosts', ++ self.client.hostname, 'sudorule'], ++ ['ipa', 'sudorule-add-runasuser', ++ '--users=root', 'sudorule'], ++ ['ipa', 'sudorule-add-allow-command', ++ '--sudocmds=/usr/bin/yum', 'sudorule'], ++ ['ipa', 'sudorule-show', 'sudorule', '--all'], ++ ['ipa', 'sudorule-add-option', ++ 'sudorule', '--sudooption', "!authenticate"] ++ ] ++ for cmd in cmdlist: ++ self.master.run_command(cmd) ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.client) ++ try: ++ cmd = 'sudo -ll -U keycloakuser' ++ test = self.client.run_command(cmd).stdout_text ++ assert "User keycloakuser may run the following commands" in test ++ assert "/usr/bin/yum" in test ++ kinit_idp(self.client, 'keycloakuser', self.client) ++ test_sudo = 'su -c "sudo yum list wget" keycloakuser' ++ self.client.run_command(test_sudo) ++ list_fail = self.master.run_command(cmd).stdout_text ++ assert "User keycloakuser is not allowed to run sudo" in list_fail ++ finally: ++ tasks.kinit_admin(self.master) ++ self.master.run_command(['ipa', 'sudorule-del', 'sudorule']) ++ self.master.run_command(["ipa", "sudocmd-del", "/usr/bin/yum"]) ++ ++ def test_auth_replica(self): ++ """ ++ Test case to check that OAuth 2.0 Device ++ Authorization is working as expected on replica. ++ """ ++ tasks.clear_sssd_cache(self.master) ++ tasks.clear_sssd_cache(self.replica) ++ tasks.kinit_admin(self.replica) ++ list_user = self.master.run_command( ++ ["ipa", "user-find", "--idp-user-id=testuser1@ipa.test"] ++ ) ++ assert "keycloakuser" in list_user.stdout_text ++ list_by_idp = self.replica.run_command(["ipa", "user-find", ++ "--idp=keycloakidp"] ++ ) ++ assert "keycloakuser" in list_by_idp.stdout_text ++ list_by_user = self.replica.run_command( ++ ["ipa", "user-find", "--idp-user-id=testuser1@ipa.test", "--all"] ++ ) ++ assert "keycloakidp" in list_by_user.stdout_text ++ kinit_idp(self.replica, 'keycloakuser', keycloak_server=self.client) ++ ++ def test_idp_with_services(self): ++ """ ++ Test case to check that services can be configured ++ auth indicator as idp. ++ """ + tasks.clear_sssd_cache(self.master) +- kinit_idp(self.master, 'keycloakuser', keycloak_srv) ++ tasks.kinit_admin(self.master) ++ domain = self.master.domain.name.upper() ++ services = [ ++ "DNS/{0}@{1}".format(self.master.hostname, domain), ++ "HTTP/{0}@{1}".format(self.client.hostname, domain), ++ "dogtag/{0}@{1}".format(self.master.hostname, domain), ++ "ipa-dnskeysyncd/{0}@{1}".format(self.master.hostname, domain) ++ ] ++ try: ++ for service in services: ++ test = self.master.run_command(["ipa", "service-mod", service, ++ "--auth-ind=idp"] ++ ) ++ assert "Authentication Indicators: idp" in test.stdout_text ++ finally: ++ for service in services: ++ self.master.run_command(["ipa", "service-mod", service, ++ "--auth-ind="]) ++ ++ def test_idp_backup_restore(self): ++ """ ++ Test case to check that after restore data is retrieved ++ with related idp configuration. ++ """ ++ tasks.kinit_admin(self.master) ++ user = "backupuser" ++ cmd = ["ipa", "idp-add", "testidp", "--provider=keycloak", ++ "--client-id=ipa_oidc_client", "--org=master", ++ "--base-url={0}:8443/auth".format(self.client.hostname)] ++ self.master.run_command(cmd, stdin_text="{0}\n{0}".format( ++ self.client.config.admin_password)) ++ ++ tasks.user_add(self.master, user, ++ extra_args=["--user-auth-type=idp", ++ "--idp-user-id=testuser1@ipa.test", ++ "--idp=testidp"] ++ ) ++ ++ backup_path = tasks.get_backup_dir(self.master) ++ # change data after backup ++ self.master.run_command(['ipa', 'user-del', user]) ++ self.master.run_command(['ipa', 'idp-del', 'testidp']) ++ dirman_password = self.master.config.dirman_password ++ self.master.run_command(['ipa-restore', backup_path], ++ stdin_text=dirman_password + '\nyes') ++ try: ++ list_user = self.master.run_command( ++ ['ipa', 'user-show', 'backupuser', '--all'] ++ ).stdout_text ++ assert "External IdP configuration: testidp" in list_user ++ assert "User authentication types: idp" in list_user ++ assert ("External IdP user identifier: " ++ "testuser1@ipa.test") in list_user ++ list_idp = self.master.run_command(['ipa', 'idp-find', 'testidp']) ++ assert "testidp" in list_idp.stdout_text ++ kinit_idp(self.master, user, self.client) ++ finally: ++ tasks.kdestroy_all(self.master) ++ tasks.kinit_admin(self.master) ++ self.master.run_command(["rm", "-rf", backup_path]) ++ self.master.run_command(["ipa", "idp-del", "testidp"]) +-- +2.36.1 + diff --git a/SOURCES/0006-webui-Do-not-allow-empty-pagination-size_rhbz#2094672.patch b/SOURCES/0006-webui-Do-not-allow-empty-pagination-size_rhbz#2094672.patch new file mode 100644 index 0000000..25e9f72 --- /dev/null +++ b/SOURCES/0006-webui-Do-not-allow-empty-pagination-size_rhbz#2094672.patch @@ -0,0 +1,67 @@ +From 991849cf58fa990ad4540a61214b5ab4fcd4baa1 Mon Sep 17 00:00:00 2001 +From: Armando Neto +Date: Fri, 8 Jul 2022 15:56:31 -0300 +Subject: [PATCH] webui: Do not allow empty pagination size + +Pagination size must be required, the current validators are triggered after +form is submitted, thus the only way for check if data is not empty is by making +the field required. + +Fixes: https://pagure.io/freeipa/issue/9192 + +Signed-off-by: Armando Neto +Reviewed-By: Florence Blanc-Renaud +--- + .../ui/src/freeipa/Application_controller.js | 1 + + ipatests/test_webui/test_misc_cases.py | 19 +++++++++++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/install/ui/src/freeipa/Application_controller.js b/install/ui/src/freeipa/Application_controller.js +index 46aabc9c4..140ee8fe0 100644 +--- a/install/ui/src/freeipa/Application_controller.js ++++ b/install/ui/src/freeipa/Application_controller.js +@@ -318,6 +318,7 @@ define([ + $type: 'text', + name: 'pagination_size', + label: '@i18n:customization.table_pagination', ++ required: true, + validators: ['positive_integer'] + } + ] +diff --git a/ipatests/test_webui/test_misc_cases.py b/ipatests/test_webui/test_misc_cases.py +index 5f7ffb54e..aca9e1a99 100644 +--- a/ipatests/test_webui/test_misc_cases.py ++++ b/ipatests/test_webui/test_misc_cases.py +@@ -11,6 +11,11 @@ from ipatests.test_webui.ui_driver import screenshot + import pytest + import re + ++try: ++ from selenium.webdriver.common.by import By ++except ImportError: ++ pass ++ + + @pytest.mark.tier1 + class TestMiscCases(UI_driver): +@@ -26,3 +31,17 @@ class TestMiscCases(UI_driver): + ver_re = re.compile('version: .*') + assert re.search(ver_re, about_text), 'Version not found' + self.dialog_button_click('ok') ++ ++ @screenshot ++ def test_customization_pagination_input_required(self): ++ """Test if 'pagination size' is required when submitting the form.""" ++ self.init_app() ++ ++ self.profile_menu_action('configuration') ++ self.fill_input('pagination_size', '') ++ self.dialog_button_click('save') ++ ++ pagination_size_elem = self.find( ++ ".widget[name='pagination_size']", By.CSS_SELECTOR) ++ ++ self.assert_field_validation_required(parent=pagination_size_elem) +-- +2.36.1 + diff --git a/SOURCES/0007-webui-Allow-grace-login-limit_rhbz#2109243.patch b/SOURCES/0007-webui-Allow-grace-login-limit_rhbz#2109243.patch new file mode 100644 index 0000000..93cfab9 --- /dev/null +++ b/SOURCES/0007-webui-Allow-grace-login-limit_rhbz#2109243.patch @@ -0,0 +1,56 @@ +From ade5093b08f92b279c200f341e96972a74f644d8 Mon Sep 17 00:00:00 2001 +From: Carla Martinez +Date: Fri, 29 Jul 2022 13:16:16 +0200 +Subject: [PATCH] webui: Allow grace login limit + +There was no support for setting the grace login limit on the WebUI. The +only way to so was only via CLI: + + `ipa pwpolicy-mod --gracelimit=2 global_policy` + +Thus, the grace login limit must be updated from the policy section and +this will reflect also on the user settings (under the 'Password Policy' +section) + +Fixes: https://pagure.io/freeipa/issue/9211 + +Signed-off-by: Carla Martinez +Reviewed-By: Florence Blanc-Renaud +--- + install/ui/src/freeipa/policy.js | 3 +++ + install/ui/src/freeipa/user.js | 5 +++++ + 2 files changed, 8 insertions(+) + +diff --git a/install/ui/src/freeipa/policy.js b/install/ui/src/freeipa/policy.js +index fa2028a52..7ec103636 100644 +--- a/install/ui/src/freeipa/policy.js ++++ b/install/ui/src/freeipa/policy.js +@@ -72,6 +72,9 @@ return { + { + name: 'cospriority', + required: true ++ }, ++ { ++ name: 'passwordgracelimit' + } + ] + }] +diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js +index a580db035..b47c97f72 100644 +--- a/install/ui/src/freeipa/user.js ++++ b/install/ui/src/freeipa/user.js +@@ -318,6 +318,11 @@ return { + label: '@mo-param:pwpolicy:krbpwdlockoutduration:label', + read_only: true, + measurement_unit: 'seconds' ++ }, ++ { ++ name: 'passwordgracelimit', ++ label: '@mo-param:pwpolicy:passwordgracelimit:label', ++ read_only: true + } + ] + }, +-- +2.37.2 + diff --git a/SOURCES/0008-check_repl_update-in-progress-is-a-boolean_rhbz#2117303.patch b/SOURCES/0008-check_repl_update-in-progress-is-a-boolean_rhbz#2117303.patch new file mode 100644 index 0000000..36629da --- /dev/null +++ b/SOURCES/0008-check_repl_update-in-progress-is-a-boolean_rhbz#2117303.patch @@ -0,0 +1,35 @@ +From 05a298f56485222583cb7dd4f6a3a4c5c77fc8cf Mon Sep 17 00:00:00 2001 +From: Florence Blanc-Renaud +Date: Sun, 7 Aug 2022 12:44:47 +0200 +Subject: [PATCH] check_repl_update: in progress is a boolean + +With the fix for https://pagure.io/freeipa/issue/9171, +nsds5replicaUpdateInProgress is now handled as a boolean. +One remaining occurrence was still handling it as a string +and calling lower() on its value. + +Replace with direct boolean comparison. + +Fixes: https://pagure.io/freeipa/issue/9218 +Signed-off-by: Florence Blanc-Renaud +Reviewed-By: Alexander Bokovoy +--- + ipaserver/install/replication.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py +index 16be3760c..9d9aa1c4b 100644 +--- a/ipaserver/install/replication.py ++++ b/ipaserver/install/replication.py +@@ -1152,7 +1152,7 @@ class ReplicationManager: + except (ValueError, TypeError, KeyError): + end = 0 + # incremental update is done if inprogress is false and end >= start +- done = inprogress and inprogress.lower() == 'false' and start <= end ++ done = inprogress is not None and not inprogress and start <= end + logger.info("Replication Update in progress: %s: status: %s: " + "start: %d: end: %d", + inprogress, status, start, end) +-- +2.37.2 + diff --git a/SOURCES/0009-Disabling-gracelimit-does-not-prevent-LDAP-binds_rhbz#2109236.patch b/SOURCES/0009-Disabling-gracelimit-does-not-prevent-LDAP-binds_rhbz#2109236.patch new file mode 100644 index 0000000..17088cf --- /dev/null +++ b/SOURCES/0009-Disabling-gracelimit-does-not-prevent-LDAP-binds_rhbz#2109236.patch @@ -0,0 +1,125 @@ +From 1316cd8b2252c2543cf2ef2186956a8833037b1e Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 21 Jul 2022 09:28:46 -0400 +Subject: [PATCH] Disabling gracelimit does not prevent LDAP binds + +Originally the code treated 0 as disabled. This was +changed during the review process to -1 but one remnant +was missed effetively allowing gracelimit 0 to also mean +disabled. + +Add explicit tests for testing with gracelimit = 0 and +gracelimit = -1. + +Also remove some extranous "str(self.master.domain.basedn)" +lines from some of the tests. + +Fixes: https://pagure.io/freeipa/issue/9206 + +Signed-off-by: Rob Crittenden +Reviewed-By: Francisco Trivino +--- + .../ipa-graceperiod/ipa_graceperiod.c | 2 +- + ipatests/test_integration/test_pwpolicy.py | 55 ++++++++++++++++++- + 2 files changed, 53 insertions(+), 4 deletions(-) + +diff --git a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c +index a3f57cb4b..345e1dee7 100644 +--- a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c ++++ b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c +@@ -479,7 +479,7 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb) + if (pwresponse_requested) { + slapi_pwpolicy_make_response_control(pb, -1, grace_limit - grace_user_time , -1); + } +- } else if ((grace_limit > 0) && (grace_user_time >= grace_limit)) { ++ } else if (grace_user_time >= grace_limit) { + LOG_TRACE("%s password is expired and out of grace limit\n", dn); + errstr = "Password is expired.\n"; + ret = LDAP_INVALID_CREDENTIALS; +diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py +index 6d6698284..41d6e9070 100644 +--- a/ipatests/test_integration/test_pwpolicy.py ++++ b/ipatests/test_integration/test_pwpolicy.py +@@ -36,7 +36,7 @@ class TestPWPolicy(IntegrationTest): + cls.master.run_command(['ipa', 'group-add-member', POLICY, + '--users', USER]) + cls.master.run_command(['ipa', 'pwpolicy-add', POLICY, +- '--priority', '1']) ++ '--priority', '1', '--gracelimit', '-1']) + cls.master.run_command(['ipa', 'passwd', USER], + stdin_text='{password}\n{password}\n'.format( + password=PASSWORD +@@ -265,7 +265,6 @@ class TestPWPolicy(IntegrationTest): + + def test_graceperiod_expired(self): + """Test the LDAP bind grace period""" +- str(self.master.domain.basedn) + dn = "uid={user},cn=users,cn=accounts,{base_dn}".format( + user=USER, base_dn=str(self.master.domain.basedn)) + +@@ -308,7 +307,6 @@ class TestPWPolicy(IntegrationTest): + + def test_graceperiod_not_replicated(self): + """Test that the grace period is reset on password reset""" +- str(self.master.domain.basedn) + dn = "uid={user},cn=users,cn=accounts,{base_dn}".format( + user=USER, base_dn=str(self.master.domain.basedn)) + +@@ -341,3 +339,54 @@ class TestPWPolicy(IntegrationTest): + ) + assert 'passwordgraceusertime: 0' in result.stdout_text.lower() + self.reset_password(self.master) ++ ++ def test_graceperiod_zero(self): ++ """Test the LDAP bind with zero grace period""" ++ dn = "uid={user},cn=users,cn=accounts,{base_dn}".format( ++ user=USER, base_dn=str(self.master.domain.basedn)) ++ ++ self.master.run_command( ++ ["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "0", ], ++ ) ++ ++ # Resetting the password will mark it as expired ++ self.reset_password(self.master) ++ ++ # Now grace is done and binds should fail. ++ result = self.master.run_command( ++ ["ldapsearch", "-e", "ppolicy", "-D", dn, ++ "-w", PASSWORD, "-b", dn], raiseonerr=False ++ ) ++ assert result.returncode == 49 ++ ++ assert 'Password is expired' in result.stderr_text ++ assert 'Password expired, 0 grace logins remain' in result.stderr_text ++ ++ def test_graceperiod_disabled(self): ++ """Test the LDAP bind with grace period disabled (-1)""" ++ str(self.master.domain.basedn) ++ dn = "uid={user},cn=users,cn=accounts,{base_dn}".format( ++ user=USER, base_dn=str(self.master.domain.basedn)) ++ ++ # This can fail if gracelimit is already -1 so ignore it ++ self.master.run_command( ++ ["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "-1",], ++ raiseonerr=False, ++ ) ++ ++ # Ensure the password is expired ++ self.reset_password(self.master) ++ ++ result = self.kinit_as_user(self.master, PASSWORD, PASSWORD) ++ ++ for _i in range(0, 10): ++ result = self.master.run_command( ++ ["ldapsearch", "-e", "ppolicy", "-D", dn, ++ "-w", PASSWORD, "-b", dn] ++ ) ++ ++ # With graceperiod disabled it should not increment ++ result = tasks.ldapsearch_dm( ++ self.master, dn, ['passwordgraceusertime',], ++ ) ++ assert 'passwordgraceusertime: 0' in result.stdout_text.lower() +-- +2.37.2 + diff --git a/SOURCES/0010-Set-passwordgracelimit-to-match-global-policy-on-group-pw-policies_rhbz#2115475.patch b/SOURCES/0010-Set-passwordgracelimit-to-match-global-policy-on-group-pw-policies_rhbz#2115475.patch new file mode 100644 index 0000000..952e49b --- /dev/null +++ b/SOURCES/0010-Set-passwordgracelimit-to-match-global-policy-on-group-pw-policies_rhbz#2115475.patch @@ -0,0 +1,230 @@ +From 434620ee342ac4767beccec647a318bfa7743dfa Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 18 Aug 2022 08:21:58 -0400 +Subject: [PATCH] doc: Update LDAP grace period design with default values + +New group password policies will get -1 (unlimited) on creation +by default. + +Existing group password policies will remain untouched and +those created prior will be treated as no BIND allowed. + +Fixes: https://pagure.io/freeipa/issue/9212 + +Signed-off-by: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + doc/designs/ldap_grace_period.md | 17 ++++++++++++++++- + 1 file changed, 16 insertions(+), 1 deletion(-) + +diff --git a/doc/designs/ldap_grace_period.md b/doc/designs/ldap_grace_period.md +index 4b9db3424..e26aedda9 100644 +--- a/doc/designs/ldap_grace_period.md ++++ b/doc/designs/ldap_grace_period.md +@@ -51,7 +51,22 @@ The basic flow is: + + On successful password reset (by anyone) reset the user's passwordGraceUserTime to 0. + +-The default value on install/upgrade will be -1 to retail existing behavior. ++Range values for passwordgracelimit are: ++ ++-1 : password grace checking is disabled ++ 0 : no grace BIND are allowed at all post-expiration ++ 1..MAXINT: the number of BIND allowed post-expiration ++ ++The default value for the global policy on install/upgrade will be -1 to ++retain existing behavior. ++ ++New group password policies will default to -1 to retain previous ++behavior. ++ ++Existing group policies with no grace limit set are updated to use ++the default unlimited value, -1. This is done because lack of value in ++LDAP is treated as 0 so any existing group policies would not allow ++post-expiration BIND so this will avoid confusion. + + The per-user attempts will not be replicated. + +-- +2.37.2 + +From 497a57e7a6872fa30d1855a1d91a455bfdbf9300 Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 4 Aug 2022 12:04:22 -0400 +Subject: [PATCH] Set default gracelimit on group password policies to -1 + +This will retain previous behavior of unlimited LDAP BIND +post-expiration. + +Fixes: https://pagure.io/freeipa/issue/9212 + +Signed-off-by: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + API.txt | 2 +- + ipaserver/plugins/pwpolicy.py | 2 ++ + ipatests/test_xmlrpc/test_pwpolicy_plugin.py | 2 ++ + 3 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/API.txt b/API.txt +index 5ba9add13..d7ea74f08 100644 +--- a/API.txt ++++ b/API.txt +@@ -4075,7 +4075,7 @@ option: Int('krbpwdlockoutduration?', cli_name='lockouttime') + option: Int('krbpwdmaxfailure?', cli_name='maxfail') + option: Int('krbpwdmindiffchars?', cli_name='minclasses') + option: Int('krbpwdminlength?', cli_name='minlength') +-option: Int('passwordgracelimit?', cli_name='gracelimit', default=-1) ++option: Int('passwordgracelimit?', autofill=True, cli_name='gracelimit', default=-1) + option: Flag('raw', autofill=True, cli_name='raw', default=False) + option: Str('setattr*', cli_name='setattr') + option: Str('version?') +diff --git a/ipaserver/plugins/pwpolicy.py b/ipaserver/plugins/pwpolicy.py +index 4428aede2..f4ebffd5c 100644 +--- a/ipaserver/plugins/pwpolicy.py ++++ b/ipaserver/plugins/pwpolicy.py +@@ -408,6 +408,7 @@ class pwpolicy(LDAPObject): + minvalue=-1, + maxvalue=Int.MAX_UINT32, + default=-1, ++ autofill=True, + ), + ) + +@@ -539,6 +540,7 @@ class pwpolicy_add(LDAPCreate): + keys[-1], krbpwdpolicyreference=dn, + cospriority=options.get('cospriority') + ) ++ + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): +diff --git a/ipatests/test_xmlrpc/test_pwpolicy_plugin.py b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py +index 8eee69c18..fc785223b 100644 +--- a/ipatests/test_xmlrpc/test_pwpolicy_plugin.py ++++ b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py +@@ -387,6 +387,7 @@ class test_pwpolicy_mod_cospriority(Declarative): + krbpwdhistorylength=[u'10'], + krbpwdmindiffchars=[u'3'], + krbpwdminlength=[u'8'], ++ passwordgracelimit=[u'-1'], + objectclass=objectclasses.pwpolicy, + ), + summary=None, +@@ -417,6 +418,7 @@ class test_pwpolicy_mod_cospriority(Declarative): + krbpwdhistorylength=[u'10'], + krbpwdmindiffchars=[u'3'], + krbpwdminlength=[u'8'], ++ passwordgracelimit=[u'-1'], + ), + summary=None, + value=u'ipausers', +-- +2.37.2 + +From a4ddaaf3048c4e8d78a1807af7266ee40ab3a30b Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Thu, 4 Aug 2022 12:04:41 -0400 +Subject: [PATCH] Set default on group pwpolicy with no grace limit in upgrade + +If an existing group policy lacks a password grace limit +update it to -1 on upgrade. + +Fixes: https://pagure.io/freeipa/issue/9212 + +Signed-off-by: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud +--- + .../updates/90-post_upgrade_plugins.update | 1 + + ipaserver/install/plugins/update_pwpolicy.py | 66 +++++++++++++++++++ + 2 files changed, 67 insertions(+) + +diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update +index c7ec71d49..6fe91aa6c 100644 +--- a/install/updates/90-post_upgrade_plugins.update ++++ b/install/updates/90-post_upgrade_plugins.update +@@ -26,6 +26,7 @@ plugin: update_ra_cert_store + plugin: update_mapping_Guests_to_nobody + plugin: fix_kra_people_entry + plugin: update_pwpolicy ++plugin: update_pwpolicy_grace + + # last + # DNS version 1 +diff --git a/ipaserver/install/plugins/update_pwpolicy.py b/ipaserver/install/plugins/update_pwpolicy.py +index dca44ce43..4185f0343 100644 +--- a/ipaserver/install/plugins/update_pwpolicy.py ++++ b/ipaserver/install/plugins/update_pwpolicy.py +@@ -78,3 +78,69 @@ class update_pwpolicy(Updater): + return False, [] + + return False, [] ++ ++ ++@register() ++class update_pwpolicy_grace(Updater): ++ """ ++ Ensure all group policies have a grace period set. ++ """ ++ ++ def execute(self, **options): ++ ldap = self.api.Backend.ldap2 ++ ++ base_dn = DN(('cn', self.api.env.realm), ('cn', 'kerberos'), ++ self.api.env.basedn) ++ search_filter = ( ++ "(&(objectClass=krbpwdpolicy)(!(passwordgracelimit=*)))" ++ ) ++ ++ while True: ++ # Run the search in loop to avoid issues when LDAP limits are hit ++ # during update ++ ++ try: ++ (entries, truncated) = ldap.find_entries( ++ search_filter, ['objectclass'], base_dn, time_limit=0, ++ size_limit=0) ++ ++ except errors.EmptyResult: ++ logger.debug("update_pwpolicy: no policies without " ++ "passwordgracelimit set") ++ return False, [] ++ ++ except errors.ExecutionError as e: ++ logger.error("update_pwpolicy: cannot retrieve list " ++ "of policies missing passwordgracelimit: %s", e) ++ return False, [] ++ ++ logger.debug("update_pwpolicy: found %d " ++ "policies to update, truncated: %s", ++ len(entries), truncated) ++ ++ error = False ++ ++ for entry in entries: ++ # Set unlimited BIND by default ++ entry['passwordgracelimit'] = -1 ++ try: ++ ldap.update_entry(entry) ++ except (errors.EmptyModlist, errors.NotFound): ++ pass ++ except errors.ExecutionError as e: ++ logger.debug("update_pwpolicy: cannot " ++ "update policy: %s", e) ++ error = True ++ ++ if error: ++ # Exit loop to avoid infinite cycles ++ logger.error("update_pwpolicy: error(s) " ++ "detected during pwpolicy update") ++ return False, [] ++ ++ elif not truncated: ++ # All affected entries updated, exit the loop ++ logger.debug("update_pwpolicy: all policies updated") ++ return False, [] ++ ++ return False, [] +-- +2.37.2 + diff --git a/SOURCES/0011-fix-canonicalization-issue-in-Web-UI_rhbz#2133050.patch b/SOURCES/0011-fix-canonicalization-issue-in-Web-UI_rhbz#2133050.patch new file mode 100644 index 0000000..4fa0b23 --- /dev/null +++ b/SOURCES/0011-fix-canonicalization-issue-in-Web-UI_rhbz#2133050.patch @@ -0,0 +1,62 @@ +From 109cd579e3b089b7fad4c92bf25594eba1af8a21 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Tue, 23 Aug 2022 16:58:07 +0300 +Subject: [PATCH] fix canonicalization issue in Web UI + +When Kerberos principal alias is used to login to a Web UI, we end up +with a request that is authenticated by a ticket issued in the alias +name but metadata processed for the canonical user name. This confuses +RPC layer of Web UI code and causes infinite loop to reload the page. + +Fix it by doing two things: + + - force use of canonicalization of an enterprise principal on server + side, not just specifying that the principal is an enterprise one; + + - recognize that a principal in the whoami()-returned object can have + aliases and the principal returned by the server in the JSON response + may be one of those aliases. + +Fixes: https://pagure.io/freeipa/issue/9226 + +Signed-off-by: Alexander Bokovoy +Reviewed-By: Armando Neto +--- + install/ui/src/freeipa/ipa.js | 8 +++++++- + ipaserver/rpcserver.py | 1 + + 2 files changed, 8 insertions(+), 1 deletion(-) + +diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js +index 758db1b00..a08d632e9 100644 +--- a/install/ui/src/freeipa/ipa.js ++++ b/install/ui/src/freeipa/ipa.js +@@ -271,7 +271,13 @@ var IPA = function () { + var cn = that.whoami.data.krbcanonicalname; + if (cn) that.principal = cn[0]; + if (!that.principal) { +- that.principal = that.whoami.data.krbprincipalname[0]; ++ var principal = data.principal; ++ var idx = that.whoami.data.krbprincipalname.indexOf(principal); ++ if (idx > -1) { ++ that.principal = principal; ++ } else { ++ that.principal = that.whoami.data.krbprincipalname[0]; ++ } + } + } else if (entity === 'idoverrideuser') { + that.principal = that.whoami.data.ipaoriginaluid[0]; +diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py +index 1f85e9898..4e8a08b66 100644 +--- a/ipaserver/rpcserver.py ++++ b/ipaserver/rpcserver.py +@@ -1109,6 +1109,7 @@ class login_password(Backend, KerberosSession): + ccache_name, + armor_ccache_name=armor_path, + enterprise=True, ++ canonicalize=True, + lifetime=self.api.env.kinit_lifetime) + + if armor_path: +-- +2.37.3 + diff --git a/SOURCES/0012-Defer-creating-the-final-krb5-conf-on-clients_rhbz#2150246.patch b/SOURCES/0012-Defer-creating-the-final-krb5-conf-on-clients_rhbz#2150246.patch new file mode 100644 index 0000000..30114a5 --- /dev/null +++ b/SOURCES/0012-Defer-creating-the-final-krb5-conf-on-clients_rhbz#2150246.patch @@ -0,0 +1,473 @@ +From 69413325158a3ea06d1491acd77ee6e0955ee89a Mon Sep 17 00:00:00 2001 +From: Rob Crittenden +Date: Sep 26 2022 11:48:47 +0000 +Subject: Defer creating the final krb5.conf on clients + + +A temporary krb5.conf is created early during client enrollment +and was previously used only during the initial ipa-join call. +The final krb5.conf was written soon afterward. + +If there are multiple servers it is possible that the client +may then choose a different KDC to connect. If the client +is faster than replication then the client may not exist +on all servers and therefore enrollment will fail. + +This was seen in performance testing of how many simultaneous +client enrollments are possible. + +Use a decorator to wrap the _install() method to ensure the +temporary files created during installation are cleaned up. + +https://pagure.io/freeipa/issue/9228 + +Signed-off-by: Rob Crittenden +Reviewed-By: Florence Blanc-Renaud + +--- + +diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py +index 920c517..93bc740 100644 +--- a/ipaclient/install/client.py ++++ b/ipaclient/install/client.py +@@ -101,6 +101,37 @@ cli_basedn = None + # end of global variables + + ++def cleanup(func): ++ def inner(options, tdict): ++ # Add some additional options which contain the temporary files ++ # needed during installation. ++ fd, krb_name = tempfile.mkstemp() ++ os.close(fd) ++ ccache_dir = tempfile.mkdtemp(prefix='krbcc') ++ ++ tdict['krb_name'] = krb_name ++ tdict['ccache_dir'] = ccache_dir ++ ++ func(options, tdict) ++ ++ os.environ.pop('KRB5_CONFIG', None) ++ ++ try: ++ os.remove(krb_name) ++ except OSError: ++ logger.error("Could not remove %s", krb_name) ++ try: ++ os.rmdir(ccache_dir) ++ except OSError: ++ pass ++ try: ++ os.remove(krb_name + ".ipabkp") ++ except OSError: ++ logger.error("Could not remove %s.ipabkp", krb_name) ++ ++ return inner ++ ++ + def remove_file(filename): + """ + Deletes a file. If the file does not exist (OSError 2) does nothing. +@@ -2652,7 +2683,7 @@ def restore_time_sync(statestore, fstore): + + def install(options): + try: +- _install(options) ++ _install(options, dict()) + except ScriptError as e: + if e.rval == CLIENT_INSTALL_ERROR: + if options.force: +@@ -2679,7 +2710,8 @@ def install(options): + pass + + +-def _install(options): ++@cleanup ++def _install(options, tdict): + env = {'PATH': SECURE_PATH} + + fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) +@@ -2687,6 +2719,9 @@ def _install(options): + + statestore.backup_state('installation', 'complete', False) + ++ krb_name = tdict['krb_name'] ++ ccache_dir = tdict['ccache_dir'] ++ + if not options.on_master: + # Try removing old principals from the keytab + purge_host_keytab(cli_realm) +@@ -2719,182 +2754,162 @@ def _install(options): + host_principal = 'host/%s@%s' % (hostname, cli_realm) + if not options.on_master: + nolog = tuple() +- # First test out the kerberos configuration +- fd, krb_name = tempfile.mkstemp() +- os.close(fd) +- ccache_dir = tempfile.mkdtemp(prefix='krbcc') +- try: +- configure_krb5_conf( +- cli_realm=cli_realm, +- cli_domain=cli_domain, +- cli_server=cli_server, +- cli_kdc=cli_kdc, +- dnsok=False, +- filename=krb_name, +- client_domain=client_domain, +- client_hostname=hostname, +- configure_sssd=options.sssd, +- force=options.force) +- env['KRB5_CONFIG'] = krb_name +- ccache_name = os.path.join(ccache_dir, 'ccache') +- join_args = [ +- paths.SBIN_IPA_JOIN, +- "-s", cli_server[0], +- "-b", str(realm_to_suffix(cli_realm)), +- "-h", hostname, +- "-k", paths.KRB5_KEYTAB +- ] +- if options.debug: +- join_args.append("-d") +- env['XMLRPC_TRACE_CURL'] = 'yes' +- if options.force_join: +- join_args.append("-f") +- if options.principal is not None: +- stdin = None +- principal = options.principal +- if principal.find('@') == -1: +- principal = '%s@%s' % (principal, cli_realm) +- if options.password is not None: +- stdin = options.password ++ configure_krb5_conf( ++ cli_realm=cli_realm, ++ cli_domain=cli_domain, ++ cli_server=cli_server, ++ cli_kdc=cli_kdc, ++ dnsok=False, ++ filename=krb_name, ++ client_domain=client_domain, ++ client_hostname=hostname, ++ configure_sssd=options.sssd, ++ force=options.force) ++ env['KRB5_CONFIG'] = krb_name ++ ccache_name = os.path.join(ccache_dir, 'ccache') ++ join_args = [ ++ paths.SBIN_IPA_JOIN, ++ "-s", cli_server[0], ++ "-b", str(realm_to_suffix(cli_realm)), ++ "-h", hostname, ++ "-k", paths.KRB5_KEYTAB ++ ] ++ if options.debug: ++ join_args.append("-d") ++ env['XMLRPC_TRACE_CURL'] = 'yes' ++ if options.force_join: ++ join_args.append("-f") ++ if options.principal is not None: ++ stdin = None ++ principal = options.principal ++ if principal.find('@') == -1: ++ principal = '%s@%s' % (principal, cli_realm) ++ if options.password is not None: ++ stdin = options.password ++ else: ++ if not options.unattended: ++ try: ++ stdin = getpass.getpass( ++ "Password for %s: " % principal) ++ except EOFError: ++ stdin = None ++ if not stdin: ++ raise ScriptError( ++ "Password must be provided for {}.".format( ++ principal), ++ rval=CLIENT_INSTALL_ERROR) + else: +- if not options.unattended: +- try: +- stdin = getpass.getpass( +- "Password for %s: " % principal) +- except EOFError: +- stdin = None +- if not stdin: +- raise ScriptError( +- "Password must be provided for {}.".format( +- principal), +- rval=CLIENT_INSTALL_ERROR) ++ if sys.stdin.isatty(): ++ logger.error( ++ "Password must be provided in " ++ "non-interactive mode.") ++ logger.info( ++ "This can be done via " ++ "echo password | ipa-client-install ... " ++ "or with the -w option.") ++ raise ScriptError(rval=CLIENT_INSTALL_ERROR) + else: +- if sys.stdin.isatty(): +- logger.error( +- "Password must be provided in " +- "non-interactive mode.") +- logger.info( +- "This can be done via " +- "echo password | ipa-client-install ... " +- "or with the -w option.") +- raise ScriptError(rval=CLIENT_INSTALL_ERROR) +- else: +- stdin = sys.stdin.readline() ++ stdin = sys.stdin.readline() + ++ try: ++ kinit_password(principal, stdin, ccache_name, ++ config=krb_name) ++ except RuntimeError as e: ++ print_port_conf_info() ++ raise ScriptError( ++ "Kerberos authentication failed: {}".format(e), ++ rval=CLIENT_INSTALL_ERROR) ++ elif options.keytab: ++ join_args.append("-f") ++ if os.path.exists(options.keytab): + try: +- kinit_password(principal, stdin, ccache_name, +- config=krb_name) +- except RuntimeError as e: ++ kinit_keytab(host_principal, ++ options.keytab, ++ ccache_name, ++ config=krb_name, ++ attempts=options.kinit_attempts) ++ except gssapi.exceptions.GSSError as e: + print_port_conf_info() + raise ScriptError( + "Kerberos authentication failed: {}".format(e), + rval=CLIENT_INSTALL_ERROR) +- elif options.keytab: +- join_args.append("-f") +- if os.path.exists(options.keytab): +- try: +- kinit_keytab(host_principal, +- options.keytab, +- ccache_name, +- config=krb_name, +- attempts=options.kinit_attempts) +- except gssapi.exceptions.GSSError as e: +- print_port_conf_info() +- raise ScriptError( +- "Kerberos authentication failed: {}".format(e), +- rval=CLIENT_INSTALL_ERROR) +- else: +- raise ScriptError( +- "Keytab file could not be found: {}".format( +- options.keytab), +- rval=CLIENT_INSTALL_ERROR) +- elif options.password: +- nolog = (options.password,) +- join_args.append("-w") +- join_args.append(options.password) +- elif options.prompt_password: +- if options.unattended: +- raise ScriptError( +- "Password must be provided in non-interactive mode", +- rval=CLIENT_INSTALL_ERROR) +- try: +- password = getpass.getpass("Password: ") +- except EOFError: +- password = None +- if not password: +- raise ScriptError( +- "Password must be provided.", +- rval=CLIENT_INSTALL_ERROR) +- join_args.append("-w") +- join_args.append(password) +- nolog = (password,) +- +- env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = ccache_name +- # Get the CA certificate ++ else: ++ raise ScriptError( ++ "Keytab file could not be found: {}".format( ++ options.keytab), ++ rval=CLIENT_INSTALL_ERROR) ++ elif options.password: ++ nolog = (options.password,) ++ join_args.append("-w") ++ join_args.append(options.password) ++ elif options.prompt_password: ++ if options.unattended: ++ raise ScriptError( ++ "Password must be provided in non-interactive mode", ++ rval=CLIENT_INSTALL_ERROR) + try: +- os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG'] +- get_ca_certs(fstore, options, cli_server[0], cli_basedn, +- cli_realm) +- del os.environ['KRB5_CONFIG'] +- except errors.FileError as e: +- logger.error('%s', e) +- raise ScriptError(rval=CLIENT_INSTALL_ERROR) +- except Exception as e: +- logger.error("Cannot obtain CA certificate\n%s", e) +- raise ScriptError(rval=CLIENT_INSTALL_ERROR) +- +- # Now join the domain +- result = run( +- join_args, raiseonerr=False, env=env, nolog=nolog, +- capture_error=True) +- stderr = result.error_output ++ password = getpass.getpass("Password: ") ++ except EOFError: ++ password = None ++ if not password: ++ raise ScriptError( ++ "Password must be provided.", ++ rval=CLIENT_INSTALL_ERROR) ++ join_args.append("-w") ++ join_args.append(password) ++ nolog = (password,) + +- if result.returncode != 0: +- logger.error("Joining realm failed: %s", stderr) +- if not options.force: +- if result.returncode == 13: +- logger.info( +- "Use --force-join option to override the host " +- "entry on the server and force client enrollment.") +- raise ScriptError(rval=CLIENT_INSTALL_ERROR) +- logger.info( +- "Use ipa-getkeytab to obtain a host " +- "principal for this server.") +- else: +- logger.info("Enrolled in IPA realm %s", cli_realm) ++ env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = ccache_name ++ # Get the CA certificate ++ try: ++ os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG'] ++ get_ca_certs(fstore, options, cli_server[0], cli_basedn, ++ cli_realm) ++ except errors.FileError as e: ++ logger.error('%s', e) ++ raise ScriptError(rval=CLIENT_INSTALL_ERROR) ++ except Exception as e: ++ logger.error("Cannot obtain CA certificate\n%s", e) ++ raise ScriptError(rval=CLIENT_INSTALL_ERROR) + +- if options.principal is not None: +- run([paths.KDESTROY], raiseonerr=False, env=env) ++ # Now join the domain ++ result = run( ++ join_args, raiseonerr=False, env=env, nolog=nolog, ++ capture_error=True) ++ stderr = result.error_output + +- # Obtain the TGT. We do it with the temporary krb5.conf, so that +- # only the KDC we're installing under is contacted. +- # Other KDCs might not have replicated the principal yet. +- # Once we have the TGT, it's usable on any server. +- try: +- kinit_keytab(host_principal, paths.KRB5_KEYTAB, CCACHE_FILE, +- config=krb_name, +- attempts=options.kinit_attempts) +- env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = CCACHE_FILE +- except gssapi.exceptions.GSSError as e: +- print_port_conf_info() +- logger.error("Failed to obtain host TGT: %s", e) +- # failure to get ticket makes it impossible to login and bind +- # from sssd to LDAP, abort installation and rollback changes ++ if result.returncode != 0: ++ logger.error("Joining realm failed: %s", stderr) ++ if not options.force: ++ if result.returncode == 13: ++ logger.info( ++ "Use --force-join option to override the host " ++ "entry on the server and force client enrollment.") + raise ScriptError(rval=CLIENT_INSTALL_ERROR) ++ logger.info( ++ "Use ipa-getkeytab to obtain a host " ++ "principal for this server.") ++ else: ++ logger.info("Enrolled in IPA realm %s", cli_realm) + +- finally: +- try: +- os.remove(krb_name) +- except OSError: +- logger.error("Could not remove %s", krb_name) +- try: +- os.rmdir(ccache_dir) +- except OSError: +- pass +- try: +- os.remove(krb_name + ".ipabkp") +- except OSError: +- logger.error("Could not remove %s.ipabkp", krb_name) ++ if options.principal is not None: ++ run([paths.KDESTROY], raiseonerr=False, env=env) ++ ++ # Obtain the TGT. We do it with the temporary krb5.conf, so that ++ # only the KDC we're installing under is contacted. ++ # Other KDCs might not have replicated the principal yet. ++ # Once we have the TGT, it's usable on any server. ++ try: ++ kinit_keytab(host_principal, paths.KRB5_KEYTAB, CCACHE_FILE, ++ config=krb_name, ++ attempts=options.kinit_attempts) ++ env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = CCACHE_FILE ++ except gssapi.exceptions.GSSError as e: ++ print_port_conf_info() ++ logger.error("Failed to obtain host TGT: %s", e) ++ # failure to get ticket makes it impossible to login and bind ++ # from sssd to LDAP, abort installation and rollback changes ++ raise ScriptError(rval=CLIENT_INSTALL_ERROR) + + # Configure ipa.conf + if not options.on_master: +@@ -2931,23 +2946,6 @@ def _install(options): + except gssapi.exceptions.GSSError as e: + logger.error("Failed to obtain host TGT: %s", e) + raise ScriptError(rval=CLIENT_INSTALL_ERROR) +- else: +- # Configure krb5.conf +- fstore.backup_file(paths.KRB5_CONF) +- configure_krb5_conf( +- cli_realm=cli_realm, +- cli_domain=cli_domain, +- cli_server=cli_server, +- cli_kdc=cli_kdc, +- dnsok=dnsok, +- filename=paths.KRB5_CONF, +- client_domain=client_domain, +- client_hostname=hostname, +- configure_sssd=options.sssd, +- force=options.force) +- +- logger.info( +- "Configured /etc/krb5.conf for IPA realm %s", cli_realm) + + # Clear out any current session keyring information + try: +@@ -3274,6 +3272,23 @@ def _install(options): + configure_nisdomain( + options=options, domain=cli_domain, statestore=statestore) + ++ # Configure the final krb5.conf ++ if not options.on_master: ++ fstore.backup_file(paths.KRB5_CONF) ++ configure_krb5_conf( ++ cli_realm=cli_realm, ++ cli_domain=cli_domain, ++ cli_server=cli_server, ++ cli_kdc=cli_kdc, ++ dnsok=dnsok, ++ filename=paths.KRB5_CONF, ++ client_domain=client_domain, ++ client_hostname=hostname, ++ configure_sssd=options.sssd, ++ force=options.force) ++ ++ logger.info("Configured /etc/krb5.conf for IPA realm %s", cli_realm) ++ + statestore.delete_state('installation', 'complete') + statestore.backup_state('installation', 'complete', True) + logger.info('Client configuration complete.') + diff --git a/SOURCES/0013-Vault-fix-interoperability-issues-with-older-RHEL-systems_rhbz#2148255.patch b/SOURCES/0013-Vault-fix-interoperability-issues-with-older-RHEL-systems_rhbz#2148255.patch new file mode 100644 index 0000000..f8c55fe --- /dev/null +++ b/SOURCES/0013-Vault-fix-interoperability-issues-with-older-RHEL-systems_rhbz#2148255.patch @@ -0,0 +1,123 @@ +From c643e56e4c45b7cb61aa53989657143627c23e04 Mon Sep 17 00:00:00 2001 +From: Francisco Trivino +Date: Nov 22 2022 06:56:00 +0000 +Subject: Vault: fix interoperability issues with older RHEL systems + + +AES-128-CBC was recently enabled as default wrapping algorithm for transport of secrets. +This change was done in favor of FIPS as crypto-policies disabled 3DES in RHEL9, but +setting AES as default ended-up breaking backwards compatibility with older RHEL systems. + +This commit is tuning some defaults so that interoperability with older RHEL systems +works again. The new logic reflects: + +- when an old client is calling a new server, it doesn't send any value for wrapping_algo + and the old value is used (3DES), so that the client can decrypt using 3DES. + +- when a new client is calling a new server, it sends wrapping_algo = AES128_CBC + +- when a new client is calling an old server, it doesn't send any value and the default is + to use 3DES. + +Finally, as this logic is able to handle overlapping wrapping algorithm between server and +client, the Option "--wrapping-algo" is hidden from "ipa vault-archive --help" and "ipa +vault-retrieve --help" commands. + +Fixes: https://pagure.io/freeipa/issue/9259 +Signed-off-by: Francisco Trivino +Reviewed-By: Florence Blanc-Renaud +Reviewed-By: Rob Crittenden + +--- + +diff --git a/API.txt b/API.txt +index 9892211..2bd1cc2 100644 +--- a/API.txt ++++ b/API.txt +@@ -6666,7 +6666,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'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) ++option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +@@ -6766,7 +6766,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'aes-128-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) ++option: StrEnum('wrapping_algo?', autofill=True, default=u'des-ede3-cbc', values=[u'aes-128-cbc', u'des-ede3-cbc']) + output: Entry('result') + output: Output('summary', type=[, ]) + output: PrimaryKey('value') +diff --git a/VERSION.m4 b/VERSION.m4 +index 7d60b01..b4b1774 100644 +--- a/VERSION.m4 ++++ b/VERSION.m4 +@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) + # # + ######################################################## + define(IPA_API_VERSION_MAJOR, 2) +-# Last change: add graceperiodlimit +-define(IPA_API_VERSION_MINOR, 248) ++# Last change: fix vault interoperability issues. ++define(IPA_API_VERSION_MINOR, 251) + + ######################################################## + # Following values are auto-generated from values above +diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py +index 115171c..d4c84eb 100644 +--- a/ipaclient/plugins/vault.py ++++ b/ipaclient/plugins/vault.py +@@ -687,7 +687,7 @@ class ModVaultData(Local): + default_algo = config.get('wrapping_default_algorithm') + if default_algo is None: + # old server +- wrapping_algo = constants.VAULT_WRAPPING_AES128_CBC ++ wrapping_algo = constants.VAULT_WRAPPING_3DES + elif default_algo in constants.VAULT_WRAPPING_SUPPORTED_ALGOS: + # try to use server default + wrapping_algo = default_algo +@@ -801,7 +801,8 @@ class vault_archive(ModVaultData): + if option.name not in ('nonce', + 'session_key', + 'vault_data', +- 'version'): ++ 'version', ++ 'wrapping_algo'): + yield option + for option in super(vault_archive, self).get_options(): + yield option +@@ -1053,7 +1054,7 @@ class vault_retrieve(ModVaultData): + + def get_options(self): + for option in self.api.Command.vault_retrieve_internal.options(): +- if option.name not in ('session_key', 'version'): ++ if option.name not in ('session_key', 'version', 'wrapping_algo'): + yield option + for option in super(vault_retrieve, self).get_options(): + yield option +diff --git a/ipaserver/plugins/vault.py b/ipaserver/plugins/vault.py +index 4d40f66..574c83a 100644 +--- a/ipaserver/plugins/vault.py ++++ b/ipaserver/plugins/vault.py +@@ -1051,7 +1051,7 @@ class vault_archive_internal(PKQuery): + 'wrapping_algo?', + doc=_('Key wrapping algorithm'), + values=VAULT_WRAPPING_SUPPORTED_ALGOS, +- default=VAULT_WRAPPING_DEFAULT_ALGO, ++ default=VAULT_WRAPPING_3DES, + autofill=True, + ), + ) +@@ -1130,7 +1130,7 @@ class vault_retrieve_internal(PKQuery): + 'wrapping_algo?', + doc=_('Key wrapping algorithm'), + values=VAULT_WRAPPING_SUPPORTED_ALGOS, +- default=VAULT_WRAPPING_DEFAULT_ALGO, ++ default=VAULT_WRAPPING_3DES, + autofill=True, + ), + ) diff --git a/SPECS/ipa.spec b/SPECS/ipa.spec index d53f198..83173c6 100644 --- a/SPECS/ipa.spec +++ b/SPECS/ipa.spec @@ -191,7 +191,7 @@ Name: %{package_name} Version: %{IPA_VERSION} -Release: 3%{?rc_version:.%rc_version}%{?dist} +Release: 9%{?rc_version:.%rc_version}%{?dist} Summary: The Identity, Policy and Audit system License: GPLv3+ @@ -215,6 +215,15 @@ Patch0001: 0001-ipa-otpd-Fix-build-on-older-versions-of-gcc.patch Patch0002: 0002-webui-IdP-Remove-arrow-notation-due-to-uglify-js-lim.patch Patch0003: 0003-Preserve-user-fix-the-confusing-summary_rhbz#2022028.patch Patch0004: 0004-Only-calculate-LDAP-password-grace-when-the-password_rhbz#782917.patch +Patch0005: 0005-Add-end-to-end-integration-tests-for-external-IdP.patch +Patch0006: 0006-webui-Do-not-allow-empty-pagination-size_rhbz#2094672.patch +Patch0007: 0007-webui-Allow-grace-login-limit_rhbz#2109243.patch +Patch0008: 0008-check_repl_update-in-progress-is-a-boolean_rhbz#2117303.patch +Patch0009: 0009-Disabling-gracelimit-does-not-prevent-LDAP-binds_rhbz#2109236.patch +Patch0010: 0010-Set-passwordgracelimit-to-match-global-policy-on-group-pw-policies_rhbz#2115475.patch +Patch0011: 0011-fix-canonicalization-issue-in-Web-UI_rhbz#2133050.patch +Patch0012: 0012-Defer-creating-the-final-krb5-conf-on-clients_rhbz#2150246.patch +Patch0013: 0013-Vault-fix-interoperability-issues-with-older-RHEL-systems_rhbz#2148255.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch Patch1002: 1002-Revert-freeipa.spec-depend-on-bind-dnssec-utils.patch %endif @@ -1712,11 +1721,43 @@ fi %if %{with selinux} %files selinux %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.* -%ghost %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} +%ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} # with selinux %endif %changelog +* Mon Dec 5 2022 Rafael Jeffman - 4.9.10-9 +- Exclude installed policy module file from RPM verification + Resolves: RHBZ#2150243 + +* Fri Dec 2 2022 Rafael Jeffman - 4.9.10-8 +- Defer creating the final krb5.conf on clients + Resolves: RHBZ#2150246 +- Vault: fix interoperability issues with older RHEL systems + Resolves: RHBZ#2148255 + +* Thu Oct 13 2022 Rafael Jeffman - 4.9.10-7 +- Fix canonicalization issue in Web UI + Resolves: RHBZ#2133050 + +* Mon Aug 22 2022 Rafael Jeffman - 4.9.10-6 +- webui: Allow grace login limit + Resolves: RHBZ#2109243 +- check_repl_update: in progress is a boolean + Resolves: RHBZ#2117303 +- Disabling gracelimit does not prevent LDAP binds + Resolves: RHBZ#2109236 +- Set passwordgracelimit to match global policy on group pw policies + Resolves: RHBZ#2115475 + +* Tue Jul 19 2022 Rafael Jeffman - 4.9.10-5 +- webui: Do not allow empty pagination size + Resolves: RHBZ#2094672 + +* Tue Jul 12 2022 Rafael Jeffman - 4.9.10-4 +- Add end to end integration tests for external IdP + Resolves: RHBZ#2106346 + * Thu Jul 07 2022 Rafael Jeffman - 4.9.10-3 - Add explicit dependency for libvert-libev Resolves: RHBZ#2104929