From 4128cf0f3799078d062c250efd6d2988a52dc7d6 Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Mon, 24 Aug 2026 09:11:15 -0400 Subject: [PATCH] import CS ipa-4.13.2-2.el9 --- .gitignore | 2 +- .ipa.metadata | 2 +- ...003-Revert-Remove-NIS-server-support.patch | 20 +- .../0004-Change-u-to-u-for-sysusersd.patch | 31 + ...-Stop-using-deprecated-pkg_resources.patch | 21 +- ...pire_password-fixture-into-TestIPACo.patch | 120 -- ...-Fix-xfail-assertion-for-sssd-2.12.0.patch | 48 - ...Add-DNS-functional-integration-tests.patch | 1279 ----------------- ...-card-client-script-does-not-need-kr.patch | 99 -- ...olver-state-tracking-in-enforced-DNS.patch | 177 --- ...Use-systemd-sysusers-to-setup-users-.patch | 224 --- ...dom-Password-based-replica-promotion.patch | 196 --- ...tegration-tests-for-ipa-join-command.patch | 760 ---------- ...e-case-insensitive-comparison-for-do.patch | 48 - ...andle-IPACertificate-types-in-xmlrpc.patch | 43 - ...place-None-with-when-uninstalling-CA.patch | 159 -- ...-xmlrpc-tests-for-ipa-delegation-cli.patch | 416 ------ .../0019-ipa-join-initialize-pointer.patch | 49 - ...uning-is-enabled-when-RSN-is-enabled.patch | 39 - SOURCES/freeipa-4.13.1.tar.gz.asc | 16 - SOURCES/freeipa-4.13.2.tar.gz.asc | 16 + SPECS/freeipa.spec | 85 +- 22 files changed, 123 insertions(+), 3727 deletions(-) create mode 100644 SOURCES/0004-Change-u-to-u-for-sysusersd.patch delete mode 100644 SOURCES/0007-ipatests-Move-expire_password-fixture-into-TestIPACo.patch delete mode 100644 SOURCES/0008-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch delete mode 100644 SOURCES/0009-ipatests-Add-DNS-functional-integration-tests.patch delete mode 100644 SOURCES/0010-ipa-advise-smart-card-client-script-does-not-need-kr.patch delete mode 100644 SOURCES/0011-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch delete mode 100644 SOURCES/0012-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch delete mode 100644 SOURCES/0013-ipatests-add-Random-Password-based-replica-promotion.patch delete mode 100644 SOURCES/0014-ipatests-Add-integration-tests-for-ipa-join-command.patch delete mode 100644 SOURCES/0015-fetch_domains-Use-case-insensitive-comparison-for-do.patch delete mode 100644 SOURCES/0016-Handle-IPACertificate-types-in-xmlrpc.patch delete mode 100644 SOURCES/0017-Replace-None-with-when-uninstalling-CA.patch delete mode 100644 SOURCES/0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch delete mode 100644 SOURCES/0019-ipa-join-initialize-pointer.patch delete mode 100644 SOURCES/0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch delete mode 100644 SOURCES/freeipa-4.13.1.tar.gz.asc create mode 100644 SOURCES/freeipa-4.13.2.tar.gz.asc diff --git a/.gitignore b/.gitignore index 36ad350..a0e598e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/freeipa-4.13.1.tar.gz +SOURCES/freeipa-4.13.2.tar.gz diff --git a/.ipa.metadata b/.ipa.metadata index debf4c5..35684f3 100644 --- a/.ipa.metadata +++ b/.ipa.metadata @@ -1 +1 @@ -bc82618e66b45376464cd2206bf6e7a02f766a11 SOURCES/freeipa-4.13.1.tar.gz +1b1aa6194b4d2cc3efdcc2e8aaa1e5063c6afe3b SOURCES/freeipa-4.13.2.tar.gz diff --git a/SOURCES/0003-Revert-Remove-NIS-server-support.patch b/SOURCES/0003-Revert-Remove-NIS-server-support.patch index fb9b690..63976ba 100644 --- a/SOURCES/0003-Revert-Remove-NIS-server-support.patch +++ b/SOURCES/0003-Revert-Remove-NIS-server-support.patch @@ -32,10 +32,10 @@ This reverts commit e98a80827bcc42dc16b516355077fed844220107. create mode 100644 ipaserver/install/plugins/update_nis.py diff --git a/freeipa.spec.in b/freeipa.spec.in -index b93199a5783a89e0ff58affd3d416556f72dd00c..6ee0f43f059dad4bba2288b1b0d3a63437371861 100644 +index 36cf4f185c5d269b485e08a57641c33986876daa..42bcd57e64b6d8de3ac457e9d2faa4925d29250c 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in -@@ -1619,6 +1619,7 @@ fi +@@ -1607,6 +1607,7 @@ fi %{_sbindir}/ipa-ldap-updater %{_sbindir}/ipa-otptoken-import %{_sbindir}/ipa-compat-manage @@ -43,7 +43,7 @@ index b93199a5783a89e0ff58affd3d416556f72dd00c..6ee0f43f059dad4bba2288b1b0d3a634 %{_sbindir}/ipa-managed-entries %{_sbindir}/ipactl %{_sbindir}/ipa-advise -@@ -1694,6 +1695,7 @@ fi +@@ -1682,6 +1683,7 @@ fi %{_mandir}/man1/ipa-ca-install.1* %{_mandir}/man1/ipa-kra-install.1* %{_mandir}/man1/ipa-compat-manage.1* @@ -591,7 +591,7 @@ index 5bc0e3ac3eed257fd7fa6a0830e3e2dbeb3d01d2..b84925bfc7d9af8584d9d4b185aa04b6 SCHEMA_COMPAT_POST_ULDIF = "/usr/share/ipa/schema_compat_post.uldif" SUBID_GENERATORS_ULDIF = "/usr/share/ipa/subid-generators.uldif" diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py -index f07a878e739002346df894f93745ea60345e0557..b0a27de5dd40aa661271faa3a8a0855226b5892f 100644 +index 9f909a2e70ed7f7b7bf52001950c6a504f78f3cd..126a3c7618c4dd2a5a0b7ee6056a1d31c3a2c3da 100644 --- a/ipaserver/install/ipa_migrate.py +++ b/ipaserver/install/ipa_migrate.py @@ -32,7 +32,7 @@ from ipapython.admintool import admin_cleanup_global_argv @@ -600,7 +600,7 @@ index f07a878e739002346df894f93745ea60345e0557..b0a27de5dd40aa661271faa3a8a08552 STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS, - DNA_REGEN_VAL, DNA_REGEN_ATTRS, IGNORE_ATTRS, + DNA_REGEN_VAL, DNA_REGEN_ATTRS, NIS_PLUGIN, IGNORE_ATTRS, - DB_EXCLUDE_TREES, POLICY_OP_ATTRS, STATE_OPTIONS + DB_EXCLUDE_TREES, POLICY_OP_ATTRS, STATE_OPTIONS, REDACTED_ATTRS, ) @@ -749,7 +749,8 @@ class IPAMigrate(): @@ -613,7 +613,7 @@ index f07a878e739002346df894f93745ea60345e0557..b0a27de5dd40aa661271faa3a8a08552 logged_something = True if not self.log_stats(DS_INDEXES) and not logged_something: self.log_info(" - No updates") -@@ -1983,6 +1984,28 @@ class IPAMigrate(): +@@ -2063,6 +2064,28 @@ class IPAMigrate(): add_missing=True) stats['config_processed'] += 1 @@ -643,10 +643,10 @@ index f07a878e739002346df894f93745ea60345e0557..b0a27de5dd40aa661271faa3a8a08552 # Migration # diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py -index 65e920d4bdc7511adb60bbdf87db88beb2630645..4fe8b467978936e4f8d621b7297ff577127c7298 100644 +index 64dd11cd1244ee30a2eceea3ef823e9092035738..e2f97e6d38c4d47553b854e29b8e0e8903477639 100644 --- a/ipaserver/install/ipa_migrate_constants.py +++ b/ipaserver/install/ipa_migrate_constants.py -@@ -527,6 +527,30 @@ DS_CONFIG = { +@@ -542,6 +542,30 @@ DS_CONFIG = { }, } @@ -788,10 +788,10 @@ index 6c86bbb657a0d9a7b74ef34ad20a796a10073315..4acf8dd5290c687723a9c43a21e194bf (['ipa-replica-manage', 'list'], 1, IPA_NOT_CONFIGURED, None), (['ipa-server-certinstall'], 2, None, IPA_NOT_CONFIGURED), diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py -index 5bc871ab71bc05ec1c26df8352e996a9e627b466..8f8548494eaec3afd00961c096772b9d0b1ab3a4 100644 +index 5021747b67e0b97d322b13261f7e90e688f50cfe..bcd4e2aeb30f45b2a5f2713d213f077b571b6191 100644 --- a/ipatests/test_integration/test_commands.py +++ b/ipatests/test_integration/test_commands.py -@@ -1382,6 +1382,93 @@ class TestIPACommand(IntegrationTest): +@@ -1480,6 +1480,93 @@ class TestIPACommand(IntegrationTest): serverid = realm_to_serverid(self.master.domain.realm) return ("dirsrv@%s.service" % serverid) diff --git a/SOURCES/0004-Change-u-to-u-for-sysusersd.patch b/SOURCES/0004-Change-u-to-u-for-sysusersd.patch new file mode 100644 index 0000000..5d8d7e5 --- /dev/null +++ b/SOURCES/0004-Change-u-to-u-for-sysusersd.patch @@ -0,0 +1,31 @@ +From c1f9626a4b2b42cade05f83bd5699418e60767a4 Mon Sep 17 00:00:00 2001 +From: David Hanina +Date: Tue, 21 Jul 2026 15:10:49 +0200 +Subject: [PATCH] Change u! to u for sysusersd + +Adapts the sysusersd settings for RHEL9 to only use u, instead of u!, +which errors out. + +Signed-off-by: David Hanina +--- + init/sysusersd/ipa.conf.in | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/init/sysusersd/ipa.conf.in b/init/sysusersd/ipa.conf.in +index dcddfc2fc..0955fd341 100644 +--- a/init/sysusersd/ipa.conf.in ++++ b/init/sysusersd/ipa.conf.in +@@ -1,8 +1,8 @@ + # IPA KDC Proxy user and group +-u! kdcproxy - "IPA KDC Proxy User" ++u kdcproxy - "IPA KDC Proxy User" + + # IPA API user and group +-u! ipaapi - "IPA API User" ++u ipaapi - "IPA API User" + + # - add Apache system account to ipaapi group (platform-specific) + m @HTTPD_USER@ ipaapi +-- +2.54.0 + diff --git a/SOURCES/0006-Revert-Stop-using-deprecated-pkg_resources.patch b/SOURCES/0006-Revert-Stop-using-deprecated-pkg_resources.patch index a21b23c..2b16861 100644 --- a/SOURCES/0006-Revert-Stop-using-deprecated-pkg_resources.patch +++ b/SOURCES/0006-Revert-Stop-using-deprecated-pkg_resources.patch @@ -29,7 +29,7 @@ diff --git a/freeipa.spec.in b/freeipa.spec.in index b93199a5783a89e0ff58affd3d416556f72dd00c..bf6fa216dcc4cfbac903aad43c6219f1e57db4dd 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in -@@ -983,7 +983,6 @@ Requires: python3-jwcrypto >= 0.4.2 +@@ -977,7 +977,6 @@ Requires: python3-jwcrypto >= 0.4.2 Requires: python3-libipa_hbac Requires: python3-netaddr >= %{python_netaddr_version} Requires: python3-netifaces >= 0.10.4 @@ -37,7 +37,7 @@ index b93199a5783a89e0ff58affd3d416556f72dd00c..bf6fa216dcc4cfbac903aad43c6219f1 Requires: python3-pyasn1 >= 0.3.2-2 Requires: python3-pyasn1-modules >= 0.3.2-2 Requires: python3-pyusb -@@ -992,6 +991,11 @@ Requires: python3-requests +@@ -986,6 +985,11 @@ Requires: python3-requests Requires: python3-six Requires: python3-sss-murmur Requires: python3-yubico >= 1.3.2-7 @@ -269,15 +269,14 @@ diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/t index 9983b3c9d62826afa395c2739b4fb9945afed80d..a29333ef232b0ae8229484e404866d8bb720554a 100644 --- a/ipatests/test_integration/test_idp.py +++ b/ipatests/test_integration/test_idp.py -@@ -12,7 +12,7 @@ from ipatests.pytest_ipa.integration import tasks, create_keycloak - user_code_script = textwrap.dedent(""" - from selenium import webdriver - from datetime import datetime --from packaging.version import parse as parse_version -+from pkg_resources import parse_version - from selenium.webdriver.firefox.options import Options - from selenium.webdriver.common.by import By - from selenium.webdriver.support.ui import WebDriverWait +@@ -36,6 +36,6 @@ SELENIUM_REMOTE_HEAD = textwrap.dedent( + from selenium import webdriver + from datetime import datetime +- from packaging.version import parse as parse_version ++ from pkg_resources import parse_version + from selenium.webdriver.firefox.options import Options + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import WebDriverWait diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py index 47392cec9345b6d2447b728fc21050edabe8f0cd..15fc38a6e155b343259ee61c6c7c5c561c7f6410 100644 --- a/ipatests/test_integration/test_ipahealthcheck.py diff --git a/SOURCES/0007-ipatests-Move-expire_password-fixture-into-TestIPACo.patch b/SOURCES/0007-ipatests-Move-expire_password-fixture-into-TestIPACo.patch deleted file mode 100644 index 53a9baa..0000000 --- a/SOURCES/0007-ipatests-Move-expire_password-fixture-into-TestIPACo.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 1c86c973c8dc778a71c8e2abf54ff37ececdd696 Mon Sep 17 00:00:00 2001 -From: PRANAV THUBE -Date: Tue, 13 Jan 2026 19:30:09 +0530 -Subject: [PATCH] ipatests: Move expire_password fixture into TestIPACommand - class - -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden ---- - .../nightly_ipa-4-13_latest.yaml | 2 +- - .../nightly_ipa-4-13_latest_selinux.yaml | 2 +- - ipatests/test_integration/test_commands.py | 59 +++++++++---------- - 3 files changed, 31 insertions(+), 32 deletions(-) - -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -index 210739d4b576882ee43e06d85bce819ff30d2357..aff55727e463207fb235ff340989491e62162149 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -@@ -1914,7 +1914,7 @@ jobs: - class: RunPytest - args: - build_url: '{fedora-latest-ipa-4-13/build_url}' -- test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN::test_certificate_out_write_to_file -+ test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN - template: *ci-ipa-4-13-latest - timeout: 5400 - topology: *master_1repl_1client -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -index 0fb7c050b97bf66645599fbff46b53c048211f96..e6c57ea060b3bb8bfdf8b6f981f8fd28e4a7d320 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -@@ -2066,7 +2066,7 @@ jobs: - args: - build_url: '{fedora-latest-ipa-4-13/build_url}' - selinux_enforcing: True -- test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN::test_certificate_out_write_to_file -+ test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN - template: *ci-ipa-4-13-latest - timeout: 5400 - topology: *master_1repl_1client -diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py -index 01001e4c92037599b075e9bca3ddda7d0c8e8ffa..eda98e92b6b494752d74c6381bdcb6d5c47a26d1 100644 ---- a/ipatests/test_integration/test_commands.py -+++ b/ipatests/test_integration/test_commands.py -@@ -199,36 +199,6 @@ duplicatesubject = ( - duplicate_serial = "4097" - - --@pytest.fixture() --def expire_password(): -- """ -- Fixture to expire a user's password far into the future past -- 2038, then revert time back. -- """ -- hosts = dict() -- -- def _expire_password(host): -- hosts['host'] = host -- tasks.move_date(host, 'stop', '+20Years') -- host.run_command( -- ['ipactl', 'restart', '--ignore-service-failures'] -- ) -- -- yield _expire_password -- -- host = hosts.pop('host') -- # Prior to uninstall remove all the cert tracking to prevent -- # errors from certmonger trying to check the status of certs -- # that don't matter because we are uninstalling. -- host.run_command(['systemctl', 'stop', 'certmonger']) -- # Important: run_command with a str argument is able to -- # perform shell expansion but run_command with a list of -- # arguments is not -- host.run_command('rm -fv ' + paths.CERTMONGER_REQUESTS_DIR + '*') -- tasks.uninstall_master(host) -- tasks.move_date(host, 'start', '-20Years') -- -- - class TestIPACommand(IntegrationTest): - """ - A lot of commands can be executed against a single IPA installation -@@ -239,6 +209,35 @@ class TestIPACommand(IntegrationTest): - num_replicas = 1 - num_clients = 1 - -+ @pytest.fixture -+ def expire_password(self): -+ """ -+ Fixture to expire a user's password far into the future past -+ 2038, then revert time back. -+ """ -+ hosts = dict() -+ -+ def _expire_password(host): -+ hosts['host'] = host -+ tasks.move_date(host, 'stop', '+20Years') -+ host.run_command( -+ ['ipactl', 'restart', '--ignore-service-failures'] -+ ) -+ -+ yield _expire_password -+ -+ host = hosts.pop('host') -+ # Prior to uninstall remove all the cert tracking to prevent -+ # errors from certmonger trying to check the status of certs -+ # that don't matter because we are uninstalling. -+ host.run_command(['systemctl', 'stop', 'certmonger']) -+ # Important: run_command with a str argument is able to -+ # perform shell expansion but run_command with a list of -+ # arguments is not -+ host.run_command('rm -fv ' + paths.CERTMONGER_REQUESTS_DIR + '*') -+ tasks.uninstall_master(host) -+ tasks.move_date(host, 'start', '-20Years') -+ - @pytest.fixture - def pwpolicy_global(self): - """Fixture to change global password history policy and reset it""" --- -2.52.0 - diff --git a/SOURCES/0008-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch b/SOURCES/0008-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch deleted file mode 100644 index b564201..0000000 --- a/SOURCES/0008-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch +++ /dev/null @@ -1,48 +0,0 @@ -From ecc0efa96e3c446586425d470657de7f2d5376bf Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Mon, 19 Jan 2026 17:00:41 +0100 -Subject: [PATCH] ipatests: Fix xfail assertion for sssd 2.12.0 - -SSSD 2.12.0 provides fixes for -https://github.com/SSSD/sssd/issues/5989 -https://github.com/SSSD/sssd/issues/7169 - -Update the condition expecting test failures in test_trust.py - -Signed-off-by: Florence Blanc-Renaud -Reviewed-By: David Hanina ---- - ipatests/test_integration/test_trust.py | 10 ++++++---- - 1 file changed, 6 insertions(+), 4 deletions(-) - -diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py -index 13ad0afa4c1fb032d50f40cf7cb9b79283203225..0cab277c910a6d35f35b57e3068ee6f38706af59 100644 ---- a/ipatests/test_integration/test_trust.py -+++ b/ipatests/test_integration/test_trust.py -@@ -1219,9 +1219,10 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust): - assert (uid == self.uid_override and gid == self.gid_override) - test_group = self.clients[0].run_command( - ["id", nonposixuser]).stdout_text -- cond2 = ((type == 'false' -- and sssd_version >= tasks.parse_version("2.9.4")) -- or type == 'hybrid') -+ cond2 = (((type == 'false' -+ and sssd_version >= tasks.parse_version("2.9.4")) -+ or type == 'hybrid') -+ and sssd_version < tasks.parse_version("2.12.0")) - with xfail_context(cond2, - 'https://github.com/SSSD/sssd/issues/5989 ' - 'and 7169'): -@@ -1347,7 +1348,8 @@ class TestPosixAutoPrivateGroup(BaseTestTrust): - and gid == self.gid_override) - result = self.clients[0].run_command(['id', posixuser]) - sssd_version = tasks.get_sssd_version(self.clients[0]) -- bad_version = sssd_version >= tasks.parse_version("2.9.4") -+ bad_version = (tasks.parse_version("2.9.4") <= sssd_version -+ < tasks.parse_version("2.12.0")) - with xfail_context(bad_version and type in ('false', 'hybrid'), - "https://github.com/SSSD/sssd/issues/7169"): - assert "10047(testgroup@{0})".format( --- -2.52.0 - diff --git a/SOURCES/0009-ipatests-Add-DNS-functional-integration-tests.patch b/SOURCES/0009-ipatests-Add-DNS-functional-integration-tests.patch deleted file mode 100644 index 10085a7..0000000 --- a/SOURCES/0009-ipatests-Add-DNS-functional-integration-tests.patch +++ /dev/null @@ -1,1279 +0,0 @@ -From aa90ce09c3f6d496ee2cf63123c619f1008140b4 Mon Sep 17 00:00:00 2001 -From: PRANAV THUBE -Date: Wed, 31 Dec 2025 12:15:51 +0530 -Subject: [PATCH] ipatests: Add DNS functional integration tests. - -Add tests covering DNS zone management and all record types: -A, AAAA, AFSDB, CNAME, TXT, SRV, MX, PTR, NAPTR, DNAME, CERT, LOC, KX -Including zone permissions and negative test cases. - -Related: https://pagure.io/freeipa/issue/9911 -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Rafael Guterres Jeffman -Reviewed-By: David Hanina ---- - ipatests/pytest_ipa/integration/tasks.py | 126 +++- - ipatests/test_integration/test_dns.py | 707 +++++++++++++++++++++++ - ipatests/test_xmlrpc/test_dns_plugin.py | 288 ++++++++- - 3 files changed, 1113 insertions(+), 8 deletions(-) - -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index 7def8fe3962487f28999cb975aff512cd36331fe..32ac5cbc2c6fe87850dfb15c1d5beae6fa648dfb 100755 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -1628,6 +1628,71 @@ def add_a_record(master, host): - '--a-rec', host.ip]) - - -+def add_dns_record(master, zone, name, record_type, record_value, -+ raiseonerr=True): -+ """Add DNS record of any type. -+ -+ :param master: The IPA master host to run command on -+ :param zone: DNS zone name (e.g., 'example.com.') -+ :param name: Record name (e.g., 'www' or '@' for zone apex) -+ :param record_type: Record type like 'a', 'aaaa', 'afsdb', 'cname', -+ 'txt', 'srv', 'mx', 'ptr', 'naptr', 'dname', -+ 'cert', 'loc', 'kx', etc. -+ :param record_value: List of values for the record -+ :param raiseonerr: If True, raise exception on command failure -+ :return: Command result object -+ """ -+ command = ['ipa', 'dnsrecord-add', zone, name] -+ opt = f'--{record_type}-rec' -+ for val in record_value: -+ command.extend([opt, val]) -+ return master.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def del_dns_record(master, zone, name, record_type=None, record_value=None, -+ del_all=False, raiseonerr=True): -+ """Delete DNS record of any type. -+ -+ :param record_type: Record type like 'a', 'aaaa', 'afsdb', 'cname', etc. -+ :param record_value: List of values (optional) -+ :param del_all: If True, delete all records for this name -+ """ -+ command = ['ipa', 'dnsrecord-del', zone, name] -+ if del_all: -+ command.append('--del-all') -+ elif record_type and record_value: -+ opt = f'--{record_type}-rec' -+ for val in record_value: -+ command.extend([opt, val]) -+ return master.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def find_dns_record(master, zone, name=None, raiseonerr=True): -+ """Find DNS record. -+ -+ :param name: Record name, if not provided searches all records in zone -+ """ -+ command = ['ipa', 'dnsrecord-find', zone] -+ if name is not None: -+ command.append(name) -+ return master.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def mod_dns_record(master, zone, name, record_type, old_value, new_value, -+ raiseonerr=True): -+ """Modify DNS record value. -+ -+ :param record_type: Record type like 'a', 'aaaa', 'txt', etc. -+ :param old_value: Current record value -+ :param new_value: New record value -+ """ -+ return master.run_command([ -+ 'ipa', 'dnsrecord-mod', zone, name, -+ f'--{record_type}-rec={old_value}', -+ f'--{record_type}-data={new_value}' -+ ], raiseonerr=raiseonerr) -+ -+ - def resolve_record(nameserver, query, rtype="SOA", retry=True, timeout=100): - """Resolve DNS record - :retry: if resolution failed try again until timeout is reached -@@ -2002,7 +2067,9 @@ def ldappasswd_sysaccount_change(user, oldpw, newpw, master, use_dirman=False): - - - def add_dns_zone(master, zone, skip_overlap_check=False, -- dynamic_update=False, add_a_record_hosts=None): -+ dynamic_update=False, add_a_record_hosts=None, -+ admin_email=None, refresh=None, retry=None, -+ expire=None, minimum=None, ttl=None, raiseonerr=True): - """ - Add DNS zone if it is not already added. - """ -@@ -2010,14 +2077,27 @@ def add_dns_zone(master, zone, skip_overlap_check=False, - result = master.run_command( - ['ipa', 'dnszone-show', zone], raiseonerr=False) - -+ # Verify both return code and zone name before adding - if result.returncode != 0: - command = ['ipa', 'dnszone-add', zone] - if skip_overlap_check: - command.append('--skip-overlap-check') - if dynamic_update: - command.append('--dynamic-update=True') -- -- master.run_command(command) -+ if admin_email: -+ command.append('--admin-email=' + admin_email) -+ if refresh: -+ command.append('--refresh=' + str(refresh)) -+ if retry: -+ command.append('--retry=' + str(retry)) -+ if expire: -+ command.append('--expire=' + str(expire)) -+ if minimum: -+ command.append('--minimum=' + str(minimum)) -+ if ttl: -+ command.append('--ttl=' + str(ttl)) -+ -+ master.run_command(command, raiseonerr=raiseonerr) - - if add_a_record_hosts: - for host in add_a_record_hosts: -@@ -2027,6 +2107,46 @@ def add_dns_zone(master, zone, skip_overlap_check=False, - logger.debug('Zone %s already added.', zone) - - -+def del_dns_zone(host, zone, raiseonerr=False): -+ """Delete DNS zone.""" -+ return host.run_command( -+ ['ipa', 'dnszone-del', zone], raiseonerr=raiseonerr) -+ -+ -+def find_dns_zone(host, zone, all_attrs=False, raiseonerr=True): -+ """Find DNS zone.""" -+ command = ['ipa', 'dnszone-find', zone] -+ if all_attrs: -+ command.append('--all') -+ return host.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def show_dns_zone(host, zone, all_attrs=False, raiseonerr=True): -+ """Show DNS zone.""" -+ command = ['ipa', 'dnszone-show', zone] -+ if all_attrs: -+ command.append('--all') -+ return host.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def add_dns_zone_permission(host, zone, raiseonerr=True): -+ """Add permission to manage DNS zone.""" -+ return host.run_command(['ipa', 'dnszone-add-permission', zone], -+ raiseonerr=raiseonerr) -+ -+ -+def remove_dns_zone_permission(host, zone, raiseonerr=True): -+ """Remove permission to manage DNS zone.""" -+ return host.run_command(['ipa', 'dnszone-remove-permission', zone], -+ raiseonerr=raiseonerr) -+ -+ -+def find_permission(host, permission, raiseonerr=True): -+ """Find permission.""" -+ return host.run_command(['ipa', 'permission-find', permission], -+ raiseonerr=raiseonerr) -+ -+ - def sign_ca_and_transport(host, csr_name, root_ca_name, ipa_ca_name, - root_ca_path_length=None, ipa_ca_path_length=1, - key_size=None, root_ca_extensions=()): -diff --git a/ipatests/test_integration/test_dns.py b/ipatests/test_integration/test_dns.py -index 1cef22525b690050aa1230733f115cd7f4099c53..905227497c8879bfe4cf8b027e396689c0451208 100644 ---- a/ipatests/test_integration/test_dns.py -+++ b/ipatests/test_integration/test_dns.py -@@ -5,9 +5,70 @@ - - from __future__ import absolute_import - -+import time -+ -+import dns.resolver -+from ipapython.dnsutil import DNSResolver - from ipatests.pytest_ipa.integration import tasks - from ipatests.test_integration.base import IntegrationTest - -+# ============================================================================= -+# DNS Test Constants -+# ============================================================================= -+# Test zone configuration -+ZONE = "testzone" -+EMAIL = "ipaqar.redhat.com" -+REFRESH = 303 -+RETRY = 101 -+EXPIRE = 1202 -+MINIMUM = 33 -+TTL = 55 -+ -+# A record values -+A_RECORD = "1.2.3.4" -+MULTI_A_RECORD1 = "1.2.3.4" -+MULTI_A_RECORD2 = "2.3.4.5" -+ -+# AAAA record values -+AAAA = "fec0:0:a10:6000:10:16ff:fe98:193" -+AAAA_BAD1 = "bada:aaaa:real:ly:bad:dude:extr:a" -+AAAA_BAD2 = "aaaa:bbbb:cccc:dddd:eeee:fffff" -+ -+# Other record types -+AFSDB = "green.femto.edu." -+CNAME = "m.l.k." -+TXT = "none=1.2.3.4" -+SRV_A = "0 100 389" -+SRV = "why.go.here.com." -+NAPTR = '100 10 U E2U+msg !^.*$!mailto:info@example.com! .' -+NAPTR_FIND = "info@example.com" -+DNAME = f"bar.{ZONE}." -+DNAME2 = f"bar_underscore.{ZONE}." -+CERT_B = "1 1 1" -+CERT = "F835EDA21E94B565716F" -+LOC = "37 23 30.900 N 121 59 19.000 W 7.00m 100.00m 100.00m 2.00m" -+ -+# KX records -+KX_PREF1 = "1234" -+KX_BAD_PREF1 = "-1" -+KX_BAD_PREF2 = "123345678" -+ -+# PTR zone configuration -+PTR_OCTET = "4.4.4" -+PTR_ZONE = f"{PTR_OCTET}.in-addr.arpa." -+PTR = "8" -+PTR_VALUE = "in.awesome.domain." -+PTR_EMAIL = "ipaqar.redhat.com" -+PTR_REFRESH = 393 -+PTR_RETRY = 191 -+PTR_EXPIRE = 1292 -+PTR_MINIMUM = 39 -+PTR_TTL = 59 -+ -+# Persistent search test values -+NEW_TXT = "newip=5.6.7.8" -+NEWER_TXT = "newip=8.7.6.5" -+ - - class TestDNS(IntegrationTest): - """Tests for DNS feature. -@@ -38,3 +99,649 @@ class TestDNS(IntegrationTest): - cmd = self.master.run_command(['dig', '+short', '-t', 'SOA', - self.master.domain.name]) - assert 'fake' not in cmd.stdout_text -+ -+ -+class TestDNSAcceptance(IntegrationTest): -+ """DNS Acceptance tests. -+ -+ This test class covers all the DNS acceptance tests including -+ zone management, record types (A, AAAA, AFSDB, CNAME, TXT, SRV, MX, -+ PTR, NAPTR, DNAME, CERT, LOC, KX), zone permissions, and persistent -+ search functionality. -+ -+ Converted from bash test script t.dns.sh -+ """ -+ topology = 'line' -+ num_replicas = 0 -+ -+ @classmethod -+ def install(cls, mh): -+ super(TestDNSAcceptance, cls).install(mh) -+ # Set domain-dependent values -+ cls.MANAGED_ZONE = f"qa.{cls.master.domain.name}" -+ cls.MANAGED_ZONE1 = f"dev.{cls.master.domain.name}" -+ cls.NONEXISTENT_ZONE = f"nonexistent.{cls.master.domain.name}" -+ cls.ZONE_PSEARCH = f"westford.{cls.master.domain.name}" -+ cls.MX = f"mail.{cls.master.domain.name}" -+ cls.A_HOST = f"1.{cls.master.domain.name}" -+ # Setup DNS resolver for test queries -+ cls.resolver = DNSResolver() -+ cls.resolver.nameservers = [cls.master.ip] -+ cls.resolver.lifetime = 10 -+ -+ @classmethod -+ def uninstall(cls, mh): -+ # Cleanup zones if they exist -+ tasks.kinit_admin(cls.master) -+ for zone in [ZONE, PTR_ZONE, cls.MANAGED_ZONE, -+ cls.MANAGED_ZONE1, cls.ZONE_PSEARCH]: -+ if zone: -+ cls.master.run_command( -+ ['ipa', 'dnszone-del', zone], raiseonerr=False -+ ) -+ super(TestDNSAcceptance, cls).uninstall(mh) -+ -+ # ========================================================================= -+ # DNS Zone Tests -+ # ========================================================================= -+ -+ def test_dns_zone(self): -+ """Test DNS zone creation, verification, and dig queries. -+ -+ This test covers zone management operations including creating zones -+ with valid and invalid parameters, and verifying zone attributes -+ via IPA and dig. -+ """ -+ tasks.kinit_admin(self.master) -+ -+ # Create a new DNS zone with all SOA parameters -+ tasks.add_dns_zone( -+ self.master, ZONE, -+ admin_email=EMAIL, -+ refresh=REFRESH, -+ retry=RETRY, -+ expire=EXPIRE, -+ minimum=MINIMUM, -+ ttl=TTL -+ ) -+ # Verify the new zone was created and is findable -+ result = tasks.find_dns_zone(self.master, ZONE, all_attrs=True) -+ assert ZONE in result.stdout_text -+ -+ # Verify DNS server returns correct SOA attributes using DNS API -+ self.master.run_command(['ipactl', 'restart']) -+ time.sleep(5) -+ soa = self.resolver.resolve(ZONE, 'SOA')[0] -+ assert self.master.hostname in str(soa.mname) -+ assert EMAIL.replace('@', '.') in str(soa.rname) -+ assert soa.refresh == REFRESH -+ assert soa.retry == RETRY -+ assert soa.expire == EXPIRE -+ assert soa.minimum == MINIMUM -+ -+ # ========================================================================= -+ # A Record Tests -+ # ========================================================================= -+ -+ def test_a_record(self): -+ """Test A record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # Single A record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'allll', -+ record_type='a', record_value=[A_RECORD]) -+ ans = self.resolver.resolve(f'allll.{ZONE}', 'A') -+ assert A_RECORD in [r.address for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'allll', -+ record_type='a', record_value=[A_RECORD]) -+ try: -+ self.resolver.resolve(f'allll.{ZONE}', 'A') -+ raise AssertionError( -+ f"Resolving allll.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # Multiple A records: add, verify, delete, verify deleted -+ multi_recs = [MULTI_A_RECORD1, MULTI_A_RECORD2] -+ tasks.add_dns_record(self.master, ZONE, 'aa2', -+ record_type='a', record_value=multi_recs) -+ ans = self.resolver.resolve(f'aa2.{ZONE}', 'A') -+ assert MULTI_A_RECORD1 in [r.address for r in ans] -+ assert MULTI_A_RECORD2 in [r.address for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'aa2', -+ record_type='a', record_value=multi_recs) -+ try: -+ self.resolver.resolve(f'aa2.{ZONE}', 'A') -+ raise AssertionError( -+ f"Resolving aa2.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Records deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # AAAA Record Tests -+ # ========================================================================= -+ -+ def test_aaaa_record(self): -+ """Test AAAA record add, verify, delete, and invalid values.""" -+ tasks.kinit_admin(self.master) -+ -+ # AAAA record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'aaaa', -+ record_type='aaaa', record_value=[AAAA]) -+ ans = self.resolver.resolve(f'aaaa.{ZONE}', 'AAAA') -+ assert AAAA in [r.address for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'aaaa', -+ record_type='aaaa', record_value=[AAAA]) -+ try: -+ self.resolver.resolve(f'aaaa.{ZONE}', 'AAAA') -+ raise AssertionError( -+ f"Resolving aaaa.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # Invalid AAAA record should fail and not be created -+ result = tasks.add_dns_record(self.master, ZONE, 'aaaab', -+ record_type='aaaa', -+ record_value=[AAAA_BAD1], -+ raiseonerr=False) -+ assert result.returncode != 0 -+ try: -+ self.resolver.resolve(f'aaaab.{ZONE}', 'AAAA') -+ raise AssertionError( -+ f"Resolving aaaab.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record was never created - expected -+ -+ # ========================================================================= -+ # AFSDB Record Tests -+ # ========================================================================= -+ -+ def test_afsdb_record(self): -+ """Test AFSDB record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # AFSDB record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'afsdb', -+ record_type='afsdb', -+ record_value=[f'0 {AFSDB}']) -+ ans = self.resolver.resolve(f'afsdb.{ZONE}', 'AFSDB') -+ assert AFSDB in [str(r.hostname) for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'afsdb', -+ record_type='afsdb', -+ record_value=[f'0 {AFSDB}']) -+ try: -+ self.resolver.resolve(f'afsdb.{ZONE}', 'AFSDB') -+ raise AssertionError( -+ f"Resolving afsdb.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # CNAME Record Tests -+ # ========================================================================= -+ -+ def test_cname_record(self): -+ """Test CNAME record add, verify, delete, and duplicate (bz915807).""" -+ tasks.kinit_admin(self.master) -+ -+ # CNAME record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'cname', -+ record_type='cname', record_value=[CNAME]) -+ ans = self.resolver.resolve(f'cname.{ZONE}', 'CNAME') -+ assert CNAME in [str(r.target) for r in ans] -+ -+ # Duplicate CNAME should fail and not be created (bz915807) -+ result = tasks.add_dns_record(self.master, ZONE, 'cname', -+ record_type='cname', -+ record_value=['a.b.c'], raiseonerr=False) -+ assert result.returncode != 0 -+ ans = self.resolver.resolve(f'cname.{ZONE}', 'CNAME') -+ assert 'a.b.c' not in [str(r.target) for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'cname', -+ record_type='cname', record_value=[CNAME]) -+ try: -+ self.resolver.resolve(f'cname.{ZONE}', 'CNAME') -+ raise AssertionError( -+ f"Resolving cname.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # TXT Record Tests -+ # ========================================================================= -+ -+ def test_txt_record(self): -+ """Test TXT record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # TXT record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'txt', -+ record_type='txt', record_value=[TXT]) -+ ans = self.resolver.resolve(f'txt.{ZONE}', 'TXT') -+ assert any(TXT in str(r) for r in ans) -+ -+ tasks.del_dns_record(self.master, ZONE, 'txt', -+ record_type='txt', record_value=[TXT]) -+ try: -+ self.resolver.resolve(f'txt.{ZONE}', 'TXT') -+ raise AssertionError( -+ f"Resolving txt.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # SRV Record Tests -+ # ========================================================================= -+ -+ def test_srv_record(self): -+ """Test SRV record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # SRV record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, '_srv', -+ record_type='srv', -+ record_value=[f'{SRV_A} {SRV}']) -+ ans = self.resolver.resolve(f'_srv.{ZONE}', 'SRV') -+ assert SRV in [str(r.target) for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, '_srv', del_all=True) -+ try: -+ self.resolver.resolve(f'_srv.{ZONE}', 'SRV') -+ raise AssertionError( -+ f"Resolving _srv.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # MX Record Tests -+ # ========================================================================= -+ -+ def test_mx_record(self): -+ """Test MX record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # MX record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, '@', -+ record_type='mx', -+ record_value=[f'10 {self.MX}.']) -+ ans = self.resolver.resolve(ZONE, 'MX') -+ assert f'{self.MX}.' in [str(r.exchange) for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, '@', -+ record_type='mx', -+ record_value=[f'10 {self.MX}.']) -+ try: -+ self.resolver.resolve(ZONE, 'MX') -+ raise AssertionError( -+ f"Resolving MX for {ZONE} should have raised NoAnswer") -+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): -+ pass # Record deleted or no MX records left - expected -+ -+ # ========================================================================= -+ # PTR Zone Tests -+ # ========================================================================= -+ def test_ptr_zone(self): -+ """Test PTR zone creation and verification. -+ -+ This test covers PTR zone management including creating zone -+ with all SOA parameters, and verifying attributes via IPA and dig. -+ """ -+ tasks.kinit_admin(self.master) -+ # Clean up if zone exists -+ tasks.del_dns_zone(self.master, PTR_ZONE) -+ -+ # Create PTR zone with all SOA parameters -+ tasks.add_dns_zone( -+ self.master, PTR_ZONE, -+ skip_overlap_check=True, -+ admin_email=PTR_EMAIL, -+ refresh=PTR_REFRESH, -+ retry=PTR_RETRY, -+ expire=PTR_EXPIRE, -+ minimum=PTR_MINIMUM, -+ ttl=PTR_TTL -+ ) -+ -+ # Verify PTR zone gets created with the correct attributes -+ result = tasks.find_dns_zone( -+ self.master, PTR_ZONE, all_attrs=True) -+ assert PTR_ZONE in result.stdout_text -+ -+ # Verify PTR zone SOA attributes using DNS resolver -+ soa = self.resolver.resolve(PTR_ZONE, 'SOA')[0] -+ assert self.master.hostname in str(soa.mname) -+ assert PTR_EMAIL.replace('@', '.') in str(soa.rname) -+ assert soa.refresh == PTR_REFRESH -+ assert soa.retry == PTR_RETRY -+ assert soa.expire == PTR_EXPIRE -+ assert soa.minimum == PTR_MINIMUM -+ -+ # ========================================================================= -+ # PTR Record Tests -+ # ========================================================================= -+ -+ def test_ptr_record(self): -+ """Test PTR record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # PTR record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, PTR_ZONE, PTR, -+ record_type='ptr', record_value=[PTR_VALUE]) -+ ans = self.resolver.resolve(f'{PTR}.{PTR_ZONE}', 'PTR') -+ assert PTR_VALUE in [str(r.target) for r in ans] -+ -+ tasks.del_dns_record(self.master, PTR_ZONE, PTR, -+ record_type='ptr', record_value=[PTR_VALUE]) -+ try: -+ self.resolver.resolve(f'{PTR}.{PTR_ZONE}', 'PTR') -+ raise AssertionError( -+ f"Resolving {PTR}.{PTR_ZONE} should have raised " -+ "NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # NAPTR Record Tests -+ # ========================================================================= -+ -+ def test_naptr_record(self): -+ """Test NAPTR record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # NAPTR record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'naptr', -+ record_type='naptr', record_value=[NAPTR]) -+ ans = self.resolver.resolve(f'naptr.{ZONE}', 'NAPTR') -+ assert any(NAPTR_FIND in str(r.regexp) for r in ans) -+ -+ tasks.del_dns_record(self.master, ZONE, 'naptr', -+ record_type='naptr', record_value=[NAPTR]) -+ try: -+ self.resolver.resolve(f'naptr.{ZONE}', 'NAPTR') -+ raise AssertionError( -+ f"Resolving naptr.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted, name no longer exists - expected -+ -+ # ========================================================================= -+ # DNAME Record Tests -+ # ========================================================================= -+ -+ def test_dname_record(self): -+ """Test DNAME record add, verify, delete, and underscore (bz915797).""" -+ tasks.kinit_admin(self.master) -+ -+ # DNAME record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'dname', -+ record_type='dname', record_value=[DNAME]) -+ ans = self.resolver.resolve(f'dname.{ZONE}', 'DNAME') -+ assert DNAME in [str(r.target) for r in ans] -+ -+ # Duplicate DNAME should fail (bz915797) -+ result = tasks.add_dns_record(self.master, ZONE, 'dname', -+ record_type='dname', -+ record_value=[DNAME2], -+ raiseonerr=False) -+ assert result.returncode != 0 -+ -+ tasks.del_dns_record(self.master, ZONE, 'dname', -+ record_type='dname', record_value=[DNAME]) -+ try: -+ self.resolver.resolve(f'dname.{ZONE}', 'DNAME') -+ raise AssertionError( -+ f"Resolving dname.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted - expected -+ -+ # DNAME with underscore: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'dname', -+ record_type='dname', record_value=[DNAME2]) -+ ans = self.resolver.resolve(f'dname.{ZONE}', 'DNAME') -+ assert DNAME2 in [str(r.target) for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, 'dname', -+ record_type='dname', record_value=[DNAME2]) -+ try: -+ self.resolver.resolve(f'dname.{ZONE}', 'DNAME') -+ raise AssertionError( -+ f"Resolving dname.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted - expected -+ -+ # ========================================================================= -+ # CERT Record Tests -+ # ========================================================================= -+ -+ def test_cert_record(self): -+ """Test CERT record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # CERT record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, 'cert', -+ record_type='cert', -+ record_value=[f'{CERT_B} {CERT}']) -+ ans = self.resolver.resolve(f'cert.{ZONE}', 'CERT') -+ assert any(CERT in str(r) for r in ans) -+ -+ tasks.del_dns_record(self.master, ZONE, 'cert', -+ record_type='cert', -+ record_value=[f'{CERT_B} {CERT}']) -+ try: -+ self.resolver.resolve(f'cert.{ZONE}', 'CERT') -+ raise AssertionError( -+ f"Resolving cert.{ZONE} should have raised NXDOMAIN") -+ except dns.resolver.NXDOMAIN: -+ pass # Record deleted - expected -+ -+ # ========================================================================= -+ # LOC Record Tests -+ # ========================================================================= -+ -+ def test_loc_record(self): -+ """Test LOC record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # LOC record: add, verify, delete, verify deleted -+ tasks.add_dns_record(self.master, ZONE, '@', -+ record_type='loc', record_value=[LOC]) -+ ans = self.resolver.resolve(ZONE, 'LOC') -+ assert any(LOC in str(r) for r in ans) -+ -+ tasks.del_dns_record(self.master, ZONE, '@', -+ record_type='loc', record_value=[LOC]) -+ try: -+ self.resolver.resolve(ZONE, 'LOC') -+ raise AssertionError( -+ f"Resolving LOC for {ZONE} should have raised NoAnswer") -+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): -+ pass # Record deleted - expected -+ -+ # ========================================================================= -+ # KX Record Tests -+ # ========================================================================= -+ -+ def test_kx_record(self): -+ """Test KX record add, verify, and delete operations.""" -+ tasks.kinit_admin(self.master) -+ -+ # KX record: add, verify, delete, verify deleted -+ kx_val = f'{KX_PREF1} {self.A_HOST}' -+ tasks.add_dns_record(self.master, ZONE, '@', -+ record_type='kx', record_value=[kx_val]) -+ ans = self.resolver.resolve(ZONE, 'KX') -+ assert int(KX_PREF1) in [r.preference for r in ans] -+ -+ tasks.del_dns_record(self.master, ZONE, '@', -+ record_type='kx', record_value=[kx_val]) -+ try: -+ self.resolver.resolve(ZONE, 'KX') -+ raise AssertionError( -+ f"Resolving KX for {ZONE} should have raised NoAnswer") -+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): -+ pass # Record deleted or no KX records left - expected -+ -+ # Invalid KX records should fail and not be created -+ bad_vals = [ -+ (KX_BAD_PREF1, A_RECORD), -+ (KX_BAD_PREF2, ZONE) -+ ] -+ for bad_pref, bad_target in bad_vals: -+ result = tasks.add_dns_record( -+ self.master, ZONE, '@', record_type='kx', -+ record_value=[f'{bad_pref} {bad_target}'], raiseonerr=False) -+ assert result.returncode != 0 -+ try: -+ self.resolver.resolve(ZONE, 'KX') -+ raise AssertionError( -+ f"Resolving KX for {ZONE} should have raised " -+ "NoAnswer") -+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): -+ pass # No KX records - expected -+ -+ # ========================================================================= -+ # Zone Permission Tests -+ # ========================================================================= -+ -+ def test_zone_permission(self): -+ """Test DNS zone permission add, verify, and remove operations. -+ -+ This test covers zone permission management including adding -+ permission, verifying managedby attribute, removing permission, -+ testing duplicate add, testing non-existent zone, and verifying -+ permission cleanup on zone deletion. -+ """ -+ tasks.kinit_admin(self.master) -+ # Clean up if zone exists -+ tasks.del_dns_zone(self.master, self.MANAGED_ZONE) -+ -+ # Add zone, then permission to manage it -+ tasks.add_dns_zone( -+ self.master, self.MANAGED_ZONE, admin_email=EMAIL) -+ result = tasks.add_dns_zone_permission(self.master, self.MANAGED_ZONE) -+ assert result.returncode == 0 -+ -+ # Verify managedby attribute is set -+ result = tasks.show_dns_zone( -+ self.master, self.MANAGED_ZONE, all_attrs=True) -+ assert 'managedby' in result.stdout_text.lower() -+ -+ # Verify permission is added -+ perm_name = f'Manage DNS zone {self.MANAGED_ZONE}' -+ result = tasks.find_permission(self.master, permission=perm_name) -+ assert result.returncode == 0 -+ -+ # Remove permission to manage zone -+ result = tasks.remove_dns_zone_permission( -+ self.master, self.MANAGED_ZONE) -+ assert result.returncode == 0 -+ -+ # Verify managedby attribute is not available -+ result = tasks.show_dns_zone( -+ self.master, self.MANAGED_ZONE, all_attrs=True) -+ assert 'managedby' not in result.stdout_text.lower() -+ -+ # Verify permission is removed -+ result = tasks.find_permission( -+ self.master, permission=perm_name, raiseonerr=False) -+ assert result.returncode != 0 -+ -+ # Add zone with permission, delete zone, verify permission deleted -+ tasks.del_dns_zone(self.master, self.MANAGED_ZONE1) -+ tasks.add_dns_zone( -+ self.master, self.MANAGED_ZONE1, admin_email=EMAIL) -+ tasks.add_dns_zone_permission(self.master, self.MANAGED_ZONE1) -+ tasks.del_dns_zone(self.master, self.MANAGED_ZONE1) -+ perm_name1 = f'Manage DNS zone {self.MANAGED_ZONE1}' -+ result = tasks.find_permission( -+ self.master, permission=perm_name1, raiseonerr=False) -+ assert result.returncode != 0 -+ -+ # Add duplicate permission to manage zone -+ tasks.add_dns_zone_permission( -+ self.master, self.MANAGED_ZONE, raiseonerr=False) -+ result = tasks.add_dns_zone_permission( -+ self.master, self.MANAGED_ZONE, raiseonerr=False) -+ assert result.returncode != 0 -+ assert 'already exists' in result.stderr_text -+ -+ # Add permission to manage non-existent zone -+ result = tasks.add_dns_zone_permission( -+ self.master, self.NONEXISTENT_ZONE, raiseonerr=False) -+ assert result.returncode != 0 -+ assert 'DNS zone not found' in result.stderr_text -+ -+ # Remove permission to manage zone again (should fail) -+ tasks.remove_dns_zone_permission( -+ self.master, self.MANAGED_ZONE, raiseonerr=False) -+ result = tasks.remove_dns_zone_permission( -+ self.master, self.MANAGED_ZONE, raiseonerr=False) -+ assert result.returncode != 0 -+ assert 'permission not found' in result.stderr_text -+ -+ # Remove permission for non-existent zone -+ result = tasks.remove_dns_zone_permission(self.master, -+ self.NONEXISTENT_ZONE, -+ raiseonerr=False) -+ assert result.returncode != 0 -+ assert 'DNS zone not found' in result.stderr_text -+ -+ # Cleanup zones from zone permission tests -+ tasks.del_dns_zone(self.master, self.MANAGED_ZONE) -+ -+ # ========================================================================= -+ # Persistent Search Tests -+ # ========================================================================= -+ -+ def test_psearch(self): -+ """Test persistent search and zone serial updates.""" -+ tasks.kinit_admin(self.master) -+ -+ # Verify psearch is not used when IPA server is installed -+ result = self.master.run_command([ -+ 'grep', 'psearch yes', '/etc/named.conf' -+ ], raiseonerr=False) -+ assert result.returncode != 0 -+ -+ # Create zone with SOA parameters -+ tasks.add_dns_zone( -+ self.master, self.ZONE_PSEARCH, admin_email=EMAIL, -+ refresh=REFRESH, retry=RETRY, expire=EXPIRE, -+ minimum=MINIMUM, ttl=TTL) -+ -+ # Verify zone SOA exists -+ ans = self.resolver.resolve(self.ZONE_PSEARCH, 'SOA') -+ assert len(ans) > 0 -+ -+ # Add TXT record and verify -+ tasks.add_dns_record(self.master, self.ZONE_PSEARCH, 'txt', -+ record_type='txt', record_value=[TXT]) -+ ans = self.resolver.resolve(f'txt.{self.ZONE_PSEARCH}', 'TXT') -+ assert any(TXT in str(r) for r in ans) -+ -+ # Update TXT record and verify -+ tasks.mod_dns_record(self.master, self.ZONE_PSEARCH, 'txt', -+ record_type='txt', old_value=TXT, -+ new_value=NEW_TXT) -+ ans = self.resolver.resolve(f'txt.{self.ZONE_PSEARCH}', 'TXT') -+ assert any(NEW_TXT in str(r) for r in ans) -+ -+ # Get old serial -+ ans = self.resolver.resolve(self.ZONE_PSEARCH, 'SOA') -+ old_serial = ans[0].serial -+ -+ # Update TXT record again -+ tasks.mod_dns_record(self.master, self.ZONE_PSEARCH, 'txt', -+ record_type='txt', old_value=NEW_TXT, -+ new_value=NEWER_TXT) -+ -+ # Verify serial increased -+ ans = self.resolver.resolve(self.ZONE_PSEARCH, 'SOA') -+ new_serial = ans[0].serial -+ assert new_serial > old_serial, ( -+ f"New serial ({new_serial}) should be higher " -+ f"than old ({old_serial})") -diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py -index 864d5287f8317a5154aec4c792f56deab7ff0120..bff4b40aef6e5adec21c8929719e99669b80cdf0 100644 ---- a/ipatests/test_xmlrpc/test_dns_plugin.py -+++ b/ipatests/test_xmlrpc/test_dns_plugin.py -@@ -1088,7 +1088,7 @@ class test_dns(Declarative): - - - dict( -- desc='Create record %r in zone %r' % (zone1, name1), -+ desc='Create single A record %r in zone %r' % (name1, zone1), - command=('dnsrecord_add', [zone1, name1], {'arecord': arec2}), - expected={ - 'value': name1_dnsname, -@@ -1132,8 +1132,20 @@ class test_dns(Declarative): - - - dict( -- desc='Add A record to %r in zone %r' % (name1, zone1), -- command=('dnsrecord_add', [zone1, name1], {'arecord': arec3}), -+ desc='Delete single A record from %r in zone %r' % (name1, zone1), -+ command=('dnsrecord_del', [zone1, name1], {'arecord': arec2}), -+ expected={ -+ 'value': [name1_dnsname], -+ 'summary': u'Deleted record "%s"' % name1, -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Add multiple A records to %r in zone %r' % (name1, zone1), -+ command=('dnsrecord_add', [zone1, name1], -+ {'arecord': [arec2, arec3]}), - expected={ - 'value': name1_dnsname, - 'summary': None, -@@ -1148,14 +1160,29 @@ class test_dns(Declarative): - - - dict( -- desc='Remove A record from %r in zone %r' % (name1, zone1), -- command=('dnsrecord_del', [zone1, name1], {'arecord': arec2}), -+ desc='Delete multiple A records from %r in zone %r' % ( -+ name1, zone1), -+ command=('dnsrecord_del', [zone1, name1], -+ {'arecord': [arec2, arec3]}), - expected={ - 'value': [name1_dnsname], -+ 'summary': u'Deleted record "%s"' % name1, -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Re-add A record %r for subsequent tests' % arec3, -+ command=('dnsrecord_add', [zone1, name1], {'arecord': arec3}), -+ expected={ -+ 'value': name1_dnsname, - 'summary': None, - 'result': { -+ 'dn': name1_dn, - 'idnsname': [name1_dnsname], - 'arecord': [arec3], -+ 'objectclass': objectclasses.dnsrecord, - }, - }, - ), -@@ -1228,6 +1255,50 @@ class test_dns(Declarative): - }, - ), - -+ -+ dict( -+ desc='Try to add invalid AAAA record to %r in zone %r' % ( -+ name1, zone1), -+ command=('dnsrecord_add', [zone1, name1], -+ {'aaaarecord': u'invalid:ipv6:addr'}), -+ expected=errors.ValidationError( -+ name='ip_address', -+ error=u'invalid IP address format'), -+ ), -+ -+ -+ dict( -+ desc='Add AAAA record to %r in zone %r using dnsrecord_add' % ( -+ name1, zone1), -+ command=('dnsrecord_add', [zone1, name1], {'aaaarecord': aaaarec1}), -+ expected={ -+ 'value': name1_dnsname, -+ 'summary': None, -+ 'result': { -+ 'dn': name1_dn, -+ 'idnsname': [name1_dnsname], -+ 'arecord': [arec3], -+ 'aaaarecord': [aaaarec1], -+ 'objectclass': objectclasses.dnsrecord, -+ }, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Delete AAAA record from %r in zone %r using dnsrecord_del' % ( -+ name1, zone1), -+ command=('dnsrecord_del', [zone1, name1], {'aaaarecord': aaaarec1}), -+ expected={ -+ 'value': [name1_dnsname], -+ 'summary': None, -+ 'result': { -+ 'idnsname': [name1_dnsname], -+ 'arecord': [arec3], -+ }, -+ }, -+ ), -+ - dict( - desc='Try to add invalid MX record to zone %r using dnsrecord_add' % (zone1), - command=('dnsrecord_add', [zone1, u'@'], {'mxrecord': zone1_ns }), -@@ -1350,6 +1421,37 @@ class test_dns(Declarative): - }, - ), - -+ -+ dict( -+ desc='Add NAPTR record to zone %r using dnsrecord_add' % (zone1), -+ command=('dnsrecord_add', [zone1, u'_naptr'], -+ {'naptrrecord': u'100 10 "U" "E2U+sip" "" _sip._udp'}), -+ expected={ -+ 'value': DNSName(u'_naptr'), -+ 'summary': None, -+ 'result': { -+ 'objectclass': objectclasses.dnsrecord, -+ 'dn': DN(('idnsname', '_naptr'), zone1_dn), -+ 'idnsname': [DNSName(u'_naptr')], -+ 'naptrrecord': [u'100 10 "U" "E2U+sip" "" _sip._udp'], -+ }, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Delete NAPTR record from zone %r using dnsrecord_del' % ( -+ zone1), -+ command=('dnsrecord_del', [zone1, u'_naptr'], -+ {'naptrrecord': u'100 10 "U" "E2U+sip" "" _sip._udp'}), -+ expected={ -+ 'value': [DNSName(u'_naptr')], -+ 'summary': u'Deleted record "%s"' % u'_naptr', -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ - dict( - desc='Try to add CNAME record to %r using dnsrecord_add' % (name1), - command=('dnsrecord_add', [zone1, name1], {'cnamerecord': absnxname}), -@@ -1456,6 +1558,66 @@ class test_dns(Declarative): - '(RFC 2181, section 6.1)'), - ), - -+ -+ dict( -+ desc='Add DNAME record with underscore to zone %r' % (zone1), -+ command=('dnsrecord_add', [zone1, u'bar_underscore'], -+ {'dnamerecord': absnxname}), -+ expected={ -+ 'value': DNSName(u'bar_underscore'), -+ 'summary': None, -+ 'result': { -+ 'objectclass': objectclasses.dnsrecord, -+ 'dn': DN(('idnsname', 'bar_underscore'), zone1_dn), -+ 'idnsname': [DNSName(u'bar_underscore')], -+ 'dnamerecord': [absnxname], -+ }, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Delete DNAME record with underscore from zone %r' % (zone1), -+ command=('dnsrecord_del', [zone1, u'bar_underscore'], -+ {'dnamerecord': absnxname}), -+ expected={ -+ 'value': [DNSName(u'bar_underscore')], -+ 'summary': u'Deleted record "%s"' % u'bar_underscore', -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Add CERT record to zone %r using dnsrecord_add' % (zone1), -+ command=('dnsrecord_add', [zone1, u'_cert'], -+ {'certrecord': u'1 1 1 F835EDA21E94B565716F'}), -+ expected={ -+ 'value': DNSName(u'_cert'), -+ 'summary': None, -+ 'result': { -+ 'objectclass': objectclasses.dnsrecord, -+ 'dn': DN(('idnsname', '_cert'), zone1_dn), -+ 'idnsname': [DNSName(u'_cert')], -+ 'certrecord': [u'1 1 1 F835EDA21E94B565716F'], -+ }, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Delete CERT record from zone %r using dnsrecord_del' % ( -+ zone1), -+ command=('dnsrecord_del', [zone1, u'_cert'], -+ {'certrecord': u'1 1 1 F835EDA21E94B565716F'}), -+ expected={ -+ 'value': [DNSName(u'_cert')], -+ 'summary': u'Deleted record "%s"' % u'_cert', -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ - dict( - desc='Add NS+DNAME record to %r zone record using dnsrecord_add' % (zone2), - command=('dnsrecord_add', [zone2, u'@'], -@@ -1527,6 +1689,22 @@ class test_dns(Declarative): - ), - - -+ dict( -+ desc='Delete TXT record from %r using dnsrecord_del' % (name1), -+ command=('dnsrecord_del', [zone1, name1], -+ {'txtrecord': u'foo bar'}), -+ expected={ -+ 'value': [name1_dnsname], -+ 'summary': None, -+ 'result': { -+ 'idnsname': [name1_dnsname], -+ 'arecord': [arec3], -+ 'kxrecord': [u'1 foo-1'], -+ }, -+ }, -+ ), -+ -+ - dict( - desc='Try to add unresolvable absolute NS record to %r using dnsrecord_add' % (name_ns), - command=( -@@ -1879,6 +2057,38 @@ class test_dns(Declarative): - }, - ), - -+ -+ dict( -+ desc='Delete PTR record %r from %r using dnsrecord_del' % ( -+ revname1, revzone1), -+ command=('dnsrecord_del', [revzone1, revname1], -+ {'ptrrecord': absnxname}), -+ expected={ -+ 'value': [revname1_dnsname], -+ 'summary': u'Deleted record "%s"' % revname1, -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ -+ dict( -+ desc='Re-add PTR record %r to %r for subsequent tests' % ( -+ revname1, revzone1), -+ command=('dnsrecord_add', [revzone1, revname1], -+ {'ptrrecord': absnxname}), -+ expected={ -+ 'value': revname1_dnsname, -+ 'summary': None, -+ 'result': { -+ 'objectclass': objectclasses.dnsrecord, -+ 'dn': revname1_dn, -+ 'idnsname': [revname1_dnsname], -+ 'ptrrecord': [absnxname], -+ }, -+ }, -+ ), -+ -+ - dict( - desc='Update global DNS settings', - command=('dnsconfig_mod', [], {'idnsforwarders' : [fwd_ip],}), -@@ -3114,6 +3324,19 @@ class test_dns(Declarative): - ), - - -+ dict( -+ desc='Delete AFSDB record from %r in zone %r' % ( -+ dnsafsdbres1, idnzone1), -+ command=('dnsrecord_del', [idnzone1, dnsafsdbres1], -+ {'afsdbrecord': u'0 ' + idnzone1_mname}), -+ expected={ -+ 'value': [dnsafsdbres1_dnsname], -+ 'summary': u'Deleted record "%s"' % dnsafsdbres1, -+ 'result': {'failed': []}, -+ }, -+ ), -+ -+ - dict( - desc='Add A denormalized record in zone %r' % (idnzone1), - command=('dnsrecord_add', [idnzone1, u'gro\xdf'], {'arecord': u'172.16.0.1'}), -@@ -6427,6 +6650,61 @@ class test_dns_soa(Declarative): - u"A/AAAA record" % - zone6_unresolvable_ns_dnsname,), - ), -+ -+ dict( -+ desc='Adding a zone - %r - with invalid SOA refresh value' % zone6, -+ command=( -+ 'dnszone_add', [zone6], { -+ 'idnssoarefresh': 12345678901234, -+ }), -+ expected=errors.ValidationError( -+ name='refresh', -+ error=u'can be at most 2147483647'), -+ ), -+ -+ dict( -+ desc='Adding a zone - %r - with invalid SOA retry value' % zone6, -+ command=( -+ 'dnszone_add', [zone6], { -+ 'idnssoaretry': 12345678901234, -+ }), -+ expected=errors.ValidationError( -+ name='retry', -+ error=u'can be at most 2147483647'), -+ ), -+ -+ dict( -+ desc='Adding a zone - %r - with invalid SOA expire value' % zone6, -+ command=( -+ 'dnszone_add', [zone6], { -+ 'idnssoaexpire': 12345678901234, -+ }), -+ expected=errors.ValidationError( -+ name='expire', -+ error=u'can be at most 2147483647'), -+ ), -+ -+ dict( -+ desc='Adding a zone - %r - with invalid SOA minimum value' % zone6, -+ command=( -+ 'dnszone_add', [zone6], { -+ 'idnssoaminimum': 12345678901234, -+ }), -+ expected=errors.ValidationError( -+ name='minimum', -+ error=u'can be at most 2147483647'), -+ ), -+ -+ dict( -+ desc='Adding a zone - %r - with invalid TTL value' % zone6, -+ command=( -+ 'dnszone_add', [zone6], { -+ 'dnsttl': 12345678901234, -+ }), -+ expected=errors.ValidationError( -+ name='ttl', -+ error=u'can be at most 2147483647'), -+ ), - ] - - --- -2.52.0 - diff --git a/SOURCES/0010-ipa-advise-smart-card-client-script-does-not-need-kr.patch b/SOURCES/0010-ipa-advise-smart-card-client-script-does-not-need-kr.patch deleted file mode 100644 index 06dff82..0000000 --- a/SOURCES/0010-ipa-advise-smart-card-client-script-does-not-need-kr.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 5756ed2af940378c16d9d52e083b8c4005d41a13 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Wed, 21 Jan 2026 17:19:18 +0100 -Subject: [PATCH] ipa-advise: smart card client script does not need krb ticket - -The script generated by ipa-advise config-client-for-smart-card-auth -currently requires a kerberos ticket because it calls ipa-certupdate. - -Since IPA 4.9.0 and commit 1a09ce9, ipa-certupdate can be called -without a ticket. Update the script so that it detects if it gets -executed on a client recent enough to skip that requirement. - -Update the test for config-client-for-smart-card-auth, do not -call kinit admin on the client. - -Fixes: https://pagure.io/freeipa/issue/9923 -Signed-off-by: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden ---- - ipaserver/advise/plugins/smart_card_auth.py | 22 ++++++++++++++++++++- - ipatests/test_integration/test_advise.py | 10 +++++++--- - 2 files changed, 28 insertions(+), 4 deletions(-) - -diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py -index b79797dcaee0c881d3ef752a268ed520d96b433b..a0e50e9806f7843d2981141d8941d5e37f53c0cd 100644 ---- a/ipaserver/advise/plugins/smart_card_auth.py -+++ b/ipaserver/advise/plugins/smart_card_auth.py -@@ -34,6 +34,26 @@ class common_smart_card_auth_config(Advice): - 'Use kinit as privileged user to obtain Kerberos credentials' - ]) - -+ def check_ccache_not_empty_if_old_version(self): -+ self.log.comment("On version before IPA 4.9, " -+ "check that the credential cache is not empty") -+ self.log.command( -+ "python3 -c \"from ipapython.version import VERSION;" -+ "from ipaplatform.tasks import tasks;" -+ "exit(tasks.parse_ipa_version(VERSION) >= " -+ "tasks.parse_ipa_version('4.9.0'))\"") -+ with self.log.if_branch('[ "$?" -eq "0" ]'): -+ self.log.exit_on_failed_command( -+ 'klist', -+ [ -+ "Credential cache is empty", -+ 'Use kinit as privileged user to obtain Kerberos ' -+ 'credentials' -+ ]) -+ with self.log.else_branch(): -+ self.log.command( -+ "echo 'Version 4.9.0+ does not require Kerberos credentials'") -+ - def check_and_set_ca_cert_paths(self): - ca_paths_variable = self.smart_card_ca_certs_variable_name - single_ca_path_variable = self.single_ca_cert_variable_name -@@ -260,7 +280,7 @@ class config_client_for_smart_card_auth(common_smart_card_auth_config): - def get_info(self): - self.log.exit_on_nonroot_euid() - self.check_and_set_ca_cert_paths() -- self.check_ccache_not_empty() -+ self.check_ccache_not_empty_if_old_version() - self.check_and_remove_pam_pkcs11() - self.install_opensc_and_dconf_packages() - self.install_krb5_client_dependencies() -diff --git a/ipatests/test_integration/test_advise.py b/ipatests/test_integration/test_advise.py -index 3d5cadee319ebba14ebc43ebb1dc90a502e5d3b8..a336634ae9627133c5ad4dea4b1c43ffd726df10 100644 ---- a/ipatests/test_integration/test_advise.py -+++ b/ipatests/test_integration/test_advise.py -@@ -60,13 +60,17 @@ class TestAdvice(IntegrationTest): - ) - tasks.install_client(cls.master, cls.clients[0]) - -- def execute_advise(self, host, advice_id, *args): -+ def execute_advise(self, host, advice_id, *args, kinit=True): - # ipa-advise script is only available on a server - tasks.kinit_admin(self.master) - advice = self.master.run_command(['ipa-advise', advice_id]) - # execute script on host (client or master) - if host is not self.master: -- tasks.kinit_admin(host) -+ if kinit: -+ tasks.kinit_admin(host) -+ else: -+ # Make sure we don't have any ticket -+ tasks.kdestroy_all(host) - filename = tasks.upload_temp_contents(host, advice.stdout_text) - cmd = ['sh', filename] - cmd.extend(args) -@@ -181,7 +185,7 @@ class TestAdvice(IntegrationTest): - ca_pem = ExternalCA().create_ca() - ca_file = tasks.upload_temp_contents(client, ca_pem) - try: -- self.execute_advise(client, advice_id, ca_file) -+ self.execute_advise(client, advice_id, ca_file, kinit=False) - finally: - client.run_command(['rm', '-f', ca_file]) - --- -2.52.0 - diff --git a/SOURCES/0011-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch b/SOURCES/0011-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch deleted file mode 100644 index bc1e9a4..0000000 --- a/SOURCES/0011-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch +++ /dev/null @@ -1,177 +0,0 @@ -From fd84cec77d18e0e608285ab712f8211b0b49a7fe Mon Sep 17 00:00:00 2001 -From: PRANAV THUBE -Date: Thu, 18 Dec 2025 17:13:29 +0530 -Subject: [PATCH] ipatests: Fix resolver state tracking in enforced DNS policy - tests. - -Use resolver.setup_resolver() instead of direct file manipulation to -prevent state tracking failures in IDM-CI. Remove redundant manual -resolv.conf changes and fix operation order. - -Related: https://pagure.io/freeipa/issue/9904 -Reviewed-By: Antonio Torres ---- - ipatests/test_integration/test_edns.py | 70 ++++++++++++++++++-------- - 1 file changed, 50 insertions(+), 20 deletions(-) - -diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py -index d02ce45a1f9c0b04332e4ff4a055ab4c61b35eb4..54b7859da1b9fa320c07bdf29f79d2fdb292c337 100644 ---- a/ipatests/test_integration/test_edns.py -+++ b/ipatests/test_integration/test_edns.py -@@ -15,6 +15,9 @@ from ipatests.test_integration.test_dns import TestDNS - from ipatests.pytest_ipa.integration.firewall import Firewall - from ipaplatform.osinfo import osinfo - from ipaplatform.paths import paths -+from ipatests.pytest_ipa.integration.resolver import ( -+ resolver as detect_resolver -+) - - - def apply_enforced_dns_preconfig(host, master_ip, master_host, -@@ -30,6 +33,9 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host, - ca_cert_path: Path to the CA certificate on the master - dest_cert_name: Destination certificate filename in trust anchors - """ -+ # Backup resolver before making any changes (with original resolver type) -+ host.resolver.backup() -+ - # Get the network interface for routing - iface_cmd = host.run_command([ - "bash", "-c", -@@ -43,7 +49,8 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host, - host.put_file_contents(dest_ca_path, ca_cert_data) - host.run_command(["update-ca-trust", "extract"]) - -- if osinfo.id in ['rhel', 'centos']: -+ platform = tasks.get_platform(host) -+ if platform in ['rhel', 'centos']: - # RHEL/CentOS configuration - # Install and configure dnsconfd - tasks.install_packages(host, ['dnsconfd']) -@@ -51,31 +58,60 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host, - host.run_command(["systemctl", "enable", "--now", "dnsconfd"]) - host.run_command(["nmcli", "g", "reload"]) - -- # Configure DNS over TLS via NetworkManager -+ # Configure DNS over TLS via NetworkManager device-specific settings. -+ # Device-specific DNS settings work alongside global resolver config. - host.run_command([ - "nmcli", "device", "modify", iface, - "ipv4.dns", f"dns+tls://{master_ip}" - ]) - -- elif osinfo.id == 'fedora': -- # Fedora configuration -+ elif platform == 'fedora': - # Configure systemd-resolved for DNS over TLS - host.run_command([ -- "ln", "-sf", "../run/systemd/resolve/stub-resolv.conf", -+ "ln", "-sf", "/run/systemd/resolve/stub-resolv.conf", - "/etc/resolv.conf" - ]) -+ # Restart systemd-resolved to ensure DoT settings are fully applied -+ # and resolv.conf is updated - host.run_command(["systemctl", "restart", "systemd-resolved"]) -- -- # Configure DNS over TLS via systemd-resolve -+ # Configure DNS over TLS via systemd-resolve per-interface settings -+ # Per-interface DNS settings work alongside the global resolver config - host.run_command([ - "systemd-resolve", "--set-dns", master_ip, - "--set-dnsovertls=yes", f"--interface={iface}" - ]) - else: - raise ValueError( -- f"Unsupported OS for enforced DNS policy: {osinfo.id}" -+ f"Unsupported OS for enforced DNS policy: {platform}" - ) - -+ # Re-detect resolver since we may have changed the resolver type -+ # (e.g., from PlainFileResolver to ResolvedResolver on Fedora) -+ # This ensures state tracking works correctly with the new resolver type -+ # Store the original resolver so we can restore from it during uninstall -+ host._enforced_dns_original_resolver = host.resolver -+ host.resolver = detect_resolver(host) -+ host.resolver.current_state = host.resolver._get_state() -+ -+ -+def restore_enforced_dns_resolver(host): -+ """ -+ Restore resolver state after enforced DNS preconfig. -+ -+ This restores the original resolver backup created by -+ apply_enforced_dns_preconfig() and syncs host.resolver state. -+ """ -+ if not hasattr(host, '_enforced_dns_original_resolver'): -+ return -+ original_resolver = host._enforced_dns_original_resolver -+ if original_resolver.has_backups(): -+ original_resolver.current_state = original_resolver._get_state() -+ original_resolver.restore() -+ # Sync host.resolver state since original_resolver changed the config -+ host.resolver.backups.clear() -+ host.resolver.current_state = host.resolver._get_state() -+ delattr(host, '_enforced_dns_original_resolver') -+ - - def setup_dns_over_tls_environment(cls): - """ -@@ -451,17 +487,12 @@ class TestDNSOverTLS_EnforcedPolicy_IPA_CA(IntegrationTest): - tasks.install_master(self.master, extra_args=args) - - # Apply pre-configuration on client before installation -+ # This configures the resolver with master IP and domain - apply_enforced_dns_preconfig( - self.clients[0], self.master.ip, self.master, - paths.IPA_CA_CRT, "ca.crt" - ) - -- # Configure client nameserver -- self.clients[0].put_file_contents( -- paths.RESOLV_CONF, -- "nameserver %s" % self.master.ip -- ) -- - # Install client with enforced policy - args = [ - "--dns-over-tls", -@@ -506,6 +537,8 @@ class TestDNSOverTLS_EnforcedPolicy_IPA_CA(IntegrationTest): - """ - This test ensures that all hosts can be uninstalled correctly. - """ -+ for host in [self.clients[0], self.replicas[0]]: -+ restore_enforced_dns_resolver(host) - tasks.uninstall_client(self.clients[0]) - tasks.uninstall_replica(self.master, self.replicas[0]) - tasks.uninstall_master(self.master) -@@ -559,17 +592,12 @@ class TestDNSOverTLS_EnforcedPolicy_External_CA(IntegrationTest): - - # Apply pre-configuration on client before installation - # (includes CA cert setup) -+ # This configures the resolver with master IP and domain - apply_enforced_dns_preconfig( - self.clients[0], self.master.ip, self.master, - cert_dest, "certificate.pem" - ) - -- # Configure client nameserver -- self.clients[0].put_file_contents( -- paths.RESOLV_CONF, -- "nameserver %s" % self.master.ip -- ) -- - # Install client with enforced policy - args = [ - "--dns-over-tls", -@@ -615,6 +643,8 @@ class TestDNSOverTLS_EnforcedPolicy_External_CA(IntegrationTest): - """ - This test ensures that all hosts can be uninstalled correctly. - """ -+ for host in [self.clients[0], self.replicas[0]]: -+ restore_enforced_dns_resolver(host) - tasks.uninstall_client(self.clients[0]) - tasks.uninstall_replica(self.master, self.replicas[0]) - tasks.uninstall_master(self.master) --- -2.52.0 - diff --git a/SOURCES/0012-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch b/SOURCES/0012-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch deleted file mode 100644 index 23b13cf..0000000 --- a/SOURCES/0012-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch +++ /dev/null @@ -1,224 +0,0 @@ -From 0800065ac5555dba102f05c947ca47b5dc9a81af Mon Sep 17 00:00:00 2001 -From: Rafael Guterres Jeffman -Date: Fri, 23 Jan 2026 16:49:31 -0300 -Subject: [PATCH] freeipa.spec.in: Use systemd-sysusers to setup users and - groups - -System accounts for `kdcproxy` and `ipaapi` are now created with -sysusers configuration and macros. User `apache` is updated, by -adding it to group `ipaapi` using sysusers configuration. - -Fixes: https://pagure.io/freeipa/issue/9572 - -AI agent usage info: - -The initial changes were created by Claude by providing the following -context: - ->> Add support for creating users through systemd-sysusers by creating ->> a folder init/sysusersd, similar to init/tmpfilesd, changing install ->> paths in init/sysusersd/Makefile.am, adding configure option ->> --with-systemdsysusersdir similar to --with-systemdtmpfilesdir, and ->> adding a new file init/sysusersd/freeeipo.sysusers.in with the ->> contents: ->> ``` ->> # system accounts for IPA ->> u! kdcproxy - "IPA KDC Proxy Uer" ->> u! ipaapi - "IPA Framework User" ->> # - add Apache HTTPd user to ipaapi group ->> m apache ipaapi ->> ``` ->> and updating de spec file freeipa.spec.in - -LLM model used was Claude Sonnet 4.5, and a CLAUDE.md file was -automatically created by claude based on the freeipa repository. -No custom context was available for the agent. - -Assisted-by: Claude -Signed-off-by: Rafael Guterres Jeffman -Reviewed-By: David Hanina -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Alexander Bokovoy ---- - configure.ac | 42 ++++++++++++++++++++++++++------------ - freeipa.spec.in | 16 +++------------ - init/Makefile.am | 2 +- - init/sysusersd/Makefile.am | 12 +++++++++++ - init/sysusersd/ipa.conf.in | 8 ++++++++ - 5 files changed, 53 insertions(+), 27 deletions(-) - create mode 100644 init/sysusersd/Makefile.am - create mode 100644 init/sysusersd/ipa.conf.in - -diff --git a/configure.ac b/configure.ac -index 8b9adec1559c8831ef39c27860c1d31496ec5474..b0462bf779dedb7c2fe59494d4eb64a6dd121b1a 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -267,6 +267,13 @@ AC_ARG_WITH([systemdtmpfilesdir], - [systemdtmpfilesdir=$($PKG_CONFIG --define-variable=prefix='${prefix}' --variable=tmpfilesdir systemd)]) - AC_SUBST([systemdtmpfilesdir]) - -+AC_ARG_WITH([systemdsysusersdir], -+ AS_HELP_STRING([--with-systemdsysusersdir=DIR], -+ [Directory for systemd-sysusers configuration files]), -+ [systemdsysusersdir=$with_systemdsysusersdir], -+ [systemdsysusersdir=$($PKG_CONFIG --define-variable=prefix='${prefix}' --variable=sysusersdir systemd)]) -+AC_SUBST([systemdsysusersdir]) -+ - AC_ARG_WITH([systemdcatalogdir], - AS_HELP_STRING([--with-systemdcatalogdir=DIR], - [Directory for systemd journal catalog files]), -@@ -398,22 +405,29 @@ AC_SUBST([IPAPLATFORM]) - AC_MSG_RESULT([${IPAPLATFORM}]) - - if test "x${IPAPLATFORM}" == "xdebian"; then -- HTTPD_GROUP="www-data" -- KRB5KDC_SERVICE="krb5-kdc.service" -- NAMED_GROUP="bind" -- ODS_USER="opendnssec" -- ODS_GROUP="opendnssec" -- # see https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html -- PYTHON_INSTALL_EXTRA_OPTIONS="--install-layout=deb" -+ dnl Ubuntu http user is www-data -+ HTTPD_USER="www-data" -+ HTTPD_GROUP="www-data" -+ KRB5KDC_SERVICE="krb5-kdc.service" -+ NAMED_GROUP="bind" -+ ODS_USER="opendnssec" -+ ODS_GROUP="opendnssec" -+ # see https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html -+ PYTHON_INSTALL_EXTRA_OPTIONS="--install-layout=deb" - else -- HTTPD_GROUP="apache" -- KRB5KDC_SERVICE="krb5kdc.service" -- NAMED_GROUP="named" -- ODS_USER="ods" -- ODS_GROUP="ods" -- PYTHON_INSTALL_EXTRA_OPTIONS="" -+ HTTPD_USER="apache" -+ HTTPD_GROUP="apache" -+ KRB5KDC_SERVICE="krb5kdc.service" -+ NAMED_GROUP="named" -+ ODS_USER="ods" -+ ODS_GROUP="ods" -+ PYTHON_INSTALL_EXTRA_OPTIONS="" - fi - -+AC_MSG_CHECKING([HTTPD_USER]) -+AC_SUBST([HTTPD_USER]) -+AC_MSG_RESULT([${HTTPD_USER}]) -+ - AC_MSG_CHECKING([HTTPD_GROUP]) - AC_SUBST([HTTPD_GROUP]) - AC_MSG_RESULT([${HTTPD_GROUP}]) -@@ -654,6 +668,7 @@ AC_CONFIG_FILES([ - daemons/ipa-slapi-plugins/topology/Makefile - init/systemd/Makefile - init/tmpfilesd/Makefile -+ init/sysusersd/Makefile - init/Makefile - install/Makefile - install/certmonger/Makefile -@@ -736,6 +751,7 @@ AM_COND_IF([ENABLE_SERVER], [ - KRAD libs: ${KRAD_LIBS} - krb5rundir: ${krb5rundir} - systemdtmpfilesdir: ${systemdtmpfilesdir} -+ systemdsysusersdir: ${systemdsysusersdir} - build mode: server & client" - ], [ - echo "\ -diff --git a/freeipa.spec.in b/freeipa.spec.in -index f3b45a5308f93928a4d4bb4cbb2ae96c487cf88a..48912185073472c11f08d000dacf3a0b7f2ec668 100644 ---- a/freeipa.spec.in -+++ b/freeipa.spec.in -@@ -620,7 +620,7 @@ Requires: systemd-units >= %{systemd_version} - Requires: system-logos-ipa >= 80.4 - %endif - --# The list below is automatically generated by `fix-spec.sh -i` -+# The list below is automatically generated by `fix-spec.sh -i` - # from the install/freeipa-webui - Provides: bundled(npm(attr-accept)) = 2.2.5 - Provides: bundled(npm(cookie)) = 1.0.2 -@@ -1274,6 +1274,7 @@ fi - /bin/systemctl reload-or-try-restart dbus - /bin/systemctl reload-or-try-restart oddjobd - -+%sysusers_create %{_sysusersdir}/ipa.conf - %tmpfiles_create ipa.conf - %journal_catalog_update - -@@ -1331,18 +1332,6 @@ if [ -e /usr/sbin/ipa_kpasswd ]; then - fi - - --%pre server-common --# create users and groups --# create kdcproxy group and user --getent group kdcproxy >/dev/null || groupadd -f -r kdcproxy --getent passwd kdcproxy >/dev/null || useradd -r -g kdcproxy -s /sbin/nologin -d / -c "IPA KDC Proxy User" kdcproxy --# create ipaapi group and user --getent group ipaapi >/dev/null || groupadd -f -r ipaapi --getent passwd ipaapi >/dev/null || useradd -r -g ipaapi -s /sbin/nologin -d / -c "IPA Framework User" ipaapi --# add apache to ipaaapi group --id -Gn apache | grep '\bipaapi\b' >/dev/null || usermod apache -a -G ipaapi -- -- - %post server-dns - %systemd_post ipa-dnskeysyncd.service ipa-ods-exporter.socket ipa-ods-exporter.service - -@@ -1729,6 +1718,7 @@ fi - %dir %attr(0755,root,root) %{_sysconfdir}/ipa/kdcproxy - %config(noreplace) %{_sysconfdir}/ipa/kdcproxy/kdcproxy.conf - # NOTE: systemd specific section -+%{_sysusersdir}/ipa.conf - %{_tmpfilesdir}/ipa.conf - %attr(644,root,root) %{_unitdir}/ipa-custodia.service - %ghost %attr(644,root,root) %{etc_systemd_dir}/httpd.d/ipa.conf -diff --git a/init/Makefile.am b/init/Makefile.am -index 8f4d1d0a8f7e9739cf7587de6e000dd027a85146..1d4a85ab20e892c8a7c428b84a6393d29e9616e5 100644 ---- a/init/Makefile.am -+++ b/init/Makefile.am -@@ -2,7 +2,7 @@ - # - AUTOMAKE_OPTIONS = 1.7 - --SUBDIRS = systemd tmpfilesd -+SUBDIRS = systemd tmpfilesd sysusersd - - dist_sysconfenv_DATA = \ - ipa-dnskeysyncd \ -diff --git a/init/sysusersd/Makefile.am b/init/sysusersd/Makefile.am -new file mode 100644 -index 0000000000000000000000000000000000000000..8577255a61ac796353995d3d1f99de195f9bd7c0 ---- /dev/null -+++ b/init/sysusersd/Makefile.am -@@ -0,0 +1,12 @@ -+dist_noinst_DATA = \ -+ ipa.conf.in -+ -+systemdsysusers_DATA = \ -+ ipa.conf -+ -+CLEANFILES = $(systemdsysusers_DATA) -+ -+%: %.in Makefile -+ sed \ -+ -e 's|@HTTPD_USER[@]|$(HTTPD_USER)|g' \ -+ '$(srcdir)/$@.in' >$@ -diff --git a/init/sysusersd/ipa.conf.in b/init/sysusersd/ipa.conf.in -new file mode 100644 -index 0000000000000000000000000000000000000000..dcddfc2fc7969b86913ffcd8c397152e4f800fda ---- /dev/null -+++ b/init/sysusersd/ipa.conf.in -@@ -0,0 +1,8 @@ -+# IPA KDC Proxy user and group -+u kdcproxy - "IPA KDC Proxy User" -+ -+# IPA API user and group -+u ipaapi - "IPA API User" -+ -+# - add Apache system account to ipaapi group (platform-specific) -+m @HTTPD_USER@ ipaapi --- -2.52.0 - diff --git a/SOURCES/0013-ipatests-add-Random-Password-based-replica-promotion.patch b/SOURCES/0013-ipatests-add-Random-Password-based-replica-promotion.patch deleted file mode 100644 index e32bda0..0000000 --- a/SOURCES/0013-ipatests-add-Random-Password-based-replica-promotion.patch +++ /dev/null @@ -1,196 +0,0 @@ -From a55f9185c96457bdffe9099ddde39ec696f1f998 Mon Sep 17 00:00:00 2001 -From: Anuja More -Date: Tue, 6 Jan 2026 18:30:06 +0530 -Subject: [PATCH] ipatests: add Random Password based replica promotion - coverage - -Added missing test coverage for : -- Installing IPA replica server using random password. -- Installing IPA replica server using random password installed client - -- Automated with Cursor+Claude - -Fixes: https://pagure.io/freeipa/issue/9922 - -Signed-off-by: Anuja More -Reviewed-By: David Hanina -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden -Reviewed-By: Rob Crittenden -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: David Hanina ---- - .../nightly_ipa-4-13_latest.yaml | 12 +++ - .../nightly_ipa-4-13_latest_selinux.yaml | 13 +++ - ipatests/pytest_ipa/integration/tasks.py | 15 ++++ - .../test_replica_promotion.py | 87 +++++++++++++++++++ - 4 files changed, 127 insertions(+) - -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -index aff55727e463207fb235ff340989491e62162149..c61701ef5f88760f1d6fc36d4acce453a22b6f8f 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -@@ -1000,6 +1000,18 @@ jobs: - timeout: 7200 - topology: *ad_master_1repl_1client - -+ fedora-latest-ipa-4-13/test_replica_promotion_TestReplicaPromotionRandomPassword: -+ requires: [fedora-latest-ipa-4-13/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-latest-ipa-4-13/build_url}' -+ test_suite: test_integration/test_replica_promotion.py::TestReplicaPromotionRandomPassword -+ template: *ci-ipa-4-13-latest -+ timeout: 7200 -+ topology: *master_1repl -+ - fedora-latest-ipa-4-13/test_upgrade: - requires: [fedora-latest-ipa-4-13/build] - priority: 50 -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -index e6c57ea060b3bb8bfdf8b6f981f8fd28e4a7d320..9b96f3e857e2125478b45632d8d58e42b6e92668 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -@@ -1078,6 +1078,19 @@ jobs: - timeout: 7200 - topology: *ad_master_1repl_1client - -+ fedora-latest-ipa-4-13/test_replica_promotion_TestReplicaPromotionRandomPassword: -+ requires: [fedora-latest-ipa-4-13/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-latest-ipa-4-13/build_url}' -+ selinux_enforcing: True -+ test_suite: test_integration/test_replica_promotion.py::TestReplicaPromotionRandomPassword -+ template: *ci-ipa-4-13-latest -+ timeout: 7200 -+ topology: *master_1repl -+ - fedora-latest-ipa-4-13/test_upgrade: - requires: [fedora-latest-ipa-4-13/build] - priority: 50 -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index 32ac5cbc2c6fe87850dfb15c1d5beae6fa648dfb..ff2ea9792d04ebd2e6bd7bb3b51d97f35cb3fbfb 100755 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -3340,3 +3340,18 @@ def service_control_dirsrv(host, function='restart'): - instance = realm_to_serverid(host.domain.realm) - cmd = host.run_command(['systemctl', function, f"dirsrv@{instance}"]) - assert cmd.returncode == 0 -+ -+ -+def host_add_with_random_password(host, new_host): -+ """ -+ Add a new host with a random password and return the generated password. -+ """ -+ kinit_admin(host) -+ cmd = host.run_command( -+ ['ipa', 'host-add', new_host.hostname, '--random'] -+ ) -+ result = re.search("Random password: (?P.*$)", -+ cmd.stdout_text, -+ re.MULTILINE) -+ randpasswd1 = result.group('password') -+ return randpasswd1 -diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py -index 76d6aa24e2ab3d88b7013e0d107d0e27ae7f3426..f8c8414eefbc015cfc0947de575ea349a65a5e73 100644 ---- a/ipatests/test_integration/test_replica_promotion.py -+++ b/ipatests/test_integration/test_replica_promotion.py -@@ -1368,3 +1368,90 @@ class TestReplicaConn(IntegrationTest): - logs = self.replica.get_file_contents(paths.IPAREPLICA_CONNCHECK_LOG) - error = "not allowed to perform server connection check" - assert error.encode() not in logs -+ -+ -+class TestReplicaPromotionRandomPassword(IntegrationTest): -+ """ -+ Test installation of a replica using Random Password -+ (one step install and two-steps installation -+ with client and promotion). -+ """ -+ num_replicas = 1 -+ -+ @classmethod -+ def install(cls, mh): -+ tasks.install_master(cls.master, setup_dns=True) -+ cls.replicas[0].resolver.backup() -+ nameservers = cls.master.ip -+ cls.replicas[0].resolver.setup_resolver( -+ nameservers, cls.master.domain.name -+ ) -+ -+ @replicas_cleanup -+ def test_replica_random_password_install(self): -+ """ -+ Installing IPA replica server using Random Password. -+ -+ Steps: -+ 1. Ensure replica host/server entries are clean and add DNS A record. -+ 2. Add the replica host with a random password and add it to -+ the ipaservers hostgroup. -+ 3. Install the replica using random password. -+ """ -+ replica = self.replicas[0] -+ tasks.kinit_admin(self.master) -+ tasks.add_a_record(self.master, replica) -+ randpasswd = tasks.host_add_with_random_password(self.master, -+ replica) -+ self.master.run_command([ -+ 'ipa', 'hostgroup-add-member', '--hosts', -+ replica.hostname, 'ipaservers' -+ ]) -+ replica.run_command( -+ ['ipa-replica-install', '-p', randpasswd, '-U'] -+ ) -+ -+ @replicas_cleanup -+ def test_replica_two_step_install(self): -+ """ -+ Installing IPA replica server using Random Password installed client -+ -+ Steps: -+ 1. Ensure replica host/server entries are clean and add DNS A record. -+ 2. Add the replica host with a random password and add it to -+ the ipaservers hostgroup. -+ 3. Install the IPA client using the Random Password. -+ 4. Promote the client to a replica. -+ 5. Install CA on the replica and verify the server role. -+ """ -+ replica = self.replicas[0] -+ replica.resolver.backup() -+ tasks.kinit_admin(self.master) -+ tasks.add_a_record(self.master, replica) -+ randpasswd = tasks.host_add_with_random_password(self.master, -+ replica) -+ self.master.run_command([ -+ 'ipa', 'hostgroup-add-member', '--hosts', -+ replica.hostname, 'ipaservers' -+ ]) -+ replica.resolver.setup_resolver( -+ self.master.ip, self.master.domain.name -+ ) -+ replica.run_command( -+ ['ipa-client-install', '-w', randpasswd, '-U'] -+ ) -+ Firewall(replica).enable_services(["freeipa-ldap", -+ "freeipa-ldaps"]) -+ replica.run_command(['ipa-replica-install', '-U']) -+ tasks.kinit_admin(replica) -+ replica.run_command([ -+ 'ipa-ca-install', '-p', -+ self.master.config.admin_password, -+ '-w', self.master.config.admin_password -+ ]) -+ result = self.replicas[0].run_command([ -+ 'ipa', 'server-role-find', -+ '--server', self.replicas[0].hostname, -+ '--role', 'CA server' -+ ]) -+ assert 'Role status: enabled' in result.stdout_text --- -2.52.0 - diff --git a/SOURCES/0014-ipatests-Add-integration-tests-for-ipa-join-command.patch b/SOURCES/0014-ipatests-Add-integration-tests-for-ipa-join-command.patch deleted file mode 100644 index c94343b..0000000 --- a/SOURCES/0014-ipatests-Add-integration-tests-for-ipa-join-command.patch +++ /dev/null @@ -1,760 +0,0 @@ -From 7cb2c5d38a84eb31aa9ddd20cf9dc0b6d90fa242 Mon Sep 17 00:00:00 2001 -From: PRANAV THUBE -Date: Tue, 27 Jan 2026 18:45:53 +0530 -Subject: [PATCH] ipatests: Add integration tests for ipa-join command - -Add tests for ipa-join command covering hostname, server, keytab, -and bindpw options with positive and negative scenarios. - -Related: https://pagure.io/freeipa/issue/9930 -Reviewed-By: Rob Crittenden ---- - ipatests/prci_definitions/gating.yaml | 12 + - .../nightly_ipa-4-13_latest.yaml | 12 + - .../nightly_ipa-4-13_latest_selinux.yaml | 13 + - ipatests/pytest_ipa/integration/tasks.py | 49 ++ - ipatests/test_integration/test_ipa_join.py | 614 ++++++++++++++++++ - 5 files changed, 700 insertions(+) - create mode 100644 ipatests/test_integration/test_ipa_join.py - -diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml -index 0c2a0dafa9de12add8959d9974f080ba8bec0706..182f2c5a097856d22336fa66a0876bdfcf3f3f8d 100644 ---- a/ipatests/prci_definitions/gating.yaml -+++ b/ipatests/prci_definitions/gating.yaml -@@ -394,3 +394,15 @@ jobs: - template: *ci-ipa-4-13-latest - timeout: 7200 - topology: *master_3client -+ -+ fedora-latest-ipa-4-13/test_ipa_join: -+ requires: [fedora-latest-ipa-4-13/build] -+ priority: 100 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-latest-ipa-4-13/build_url}' -+ test_suite: test_integration/test_ipa_join.py -+ template: *ci-ipa-4-13-latest -+ timeout: 3600 -+ topology: *master_1repl_1client -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -index c61701ef5f88760f1d6fc36d4acce453a22b6f8f..aba33a5a05185460305c7516c93ae25c60f9dda7 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml -@@ -2292,3 +2292,15 @@ jobs: - template: *ci-ipa-4-13-latest - timeout: 2400 - topology: *ipaserver -+ -+ fedora-latest-ipa-4-13/test_ipa_join: -+ requires: [fedora-latest-ipa-4-13/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-latest-ipa-4-13/build_url}' -+ test_suite: test_integration/test_ipa_join.py -+ template: *ci-ipa-4-13-latest -+ timeout: 3600 -+ topology: *master_1repl_1client -diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -index 9b96f3e857e2125478b45632d8d58e42b6e92668..7184b722076ba2cab7d782d990a9bc218158a09f 100644 ---- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml -@@ -2476,3 +2476,16 @@ jobs: - template: *ci-ipa-4-13-latest - timeout: 2400 - topology: *ipaserver -+ -+ fedora-latest-ipa-4-13/test_ipa_join: -+ requires: [fedora-latest-ipa-4-13/build] -+ priority: 50 -+ job: -+ class: RunPytest -+ args: -+ build_url: '{fedora-latest-ipa-4-13/build_url}' -+ selinux_enforcing: True -+ test_suite: test_integration/test_ipa_join.py -+ template: *ci-ipa-4-13-latest -+ timeout: 3600 -+ topology: *master_1repl_1client -diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py -index ff2ea9792d04ebd2e6bd7bb3b51d97f35cb3fbfb..47330d6d93401485e4eb7b2501cf5ea37498d719 100755 ---- a/ipatests/pytest_ipa/integration/tasks.py -+++ b/ipatests/pytest_ipa/integration/tasks.py -@@ -3355,3 +3355,52 @@ def host_add_with_random_password(host, new_host): - re.MULTILINE) - randpasswd1 = result.group('password') - return randpasswd1 -+ -+ -+def ipa_join(host, *extra_args, raiseonerr=True): -+ """Run ipa-join command. -+ -+ :param host: The host to run command on -+ :param extra_args: Additional arguments (variable positional args) -+ e.g., '--hostname=client.example.com', -+ '--server=master.example.com', -+ '--keytab=/tmp/test.keytab', -+ '--bindpw=password', -+ '-u' (for unenroll) -+ :param raiseonerr: If True, raise exception on command failure -+ :return: Command result object -+ """ -+ command = ['ipa-join'] -+ command.extend(extra_args) -+ return host.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def host_del(host, hostname, *extra_args, raiseonerr=True): -+ """Delete a host from IPA. -+ -+ :param host: The IPA host to run command on -+ :param hostname: Hostname to delete -+ :param extra_args: Additional arguments (variable positional args) -+ :param raiseonerr: If True, raise exception on command failure -+ :return: Command result object -+ """ -+ command = ['ipa', 'host-del', hostname] -+ command.extend(extra_args) -+ return host.run_command(command, raiseonerr=raiseonerr) -+ -+ -+def host_add(host, hostname, *extra_args, password=None, raiseonerr=True): -+ """Add a host to IPA. -+ -+ :param host: The IPA host to run command on -+ :param hostname: Hostname to add -+ :param extra_args: Additional arguments (variable positional args) -+ :param password: OTP/enrollment password for the host (optional) -+ :param raiseonerr: If True, raise exception on command failure -+ :return: Command result object -+ """ -+ command = ['ipa', 'host-add', hostname] -+ if password: -+ command.append(f'--password={password}') -+ command.extend(extra_args) -+ return host.run_command(command, raiseonerr=raiseonerr) -diff --git a/ipatests/test_integration/test_ipa_join.py b/ipatests/test_integration/test_ipa_join.py -new file mode 100644 -index 0000000000000000000000000000000000000000..1f7592aec8db1bfd048ec574d06d25bc24373499 ---- /dev/null -+++ b/ipatests/test_integration/test_ipa_join.py -@@ -0,0 +1,614 @@ -+# -+# Copyright (C) 2026 FreeIPA Contributors see COPYING for license -+# -+ -+""" -+Tests for ipa-join command functionality. -+ -+This module tests various combinations of ipa-join options including: -+- hostname -+- server -+- keytab -+- bindpw (OTP/enrollment password) -+- unenroll -+ -+Ported from the shell-based test suite (t.ipajoin.sh and t.ipaotp.sh). -+""" -+ -+from __future__ import absolute_import -+ -+from ipapython.ipautil import ipa_generate_password -+from ipatests.pytest_ipa.integration import tasks -+from ipatests.test_integration.base import IntegrationTest -+ -+ -+# Constants -+OTP = ipa_generate_password(special=None) -+INVALID_PASSWORD = "WrongPassword" -+INVALID_SERVER = "No.Such.IPA.Server.Domain.com" -+TEST_KEYTAB = "/tmp/ipajoin.test.keytab" -+ -+# Error messages -+ERR_SASL_BIND_FAILED = "SASL Bind failed" -+ERR_UNAUTHENTICATED_BIND = "Unauthenticated binds are not allowed" -+ERR_COULD_NOT_RESOLVE = "JSON-RPC call failed: Could not resolve hostname" -+ERR_UNABLE_ROOT_DN = "Unable to determine root DN" -+ERR_NO_CONFIG = "Unable to determine IPA server from /etc/ipa/default.conf" -+ERR_PREAUTH_FAILED = "Generic preauthentication failure" -+ -+# Exit codes -+EXIT_SUCCESS = 0 -+EXIT_GENERAL_ERROR = 1 -+EXIT_PREAUTH_ERROR = 19 -+EXIT_ROOT_DN_ERROR = 14 -+EXIT_SASL_BIND_FAILED = 15 -+EXIT_RESOLVE_ERROR = 17 -+ -+ -+class TestIPAJoin(IntegrationTest): -+ """Tests for ipa-join command functionality. -+ -+ This test class covers various ipa-join scenarios including: -+ - Basic enrollment and unenrollment -+ - Using hostname, server, keytab, and bindpw options -+ - Positive and negative test cases -+ - OTP (one-time password) enrollment tests -+ -+ Tests require one master and one client. -+ """ -+ -+ topology = 'line' -+ num_clients = 1 -+ -+ @classmethod -+ def install(cls, mh): -+ tasks.install_master(cls.master, setup_dns=True) -+ tasks.install_client(cls.master, cls.clients[0]) -+ -+ @classmethod -+ def uninstall(cls, mh): -+ # Cleanup test keytab if exists -+ cls.clients[0].run_command( -+ ['rm', '-f', TEST_KEYTAB], -+ raiseonerr=False -+ ) -+ tasks.uninstall_client(cls.clients[0]) -+ tasks.uninstall_master(cls.master) -+ -+ # ========================================================================= -+ # ipa-join basic tests -+ # ========================================================================= -+ -+ def test_unenroll(self): -+ """Test ipa-join --unenroll option.""" -+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ assert result.returncode == EXIT_SUCCESS -+ -+ def test_unenroll_already_unenrolled(self): -+ """Test ipa-join -u on an already unenrolled client. -+ -+ When trying to unenroll a client that is not enrolled, -+ ipa-join should fail with a preauthentication error. -+ """ -+ # Client is already unenrolled from previous test -+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ assert result.returncode == EXIT_PREAUTH_ERROR -+ assert ERR_PREAUTH_FAILED in result.stderr_text -+ -+ def test_hostname_with_kerberos(self): -+ """Test ipa-join with --hostname using Kerberos auth.""" -+ tasks.kinit_admin(self.clients[0]) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_bindpw_invalid(self): -+ """Test ipa-join with hostname and invalid bindpw.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_hostname_bindpw_valid(self): -+ """Test ipa-join with hostname and valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_keytab_with_kerberos(self): -+ """Test ipa-join with hostname and keytab using Kerberos.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--keytab={TEST_KEYTAB}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_keytab_bindpw_invalid(self): -+ """Test ipa-join with hostname, keytab, and invalid bindpw.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_hostname_keytab_bindpw_valid(self): -+ """Test ipa-join with hostname, keytab, and valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_server_invalid_with_kerberos(self): -+ """Test ipa-join with hostname and invalid server.""" -+ tasks.kinit_admin(self.clients[0]) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={INVALID_SERVER}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_RESOLVE_ERROR -+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ -+ def test_hostname_server_invalid_bindpw_valid(self): -+ """Test ipa-join with hostname, invalid server, and valid OTP.""" -+ tasks.kinit_admin(self.clients[0]) -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={INVALID_SERVER}', -+ f'--bindpw={OTP}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_ROOT_DN_ERROR -+ assert ERR_UNABLE_ROOT_DN in result.stderr_text -+ -+ def test_hostname_server_invalid_keytab_with_kerberos(self): -+ """Test ipa-join with hostname, invalid server, keytab.""" -+ tasks.kinit_admin(self.clients[0]) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={INVALID_SERVER}', -+ f'--keytab={TEST_KEYTAB}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_RESOLVE_ERROR -+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ -+ def test_hostname_server_invalid_keytab_bindpw_valid(self): -+ """Test ipa-join with hostname, invalid server, keytab, valid OTP.""" -+ tasks.kinit_admin(self.clients[0]) -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={INVALID_SERVER}', -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={OTP}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_ROOT_DN_ERROR -+ assert ERR_UNABLE_ROOT_DN in result.stderr_text -+ -+ def test_hostname_server_valid_with_kerberos(self): -+ """Test ipa-join with hostname and valid server.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_server_valid_bindpw_invalid(self): -+ """Test ipa-join with hostname, valid server, invalid bindpw.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_hostname_server_valid_bindpw_valid(self): -+ """Test ipa-join with hostname, valid server, valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_server_valid_keytab_with_kerberos(self): -+ """Test ipa-join with hostname, valid server, keytab.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}', -+ f'--keytab={TEST_KEYTAB}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_hostname_server_valid_keytab_bindpw_invalid(self): -+ """Test ipa-join with hostname, valid server, keytab, bad bindpw.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}', -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ # Note: Original test had "SASL Bind Failed" (capital F), checking both -+ assert "SASL Bind" in result.stderr_text -+ assert "ailed" in result.stderr_text -+ -+ def test_hostname_server_valid_keytab_bindpw_valid(self): -+ """Test ipa-join with hostname, valid server, keytab, valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--server={self.master.hostname}', -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_keytab_only_with_kerberos(self): -+ """Test ipa-join with keytab only using Kerberos.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--keytab={TEST_KEYTAB}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_keytab_bindpw_invalid(self): -+ """Test ipa-join with keytab and invalid bindpw.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_keytab_bindpw_valid(self): -+ """Test ipa-join with keytab and valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--keytab={TEST_KEYTAB}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_server_invalid_only_with_kerberos(self): -+ """Test ipa-join with invalid server only.""" -+ tasks.kinit_admin(self.clients[0]) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={INVALID_SERVER}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_RESOLVE_ERROR -+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ -+ def test_server_invalid_bindpw_valid(self): -+ """Test ipa-join with invalid server and valid OTP.""" -+ tasks.kinit_admin(self.clients[0]) -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={INVALID_SERVER}', -+ f'--bindpw={OTP}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_ROOT_DN_ERROR -+ assert ERR_UNABLE_ROOT_DN in result.stderr_text -+ -+ def test_server_invalid_keytab_with_kerberos(self): -+ """Test ipa-join with invalid server and keytab.""" -+ tasks.kinit_admin(self.clients[0]) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={INVALID_SERVER}', -+ f'--keytab={TEST_KEYTAB}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_RESOLVE_ERROR -+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ -+ def test_server_valid_only_with_kerberos(self): -+ """Test ipa-join with valid server only.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={self.master.hostname}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_server_valid_bindpw_invalid(self): -+ """Test ipa-join with valid server and invalid bindpw.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={self.master.hostname}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_server_valid_bindpw_valid(self): -+ """Test ipa-join with valid server and valid OTP.""" -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={self.master.hostname}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_server_valid_keytab_with_kerberos(self): -+ """Test ipa-join with valid server and keytab.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname) -+ -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--server={self.master.hostname}', -+ f'--keytab={TEST_KEYTAB}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_bindpw_invalid_only(self): -+ """Test ipa-join with invalid bindpw only.""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ # ========================================================================= -+ # OTP (One-Time Password) tests -+ # ========================================================================= -+ -+ def test_otp_empty_password(self): -+ """Test ipa-join with empty OTP password (ipa_otp_1001).""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ '--bindpw=', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_ROOT_DN_ERROR -+ assert ERR_UNAUTHENTICATED_BIND in result.stderr_text -+ -+ def test_otp_wrong_password(self): -+ """Test ipa-join with wrong OTP password (ipa_otp_1002).""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={INVALID_PASSWORD}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text -+ -+ def test_otp_valid_password(self): -+ """Test ipa-join with valid OTP password (ipa_otp_1003).""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ try: -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ def test_otp_reuse_fails(self): -+ """Test that reusing the same OTP fails (ipa_otp_1004).""" -+ tasks.kinit_admin(self.clients[0]) -+ tasks.kinit_admin(self.master) -+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False) -+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP) -+ try: -+ # First use should succeed -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={OTP}' -+ ) -+ assert result.returncode == EXIT_SUCCESS -+ finally: -+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False) -+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False) -+ -+ # Second use of same OTP should fail -+ result = tasks.ipa_join( -+ self.clients[0], -+ f'--hostname={self.clients[0].hostname}', -+ f'--bindpw={OTP}', -+ raiseonerr=False -+ ) -+ -+ assert result.returncode == EXIT_SASL_BIND_FAILED -+ assert ERR_SASL_BIND_FAILED in result.stderr_text --- -2.52.0 - diff --git a/SOURCES/0015-fetch_domains-Use-case-insensitive-comparison-for-do.patch b/SOURCES/0015-fetch_domains-Use-case-insensitive-comparison-for-do.patch deleted file mode 100644 index 3fdbc7e..0000000 --- a/SOURCES/0015-fetch_domains-Use-case-insensitive-comparison-for-do.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 7f6a2835f0972af5e94b58daf47fa60bfade4279 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Thu, 22 Jan 2026 10:02:11 +0100 -Subject: [PATCH] fetch_domains: Use case-insensitive comparison for domains - names - -The fetch_domains method is using netr_DsRGetForestTrustInformation -to retrieve the forest trust information. The returned data contains -domain entries, with a DNS domain name that can contain mixed case -(for instance adDomain.Test). -The method compares the domain name with the provided parameter in a -case sensitive comparison, while it should use a case-insensitive -method (DNS names are case-insensitive). - -Fix the method and compare the lowercase value instead. - -Fixes: https://pagure.io/freeipa/issue/9924 -Signed-off-by: Florence Blanc-Renaud -Reviewed-By: Alexander Bokovoy ---- - ipaserver/dcerpc.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py -index 1182f128b4988bc699fe7a40d4834f1bead82cf5..5c05ffedb889e774e342cb6cb85ff954d06ac5e9 100644 ---- a/ipaserver/dcerpc.py -+++ b/ipaserver/dcerpc.py -@@ -1635,7 +1635,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None): - t.forest_trust_data.netbios_domain_name.string - - tname = unicode(t.forest_trust_data.dns_domain_name.string) -- if tname != trustdomain: -+ if tname.lower() != trustdomain.lower(): - result['domains'][tname] = { - 'cn': tname, - 'ipantflatname': unicode( -@@ -1647,7 +1647,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None): - record.data.string = t.forest_trust_data.string - - tname = unicode(t.forest_trust_data.string) -- if tname == trustdomain: -+ if tname.lower() == trustdomain.lower(): - continue - - result['suffixes'][tname] = {'cn': tname} --- -2.52.0 - diff --git a/SOURCES/0016-Handle-IPACertificate-types-in-xmlrpc.patch b/SOURCES/0016-Handle-IPACertificate-types-in-xmlrpc.patch deleted file mode 100644 index 1ac2456..0000000 --- a/SOURCES/0016-Handle-IPACertificate-types-in-xmlrpc.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 8deb4be0962b25dfd43e1245307a8bb9d58cfc48 Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Tue, 3 Feb 2026 09:46:25 -0500 -Subject: [PATCH] Handle IPACertificate types in xmlrpc - -The wrapping code didn't understand the IPACertificate class -so retrieving any entry that contained one would fail. - -Treat it the same was as its parent class cryptography.Certificate. - -Fixes: https://pagure.io/freeipa/issue/9935 - -Signed-off-by: Rob Crittenden -Reviewed-By: David Hanina ---- - ipalib/rpc.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/ipalib/rpc.py b/ipalib/rpc.py -index ed35afc965308e03269f05e01400660b207b548d..9773626eb054dd404256267c5fffbba1aa0579dd 100644 ---- a/ipalib/rpc.py -+++ b/ipalib/rpc.py -@@ -56,7 +56,7 @@ from ipalib.errors import (errors_by_code, UnknownError, NetworkError, - XMLRPCMarshallError, JSONError) - from ipalib import errors, capabilities - from ipalib.request import context, Connection --from ipalib.x509 import Encoding as x509_Encoding -+from ipalib.x509 import Encoding as x509_Encoding, IPACertificate - from ipapython import ipautil - from ipapython import session_storage - from ipapython.cookie import Cookie -@@ -220,7 +220,7 @@ def xml_wrap(value, version): - if isinstance(value, Principal): - return unicode(value) - -- if isinstance(value, crypto_x509.Certificate): -+ if isinstance(value, (crypto_x509.Certificate, IPACertificate)): - return base64.b64encode( - value.public_bytes(x509_Encoding.DER)).decode('ascii') - --- -2.52.0 - diff --git a/SOURCES/0017-Replace-None-with-when-uninstalling-CA.patch b/SOURCES/0017-Replace-None-with-when-uninstalling-CA.patch deleted file mode 100644 index f3f750d..0000000 --- a/SOURCES/0017-Replace-None-with-when-uninstalling-CA.patch +++ /dev/null @@ -1,159 +0,0 @@ -From a583b0dc08536a50e10b76e27861864b61906355 Mon Sep 17 00:00:00 2001 -From: David Hanina -Date: Mon, 2 Feb 2026 11:14:48 +0100 -Subject: [PATCH] Replace None with '' when uninstalling CA - -At many places we're obtaining records from a config file and expect the -config to have those keys, but the user may delete the line instead of -setting the value to false, this then leads to failed uninstall. This -patch replaces None with '' at some places that do not check for None. - -Fixes: https://pagure.io/freeipa/issue/9921 -Signed-off-by: David Hanina -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden ---- - ipaserver/install/ca.py | 12 ++++-- - ipaserver/install/cainstance.py | 14 ++++++- - ipaserver/install/server/upgrade.py | 3 +- - .../test_integration/test_crlgen_manage.py | 3 +- - .../test_integration/test_uninstallation.py | 41 +++++++++++++++++++ - 5 files changed, 66 insertions(+), 7 deletions(-) - -diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py -index 5a026aa4c556f7012552052cd08223746f3c39ae..2e953a567a3a230cb2a5e35192af76c61f8c1047 100644 ---- a/ipaserver/install/ca.py -+++ b/ipaserver/install/ca.py -@@ -340,14 +340,20 @@ def uninstall_crl_check(options): - - try: - crlgen_enabled = ca.is_crlgen_enabled() -- except cainstance.InconsistentCRLGenConfigException: -+ except cainstance.InconsistentCRLGenConfigException as e: - # If config is inconsistent, let's be safe and act as if - # crl gen was enabled -+ print(e) - crlgen_enabled = True - - if crlgen_enabled: -- print("Deleting this server will leave your installation " -- "without a CRL generation master.") -+ if not options.ignore_last_of_role: -+ print("Deleting this server will leave your installation " -+ "without a CRL generation master. Use --ignore-last-of-role " -+ "to bypass this check.") -+ else: -+ print("Deleting this server will leave your installation " -+ "without a CRL generation master.") - if (options.unattended and not options.ignore_last_of_role) or \ - not (options.unattended or ipautil.user_input( - "Are you sure you want to continue with the uninstall " -diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py -index b8267a625554f9375d27160f39b67ee2e64a2dbb..4933ad23d7323859af92bd02f6ae156803e29997 100644 ---- a/ipaserver/install/cainstance.py -+++ b/ipaserver/install/cainstance.py -@@ -1421,12 +1421,22 @@ class CAInstance(DogtagInstance): - try: - cache = directivesetter.get_directive( - self.config, 'ca.crl.MasterCRL.enableCRLCache', '=') -+ -+ if cache is None: -+ raise InconsistentCRLGenConfigException( -+ "Configuration is inconsistent, please check " -+ "ca.crl.MasterCRL.enableCRLCache, " -+ "ca.crl.MasterCRL.enableCRLUpdates and " -+ "ca.listenToCloneModifications in {} and " -+ "run ipa-crlgen-manage [enable|disable] to repair".format( -+ self.config)) -+ - enableCRLCache = cache.lower() == 'true' - updates = directivesetter.get_directive( -- self.config, 'ca.crl.MasterCRL.enableCRLUpdates', '=') -+ self.config, 'ca.crl.MasterCRL.enableCRLUpdates', '=') or '' - enableCRLUpdates = updates.lower() == 'true' - listen = directivesetter.get_directive( -- self.config, 'ca.listenToCloneModifications', '=') -+ self.config, 'ca.listenToCloneModifications', '=') or '' - enableToClone = listen.lower() == 'true' - updateinterval = directivesetter.get_directive( - self.config, 'ca.certStatusUpdateInterval', '=') -diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py -index 548ee02e1e8524ce0002dca1764d48728eb0509a..8692c983409426193e1746f07fa1a0514621cb4a 100644 ---- a/ipaserver/install/server/upgrade.py -+++ b/ipaserver/install/server/upgrade.py -@@ -1712,7 +1712,8 @@ def upgrade_configuration(): - if ca.is_configured(): - crl = directivesetter.get_directive( - paths.CA_CS_CFG_PATH, 'ca.crl.MasterCRL.enableCRLUpdates', '=') -- sub_dict['CLONE']='#' if crl.lower() == 'true' else '' -+ sub_dict['CLONE'] = '#' if crl is not None and \ -+ crl.lower() == 'true' else '' - - ds_dirname = dsinstance.config_dirname(ds.serverid) - -diff --git a/ipatests/test_integration/test_crlgen_manage.py b/ipatests/test_integration/test_crlgen_manage.py -index c6f41ebf8939bad8006b1e5eaf37bad30dbfd9d8..8a2a28a75b76158fcc61a8e7612f81343336b64f 100644 ---- a/ipatests/test_integration/test_crlgen_manage.py -+++ b/ipatests/test_integration/test_crlgen_manage.py -@@ -302,7 +302,8 @@ class TestCRLGenManage(IntegrationTest): - ['ipa-server-install', '--uninstall', '-U'], raiseonerr=False) - assert result.returncode == 1 - expected_msg = "Deleting this server will leave your installation " \ -- "without a CRL generation master" -+ "without a CRL generation master. Use " \ -+ "--ignore-last-of-role to bypass this check." - assert expected_msg in result.stdout_text - - def test_uninstall_with_ignore_last_of_role(self): -diff --git a/ipatests/test_integration/test_uninstallation.py b/ipatests/test_integration/test_uninstallation.py -index 8d83f72868f5c103b0c31d2aa96630c00b2dfbd8..12b10caa60745dcbc2d811bff65d57fb5d865f09 100644 ---- a/ipatests/test_integration/test_uninstallation.py -+++ b/ipatests/test_integration/test_uninstallation.py -@@ -237,3 +237,44 @@ class TestUninstallReinstall(IntegrationTest): - - def test_reinstall_server(self): - tasks.install_master(self.master, setup_dns=False) -+ -+ -+class TestUninstallCRLGen(IntegrationTest): -+ """Test uninstallation of a replica with broken CRL configuration. -+ -+ Removing ca.crl.MasterCRL.enableCRLCache from CS.cfg crashed. -+ https://pagure.io/freeipa/issue/9921 -+ """ -+ -+ num_replicas = 1 -+ topology = 'line' -+ -+ @classmethod -+ def install(cls, mh): -+ tasks.install_master(cls.master, setup_dns=False) -+ tasks.install_replica(cls.master, cls.replicas[0]) -+ -+ def test_uninstall_replica_with_broken_crlgen(self): -+ self.replicas[0].run_command(['ipa-crlgen-manage', 'enable']) -+ -+ self.replicas[0].run_command([ -+ '/bin/sed', -+ '-i', -+ '/ca.crl.MasterCRL.enableCRLCache=true/d', -+ paths.CA_CS_CFG_PATH, -+ ]) -+ -+ result = self.replicas[0].run_command([ -+ 'ipa-server-install', -+ '--ignore-last-of-role', -+ '--uninstall', '-U', -+ ]) -+ -+ expected_msg = "Configuration is inconsistent, please check " \ -+ "ca.crl.MasterCRL.enableCRLCache, " \ -+ "ca.crl.MasterCRL.enableCRLUpdates and " \ -+ "ca.listenToCloneModifications in {} and run " \ -+ "ipa-crlgen-manage [enable|disable] to repair".format( -+ paths.CA_CS_CFG_PATH) -+ -+ assert expected_msg in result.stdout_text --- -2.52.0 - diff --git a/SOURCES/0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch b/SOURCES/0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch deleted file mode 100644 index c80a432..0000000 --- a/SOURCES/0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch +++ /dev/null @@ -1,416 +0,0 @@ -From 91a6618e51b0e767c5cc5e4b1719531dbbd7268d Mon Sep 17 00:00:00 2001 -From: Sudhir Menon -Date: Thu, 22 Jan 2026 12:43:56 +0530 -Subject: [PATCH] ipatests: Add xmlrpc tests for ipa-delegation-cli - -This patch adds below test cases to the the XML-RPC delegation plugin test suite -coverage of delegation operations and important bug regressions. - -Test cases added: - - Test basic delegation creation with write permission - Test delegation creation with --all flag - Test delegation creation with --raw flag (ACI format) - Test deletion of delegation with ipausers group - Test finding delegation by name criteria - Test finding delegation by membergroup filter - Test showing delegation by name - Test modifying delegation attrs - Test modifying delegation permissions - BZ 783548: Verify mod fails when membergroup doesn't exist - BZ 783554: Verify mod with empty attrs fails properly - BZ 888524: Verify find --group option works correctly - -Fixes: https://pagure.io/freeipa/issue/9931 -Assisted-by: Claude -Signed-off-by: Sudhir Menon -Reviewed-By: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden -Reviewed-By: Anuja More ---- - .../test_xmlrpc/test_delegation_plugin.py | 371 ++++++++++++++++++ - 1 file changed, 371 insertions(+) - -diff --git a/ipatests/test_xmlrpc/test_delegation_plugin.py b/ipatests/test_xmlrpc/test_delegation_plugin.py -index b3d2aadbddbaaff6f40e1046e4df32bcc9ee7e2d..9245f259e21cad166c3c5b0565da3bb56a341e6b 100644 ---- a/ipatests/test_xmlrpc/test_delegation_plugin.py -+++ b/ipatests/test_xmlrpc/test_delegation_plugin.py -@@ -333,4 +333,375 @@ class test_delegation(Declarative): - summary=u'Deleted delegation "%s"' % delegation1, - ) - ), -+ -+ -+ dict( -+ desc='Create delegation with mobile attr and write permission', -+ command=( -+ 'delegation_add', [u'test_mobile_delegation'], dict( -+ attrs=[u'mobile'], -+ permissions=u'write', -+ group=u'editors', -+ memberof=u'admins', -+ ) -+ ), -+ expected=dict( -+ value=u'test_mobile_delegation', -+ summary=u'Added delegation "test_mobile_delegation"', -+ result=dict( -+ attrs=[u'mobile'], -+ permissions=[u'write'], -+ aciname=u'test_mobile_delegation', -+ group=u'editors', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Create delegation with --all flag', -+ command=( -+ 'delegation_add', [u'test_all_flag'], dict( -+ attrs=[u'mobile'], -+ permissions=u'write', -+ group=u'editors', -+ memberof=u'admins', -+ all=True, -+ ) -+ ), -+ expected=dict( -+ value=u'test_all_flag', -+ summary=u'Added delegation "test_all_flag"', -+ result=dict( -+ attrs=[u'mobile'], -+ permissions=[u'write'], -+ aciname=u'test_all_flag', -+ group=u'editors', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Create delegation with --raw flag', -+ command=( -+ 'delegation_add', [u'test_raw_flag'], dict( -+ attrs=[u'mobile'], -+ permissions=u'write', -+ group=u'editors', -+ memberof=u'admins', -+ raw=True, -+ ) -+ ), -+ expected=dict( -+ value=u'test_raw_flag', -+ summary=u'Added delegation "test_raw_flag"', -+ result={ -+ 'aci': u'(targetattr = "mobile")(targetfilter = ' -+ u'"(memberOf=%s)")(version 3.0;acl ' -+ u'"delegation:test_raw_flag";allow (write) ' -+ u'groupdn = "ldap:///%s";)' % ( -+ DN(('cn', 'admins'), ('cn', 'groups'), -+ ('cn', 'accounts'), api.env.basedn), -+ DN(('cn', 'editors'), ('cn', 'groups'), -+ ('cn', 'accounts'), api.env.basedn)) -+ }, -+ ), -+ ), -+ -+ -+ dict( -+ desc='Delete test_mobile_delegation', -+ command=('delegation_del', [u'test_mobile_delegation'], {}), -+ expected=dict( -+ result=True, -+ value=u'test_mobile_delegation', -+ summary=u'Deleted delegation "test_mobile_delegation"', -+ ) -+ ), -+ -+ -+ dict( -+ desc='Delete test_all_flag', -+ command=('delegation_del', [u'test_all_flag'], {}), -+ expected=dict( -+ result=True, -+ value=u'test_all_flag', -+ summary=u'Deleted delegation "test_all_flag"', -+ ) -+ ), -+ -+ -+ dict( -+ desc='Delete test_raw_flag', -+ command=('delegation_del', [u'test_raw_flag'], {}), -+ expected=dict( -+ result=True, -+ value=u'test_raw_flag', -+ summary=u'Deleted delegation "test_raw_flag"', -+ ) -+ ), -+ -+ -+ dict( -+ desc='Create delegation for ipausers group', -+ command=( -+ 'delegation_add', [u'delegation_del_positive_1001'], dict( -+ attrs=[u'mobile'], -+ group=u'ipausers', -+ memberof=u'admins', -+ ) -+ ), -+ expected=dict( -+ value=u'delegation_del_positive_1001', -+ summary=u'Added delegation "delegation_del_positive_1001"', -+ result=dict( -+ attrs=[u'mobile'], -+ permissions=[u'write'], -+ aciname=u'delegation_del_positive_1001', -+ group=u'ipausers', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Delete delegation_del_positive_1001', -+ command=('delegation_del', [u'delegation_del_positive_1001'], {}), -+ expected=dict( -+ result=True, -+ value=u'delegation_del_positive_1001', -+ summary=u'Deleted delegation "delegation_del_positive_1001"', -+ ) -+ ), -+ -+ -+ dict( -+ desc='Create delegation for find, show, and mod tests', -+ command=( -+ 'delegation_add', [u'delegation_find_show_mod_test'], dict( -+ attrs=[u'mobile'], -+ permissions=u'write', -+ group=u'editors', -+ memberof=u'admins', -+ ) -+ ), -+ expected=dict( -+ value=u'delegation_find_show_mod_test', -+ summary=u'Added delegation "delegation_find_show_mod_test"', -+ result=dict( -+ attrs=[u'mobile'], -+ permissions=[u'write'], -+ aciname=u'delegation_find_show_mod_test', -+ group=u'editors', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Find delegation by name', -+ command=('delegation_find', [u'delegation_find_show_mod_test'], {}), -+ expected=dict( -+ count=1, -+ truncated=False, -+ summary=u'1 delegation matched', -+ result=[ -+ { -+ 'attrs': [u'mobile'], -+ 'permissions': [u'write'], -+ 'aciname': u'delegation_find_show_mod_test', -+ 'group': u'editors', -+ 'memberof': member1, -+ }, -+ ], -+ ), -+ ), -+ -+ -+ dict( -+ desc='Find delegation by membergroup', -+ command=('delegation_find', [], {'memberof': member1}), -+ expected=dict( -+ count=1, -+ truncated=False, -+ summary=u'1 delegation matched', -+ result=[ -+ { -+ 'attrs': [u'mobile'], -+ 'permissions': [u'write'], -+ 'aciname': u'delegation_find_show_mod_test', -+ 'group': u'editors', -+ 'memberof': member1, -+ }, -+ ], -+ ), -+ ), -+ -+ -+ dict( -+ desc='Show delegation by name', -+ command=('delegation_show', [u'delegation_find_show_mod_test'], {}), -+ expected=dict( -+ value=u'delegation_find_show_mod_test', -+ summary=None, -+ result={ -+ 'attrs': [u'mobile'], -+ 'permissions': [u'write'], -+ 'aciname': u'delegation_find_show_mod_test', -+ 'group': u'editors', -+ 'memberof': member1, -+ }, -+ ), -+ ), -+ -+ -+ dict( -+ desc='Modify delegation attrs', -+ command=( -+ 'delegation_mod', [u'delegation_find_show_mod_test'], -+ dict(attrs=[u'l']) -+ ), -+ expected=dict( -+ value=u'delegation_find_show_mod_test', -+ summary=u'Modified delegation "delegation_find_show_mod_test"', -+ result=dict( -+ attrs=[u'l'], -+ permissions=[u'write'], -+ aciname=u'delegation_find_show_mod_test', -+ group=u'editors', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Modify delegation permissions', -+ command=( -+ 'delegation_mod', [u'delegation_find_show_mod_test'], -+ dict(permissions=u'read') -+ ), -+ expected=dict( -+ value=u'delegation_find_show_mod_test', -+ summary=u'Modified delegation "delegation_find_show_mod_test"', -+ result=dict( -+ attrs=[u'l'], -+ permissions=[u'read'], -+ aciname=u'delegation_find_show_mod_test', -+ group=u'editors', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Delete delegation_find_show_mod_test', -+ command=('delegation_del', [u'delegation_find_show_mod_test'], {}), -+ expected=dict( -+ result=True, -+ value=u'delegation_find_show_mod_test', -+ summary=u'Deleted delegation "delegation_find_show_mod_test"', -+ ) -+ ), -+ -+ -+ dict( -+ desc='Create delegation for BZ tests', -+ command=( -+ 'delegation_add', [u'delegation_bz_test'], dict( -+ attrs=[u'mobile'], -+ permissions=u'write', -+ group=u'ipausers', -+ memberof=u'admins', -+ ) -+ ), -+ expected=dict( -+ value=u'delegation_bz_test', -+ summary=u'Added delegation "delegation_bz_test"', -+ result=dict( -+ attrs=[u'mobile'], -+ permissions=[u'write'], -+ aciname=u'delegation_bz_test', -+ group=u'ipausers', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Try to modify with non-existent membergroup (BZ 783548)', -+ command=( -+ 'delegation_mod', [u'delegation_bz_test'], -+ dict(memberof=u'badmembergroup') -+ ), -+ expected=errors.NotFound( -+ reason=u'badmembergroup: group not found'), -+ ), -+ -+ -+ dict( -+ desc='Try to modify attrs with empty value (BZ 783554)', -+ command=( -+ 'delegation_mod', [u'delegation_bz_test'], dict(attrs=u'') -+ ), -+ expected=errors.RequirementError(name='attrs'), -+ ), -+ -+ -+ dict( -+ desc='Modify attrs to prepare for next BZ test', -+ command=( -+ 'delegation_mod', [u'delegation_bz_test'], dict(attrs=[u'l']) -+ ), -+ expected=dict( -+ value=u'delegation_bz_test', -+ summary=u'Modified delegation "delegation_bz_test"', -+ result=dict( -+ attrs=[u'l'], -+ permissions=[u'write'], -+ aciname=u'delegation_bz_test', -+ group=u'ipausers', -+ memberof=member1, -+ ), -+ ), -+ ), -+ -+ -+ dict( -+ desc='Find delegation by group filter (BZ 888524)', -+ command=('delegation_find', [], {'group': u'ipausers'}), -+ expected=dict( -+ count=1, -+ truncated=False, -+ summary=u'1 delegation matched', -+ result=[ -+ { -+ 'attrs': [u'l'], -+ 'permissions': [u'write'], -+ 'aciname': u'delegation_bz_test', -+ 'group': u'ipausers', -+ 'memberof': member1, -+ }, -+ ], -+ ), -+ ), -+ -+ -+ dict( -+ desc='Delete delegation_bz_test', -+ command=('delegation_del', [u'delegation_bz_test'], {}), -+ expected=dict( -+ result=True, -+ value=u'delegation_bz_test', -+ summary=u'Deleted delegation "delegation_bz_test"', -+ ) -+ ), -+ - ] --- -2.52.0 - diff --git a/SOURCES/0019-ipa-join-initialize-pointer.patch b/SOURCES/0019-ipa-join-initialize-pointer.patch deleted file mode 100644 index edf13e6..0000000 --- a/SOURCES/0019-ipa-join-initialize-pointer.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 7cc96e42683a6d3ec9f2dc2a19e99330b6f3ce58 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Wed, 4 Feb 2026 09:21:14 +0100 -Subject: [PATCH] ipa-join: initialize pointer - -OpenScanHub detected an uninitialized pointer in ipa_join: -Slapi_DN *sdn; -... -if (sdn) slapi_sdn_free(&sdn); - -Initialize to NULL -Also initialize Slapi_Backend *be=NULL and char * filter=NULL -to avoid potential issues. - -Fixes: https://pagure.io/freeipa/issue/9936 -Signed-off-by: Florence Blanc-Renaud -Reviewed-By: Rob Crittenden -Reviewed-By: Rafael Guterres Jeffman ---- - daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c -index 3a70dd0a5594fc623e7e808ab8a734349a748a49..2f8923e10310a8a6e19ac701070d6451915c3be3 100644 ---- a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c -+++ b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c -@@ -129,8 +129,8 @@ ipa_join(Slapi_PBlock *pb) - Slapi_PBlock *pbte = NULL; - Slapi_PBlock *pbtm = NULL; - Slapi_Entry *targetEntry=NULL; -- Slapi_DN *sdn; -- Slapi_Backend *be; -+ Slapi_DN *sdn=NULL; -+ Slapi_Backend *be=NULL; - Slapi_Entry **es = NULL; - int rc=0, ret=0, res; - size_t i; -@@ -139,7 +139,7 @@ ipa_join(Slapi_PBlock *pb) - char *fqdn = NULL; - Slapi_Mods *smods = NULL; - char *attrlist[] = {"fqdn", "krbPrincipalKey", "krbLastPwdChange", "krbPrincipalName", NULL }; -- char * filter; -+ char * filter=NULL; - - int scope = LDAP_SCOPE_SUBTREE; - char *principal = NULL; --- -2.52.0 - diff --git a/SOURCES/0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch b/SOURCES/0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch deleted file mode 100644 index 88a5d7d..0000000 --- a/SOURCES/0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch +++ /dev/null @@ -1,39 +0,0 @@ -From d5efb4decb74b50c05b1d252add1c075e660d154 Mon Sep 17 00:00:00 2001 -From: Florence Blanc-Renaud -Date: Wed, 11 Feb 2026 10:23:36 +0100 -Subject: [PATCH] ipatests: pruning is enabled when RSN is enabled - -The test TestACMEPrune installs the server with --random-serial-numbers -but expects pruning to be disabled if the 389ds backend is BDB. - -This is a wrong expectation as pruning is enabled as soon as RSN -are enabled (since commit 3777d2b). -Fix the test expectation. - -Signed-off-by: Florence Blanc-Renaud ---- - ipatests/test_integration/test_acme.py | 8 ++------ - 1 file changed, 2 insertions(+), 6 deletions(-) - -diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py -index 4c66e4348beeaca95577a786a46e53fdc1532ef7..bedec9d1f9f0c168c11aceb155978b6a0dae8dd7 100644 ---- a/ipatests/test_integration/test_acme.py -+++ b/ipatests/test_integration/test_acme.py -@@ -718,12 +718,8 @@ class TestACMEPrune(IntegrationTest): - < tasks.parse_version('11.3.0')): - raise pytest.skip("Certificate pruning is not available") - -- # Pruning is enabled by default when the host supports lmdb -- if get_389ds_backend(self.master) == 'bdb': -- cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH) -- assert "jobsScheduler.job.pruning.enabled=false".encode() in cs_cfg -- self.master.run_command(['ipa-acme-manage', 'pruning', '--enable']) -- -+ # Pruning is enabled by default when server is installed -+ # with --random-serial-numbers - cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH) - assert "jobsScheduler.enabled=true".encode() in cs_cfg - assert "jobsScheduler.job.pruning.enabled=true".encode() in cs_cfg --- -2.52.0 - diff --git a/SOURCES/freeipa-4.13.1.tar.gz.asc b/SOURCES/freeipa-4.13.1.tar.gz.asc deleted file mode 100644 index 044d140..0000000 --- a/SOURCES/freeipa-4.13.1.tar.gz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCgAdFiEE11Z2TU1+KXxtrRFyaYdvcqbi008FAmlqBAcACgkQaYdvcqbi -009J4Q//cZi2ZS9RNBkU/v7v7Uvfb7WbsTAUQ2V5EJlqQ7BXbn2tkLtACbzeRn6X -aLECdZK+0etdyjbFlb7CvR6GUKw3BCZXpV62XXSQ3/EOhGRtGbrzRiIgaYOmFjzb -Hs4Ini6x/QClb0iA478ajG3YHrNYJhy3fFj+s3xzNGZuhJLvhlg3C2VDG7AgrzEx -RPiK7DIPY3SVUayxGuPLkNxsZiEQ1IcYtIu++Wp9QHmgPlAdP5aZ2TcEeotgUq4Z -+1LXEsDywEdQpg2iO92rja01q+zlcxzuQVjgdEh0G9lIw6f/GtB5WTdm2SER26oq -tld6MI6vrWZY+zMSc6FVjy0UB1Tk1kjjp7dp8OQhgzpe2i7LLKZl8bwRhvV6XlZT -+ylGHNK5cFglgtOjxIyLVGy6p6PIM+FtEFigUHB5faH+UrWTIL1+zrqWEhhiykI+ -TT7/plOxG+tALr3ZDOZVqn1Idh9UQNQ8YdPzzKQMKAx5bbm5uHJXR6FQWt/P1QVc -S2N1Jlgi8IzbrnVPqN04BDCKsbOOWK6hP8a/SxoGW2IgTd7QWXalfRkeD4vdnM/1 -9RRWRGuWWKuk2EUlKK202za+wJ7oOY4JSLXC3zxo1bTsHnUN+SYun+5BgYaiKLbM -dXZ4K+9Fs4clmT38W3I2LWPFoKcZVczN6/3MpQbTvmgEOkx8LZ0= -=jpva ------END PGP SIGNATURE----- diff --git a/SOURCES/freeipa-4.13.2.tar.gz.asc b/SOURCES/freeipa-4.13.2.tar.gz.asc new file mode 100644 index 0000000..8c3c03c --- /dev/null +++ b/SOURCES/freeipa-4.13.2.tar.gz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEE11Z2TU1+KXxtrRFyaYdvcqbi008FAmpYqDYACgkQaYdvcqbi +008XZw//bTJT+5DkI/fFA9YrZJ7aHDdiV59NmaY9p8vsONL93ZeZ1CObKsywCrMN +4lSEZ+/SANF+LHqjnXViYtctsOR0jYxwEiJxVcMJU/YkZwOJwnwp3C9vPA9OO/9Y +MXW/QYMDGGAPAtDwXqMAsZTP9JRrljw7dvdJtjK5Eq4MUoYFnrBLUXH8XYZq6UoO +X+z9hkfDVDqzGginPd+hautapF5TgN07tUBQCx4GIW9Y+4CgtnJSUTMoDo/c42AF +zy2VmPnRsHb7XIVw4nQW/ZGVq0e13C2Os11JRKWk6yd3xBLEewgSjvEWhJi4wXik +e6G1fSoIX2zl7q5cxiIlvJZv+qja3FGKYmrBC0fdVtFaBqnOJNuSm49vVz2LcB9R +VJdWWXksE+A3MEyCKE4haiMhVj9BpEaT6DUEAksQPAAXyLiOoZLIln+XHk56tTPg +1EFeUrhnrJj4s5Mo3ODxRU+VtCi+VAfvClXNuxHQBfGajO35OVeqS1Ioe4H/f4YI +YazMSSGdlfQbSeKt8n98C0QOTq7jxSnHgeUilpGC3Eum1J59jb5swGbT/yJ/5haU +PaY6kkNxoY5AzxjbcSA/k9Uw/wJF4I9IsfHuBY0ISN150amqAE1IlQzqphjTCV3o +x/Z3GT9uOqBWr4JewvpA5DpwX/bD2N+57vqWiRGXACVZnN2PwwU= +=H7LS +-----END PGP SIGNATURE----- diff --git a/SPECS/freeipa.spec b/SPECS/freeipa.spec index 7704463..63db0db 100644 --- a/SPECS/freeipa.spec +++ b/SPECS/freeipa.spec @@ -69,7 +69,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.23.0 +%global samba_version 4.24.5 %global slapi_nis_version 0.60.0 %global python_ldap_version 3.1.0-1 # version supporting Allow Uniqueness plugin to search uniqueness attributes @@ -79,12 +79,12 @@ %else %global ds_version 3.1.3-5 %endif -%global selinux_policy_version 38.1.1-1 +%global selinux_policy_version 38.1.83-1 %if 0%{?rhel} >= 10 %global krb5_version 1.21.3-6 %elif 0%{?rhel} == 9 -%global krb5_version 1.21.1-5 +%global krb5_version 1.21.1-10 %else %global krb5_version 1.18.2-31 %endif @@ -93,7 +93,7 @@ %global httpd_version 2.4.37-21 # support for passkey -%global sssd_version 2.9.7 +%global sssd_version 2.9.9-3 %else # Fedora @@ -164,7 +164,7 @@ # BIND employs 'pkcs11' OpenSSL engine instead of native PKCS11 # Fedora 31+ uses OpenSSL engine, as well as Fedora ELN (RHEL9) %if 0%{?fedora} || 0%{?rhel} >= 9 - %global openssl_pkcs11_version 0.4.10-6 + %global openssl_pkcs11_version 0.4.11-9 %global softhsm_version 2.5.0-4 %else %global with_bind_pkcs11 1 @@ -216,7 +216,7 @@ # Work-around fact that RPM SPEC parser does not accept # "Version: @VERSION@" in freeipa.spec.in used for Autoconf string replacement -%define IPA_VERSION 4.13.1 +%define IPA_VERSION 4.13.2 # Release candidate version -- uncomment with one percent for RC versions #%%global rc_version %define AT_SIGN @ @@ -229,7 +229,7 @@ Name: %{package_name} Version: %{IPA_VERSION} -Release: 3%{?rc_version:.%rc_version}%{?dist} +Release: 2%{?rc_version:.%rc_version}%{?dist} Summary: The Identity, Policy and Audit system License: GPL-3.0-or-later @@ -252,21 +252,8 @@ Source1: https://releases.pagure.org/freeipa/freeipa-%{version}%{?rc_vers Patch0001: 0001-Revert-Replace-netifaces-with-ifaddr.patch Patch0002: 0002-Revert-custodia-do-not-use-deprecated-jwcrypto-wrapp.patch Patch0003: 0003-Revert-Remove-NIS-server-support.patch +Patch0004: 0004-Change-u-to-u-for-sysusersd.patch Patch0006: 0006-Revert-Stop-using-deprecated-pkg_resources.patch -Patch0007: 0007-ipatests-Move-expire_password-fixture-into-TestIPACo.patch -Patch0008: 0008-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch -Patch0009: 0009-ipatests-Add-DNS-functional-integration-tests.patch -Patch0010: 0010-ipa-advise-smart-card-client-script-does-not-need-kr.patch -Patch0011: 0011-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch -Patch0012: 0012-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch -Patch0013: 0013-ipatests-add-Random-Password-based-replica-promotion.patch -Patch0014: 0014-ipatests-Add-integration-tests-for-ipa-join-command.patch -Patch0015: 0015-fetch_domains-Use-case-insensitive-comparison-for-do.patch -Patch0016: 0016-Handle-IPACertificate-types-in-xmlrpc.patch -Patch0017: 0017-Replace-None-with-when-uninstalling-CA.patch -Patch0018: 0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch -Patch0019: 0019-ipa-join-initialize-pointer.patch -Patch0020: 0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch %endif %endif @@ -324,7 +311,7 @@ BuildRequires: libsss_nss_idmap-devel >= %{sssd_version} BuildRequires: nodejs(abi) == 127 %elif 0%{?fedora} >= 39 # Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374 -BuildRequires: nodejs(abi) < 127 +BuildRequires: nodejs(abi) < 127, /usr/bin/node, /usr/bin/npm %else BuildRequires: nodejs(abi) < 111 %endif @@ -1405,8 +1392,8 @@ if [ $1 -gt 1 ] ; then if grep -E -q 'Include' $SSH_CLIENT_SYSTEM_CONF 2>/dev/null ; then SSH_CLIENT_SYSTEM_CONF="/etc/ssh/ssh_config.d/04-ipa.conf" fi - sed -E --in-place=.orig 's/^(GlobalKnownHostsFile \/var\/lib\/sss\/pubconf\/known_hosts)$/# disabled by ipa-client update\n# \1/' $SSH_CLIENT_SYSTEM_CONF - sed -E --in-place=.orig 's/(ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h)/# replaced by ipa-client update\n KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H/' $SSH_CLIENT_SYSTEM_CONF + sed -E --in-place=.orig 's/^(GlobalKnownHostsFile \/var\/lib\/sss\/pubconf\/known_hosts)$/# disabled by ipa-client update\n# \1/' $SSH_CLIENT_SYSTEM_CONF || : + sed -E --in-place=.orig 's/(ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h)/# replaced by ipa-client update\n KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H/' $SSH_CLIENT_SYSTEM_CONF || : fi fi fi @@ -1478,9 +1465,9 @@ if [ -f '/etc/ssh/sshd_config' -a $restore -ge 2 ]; then GLOBALKNOWNHOSTFILE="GlobalKnownHostsFile /var/lib/sss/pubconf/known_hosts/" grep -qF '$GLOBALKNOWNHOSTFILE' $SSH_CLIENT_SYSTEM_CONF if [ $? -ne 0 ]; then - sed -E --in-place=.orig '/(# IPA-related configuration changes to ssh_config)/a # added by ipa-client update\n'"$GLOBALKNOWNHOSTFILE"'' $SSH_CLIENT_SYSTEM_CONF + sed -E --in-place=.orig '/(# IPA-related configuration changes to ssh_config)/a # added by ipa-client update\n'"$GLOBALKNOWNHOSTFILE"'' $SSH_CLIENT_SYSTEM_CONF || : fi - sed -E --in-place=.orig 's/(KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H)/ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h/' $SSH_CLIENT_SYSTEM_CONF + sed -E --in-place=.orig 's/(KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H)/ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h/' $SSH_CLIENT_SYSTEM_CONF || : fi fi fi @@ -1499,8 +1486,8 @@ if [ -f '/etc/ssh/sshd_config' -a $restore -ge 2 ]; then if grep -E -q 'Include' $SSH_CLIENT_SYSTEM_CONF 2>/dev/null ; then SSH_CLIENT_SYSTEM_CONF="/etc/ssh/ssh_config.d/04-ipa.conf" fi - sed -E --in-place=.orig 's/^(GlobalKnownHostsFile \/var\/lib\/sss\/pubconf\/known_hosts)$/# disabled by ipa-client update\n# \1/' $SSH_CLIENT_SYSTEM_CONF - sed -E --in-place=.orig 's/(ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h)/# replaced by ipa-client update\n KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H/' $SSH_CLIENT_SYSTEM_CONF + sed -E --in-place=.orig 's/^(GlobalKnownHostsFile \/var\/lib\/sss\/pubconf\/known_hosts)$/# disabled by ipa-client update\n# \1/' $SSH_CLIENT_SYSTEM_CONF || : + sed -E --in-place=.orig 's/(ProxyCommand \/usr\/bin\/sss_ssh_knownhostsproxy -p \%p \%h)/# replaced by ipa-client update\n KnownHostsCommand \/usr\/bin\/sss_ssh_knownhosts \%H/' $SSH_CLIENT_SYSTEM_CONF || : fi fi fi @@ -1735,6 +1722,8 @@ fi %dir %{_usr}/share/ipa/ui/css %{_usr}/share/ipa/ui/css/*.css %dir %{_usr}/share/ipa/ui/js +%dir %{_usr}/share/ipa/ui/js/inline +%{_usr}/share/ipa/ui/js/inline/*.js %dir %{_usr}/share/ipa/ui/js/dojo %{_usr}/share/ipa/ui/js/dojo/dojo.js %dir %{_usr}/share/ipa/ui/js/libs @@ -1765,8 +1754,6 @@ fi %ghost %attr(0640,root,root) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf %ghost %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipa/kdcproxy/ipa-kdc-proxy.conf %ghost %attr(0644,root,root) %config(noreplace) %{_usr}/share/ipa/html/ca.crt -%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-ext.conf -%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-options-ext.conf %dir %{_usr}/share/ipa/updates/ %{_usr}/share/ipa/updates/* %dir %{_localstatedir}/lib/ipa @@ -1779,7 +1766,6 @@ fi %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/private %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/passwds %ghost %attr(775,root,pkiuser) %{_localstatedir}/lib/ipa/pki-ca/publish -%ghost %attr(770,named,named) %{_localstatedir}/named/dyndb-ldap/ipa %dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia %dir %{_usr}/share/ipa/schema.d %attr(0644,root,root) %{_usr}/share/ipa/schema.d/README @@ -1801,6 +1787,9 @@ fi %attr(644,root,root) %{_unitdir}/ipa-dnskeysyncd.service %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service +%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-ext.conf +%ghost %attr(0640,root,named) %config(noreplace) %{_sysconfdir}/named/ipa-options-ext.conf +%ghost %attr(770,named,named) %{_localstatedir}/named/dyndb-ldap/ipa %files server-encrypted-dns %doc README.md Contributors.txt @@ -1979,6 +1968,40 @@ fi %endif %changelog +* Mon Aug 03 2026 Florence Blanc-Renaud - 4.13.2-2 +- Resolves: RHEL-222583 ipa-server-trust-ad package installation failing for samba dependency + +* Mon Jul 20 2026 David Hanina - 4.13.2-1 +- Resolves: RHEL-166742 [Cursor Automated] Include latest fixes in python3-ipatests package [RHEL9.9] +- Resolves: RHEL-151581 Pagure #9948: Extend SELinux policy to allow use of SSSD authentication helpers on non-enrolled hosts [rhel-9] +- Resolves: RHEL-151560 ipa-migrate doesn't process users' sshpublickeys [rhel-9] +- Resolves: RHEL-179216 ipa-migrate tool is renaming host records & host info in automount information [RHEL 9.9] +- Resolves: RHEL-212442 freeipa-client %triggerin and %post can fail (rpm scriptlets should never fail, by policy) [rhel-9] +- Resolves: RHEL-212443 ipa-migrate doesn't properly handle replication conflicts [rhel-9] +- Resolves: RHEL-212444 ipa-migrate doesn't migrate IPA locations [rhel-9] +- Resolves: RHEL-212445 Pagure #9948: Extend SELinux policy to allow use of SSSD authentication helpers on non-enrolled hosts [rhel-9] +- Resolves: RHEL-212446 [RFE] X-Content-Type-Options header in IdM WebUI [rhel-9] +- Resolves: RHEL-212447 [RFE] Implement Referrer-Policy on IdM WebUI [rhel-9] +- Resolves: RHEL-212448 [RFE] CSP not implemented in IdM WebUI [rhel-9] +- Resolves: RHEL-212449 IPA compatibility with tomcat 10 [rhel-9] +- Resolves: RHEL-212450 Webui 3007 Requirement error "userCertificate" is required (not allowing certificate management for idview) [rhel-9] +- Resolves: RHEL-212451 To add "SameSite" flag to ipa_session cookies [rhel-9] +- Resolves: RHEL-212452 FreeIPA JSON-RPC CSRF Referer Validation Uses Naive Prefix Match - [rhel-9] +- Resolves: RHEL-212471 Pagure #9820: ipalockout_postop is incorrectly warning about locked user [rhel-9] +- Resolves: RHEL-178920 Rebase ipa to latest version for RHEL 9.9 + +* Tue Jun 02 2026 Florence Blanc-Renaud - 4.13.1-4 +- Related: RHEL-175124 Rebuilt for samba 4.24.3 + +* Thu Apr 16 2026 David Hanina - 4.13.1-3.2 +- Related: RHEL-166865 Include latest fixes in python3-ipatests package [rhel-9.8.z] + +* Mon Apr 13 2026 David Hanina - 4.13.1-3.1 +- Resolves: RHEL-166865 Include latest fixes in python3-ipatests package [rhel-9.8.z] +- Resolves: RHEL-155037 Pagure #9953: Adding a group with 32Bit Idrange fails. [rhel-9.8.z] +- Resolves: RHEL-153146 IdM password policy Min lifetime is not enforced when high minlife is set [rhel-9.8.z] +- Resolves: RHEL-168047 ipa ca-show ipa --all failing to list RSN version + * Tue Feb 10 2026 Florence Blanc-Renaud - 4.13.1-3 - RHEL-148282 ipa-replica-conncheck fails with "an internal error has occured" - RHEL-148481 Pruning is enabled by default with RSN on RHEL 9.8