ipa-4.13.1-2

- Resolves: RHEL-146023 When using xmlrpc, ipa server failed with assert type(value) in (unicode, float, int, bool, type(None))
-Resolves:  RHEL-145855 Include latest fixes in python3-ipatests package
-Resolves:  RHEL-88855 ipa uninstallation is failing with message "'NoneType' object has no attribute 'lower'"
-Resolves:  RHEL-43143 ipa-advise client script requires keytab (should just require root access on client system)
-Resolves:  RHEL-4895 ipa use systemd-sysusers
-Resolves:  RHEL-4823 Names of domains from a trusted forest should be compared case-insentive

Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
This commit is contained in:
Florence Blanc-Renaud 2026-02-06 12:58:04 +01:00
parent fd81c0a70e
commit 72f8d73c00
14 changed files with 3643 additions and 14 deletions

View File

@ -0,0 +1,120 @@
From 1c86c973c8dc778a71c8e2abf54ff37ececdd696 Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
Date: Tue, 13 Jan 2026 19:30:09 +0530
Subject: [PATCH] ipatests: Move expire_password fixture into TestIPACommand
class
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
.../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

View File

@ -0,0 +1,48 @@
From ecc0efa96e3c446586425d470657de7f2d5376bf Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
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 <flo@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
From 5756ed2af940378c16d9d52e083b8c4005d41a13 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
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 <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
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

View File

@ -0,0 +1,177 @@
From fd84cec77d18e0e608285ab712f8211b0b49a7fe Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
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 <antorres@redhat.com>
---
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

View File

@ -0,0 +1,224 @@
From 0800065ac5555dba102f05c947ca47b5dc9a81af Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
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 <noreply@anthropic.com>
Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
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

View File

@ -0,0 +1,196 @@
From a55f9185c96457bdffe9099ddde39ec696f1f998 Mon Sep 17 00:00:00 2001
From: Anuja More <amore@redhat.com>
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 <amore@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
.../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<password>.*$)",
+ 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

View File

@ -0,0 +1,760 @@
From 7cb2c5d38a84eb31aa9ddd20cf9dc0b6d90fa242 Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
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 <rcritten@redhat.com>
---
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

View File

@ -0,0 +1,48 @@
From 7f6a2835f0972af5e94b58daf47fa60bfade4279 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
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 <flo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
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

View File

@ -0,0 +1,43 @@
From 8deb4be0962b25dfd43e1245307a8bb9d58cfc48 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
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 <rcritten@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
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

View File

@ -0,0 +1,159 @@
From a583b0dc08536a50e10b76e27861864b61906355 Mon Sep 17 00:00:00 2001
From: David Hanina <dhanina@redhat.com>
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 <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
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

View File

@ -0,0 +1,416 @@
From 91a6618e51b0e767c5cc5e4b1719531dbbd7268d Mon Sep 17 00:00:00 2001
From: Sudhir Menon <sumenon@redhat.com>
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 <noreply@anthropic.com>
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Anuja More <amore@redhat.com>
---
.../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

View File

@ -0,0 +1,49 @@
From 7cc96e42683a6d3ec9f2dc2a19e99330b6f3ce58 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
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 <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
---
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

View File

@ -234,7 +234,7 @@
Name: %{package_name}
Version: %{IPA_VERSION}
Release: 1%{?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
@ -262,6 +262,19 @@ Source2: gpgkey-0E63D716D76AC080A4A33513F40800B6298EB963.asc
# RHEL spec file only: START
%if %{NON_DEVELOPER_BUILD}
%if 0%{?rhel} >= 9
Patch0001: 0001-ipatests-Move-expire_password-fixture-into-TestIPACo.patch
Patch0002: 0002-ipatests-Fix-xfail-assertion-for-sssd-2.12.0.patch
Patch0003: 0003-ipatests-Add-DNS-functional-integration-tests.patch
Patch0004: 0004-ipa-advise-smart-card-client-script-does-not-need-kr.patch
Patch0005: 0005-ipatests-Fix-resolver-state-tracking-in-enforced-DNS.patch
Patch0006: 0006-freeipa.spec.in-Use-systemd-sysusers-to-setup-users-.patch
Patch0007: 0007-ipatests-add-Random-Password-based-replica-promotion.patch
Patch0008: 0008-ipatests-Add-integration-tests-for-ipa-join-command.patch
Patch0009: 0009-fetch_domains-Use-case-insensitive-comparison-for-do.patch
Patch0010: 0010-Handle-IPACertificate-types-in-xmlrpc.patch
Patch0011: 0011-Replace-None-with-when-uninstalling-CA.patch
Patch0012: 0012-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch
Patch0013: 0013-ipa-join-initialize-pointer.patch
Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch
%endif
%endif
@ -601,7 +614,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
@ -1247,6 +1260,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
@ -1304,18 +1318,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
@ -1702,6 +1704,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
@ -1979,6 +1982,14 @@ fi
%endif
%changelog
* Fri Feb 06 2026 Florence Blanc-Renaud <flo@redhat.com> - 4.13.1-2
- Resolves: RHEL-146023 When using xmlrpc, ipa server failed with assert type(value) in (unicode, float, int, bool, type(None))
-Resolves: RHEL-145855 Include latest fixes in python3-ipatests package
-Resolves: RHEL-88855 ipa uninstallation is failing with message "'NoneType' object has no attribute 'lower'"
-Resolves: RHEL-43143 ipa-advise client script requires keytab (should just require root access on client system)
-Resolves: RHEL-4895 ipa use systemd-sysusers
-Resolves: RHEL-4823 Names of domains from a trusted forest should be compared case-insentive
* Fri Jan 16 2026 Florence Blanc-Renaud <flo@redhat.com> - 4.13.1-1
- Resolves: RHEL-140587 Support replaceable WebUI artwork for RHEL and CentOS
- Resolves: RHEL-113778 Command that retrieve and install new CA certificates