ipa-4.13.2-1
- Resolves: RHEL-178898 Rebase ipa to latest version for RHEL 10.3 - Resolves: RHEL-179217 ipa-migrate tool is renaming host records & host info in automount information - Resolves: RHEL-178538 [Cursor Automated] Include latest fixes in python3-ipatests package - Resolves: RHEL-174346 Release Modern UI progress to RHEL 10.3 - Resolves: RHEL-153857 ipa-migrate doesn't properly handle replication conflicts - Resolves: RHEL-153852 ipa-migrate doesn't migrate IPA locations - Resolves: RHEL-151578 Extend SELinux policy to allow use of SSSD authentication helpers on non-enrolled hosts - Resolves: RHEL-151326 freeipa-client %triggerin and %post can fail (rpm scriptlets should never fail, by policy) - Resolves: RHEL-147173 ipa-migrate doesn't process users' sshpublickeys - Resolves: RHEL-107899 [RFE] X-Content-Type-Options header in IdM WebUI - Resolves: RHEL-107897 [RFE] Implement Referrer-Policy on IdM WebUI - Resolves: RHEL-107045 [RFE] CSP not implemented in IdM WebUI - Resolves: RHEL-105139 IPA compatibility with tomcat 10 - Resolves: RHEL-103492 ipalockout_postop is incorrectly warning about locked user - Resolves: RHEL-70229 To add "SameSite" flag to ipa_session cookies - Resolves: RHEL-29029 Webui 3007 Requirement error "userCertificate" is required (not allowing certificate manage - Resolves: RHEL-154589 FreeIPA JSON-RPC CSRF Referer Validation Uses Naive Prefix Match Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
This commit is contained in:
parent
d45c80b778
commit
e41f833b15
2
.gitignore
vendored
2
.gitignore
vendored
@ -142,3 +142,5 @@
|
||||
/freeipa-4.13.0.tar.gz.asc
|
||||
/freeipa-4.13.1.tar.gz
|
||||
/freeipa-4.13.1.tar.gz.asc
|
||||
/freeipa-4.13.2.tar.gz
|
||||
/freeipa-4.13.2.tar.gz.asc
|
||||
|
||||
@ -1,120 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
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
@ -1,99 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,224 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,196 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,760 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,159 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,416 +0,0 @@
|
||||
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
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,154 +0,0 @@
|
||||
From 7b0ac4f3dd8febf0c22a11bf2db9b780a242e302 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Mon, 26 Jan 2026 19:25:17 -0500
|
||||
Subject: [PATCH] Avoid int overflow with pwpolicy minlife
|
||||
|
||||
This converts the value to an unsigned integer instead and
|
||||
sets the max value to match the maxlife (~54 years).
|
||||
|
||||
A syslog log message was added to explain why the "too young"
|
||||
message is returned
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9929
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
---
|
||||
.../ipa-slapi-plugins/ipa-pwd-extop/common.c | 2 +-
|
||||
ipaserver/plugins/pwpolicy.py | 1 +
|
||||
ipatests/test_integration/test_pwpolicy.py | 50 ++++++++++++++++---
|
||||
util/ipa_pwd.c | 1 +
|
||||
util/ipa_pwd.h | 2 +-
|
||||
5 files changed, 48 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
index 0e69f3410737ea1787e611846f1a8801239f4d2f..71642540a203e9d01f35eb1a8b2bcd3c6408245a 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
@@ -407,7 +407,7 @@ int ipapwd_getPolicy(const char *dn,
|
||||
}
|
||||
|
||||
/* read data out of policy object */
|
||||
- policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife");
|
||||
+ policy->min_pwd_life = slapi_entry_attr_get_uint(pe, "krbMinPwdLife");
|
||||
|
||||
policy->max_pwd_life = slapi_entry_attr_get_int(pe, "krbMaxPwdLife");
|
||||
|
||||
diff --git a/ipaserver/plugins/pwpolicy.py b/ipaserver/plugins/pwpolicy.py
|
||||
index e49a2e1cd35cfd4e5798c58329a7212c4a5b7598..2d9907d9d2f3723e023cc530f85d20ffad92778c 100644
|
||||
--- a/ipaserver/plugins/pwpolicy.py
|
||||
+++ b/ipaserver/plugins/pwpolicy.py
|
||||
@@ -338,6 +338,7 @@ class pwpolicy(LDAPObject):
|
||||
label=_('Min lifetime (hours)'),
|
||||
doc=_('Minimum password lifetime (in hours)'),
|
||||
minvalue=0,
|
||||
+ maxvalue=480000, # about 54 years, same as maxlife
|
||||
),
|
||||
Int('krbpwdhistorylength?',
|
||||
cli_name='history',
|
||||
diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py
|
||||
index a627b66ce69e2d554df0bdceef1d24394082a7bd..f7fd40ada226e5dc65f50f27031ebe249346fbee 100644
|
||||
--- a/ipatests/test_integration/test_pwpolicy.py
|
||||
+++ b/ipatests/test_integration/test_pwpolicy.py
|
||||
@@ -16,17 +16,14 @@ PASSWORD = 'Secret123'
|
||||
POLICY = 'test'
|
||||
|
||||
|
||||
-class TestPWPolicy(IntegrationTest):
|
||||
+class BasePWpolicy(IntegrationTest):
|
||||
"""
|
||||
- Test password policy in action.
|
||||
+ Base class for testing password policies including libpwquality
|
||||
"""
|
||||
- num_replicas = 1
|
||||
-
|
||||
- topology = 'line'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- super(TestPWPolicy, cls).install(mh)
|
||||
+ tasks.install_master(cls.master, setup_dns=True)
|
||||
|
||||
tasks.kinit_admin(cls.master)
|
||||
cls.master.run_command(['ipa', 'user-add', USER,
|
||||
@@ -66,6 +63,15 @@ class TestPWPolicy(IntegrationTest):
|
||||
host.run_command(['ipa', 'user-unlock', user])
|
||||
tasks.kdestroy_all(host)
|
||||
|
||||
+
|
||||
+class TestPWquality(BasePWpolicy):
|
||||
+ """
|
||||
+ libpwquality tests
|
||||
+ """
|
||||
+ num_replicas = 1
|
||||
+
|
||||
+ topology = 'line'
|
||||
+
|
||||
def set_pwpolicy(self, minlength=None, maxrepeat=None, maxsequence=None,
|
||||
dictcheck=None, usercheck=None, minclasses=None,
|
||||
dcredit=None, ucredit=None, lcredit=None, ocredit=None):
|
||||
@@ -534,3 +540,35 @@ class TestPWPolicy(IntegrationTest):
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
+
|
||||
+
|
||||
+class TestPWpolicy(BasePWpolicy):
|
||||
+ """
|
||||
+ Tests for original/Kerberos password policies. Excludes libpwquality
|
||||
+ """
|
||||
+
|
||||
+ # NOTE: set/reset/clear methods to be added later once there is more
|
||||
+ # than a single test.
|
||||
+
|
||||
+ def test_minlife_overflow(self):
|
||||
+ """Test that a large minlife doesn't overflow an unsigned int."""
|
||||
+ newpassword = "Secret.1234"
|
||||
+
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "pwpolicy-mod", POLICY, "--minlife", "480000",
|
||||
+ "--maxlife", "20000",]
|
||||
+ )
|
||||
+
|
||||
+ self.kinit_as_user(self.master, PASSWORD, PASSWORD)
|
||||
+
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "passwd", USER],
|
||||
+ raiseonerr=False,
|
||||
+ stdin_text='{password}\n{password}\n{newpassword}'.format(
|
||||
+ password=PASSWORD, newpassword=newpassword
|
||||
+ ))
|
||||
+
|
||||
+ assert result.returncode == 1
|
||||
+ assert "Too soon to change password" in result.stderr_text
|
||||
diff --git a/util/ipa_pwd.c b/util/ipa_pwd.c
|
||||
index ba68601066bc42d89ba00037ae3ec8b787a4e858..56135aaff333ff3a69311b55ae350e923453ea35 100644
|
||||
--- a/util/ipa_pwd.c
|
||||
+++ b/util/ipa_pwd.c
|
||||
@@ -561,6 +561,7 @@ int ipapwd_check_policy(struct ipapwd_policy *policy,
|
||||
* policy is set */
|
||||
|
||||
if (cur_time < last_pwd_change + policy->min_pwd_life) {
|
||||
+ syslog(LOG_ERR, "Password too young. %d seconds since last change, policy requires %u seconds.\n", (cur_time - last_pwd_change), policy->min_pwd_life);
|
||||
return IPAPWD_POLICY_PWD_TOO_YOUNG;
|
||||
}
|
||||
}
|
||||
diff --git a/util/ipa_pwd.h b/util/ipa_pwd.h
|
||||
index aa2c6e978d4498ac42b6ef194f0256786cf93892..d3c5f8be7b78a11b103115734fd78aa1d16f3ecc 100644
|
||||
--- a/util/ipa_pwd.h
|
||||
+++ b/util/ipa_pwd.h
|
||||
@@ -54,7 +54,7 @@ enum ipapwd_error {
|
||||
};
|
||||
|
||||
struct ipapwd_policy {
|
||||
- int min_pwd_life;
|
||||
+ unsigned int min_pwd_life;
|
||||
int max_pwd_life;
|
||||
int min_pwd_length;
|
||||
int history_length;
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
From 5cd2639f539ce220c291b00afafa72fd35e1d07e Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 16 Feb 2026 15:47:32 +0100
|
||||
Subject: [PATCH] ipatests: fix install method for BasePWpolicy
|
||||
|
||||
The test was broken by the previous commit, which creates a
|
||||
new test class TestPWquality that inherits from BasePWpolicy.
|
||||
|
||||
As BasePWpolicy overrides the install method with
|
||||
task.install_master instead of super(TestPWPolicy, cls).install(mh),
|
||||
only the master gets installed.
|
||||
|
||||
Fix the BasePWpolicy class.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9946
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_pwpolicy.py | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py
|
||||
index f7fd40ada226e5dc65f50f27031ebe249346fbee..a68252bf963c77904e1ed69acee7f846e5e99e44 100644
|
||||
--- a/ipatests/test_integration/test_pwpolicy.py
|
||||
+++ b/ipatests/test_integration/test_pwpolicy.py
|
||||
@@ -21,9 +21,12 @@ class BasePWpolicy(IntegrationTest):
|
||||
Base class for testing password policies including libpwquality
|
||||
"""
|
||||
|
||||
+ num_replicas = 0
|
||||
+ topology = 'line'
|
||||
+
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- tasks.install_master(cls.master, setup_dns=True)
|
||||
+ super(BasePWpolicy, cls).install(mh)
|
||||
|
||||
tasks.kinit_admin(cls.master)
|
||||
cls.master.run_command(['ipa', 'user-add', USER,
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
From 8a7486bc980b7f9b19e6b15dee6642679198c40f Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 16 Feb 2026 16:18:06 +0100
|
||||
Subject: [PATCH] webui tests: update expected max value for krbminpwdlife
|
||||
|
||||
The Maximum allowed value for krbminpwdlife has been changed
|
||||
by a recent commit to 480000.
|
||||
Update the test to be consistent.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9947
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Carla Martinez <carlmart@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_webui/test_pwpolicy.py | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_webui/test_pwpolicy.py b/ipatests/test_webui/test_pwpolicy.py
|
||||
index 74484ab1bbeb1522e6e0efe8e12ee8bcf66ddebb..d84bcea410647248c9082aeb64cfca4164ecb03d 100644
|
||||
--- a/ipatests/test_webui/test_pwpolicy.py
|
||||
+++ b/ipatests/test_webui/test_pwpolicy.py
|
||||
@@ -151,6 +151,10 @@ class test_pwpolicy(UI_driver):
|
||||
elif field == 'krbpwdmindiffchars':
|
||||
self.check_expected_error(field, 'Maximum value is 5',
|
||||
maximum_value)
|
||||
+ # verifying if field value is more than 480000
|
||||
+ elif field == 'krbminpwdlife':
|
||||
+ self.check_expected_error(field, 'Maximum value is 480000',
|
||||
+ maximum_value)
|
||||
# verifying if field value is more than 2147483647
|
||||
else:
|
||||
self.check_expected_error(field, 'Maximum value is 2147483647',
|
||||
--
|
||||
2.53.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,154 +0,0 @@
|
||||
From f60df430602db1e3949fa67f3744d40bd9b1d971 Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Fri, 27 Feb 2026 15:38:54 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa-selfservice BZ tests to xmlrpc
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
-Convert Bugzilla regression tests (BZ 772106, 772675, 747730, 747741, 747720, 747722) from bash to Python and add them to the existing selfservice test file as the TestSelfserviceMisc Declarative class.
|
||||
-Tests verify that --raw output, empty permissions/attrs, and invalid attrs do not cause internal errors or accidental ACI deletion.
|
||||
-Use a single selfservice rule (selfservice1) across all BZ tests instead of creating and deleting a separate rule per test case, reducing churn and keeping the tests fast.
|
||||
-Drop BZ 747693 (selfservice-find --raw) as it is already covered by the existing "Search for 'testself' with --raw" test in the main test_selfservice CRUD class (test 0011).
|
||||
|
||||
Signed-off-by: Jay Gondaliya jgondali@redhat.com
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
|
||||
Continuation of PR #8190
|
||||
|
||||
Fixes made:
|
||||
-Fixed lambda expected checkers — replaced defensive .get("result", {}) chains with direct output["result"] key access.
|
||||
-Removed redundant delete test case — dropped explicit selfservice_del test, relying solely on cleanup_commands.
|
||||
-Renamed class TestSelfserviceMisc → test_selfservice_misc.
|
||||
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 106 +++++++++++++++++-
|
||||
1 file changed, 105 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 02aea35e14084cc0e5d94a6b07751d206f4cfb4f..4c5059519fdc1aca55605fd38791a492dd108455 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -28,7 +28,6 @@ import pytest
|
||||
selfservice1 = u'testself'
|
||||
invalid_selfservice1 = u'bad+name'
|
||||
|
||||
-
|
||||
@pytest.mark.tier1
|
||||
class test_selfservice(Declarative):
|
||||
|
||||
@@ -290,3 +289,108 @@ class test_selfservice(Declarative):
|
||||
),
|
||||
|
||||
]
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_misc(Declarative):
|
||||
+ """Bugzilla regression tests for selfservice plugin."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ("selfservice_del", [selfservice1], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ # BZ 772106: selfservice-add with --raw must not return internal error
|
||||
+ dict(
|
||||
+ desc="Create %r with --raw for BZ 772106" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_add",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["l"], raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=selfservice1,
|
||||
+ summary='Added selfservice "%s"' % selfservice1,
|
||||
+ result={
|
||||
+ "aci": '(targetattr = "l")(version 3.0;acl '
|
||||
+ '"selfservice:%s";allow (write) '
|
||||
+ 'userdn = "ldap:///self";)' % selfservice1,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 772675: selfservice-mod with --raw must not return internal error
|
||||
+ dict(
|
||||
+ desc="Modify %r with --raw for BZ 772675" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["mobile"], raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=selfservice1,
|
||||
+ summary='Modified selfservice "%s"' % selfservice1,
|
||||
+ result={
|
||||
+ "aci": '(targetattr = "mobile")(version 3.0;acl '
|
||||
+ '"selfservice:%s";allow (write) '
|
||||
+ 'userdn = "ldap:///self";)' % selfservice1,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747730: selfservice-mod --permissions="" must not delete the entry
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ "Modify %r with empty permissions for BZ 747730"
|
||||
+ % selfservice1
|
||||
+ ),
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(permissions=""),
|
||||
+ ),
|
||||
+ expected=lambda got, output: True,
|
||||
+ ),
|
||||
+ dict(
|
||||
+ desc="Verify %r still exists after BZ 747730" % selfservice1,
|
||||
+ command=("selfservice_show", [selfservice1], {}),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None
|
||||
+ and output["result"]["aciname"] == selfservice1
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747741: selfservice-mod --attrs=badattrs must not delete the entry
|
||||
+ dict(
|
||||
+ desc="Modify %r with bad attrs for BZ 747741" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["badattrs"]),
|
||||
+ ),
|
||||
+ expected=lambda got, output: True,
|
||||
+ ),
|
||||
+ dict(
|
||||
+ desc="Verify %r still exists after BZ 747741" % selfservice1,
|
||||
+ command=("selfservice_show", [selfservice1], {}),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None
|
||||
+ and output["result"]["aciname"] == selfservice1
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747720: selfservice-find --permissions="" must not return
|
||||
+ # internal error
|
||||
+ dict(
|
||||
+ desc="BZ 747720: selfservice-find with empty permissions",
|
||||
+ command=("selfservice_find", [], dict(permissions="")),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None and isinstance(output["result"], (list, tuple))
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747722: selfservice-find --attrs="" must not return
|
||||
+ # internal error
|
||||
+ dict(
|
||||
+ desc="BZ 747722: selfservice-find with empty attrs",
|
||||
+ command=("selfservice_find", [], dict(attrs="")),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None and isinstance(output["result"], (list, tuple))
|
||||
+ ),
|
||||
+ ),
|
||||
+ ]
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
From 58239f9fbe33408b5cb5c52ba5132f6deb6b8f40 Mon Sep 17 00:00:00 2001
|
||||
From: David Hanina <dhanina@redhat.com>
|
||||
Date: Tue, 10 Mar 2026 10:26:33 +0100
|
||||
Subject: [PATCH] Allow 32bit gid
|
||||
|
||||
We should allow 32bit groups, by setting maxvalue we allow that.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9953
|
||||
Signed-off-by: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipaserver/plugins/group.py | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py
|
||||
index f05a39f69ec3eb3257909bd61b42a9a21212c14d..308e5458c1d00be34753f42675367a7307331514 100644
|
||||
--- a/ipaserver/plugins/group.py
|
||||
+++ b/ipaserver/plugins/group.py
|
||||
@@ -26,6 +26,7 @@ import re
|
||||
from ipalib import api
|
||||
from ipalib import Int, Str, Flag
|
||||
from ipalib.constants import PATTERN_GROUPUSER_NAME, ERRMSG_GROUPUSER_NAME
|
||||
+from ipalib.parameters import MAX_UINT32
|
||||
from ipalib.plugable import Registry
|
||||
from .baseldap import (
|
||||
add_external_post_callback,
|
||||
@@ -354,6 +355,7 @@ class group(LDAPObject):
|
||||
label=_('GID'),
|
||||
doc=_('GID (use this option to set it manually)'),
|
||||
minvalue=1,
|
||||
+ maxvalue=MAX_UINT32,
|
||||
),
|
||||
ipaexternalmember_param,
|
||||
)
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,400 +0,0 @@
|
||||
From ffae7d109b9cc968bb9942da78ea4238eee5497b Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Thu, 5 Mar 2026 19:07:06 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa-selfservice users tests to xmlrpc
|
||||
|
||||
Convert self-service users tests from bash to Python and add them to the existing selfservice test file.
|
||||
|
||||
Tests verify that users can modify their own allowed attributes under default and custom selfservice rules,
|
||||
that disallowed attributes are rejected with ACIError, and that cross-user modification is blocked.
|
||||
Also covers atomic failure on mixed permissions, self-password-change, and user-find by phone, fax, and manager
|
||||
(BZ 1188195, 781208, 985016, 967509, 985013).
|
||||
|
||||
Signed-off-by: Jay Gondaliya jgondali@redhat.com
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: PRANAV THUBE <pthube@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 360 +++++++++++++++++-
|
||||
1 file changed, 358 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 4c5059519fdc1aca55605fd38791a492dd108455..e55502a2d94a1d29263a0c3a786626d27d51027d 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -21,8 +21,12 @@
|
||||
Test the `ipaserver/plugins/selfservice.py` module.
|
||||
"""
|
||||
|
||||
-from ipalib import errors
|
||||
-from ipatests.test_xmlrpc.xmlrpc_test import Declarative
|
||||
+from ipalib import api, errors
|
||||
+from ipatests.test_xmlrpc.xmlrpc_test import (
|
||||
+ Declarative, XMLRPC_test, assert_attr_equal,
|
||||
+)
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+from ipatests.util import change_principal, unlock_principal_password
|
||||
import pytest
|
||||
|
||||
selfservice1 = u'testself'
|
||||
@@ -394,3 +398,355 @@ class test_selfservice_misc(Declarative):
|
||||
),
|
||||
),
|
||||
]
|
||||
+
|
||||
+
|
||||
+SS_USER1 = 'ssuser0001'
|
||||
+SS_USER1_PASSWORD = 'Passw0rd1'
|
||||
+SS_USER2 = 'ssuser0002'
|
||||
+SS_USER2_PASSWORD = 'Passw0rd2'
|
||||
+SS_GOOD_MANAGER = 'ss_good_manager'
|
||||
+SS_GOOD_MANAGER_PASSWORD = 'Passw0rd3'
|
||||
+
|
||||
+SS_DEFAULT_SELFSERVICE = 'User Self service'
|
||||
+SS_CUSTOM_RULE = 'ss_test_rule0001'
|
||||
+
|
||||
+SS_DEFAULT_SELFSERVICE_ATTRS = [
|
||||
+ 'givenname', 'sn', 'cn', 'displayname', 'title', 'initials',
|
||||
+ 'loginshell', 'gecos', 'homephone', 'mobile', 'pager',
|
||||
+ 'facsimiletelephonenumber', 'telephonenumber', 'street',
|
||||
+ 'roomnumber', 'l', 'st', 'postalcode', 'manager', 'secretary',
|
||||
+ 'description', 'carlicense', 'labeleduri', 'inetuserhttpurl',
|
||||
+ 'seealso', 'employeetype', 'businesscategory', 'ou',
|
||||
+]
|
||||
+
|
||||
+SS_CUSTOM_RULE_ATTRS = [
|
||||
+ 'mobile', 'pager',
|
||||
+ 'facsimiletelephonenumber', 'telephonenumber',
|
||||
+]
|
||||
+
|
||||
+
|
||||
+def _safe_del_selfservice(name):
|
||||
+ """Delete a selfservice rule, ignoring NotFound."""
|
||||
+ try:
|
||||
+ api.Command['selfservice_del'](name)
|
||||
+ except errors.NotFound:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def custom_selfservice_rule(xmlrpc_setup):
|
||||
+ """Replace the default selfservice rule with the narrow custom rule."""
|
||||
+ api.Command['selfservice_del'](SS_DEFAULT_SELFSERVICE)
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_CUSTOM_RULE, attrs=SS_CUSTOM_RULE_ATTRS,
|
||||
+ )
|
||||
+ yield
|
||||
+ _safe_del_selfservice(SS_CUSTOM_RULE)
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_DEFAULT_SELFSERVICE, attrs=SS_DEFAULT_SELFSERVICE_ATTRS,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_user1(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_USER1, givenname='Test', sn='User0001',
|
||||
+ userpassword=SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER1, SS_USER1_PASSWORD, SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_user2(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_USER2, givenname='Test', sn='User0002',
|
||||
+ userpassword=SS_USER2_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER2, SS_USER2_PASSWORD, SS_USER2_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_good_manager(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_GOOD_MANAGER, givenname='Good', sn='Manager',
|
||||
+ userpassword=SS_GOOD_MANAGER_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_GOOD_MANAGER, SS_GOOD_MANAGER_PASSWORD, SS_GOOD_MANAGER_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('ss_user1', 'ss_user2', 'ss_good_manager')
|
||||
+class test_selfservice_users(XMLRPC_test):
|
||||
+ """Test self-service user attribute modification permissions."""
|
||||
+
|
||||
+ # usertest_1001: Set all attrs allowed by default self-service rule.
|
||||
+ def test_set_all_default_selfservice_attrs(self):
|
||||
+ """Set all attrs allowed by the default self-service rule."""
|
||||
+ attrs = {
|
||||
+ 'givenname': 'Good',
|
||||
+ 'sn': 'User',
|
||||
+ 'cn': 'gooduser',
|
||||
+ 'displayname': 'gooduser',
|
||||
+ 'initials': 'GU',
|
||||
+ 'gecos': 'gooduser@good.example.com',
|
||||
+ 'loginshell': '/bin/bash',
|
||||
+ 'street': 'Good_Street_Rd',
|
||||
+ 'l': 'Good_City',
|
||||
+ 'st': 'Goodstate',
|
||||
+ 'postalcode': '33333',
|
||||
+ 'telephonenumber': '333-333-3333',
|
||||
+ 'mobile': '333-333-3333',
|
||||
+ 'pager': '333-333-3333',
|
||||
+ 'facsimiletelephonenumber': '333-333-3333',
|
||||
+ 'ou': 'good-org',
|
||||
+ 'title': 'good_admin',
|
||||
+ 'manager': SS_GOOD_MANAGER,
|
||||
+ 'carlicense': 'good-3333',
|
||||
+ }
|
||||
+
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ for attr, value in attrs.items():
|
||||
+ api.Command['user_mod'](SS_USER1, **{attr: value})
|
||||
+
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ for attr, value in attrs.items():
|
||||
+ assert_attr_equal(entry, attr, value)
|
||||
+
|
||||
+ # usertest_1002: Test that default disallowed attributes are rejected.
|
||||
+ def test_reject_uidnumber_by_default(self):
|
||||
+ """uidnumber change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, uidnumber=9999)
|
||||
+
|
||||
+ def test_reject_gidnumber_by_default(self):
|
||||
+ """gidnumber change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, gidnumber=9999)
|
||||
+
|
||||
+ def test_reject_homedirectory_by_default(self):
|
||||
+ """homedirectory change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, homedirectory='/home/gooduser')
|
||||
+
|
||||
+ def test_reject_email_by_default(self):
|
||||
+ """email change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, mail='gooduser@good.example.com')
|
||||
+
|
||||
+ # usertest_1003: All attrs rejected when the default rule is deleted.
|
||||
+ def test_all_attrs_rejected_without_default_rule(self):
|
||||
+ """All attrs are rejected when the default rule is deleted."""
|
||||
+ attrs = {
|
||||
+ 'givenname': 'Bad',
|
||||
+ 'sn': 'LUser',
|
||||
+ 'cn': 'badluser',
|
||||
+ 'displayname': 'badluser',
|
||||
+ 'initials': 'BL',
|
||||
+ 'gecos': 'badluser@bad.example.com',
|
||||
+ 'loginshell': '/bin/tcsh',
|
||||
+ 'street': 'Bad_Street_Av',
|
||||
+ 'l': 'Bad_City',
|
||||
+ 'st': 'Badstate',
|
||||
+ 'postalcode': '99999',
|
||||
+ 'telephonenumber': '999-999-9999',
|
||||
+ 'mobile': '999-999-9999',
|
||||
+ 'pager': '999-999-9999',
|
||||
+ 'facsimiletelephonenumber': '999-999-9999',
|
||||
+ 'ou': 'bad-org',
|
||||
+ 'title': 'bad_admin',
|
||||
+ 'manager': 'admin',
|
||||
+ 'carlicense': 'bad-9999',
|
||||
+ }
|
||||
+
|
||||
+ api.Command['selfservice_del'](SS_DEFAULT_SELFSERVICE)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ for attr, value in attrs.items():
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, **{attr: value})
|
||||
+ finally:
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_DEFAULT_SELFSERVICE,
|
||||
+ attrs=SS_DEFAULT_SELFSERVICE_ATTRS,
|
||||
+ )
|
||||
+
|
||||
+ # usertest_1004: Custom rule grants write access to its specified attrs.
|
||||
+ def test_custom_rule_grants_write_access(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """Custom rule grants write access to its specified attrs."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, telephonenumber='777-777-7777')
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='777-777-7777')
|
||||
+ api.Command['user_mod'](SS_USER1, pager='777-777-7777')
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ facsimiletelephonenumber='777-777-7777')
|
||||
+
|
||||
+ # usertest_1005: Persisted attrs and user-find by phone, fax, manager.
|
||||
+ def test_verify_persisted_attrs(self):
|
||||
+ """Verify attrs set by previous tests are persisted."""
|
||||
+ expected = {
|
||||
+ 'givenname': 'Good',
|
||||
+ 'sn': 'User',
|
||||
+ 'cn': 'gooduser',
|
||||
+ 'displayname': 'gooduser',
|
||||
+ 'initials': 'GU',
|
||||
+ 'gecos': 'gooduser@good.example.com',
|
||||
+ 'loginshell': '/bin/bash',
|
||||
+ 'street': 'Good_Street_Rd',
|
||||
+ 'l': 'Good_City',
|
||||
+ 'st': 'Goodstate',
|
||||
+ 'postalcode': '33333',
|
||||
+ 'telephonenumber': '777-777-7777',
|
||||
+ 'mobile': '777-777-7777',
|
||||
+ 'pager': '777-777-7777',
|
||||
+ 'facsimiletelephonenumber': '777-777-7777',
|
||||
+ 'ou': 'good-org',
|
||||
+ 'title': 'good_admin',
|
||||
+ 'carlicense': 'good-3333',
|
||||
+ }
|
||||
+
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ for attr, value in expected.items():
|
||||
+ assert_attr_equal(entry, attr, value)
|
||||
+ assert_attr_equal(entry, 'manager', SS_GOOD_MANAGER)
|
||||
+
|
||||
+ def test_user_find_by_phone(self):
|
||||
+ """BZ 1188195: user-find by phone number returns results."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ telephonenumber='777-777-7777')
|
||||
+ assert result['count'] >= 1
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ def test_user_find_by_fax(self):
|
||||
+ """BZ 1188195: user-find by fax number returns results."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ facsimiletelephonenumber='777-777-7777')
|
||||
+ assert result['count'] >= 1
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ def test_user_find_by_manager(self):
|
||||
+ """BZ 781208: user-find by manager returns matches."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ SS_USER1, manager=SS_GOOD_MANAGER)
|
||||
+ assert result['count'] >= 1, (
|
||||
+ 'BZ 781208: user-find --manager did not find matches'
|
||||
+ )
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ # usertest_1006: BZ 985016, 967509: user can modify an allowed attr.
|
||||
+ def test_user_can_modify_allowed_attr(self):
|
||||
+ """BZ 985016, 967509: user can modify an allowed attr."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='888-888-8888')
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ assert_attr_equal(entry, 'mobile', '888-888-8888')
|
||||
+
|
||||
+ # usertest_1007: BZ 985016, 967509: disallowed attribute is rejected.
|
||||
+ def test_disallowed_attr_rejected_with_custom_rule(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """BZ 985016, 967509: disallowed attribute is rejected."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, title='Dr')
|
||||
+
|
||||
+ # usertest_1008: user-mod fails atomically on mixed attr permissions.
|
||||
+ def test_user_mod_atomic_failure_mixed_perms(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """user-mod fails atomically when one attr is disallowed."""
|
||||
+ original_title = api.Command['user_show'](
|
||||
+ SS_USER1)['result'].get('title')
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ title='notgonnawork',
|
||||
+ telephonenumber='999-999-9990',
|
||||
+ )
|
||||
+ result = api.Command['user_find'](
|
||||
+ SS_USER1, telephonenumber='999-999-9990')
|
||||
+ assert result['count'] == 0, (
|
||||
+ 'Phone was changed despite disallowed title in same call'
|
||||
+ )
|
||||
+ after = api.Command['user_show'](SS_USER1)['result']
|
||||
+ assert after.get('title') == original_title, (
|
||||
+ 'Title was modified despite being disallowed'
|
||||
+ )
|
||||
+
|
||||
+ # usertest_1009: BZ 985013: user can change their own password.
|
||||
+ def test_self_password_change_via_passwd(self):
|
||||
+ """BZ 985013: user can change their own password via passwd."""
|
||||
+ policy = api.Command['pwpolicy_show']()['result']
|
||||
+ orig_minlife = policy.get('krbminpwdlife', ('1',))[0]
|
||||
+
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=0)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['passwd'](
|
||||
+ SS_USER1,
|
||||
+ password='MyN3wP@55',
|
||||
+ current_password=SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ # Reset password so the next test can authenticate
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER1, 'MyN3wP@55', SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ finally:
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=int(orig_minlife))
|
||||
+
|
||||
+ def test_self_password_change_via_user_mod(self):
|
||||
+ """BZ 985013: user can change their own password via user_mod."""
|
||||
+ policy = api.Command['pwpolicy_show']()['result']
|
||||
+ orig_minlife = policy.get('krbminpwdlife', ('1',))[0]
|
||||
+
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=0)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ userpassword='MyN3wP@55',
|
||||
+ )
|
||||
+ finally:
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=int(orig_minlife))
|
||||
+
|
||||
+ # usertest_1010: User cannot modify another user's attributes.
|
||||
+ def test_cross_user_modification_rejected(self):
|
||||
+ """User cannot modify another user's attributes."""
|
||||
+ with change_principal(SS_USER2, SS_USER2_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='867-5309')
|
||||
+
|
||||
+ def test_verify_cross_user_modification_rejected(self):
|
||||
+ """Verify attrs did not change after cross-user modification."""
|
||||
+ result = api.Command['user_find'](SS_USER1, mobile='867-5309')
|
||||
+ assert result['count'] == 0, (
|
||||
+ 'Mobile was changed by a different user'
|
||||
+ )
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
From 313bd8ff118a79dca5aad0b19ec8f69519258f89 Mon Sep 17 00:00:00 2001
|
||||
From: PRANAV THUBE <pthube@redhat.com>
|
||||
Date: Wed, 11 Mar 2026 20:13:31 +0530
|
||||
Subject: [PATCH] ipatests: Fix test_allow_query_transfer_ipv6 when IPv6 is
|
||||
disabled
|
||||
|
||||
The test was failing in environments where IPv6 is disabled at the
|
||||
kernel level because it attempted to add a temporary IPv6 address
|
||||
without first checking if IPv6 is enabled on the interface.
|
||||
|
||||
This fix restructures the test to:
|
||||
- Check if IPv6 is disabled via sysctl before attempting IPv6 setup
|
||||
- Always run IPv4 allow-query and allow-transfer tests
|
||||
- Only run IPv6-related tests when IPv6 is available
|
||||
|
||||
This ensures the test passes in IPv4-only environments while still
|
||||
providing full coverage when IPv6 is enabled.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9944
|
||||
Signed-off-by: Pranav Thube pthube@redhat.com
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_dns.py | 62 ++++++---------------------
|
||||
1 file changed, 14 insertions(+), 48 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_dns.py b/ipatests/test_integration/test_dns.py
|
||||
index 4b9ab1fe8d7b8884760ed637cb2fcc5d5a060df0..947cff5c02ea2662cc2de88860cfa294e396792d 100644
|
||||
--- a/ipatests/test_integration/test_dns.py
|
||||
+++ b/ipatests/test_integration/test_dns.py
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import pytest
|
||||
import time
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
@@ -1714,13 +1715,12 @@ class TestDNSMisc(IntegrationTest):
|
||||
tasks.del_dns_zone(self.master, zone, raiseonerr=False)
|
||||
|
||||
def test_allow_query_transfer_ipv6(self):
|
||||
- """Test allow-query and allow-transfer with IPv4 and IPv6.
|
||||
+ """Test allow-query and allow-transfer with IPv6.
|
||||
|
||||
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=701677
|
||||
"""
|
||||
tasks.kinit_admin(self.master)
|
||||
- zone = "example.com"
|
||||
- ipv4 = self.master.ip
|
||||
+ zone = "example6.com"
|
||||
ipv6_added = False
|
||||
temp_ipv6 = '2001:0db8:0:f101::1/64'
|
||||
|
||||
@@ -1732,6 +1732,13 @@ class TestDNSMisc(IntegrationTest):
|
||||
])
|
||||
eth = result.stdout_text.strip()
|
||||
|
||||
+ # Check if IPv6 is disabled on the interface
|
||||
+ result = self.master.run_command([
|
||||
+ 'sysctl', '-n', f'net.ipv6.conf.{eth}.disable_ipv6'
|
||||
+ ])
|
||||
+ if result.stdout_text.strip() == '1':
|
||||
+ pytest.skip(f"IPv6 is disabled on interface {eth}")
|
||||
+
|
||||
# Add temporary IPv6 if none exists
|
||||
result = self.master.run_command(
|
||||
['ip', 'addr', 'show', 'scope', 'global'], raiseonerr=False
|
||||
@@ -1754,62 +1761,21 @@ class TestDNSMisc(IntegrationTest):
|
||||
tasks.add_dns_zone(self.master, zone, skip_overlap_check=True,
|
||||
admin_email=self.EMAIL)
|
||||
|
||||
- # Test allow-query: IPv4 allowed, IPv6 denied
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone,
|
||||
- f"--allow-query={ipv4};!{ipv6};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', '-t', 'soa', zone], raiseonerr=False
|
||||
- )
|
||||
- assert 'ANSWER SECTION' in result.stdout_text
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv6}', '-t', 'soa', zone], raiseonerr=False
|
||||
- )
|
||||
- assert 'ANSWER SECTION' not in result.stdout_text
|
||||
-
|
||||
- # Test allow-query: IPv6 allowed, IPv4 denied
|
||||
+ # Test allow-query: IPv6 allowed
|
||||
tasks.mod_dns_zone(
|
||||
self.master, zone,
|
||||
- f"--allow-query={ipv6};!{ipv4};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', '-t', 'soa', zone], raiseonerr=False
|
||||
+ f"--allow-query={ipv6};"
|
||||
)
|
||||
- assert 'ANSWER SECTION' not in result.stdout_text
|
||||
result = self.master.run_command(
|
||||
['dig', f'@{ipv6}', '-t', 'soa', zone], raiseonerr=False
|
||||
)
|
||||
assert 'ANSWER SECTION' in result.stdout_text
|
||||
|
||||
- # Reset allow-query to any
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone, "--allow-query=any;"
|
||||
- )
|
||||
-
|
||||
- # Test allow-transfer: IPv4 allowed, IPv6 denied
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone,
|
||||
- f"--allow-transfer={ipv4};!{ipv6};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', zone, 'axfr'], raiseonerr=False
|
||||
- )
|
||||
- assert 'Transfer failed' not in result.stdout_text
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv6}', zone, 'axfr'], raiseonerr=False
|
||||
- )
|
||||
- assert 'Transfer failed' in result.stdout_text
|
||||
-
|
||||
- # Test allow-transfer: IPv6 allowed, IPv4 denied
|
||||
+ # Test allow-transfer: IPv6 allowed
|
||||
tasks.mod_dns_zone(
|
||||
self.master, zone,
|
||||
- f"--allow-transfer={ipv6};!{ipv4};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', zone, 'axfr'], raiseonerr=False
|
||||
+ f"--allow-transfer={ipv6};"
|
||||
)
|
||||
- assert 'Transfer failed' in result.stdout_text
|
||||
result = self.master.run_command(
|
||||
['dig', f'@{ipv6}', zone, 'axfr'], raiseonerr=False
|
||||
)
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,217 +0,0 @@
|
||||
From c8a832e19699a7bb6ff486055015f033a3137e5f Mon Sep 17 00:00:00 2001
|
||||
From: PRANAV THUBE <pthube@redhat.com>
|
||||
Date: Mon, 16 Mar 2026 14:29:12 +0530
|
||||
Subject: [PATCH] ipatests: Add XML-RPC tests for i18n user attributes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Add tests for internationalization support in user plugin:
|
||||
- User creation/deletion with i18n givenname and sn
|
||||
- Lastname modification with Swedish/European names (13 values)
|
||||
- Firstname modification with European accented names (4 values)
|
||||
- Firstname modification with single i18n characters (67 values)
|
||||
|
||||
Test data includes characters like Çándide, Örjan, Éric, ß, ü, etc.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9959
|
||||
Signed-off-by: Pranav Thube pthube@redhat.com
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Carla Martinez <carlmart@redhat.com>
|
||||
---
|
||||
ipatests/test_xmlrpc/test_i18n_user_plugin.py | 182 ++++++++++++++++++
|
||||
1 file changed, 182 insertions(+)
|
||||
create mode 100644 ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_i18n_user_plugin.py b/ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..146ffdc516877afa3e221717c94bee4a428934c7
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
@@ -0,0 +1,182 @@
|
||||
+# Authors:
|
||||
+# Pranav Thube <pthube@redhat.com>
|
||||
+#
|
||||
+# Copyright (C) 2026 Red Hat
|
||||
+# see file 'COPYING' for use and warranty information
|
||||
+#
|
||||
+# This program is free software; you can redistribute it and/or modify
|
||||
+# it under the terms of the GNU General Public License as published by
|
||||
+# the Free Software Foundation, either version 3 of the License, or
|
||||
+# (at your option) any later version.
|
||||
+#
|
||||
+# This program is distributed in the hope that it will be useful,
|
||||
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+# GNU General Public License for more details.
|
||||
+#
|
||||
+# You should have received a copy of the GNU General Public License
|
||||
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
+
|
||||
+"""
|
||||
+Test the i18n (internationalization) support for user plugin.
|
||||
+
|
||||
+This module tests that IPA correctly handles international characters
|
||||
+in user attributes such as first name (givenname) and last name (sn).
|
||||
+"""
|
||||
+
|
||||
+import pytest
|
||||
+
|
||||
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+
|
||||
+
|
||||
+# Test data Users with i18n names
|
||||
+I18N_USERS = {
|
||||
+ 'user1': {
|
||||
+ 'name': 'i18nuser1',
|
||||
+ 'givenname': 'Çándide',
|
||||
+ 'sn': 'Rùiz',
|
||||
+ },
|
||||
+ 'user2': {
|
||||
+ 'name': 'i18nuser2',
|
||||
+ 'givenname': 'Rôséñe',
|
||||
+ 'sn': 'zackr',
|
||||
+ },
|
||||
+ 'user3': {
|
||||
+ 'name': 'i18nuser3',
|
||||
+ 'givenname': 'Älka',
|
||||
+ 'sn': 'Màrzella',
|
||||
+ },
|
||||
+ 'user4': {
|
||||
+ 'name': 'i18nuser4',
|
||||
+ 'givenname': 'Feâtlëss',
|
||||
+ 'sn': 'Watérmân',
|
||||
+ },
|
||||
+}
|
||||
+
|
||||
+# CNS test data - Swedish/European last names
|
||||
+CNS_LASTNAMES = [
|
||||
+ 'Oskar',
|
||||
+ 'Anders',
|
||||
+ 'Örjan',
|
||||
+ 'Jonas',
|
||||
+ 'Ulf',
|
||||
+ 'Äke',
|
||||
+ 'Bertold',
|
||||
+ 'Bruno',
|
||||
+ 'Didier',
|
||||
+ 'Éric',
|
||||
+ 'Jean-Luc',
|
||||
+ 'Laurent',
|
||||
+ 'Têko',
|
||||
+]
|
||||
+
|
||||
+# European names with mixed accents for firstname tests
|
||||
+EUROPEAN_FIRSTNAMES = [
|
||||
+ 'Rôséñel',
|
||||
+ 'Tàrqùinio',
|
||||
+ 'PASSWÖRD',
|
||||
+ 'Nomeuropéen',
|
||||
+ # Names with special characters (apostrophe, space)
|
||||
+ "O'Brian",
|
||||
+ 'Maria José',
|
||||
+]
|
||||
+
|
||||
+# Firstname test data - Single characters including accented
|
||||
+# 73 characters total: 26 ASCII A-Z + 47 accented/special characters
|
||||
+FIRSTNAME_SINGLE_CHARS = [
|
||||
+ # ASCII uppercase letters A-Z (26 characters)
|
||||
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
+ # Extended Latin uppercase characters (20 characters)
|
||||
+ 'À', 'Á', 'Â', 'Ä', 'Ç', 'È', 'É', 'Ê', 'Ë',
|
||||
+ 'Í', 'Î', 'Ï', 'Ñ', 'Ó', 'Ô', 'Ö', 'Ù', 'Ú', 'Û', 'Ü',
|
||||
+ # German eszett (1 character)
|
||||
+ 'ß',
|
||||
+ # Extended Latin lowercase characters (20 characters)
|
||||
+ 'à', 'á', 'â', 'ä', 'ç', 'è', 'é', 'ê', 'ë',
|
||||
+ 'í', 'î', 'ï', 'ñ', 'ó', 'ô', 'ö', 'ù', 'ú', 'û', 'ü',
|
||||
+ # Nordic characters (4 characters)
|
||||
+ 'Ø', 'ø', 'Å', 'å',
|
||||
+ # Polish character (2 characters)
|
||||
+ 'Ł', 'ł',
|
||||
+]
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def i18n_users(request, xmlrpc_setup):
|
||||
+ """Single fixture providing all i18n test users as a dictionary"""
|
||||
+ users = {}
|
||||
+ for user_key, user_data in I18N_USERS.items():
|
||||
+ tracker = UserTracker(
|
||||
+ name=user_data['name'],
|
||||
+ givenname=user_data['givenname'],
|
||||
+ sn=user_data['sn']
|
||||
+ )
|
||||
+ users[user_key] = tracker.make_fixture(request)
|
||||
+ return users
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class TestI18nUser(XMLRPC_test):
|
||||
+ """
|
||||
+ Test i18n (internationalization) support for user plugin.
|
||||
+
|
||||
+ Tests that IPA correctly handles international characters in user
|
||||
+ attributes such as first name (givenname) and last name (sn).
|
||||
+ """
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # User Creation Tests
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('user_key', I18N_USERS.keys())
|
||||
+ def test_add_i18n_user(self, i18n_users, user_key):
|
||||
+ """Adding i18n user"""
|
||||
+ i18n_users[user_key].create()
|
||||
+
|
||||
+ @pytest.mark.parametrize('user_key', I18N_USERS.keys())
|
||||
+ def test_verify_i18n_user(self, i18n_users, user_key):
|
||||
+ """Verify i18n user has correct full name"""
|
||||
+ user = i18n_users[user_key]
|
||||
+ user.ensure_exists()
|
||||
+ command = user.make_find_command(uid=user.uid, all=True)
|
||||
+ result = command()
|
||||
+ assert result['count'] == 1
|
||||
+ entry = result['result'][0]
|
||||
+ assert I18N_USERS[user_key]['givenname'] in entry['givenname']
|
||||
+ assert I18N_USERS[user_key]['sn'] in entry['sn']
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # CNS Tests - Lastname modification with Swedish/European names
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('lastname', CNS_LASTNAMES)
|
||||
+ def test_cns_modify_lastname(self, i18n_users, lastname):
|
||||
+ """Modify lastname to Swedish/European name"""
|
||||
+ user = i18n_users['user1']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(sn=lastname))
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # European accented firstname tests
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('firstname', EUROPEAN_FIRSTNAMES)
|
||||
+ def test_european_modify_firstname(self, i18n_users, firstname):
|
||||
+ """Modify firstname to European accented name"""
|
||||
+ user = i18n_users['user2']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(givenname=firstname))
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # Firstname Tests - Single character modification
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('char', FIRSTNAME_SINGLE_CHARS)
|
||||
+ def test_firstname_modify_single_char(self, i18n_users, char):
|
||||
+ """Modify firstname to single character"""
|
||||
+ user = i18n_users['user3']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(givenname=char))
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,228 +0,0 @@
|
||||
From 3056bf8fb27732213591f4c86044ce6980054ec9 Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Tue, 10 Mar 2026 19:14:48 +0530
|
||||
Subject: [PATCH] ipatests: Add selfservice-add and selfservice-del cli tests
|
||||
|
||||
Add a new Declarative test class `test_selfservice_cli_add_del` covering CLI-level behaviour of the selfservice-add and selfservice-del commands:
|
||||
|
||||
- add_1002: bad attrs with valid permissions rejects with InvalidSyntax
|
||||
- add_1003: valid attrs with invalid permissions rejects with ValidationError
|
||||
- add_1004: valid attrs and permissions with --all --raw succeeds and
|
||||
returns the raw ACI string (BZ 772106)
|
||||
- add_1005: bad attrs only rejects with InvalidSyntax
|
||||
- add_1006: valid attrs only succeeds with default write permission
|
||||
- del_1001: deleting an existing selfservice rule succeeds
|
||||
- del_1002: deleting a non-existent rule raises NotFound
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 192 ++++++++++++++++++
|
||||
1 file changed, 192 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index e55502a2d94a1d29263a0c3a786626d27d51027d..8f2307a20cbbaa5e17a1425aea4d254382779a6a 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -750,3 +750,195 @@ class test_selfservice_users(XMLRPC_test):
|
||||
assert result['count'] == 0, (
|
||||
'Mobile was changed by a different user'
|
||||
)
|
||||
+
|
||||
+
|
||||
+# Module-level constants for CLI test classes
|
||||
+# selfservice-add / selfservice-del CLI tests
|
||||
+SS_CLI_ADD_1004 = 'selfservice_add_1004'
|
||||
+SS_CLI_ADD_1006 = 'selfservice_add_1006'
|
||||
+SS_CLI_DEL_1001 = 'selfservice_del_1001'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_cli_add_del(Declarative):
|
||||
+ """CLI tests for selfservice-add and selfservice-del commands."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_ADD_1004], {}),
|
||||
+ ('selfservice_del', [SS_CLI_ADD_1006], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+
|
||||
+ # add_1002: bad attrs + valid permissions + --all --raw
|
||||
+ dict(
|
||||
+ desc='add_1002: selfservice-add with bad attrs, valid permissions,'
|
||||
+ ' --all --raw',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1002'],
|
||||
+ dict(
|
||||
+ attrs=['badattr'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=r'targetattr "badattr" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattr" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattr\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:selfservice_add_1002\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1003: valid attrs + bad permissions + --all --raw
|
||||
+ dict(
|
||||
+ desc='add_1003: selfservice-add with valid attrs, bad permissions,'
|
||||
+ ' --all --raw',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1003'],
|
||||
+ dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions='badperm',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1004: valid attrs + valid permissions + --all --raw (BZ 772106)
|
||||
+ # selfservice-add with --raw must not return "internal error" message.
|
||||
+ dict(
|
||||
+ desc='add_1004: selfservice-add with valid attrs and permissions,'
|
||||
+ ' --all --raw (BZ 772106)',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_ADD_1004],
|
||||
+ dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_ADD_1004,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_ADD_1004,
|
||||
+ result={
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "telephonenumber || mobile || pager'
|
||||
+ ' || facsimiletelephonenumber")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_ADD_1004
|
||||
+ ),
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1005: bad attrs only
|
||||
+ dict(
|
||||
+ desc='add_1005: selfservice-add with bad attrs only',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1005'],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=r'targetattr "badattrs" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattrs" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattrs\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:selfservice_add_1005\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1006: valid attrs only
|
||||
+ dict(
|
||||
+ desc='add_1006: selfservice-add with valid attrs only',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_ADD_1006],
|
||||
+ dict(attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ]),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_ADD_1006,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_ADD_1006,
|
||||
+ result=dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_ADD_1006,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Setup for del tests: create the rule that del_1001 will delete.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Setup: create %r for selfservice-del tests'
|
||||
+ % SS_CLI_DEL_1001
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_DEL_1001],
|
||||
+ dict(attrs=['l'], permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_DEL_1001,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_DEL_1001,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_DEL_1001,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # del_1001: delete an existing rule
|
||||
+ dict(
|
||||
+ desc='del_1001: selfservice-del of an existing rule',
|
||||
+ command=('selfservice_del', [SS_CLI_DEL_1001], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_DEL_1001,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_DEL_1001,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # del_1002: delete a non-existent rule
|
||||
+ dict(
|
||||
+ desc='del_1002: selfservice-del of a non-existent rule',
|
||||
+ command=('selfservice_del', ['badname'], {}),
|
||||
+ expected=errors.NotFound(
|
||||
+ reason='ACI with name "badname" not found',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,421 +0,0 @@
|
||||
From b05586c2a6a81c7121dd40f8d627cd8a2c5908d8 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Wed, 18 Mar 2026 16:36:06 +0530
|
||||
Subject: [PATCH] ipatests: Additional tests for 32BitIdranges
|
||||
|
||||
Below tests are added
|
||||
|
||||
1. Create ipauser with 32bit id.
|
||||
2. Create ipagroup with 32Bit id.
|
||||
3. Create ipauser with 32Bit groupid range.
|
||||
4. Test ssh login with 32Bit id user.
|
||||
5. Test that ipauser with 32Bit is replicated.
|
||||
6. Test that 32Bit idrange is created in IPA-AD trust enviornment.
|
||||
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_integration/test_32bit_idranges.py | 333 +++++++++++++++---
|
||||
1 file changed, 284 insertions(+), 49 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_32bit_idranges.py b/ipatests/test_integration/test_32bit_idranges.py
|
||||
index a928628d399d3a94901f0220c3af3e97c5115ffe..9b91fc6182f324060a0e38dfaa5ec222f3ef0350 100644
|
||||
--- a/ipatests/test_integration/test_32bit_idranges.py
|
||||
+++ b/ipatests/test_integration/test_32bit_idranges.py
|
||||
@@ -4,17 +4,85 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import re
|
||||
+
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
|
||||
+# The tests focus on 32-bit UID/GID creation and replication,
|
||||
+# SID behavior is not covered in the tests.
|
||||
+
|
||||
+# Range with First Posix ID >= 2^31 is considered a 32-bit range.
|
||||
+IDRANGE_32BIT_NAME = "{realm}_upper_32bit_range"
|
||||
+IDRANGE_32BIT_BASE_ID = 1 << 31 # 2147483648
|
||||
+
|
||||
+
|
||||
+def _32bit_idrange_exists(master):
|
||||
+ """
|
||||
+ Return True if an ipa-local range with base ID >= 2^31 already exists.
|
||||
+ """
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "idrange-find", "--type", "ipa-local"]
|
||||
+ )
|
||||
+ # Parse all "First Posix ID of the range: in the output'
|
||||
+ for match in re.finditer(
|
||||
+ r"First Posix ID of the range:\s*(\d+)",
|
||||
+ result.stdout_text
|
||||
+ ):
|
||||
+ if int(match.group(1)) >= IDRANGE_32BIT_BASE_ID:
|
||||
+ return True
|
||||
+ return False
|
||||
+
|
||||
+
|
||||
+def _add_32bit_idrange_if_missing(master):
|
||||
+ """
|
||||
+ Create the 32-bit ID range only if it does not already exist.
|
||||
+ Returns True if the range was added, False if it already existed.
|
||||
+ """
|
||||
+ if _32bit_idrange_exists(master):
|
||||
+ return False
|
||||
+ idrange = IDRANGE_32BIT_NAME.format(realm=master.domain.realm)
|
||||
+ id_length = 10000
|
||||
+ rid_base = 300_000_000
|
||||
+ secondary_rid_base = 500_000_000
|
||||
+ master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "idrange-add",
|
||||
+ idrange,
|
||||
+ "--base-id", str(IDRANGE_32BIT_BASE_ID),
|
||||
+ "--range-size", str(id_length),
|
||||
+ "--rid-base", str(rid_base),
|
||||
+ "--secondary-rid-base", str(secondary_rid_base),
|
||||
+ "--type=ipa-local"
|
||||
+ ]
|
||||
+ )
|
||||
+ # Restart dirsrv instance after the new idrange is added.
|
||||
+ tasks.restart_ipa_server(master)
|
||||
+ # Clear SSSD cache
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ return True
|
||||
+
|
||||
|
||||
class Test32BitIdRanges(IntegrationTest):
|
||||
topology = "line"
|
||||
+ num_replicas = 1
|
||||
+ num_clients = 1
|
||||
+ # Counter for 32-bit UID/GID allocation; reset in install() so each
|
||||
+ # test run starts from 0 (install/uninstall gives a fresh environment).
|
||||
+ id_counter = 0
|
||||
+
|
||||
+ def get_next_32bit_id(self):
|
||||
+ """
|
||||
+ Generate unique 32-bit IDs for testing
|
||||
+ """
|
||||
+ self.id_counter += 1
|
||||
+ return IDRANGE_32BIT_BASE_ID + self.__class__.id_counter
|
||||
|
||||
def test_remove_subid_range(self):
|
||||
"""
|
||||
- Test that allocating subid will fail after disabling global option
|
||||
+ Test that allocating subids will fail after disabling the attribute
|
||||
"""
|
||||
master = self.master
|
||||
tasks.kinit_admin(master)
|
||||
@@ -23,19 +91,28 @@ class Test32BitIdRanges(IntegrationTest):
|
||||
master.run_command(
|
||||
["ipa", "config-mod", "--addattr", "ipaconfigstring=SubID:Disable"]
|
||||
)
|
||||
- master.run_command(["ipa", "idrange-del", idrange])
|
||||
+ master.run_command(
|
||||
+ ["ipa", "idrange-del", idrange]
|
||||
+ )
|
||||
+ master.run_command(["systemctl", "restart", "sssd"])
|
||||
|
||||
tasks.user_add(master, 'subiduser')
|
||||
- result = master.run_command(
|
||||
- ["ipa", "subid-generate", "--owner", "subiduser"], raiseonerr=False
|
||||
- )
|
||||
- assert result.returncode > 0
|
||||
- assert "Support for subordinate IDs is disabled" in result.stderr_text
|
||||
- tasks.user_del(master, 'subiduser')
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "subid-generate", "--owner", "subiduser"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode > 0
|
||||
+ assert "Support for subordinate IDs is disabled" in \
|
||||
+ result.stderr_text
|
||||
+ finally:
|
||||
+ # Cleanup: Remove test user
|
||||
+ tasks.user_del(master, 'subiduser')
|
||||
|
||||
def test_invoke_upgrader(self):
|
||||
- """Test that ipa-server-upgrade does not add subid ranges back"""
|
||||
-
|
||||
+ """
|
||||
+ Test that ipa-server-upgrade does not add subid ranges back.
|
||||
+ """
|
||||
master = self.master
|
||||
master.run_command(['ipa-server-upgrade'], raiseonerr=True)
|
||||
idrange = f"{master.domain.realm}_subid_range"
|
||||
@@ -58,69 +135,227 @@ class Test32BitIdRanges(IntegrationTest):
|
||||
assert "dnatype: " not in output
|
||||
|
||||
def test_create_user_with_32bit_id(self):
|
||||
- """Test that ID range above 2^31 can be used to assign IDs
|
||||
- to users and groups. Also check that SIDs generated properly.
|
||||
"""
|
||||
+ Test checks that 32Bit idrange is assigned to the user
|
||||
+ and getent passwd <username> returns the output.
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ _add_32bit_idrange_if_missing(master)
|
||||
+
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ username = "user"
|
||||
+ tasks.create_active_user(
|
||||
+ master, username, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", username, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User not found: {result.stderr_text}"
|
||||
+ )
|
||||
+ assert "ipantsecurityidentifier" in \
|
||||
+ result.stdout_text.lower(), (
|
||||
+ "SID not found in user entry"
|
||||
+ )
|
||||
+ if hasattr(self, 'clients') and self.clients:
|
||||
+ client = self.clients[0]
|
||||
+ tasks.clear_sssd_cache(client)
|
||||
+ result = client.run_command(
|
||||
+ ["getent", "passwd", username], raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"getent passwd failed: {result.stderr_text}"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text
|
||||
+ assert str(gid) in result.stdout_text
|
||||
+ finally:
|
||||
+ tasks.user_del(master, username)
|
||||
+
|
||||
+ def test_create_group_with_32bit_gid(self):
|
||||
+ """
|
||||
+ Test that a group can be created with a GID from the 32-bit range.
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ groupname = 'grp32bit'
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+ tasks.group_add(master, groupname, extra_args=["--gid", str(gid)])
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "group-show", groupname, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert str(gid) in result.stdout_text, (
|
||||
+ f"GID {gid} not in group entry"
|
||||
+ )
|
||||
+ finally:
|
||||
+ tasks.group_del(master, groupname)
|
||||
|
||||
+ def test_user_in_group_with_32bit_ids(self):
|
||||
+ """
|
||||
+ Test user with 32-bit UID in a group with 32-bit GID.
|
||||
+ """
|
||||
master = self.master
|
||||
- idrange = f"{master.domain.realm}_upper_32bit_range"
|
||||
- id_base = 1 << 31
|
||||
- id_length = (1 << 31) - 2
|
||||
- uid = id_base + 1
|
||||
- gid = id_base + 1
|
||||
- master.run_command(
|
||||
- [
|
||||
- "ipa",
|
||||
- "idrange-add",
|
||||
- idrange,
|
||||
- "--base-id", str(id_base),
|
||||
- "--range-size", str(id_length),
|
||||
- "--rid-base", str(int(id_base >> 3)),
|
||||
- "--secondary-rid-base", str(int(id_base >> 3) + id_length),
|
||||
- "--type=ipa-local"
|
||||
- ]
|
||||
+ groupname = 'grp32bit2'
|
||||
+ username = 'user32bit'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+ tasks.group_add(master, groupname, extra_args=["--gid", str(gid)])
|
||||
+ tasks.create_active_user(
|
||||
+ master, username, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
)
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ tasks.group_add_member(master, groupname, users=username)
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "group-show", groupname, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert username in result.stdout_text
|
||||
+ assert f"gidnumber: {gid}" in result.stdout_text, (
|
||||
+ f"GID {gid} not found in group entry"
|
||||
+ )
|
||||
+ assert "ipaNTSecurityIdentifier:" in result.stdout_text, (
|
||||
+ "Group does not contain a SID"
|
||||
+ )
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", username, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ finally:
|
||||
+ master.run_command(
|
||||
+ ["ipa", "group-remove-member", groupname,
|
||||
+ "--users", username],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ tasks.user_del(master, username)
|
||||
+ tasks.group_del(master, groupname)
|
||||
|
||||
- # We added new ID range, SIDGEN will only take it after
|
||||
- # restarting a directory server instance.
|
||||
- tasks.restart_ipa_server(master)
|
||||
+ def test_ssh_login_with_32bit_id(self):
|
||||
+ """
|
||||
+ Test that a user with 32-bit UID/GID can kinit and log in via SSH
|
||||
+ from the client to the master using GSSAPI (Kerberos).
|
||||
+ """
|
||||
+ client = self.clients[0]
|
||||
+ master = self.master
|
||||
+ testuser = 'sshuser32bit'
|
||||
+ password = 'Secret123'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
|
||||
- # Clear SSSD cache to pick up new ID range
|
||||
tasks.clear_sssd_cache(master)
|
||||
+ tasks.create_active_user(
|
||||
+ master, testuser, password,
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ hbac_rule = "allow_ssh_32bit_test"
|
||||
+ tasks.hbacrule_add(master, hbac_rule, extra_args=["--hostcat=all"])
|
||||
+ tasks.hbacrule_add_user(master, hbac_rule, users=testuser)
|
||||
+ tasks.hbacrule_add_service(master, hbac_rule, services="sshd")
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not found: {result.stderr_text}"
|
||||
+ )
|
||||
|
||||
- tasks.user_add(master, "user", extra_args=[
|
||||
- "--uid", str(uid), "--gid", str(gid)
|
||||
- ])
|
||||
+ tasks.clear_sssd_cache(client)
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ tasks.kdestroy_all(client)
|
||||
+ tasks.kinit_as_user(client, testuser, password)
|
||||
+ result = client.run_command([
|
||||
+ 'ssh', '-o', 'StrictHostKeyChecking=no', '-K',
|
||||
+ '-l', testuser, master.hostname, 'echo login successful'
|
||||
+ ], raiseonerr=False)
|
||||
+ assert result.returncode == 0, (
|
||||
+ "SSH (GSSAPI) from client to master failed: "
|
||||
+ f"{result.stderr_text}"
|
||||
+ )
|
||||
+ assert 'login successful' in result.stdout_text, (
|
||||
+ "SSH succeeded but expected output missing: "
|
||||
+ f"{result.stdout_text}"
|
||||
+ )
|
||||
+ finally:
|
||||
+ tasks.kdestroy_all(client)
|
||||
+ master.run_command(
|
||||
+ ["ipa", "hbacrule-del", hbac_rule], raiseonerr=False
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ tasks.user_del(master, testuser)
|
||||
|
||||
- result = master.run_command(
|
||||
- ["ipa", "user-show", "user", "--all", "--raw"], raiseonerr=False
|
||||
- )
|
||||
- assert result.returncode == 0
|
||||
- assert "ipaNTSecurityIdentifier:" in result.stdout_text
|
||||
+ def test_32bit_id_replication(self):
|
||||
+ """
|
||||
+ Test that users with 32-bit IDs replicate correctly
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ replica = self.replicas[0]
|
||||
+ tasks.kinit_admin(master)
|
||||
+ testuser = 'repluser32bit'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
|
||||
- result = master.run_command(
|
||||
- ["id", "user"], raiseonerr=False
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+
|
||||
+ # Create user on master
|
||||
+ tasks.create_active_user(
|
||||
+ master, testuser, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
)
|
||||
- assert result.returncode == 0
|
||||
- assert str(uid) in result.stdout_text
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ tasks.wait_for_replication(master.ldap_connect())
|
||||
+
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not found on master"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text, (
|
||||
+ f"UID {uid} not on master"
|
||||
+ )
|
||||
+
|
||||
+ tasks.kinit_admin(replica)
|
||||
+ result = replica.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not replicated to replica"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text, (
|
||||
+ f"UID {uid} not on replica"
|
||||
+ )
|
||||
+ finally:
|
||||
+ # Cleanup: Remove test user from master
|
||||
+ tasks.kinit_admin(master)
|
||||
+ tasks.user_del(master, testuser)
|
||||
|
||||
|
||||
class Test32BitIdrangeInTrustEnv(Test32BitIdRanges, BaseTestTrust):
|
||||
"""
|
||||
Tests to check 32BitIdrange functionality
|
||||
- in IPA-AD trust enviornment
|
||||
+ in IPA-AD trust environment
|
||||
"""
|
||||
topology = 'line'
|
||||
+ num_replicas = 1
|
||||
num_ad_domains = 1
|
||||
num_ad_subdomains = 0
|
||||
num_ad_treedomains = 0
|
||||
- num_clients = 0
|
||||
+ num_clients = 1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- super(BaseTestTrust, cls).install(mh)
|
||||
+ super(Test32BitIdrangeInTrustEnv, cls).install(mh)
|
||||
cls.ad = cls.ads[0]
|
||||
- cls.ad_domain = cls.ad.domain.name
|
||||
tasks.configure_dns_for_trust(cls.master, cls.ad)
|
||||
- tasks.install_adtrust(cls.master)
|
||||
tasks.establish_trust_with_ad(cls.master, cls.ad.domain.name)
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,263 +0,0 @@
|
||||
From 3acf55ed7fcf9a3b38deb0efb98d222d57bafbbc Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Thu, 26 Feb 2026 12:04:50 +0530
|
||||
Subject: [PATCH] ipatests: add HTTP GSSAPI Kerberos authentication tests with
|
||||
AD trust
|
||||
|
||||
Add TestTrustFunctionalHttp integration test class covering GSSAPI-protected
|
||||
HTTP access in an AD trust environment:
|
||||
|
||||
- test_ipa_trust_func_http_krb_ipauser: IPA user with a valid Kerberos ticket
|
||||
can access the GSSAPI-protected webapp; AD root and subdomain users are
|
||||
denied when only the IPA user is in the Allow list
|
||||
- test_ipa_trust_func_http_krb_aduser: AD root domain and subdomain users with
|
||||
valid Kerberos tickets can access the webapp; IPA users are denied when the
|
||||
Allow list is configured for an AD user
|
||||
- test_ipa_trust_func_http_krb_nouser: a user without a Kerberos ticket
|
||||
receives a 401 Unauthorized response
|
||||
|
||||
The class sets up an Apache httpd instance with mod_auth_gssapi on the IPA
|
||||
client, obtains a service keytab via ipa-getkeytab, and uses curl with
|
||||
GSSAPI negotiate to drive each scenario.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9845
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
---
|
||||
.../test_integration/test_trust_functional.py | 209 +++++++++++++++++-
|
||||
1 files changed, 209 insertions(+), 1 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_trust_functional.py b/ipatests/test_integration/test_trust_functional.py
|
||||
index a85f21e96463757b9a446df666d5361e65ba686c..5a9eae8cebfdae23bf37d3298e455bab8e3304ef 100644
|
||||
--- a/ipatests/test_integration/test_trust_functional.py
|
||||
+++ b/ipatests/test_integration/test_trust_functional.py
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import re
|
||||
import time
|
||||
-
|
||||
+import textwrap
|
||||
from ipaplatform.paths import paths
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
@@ -658,3 +659,209 @@ class TestTrustFunctionalSudo(BaseTestTrust):
|
||||
raiseonerr=False)
|
||||
finally:
|
||||
self._cleanup_srule(srule)
|
||||
+
|
||||
+
|
||||
+class TestTrustFunctionalHttp(BaseTestTrust):
|
||||
+ topology = 'line'
|
||||
+ num_ad_treedomains = 0
|
||||
+
|
||||
+ ad_user_password = 'Secret123'
|
||||
+
|
||||
+ # Apache configuration for GSSAPI-protected webapp. The /mywebapp
|
||||
+ # location requires Kerberos authentication and restricts access by
|
||||
+ # domain: IPA users (@IPA_REALM) or AD users (@AD_DOMAIN).
|
||||
+ apache_conf = textwrap.dedent('''
|
||||
+ Alias /mywebapp "/var/www/html/mywebapp"
|
||||
+ <Directory "/var/www/html/mywebapp">
|
||||
+ Allow from all
|
||||
+ </Directory>
|
||||
+ <Location "/mywebapp">
|
||||
+ LogLevel debug
|
||||
+ AuthType GSSAPI
|
||||
+ AuthName "IPA Kerberos authentication"
|
||||
+ GssapiNegotiateOnce on
|
||||
+ GssapiBasicAuthMech krb5
|
||||
+ GssapiCredStore keytab:{keytab_path}
|
||||
+ <RequireAll>
|
||||
+ Require valid-user
|
||||
+ # Require expr: restrict access by domain. REMOTE_USER is set by
|
||||
+ # mod_auth_gssapi after GSSAPI authentication. Allow users whose
|
||||
+ # principal ends with the domain (IPA realm or AD domain).
|
||||
+ Require expr %{{REMOTE_USER}} =~ /{allowed_domain_regex}$/
|
||||
+ </RequireAll>
|
||||
+ </Location>
|
||||
+ ''')
|
||||
+
|
||||
+ def _configure_webapp(self, allowed_domain):
|
||||
+ """Write the GSSAPI vhost config and restart httpd on the client.
|
||||
+
|
||||
+ allowed_domain: realm/domain for access control (e.g. IPA.TEST for
|
||||
+ IPA users, AD.DOMAIN for AD users). Users whose principal ends with
|
||||
+ @allowed_domain are granted access.
|
||||
+ """
|
||||
+ # Escape dots for regex (e.g. IPA.TEST -> IPA\\.TEST)
|
||||
+ escaped = re.escape(allowed_domain)
|
||||
+ allowed_domain_regex = '.*@' + escaped
|
||||
+ keytab_path = f"/etc/httpd/conf/{self.clients[0].hostname}.keytab"
|
||||
+ self.clients[0].put_file_contents(
|
||||
+ '/etc/httpd/conf.d/mywebapp.conf',
|
||||
+ self.apache_conf.format(
|
||||
+ keytab_path=keytab_path,
|
||||
+ allowed_domain_regex=allowed_domain_regex,
|
||||
+ )
|
||||
+ )
|
||||
+ self.clients[0].run_command(['systemctl', 'restart', 'httpd'])
|
||||
+
|
||||
+ def _assert_curl_ok(self, msg=None):
|
||||
+ """Run curl with GSSAPI negotiate and assert the webapp responds."""
|
||||
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
||||
+ result = self.clients[0].run_command([
|
||||
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
||||
+ ])
|
||||
+ assert "TEST_MY_WEB_APP" in result.stdout_text, (
|
||||
+ msg or f"Expected webapp content at {url}"
|
||||
+ )
|
||||
+
|
||||
+ def _assert_curl_GSSAPI_access_denied(self, msg=None):
|
||||
+ """Run curl with GSSAPI negotiate and assert a 401 is returned."""
|
||||
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
||||
+ result = self.clients[0].run_command([
|
||||
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
||||
+ ], raiseonerr=False)
|
||||
+ output = f"{result.stdout_text}{result.stderr_text}"
|
||||
+ assert ("401" in output
|
||||
+ or "Unauthorized" in output
|
||||
+ or "Authorization Required" in output), (
|
||||
+ msg or f"Expected 401/Unauthorized at {url}, got: {output[:200]}"
|
||||
+ )
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ """Extend base install to configure Apache/GSSAPI for HTTP tests.
|
||||
+
|
||||
+ Runs once before any test in this class. Sets up the AD trust,
|
||||
+ creates the HTTP service principal and IPA test user, installs
|
||||
+ mod_auth_gssapi, retrieves the service keytab, and provisions the
|
||||
+ static webapp content used by all HTTP tests.
|
||||
+ """
|
||||
+ super().install(mh)
|
||||
+ tasks.configure_dns_for_trust(cls.master, cls.ad)
|
||||
+ tasks.establish_trust_with_ad(
|
||||
+ cls.master, cls.ad_domain,
|
||||
+ extra_args=['--range-type', 'ipa-ad-trust'])
|
||||
+
|
||||
+ # Create HTTP service principal on master
|
||||
+ service_principal = f"HTTP/{cls.clients[0].hostname}"
|
||||
+ cls.master.run_command(
|
||||
+ ["ipa", "service-add", service_principal]
|
||||
+ )
|
||||
+
|
||||
+ # Create IPA user for HTTP tests
|
||||
+ tasks.create_active_user(
|
||||
+ cls.master, "ipahttpuser1", password="Passw0rd1",
|
||||
+ first="f", last="l"
|
||||
+ )
|
||||
+
|
||||
+ # Clear SSSD cache on master
|
||||
+ tasks.clear_sssd_cache(cls.master)
|
||||
+ tasks.wait_for_sssd_domain_status_online(cls.master)
|
||||
+
|
||||
+ # Install Apache and the GSSAPI module on the IPA client
|
||||
+ tasks.install_packages(
|
||||
+ cls.clients[0], ['mod_auth_gssapi', 'httpd']
|
||||
+ )
|
||||
+
|
||||
+ # Retrieve and protect the HTTP service keytab
|
||||
+ keytab_path = f"/etc/httpd/conf/{cls.clients[0].hostname}.keytab"
|
||||
+ cls.clients[0].run_command([
|
||||
+ 'ipa-getkeytab', '-s', cls.master.hostname,
|
||||
+ '-k', keytab_path,
|
||||
+ '-p', service_principal
|
||||
+ ])
|
||||
+ cls.clients[0].run_command(
|
||||
+ ['chown', 'apache:apache', keytab_path]
|
||||
+ )
|
||||
+
|
||||
+ # Create webapp directory and static content
|
||||
+ cls.clients[0].run_command(
|
||||
+ ['mkdir', '-p', '/var/www/html/mywebapp']
|
||||
+ )
|
||||
+ cls.clients[0].put_file_contents(
|
||||
+ '/var/www/html/mywebapp/index.html',
|
||||
+ 'TEST_MY_WEB_APP\n'
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_ipauser(self):
|
||||
+ """
|
||||
+ Test IPA User access http with kerberos ticket via valid user.
|
||||
+
|
||||
+ This test verifies that an IPA user with a valid Kerberos ticket
|
||||
+ can successfully access an HTTP resource protected by GSSAPI
|
||||
+ authentication and restricted to IPA users.
|
||||
+ """
|
||||
+ ipa_realm = self.clients[0].domain.realm
|
||||
+ self._configure_webapp(ipa_realm)
|
||||
+
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], f'ipahttpuser1@{ipa_realm}', "Passw0rd1"
|
||||
+ )
|
||||
+
|
||||
+ self._assert_curl_ok()
|
||||
+
|
||||
+ users = [
|
||||
+ (self.aduser, self.ad_domain),
|
||||
+ (self.subaduser, self.ad_subdomain),
|
||||
+ ]
|
||||
+ for aduser, domain in users:
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ # pylint: disable=use-maxsplit-arg
|
||||
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], principal, self.ad_user_password
|
||||
+ )
|
||||
+ self._assert_curl_GSSAPI_access_denied(
|
||||
+ msg=f"Expected 401 for AD user {aduser}"
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_aduser(self):
|
||||
+ """
|
||||
+ Test AD root and subdomain users access http with kerberos ticket.
|
||||
+
|
||||
+ This test verifies that both a root AD domain user and a child
|
||||
+ subdomain user with valid Kerberos tickets can successfully access
|
||||
+ an HTTP resource protected by GSSAPI authentication and restricted
|
||||
+ to AD domain / AD subdomain users.
|
||||
+ """
|
||||
+ users = [
|
||||
+ (self.aduser, self.ad_domain),
|
||||
+ (self.subaduser, self.ad_subdomain),
|
||||
+ ]
|
||||
+ for aduser, domain in users:
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ # pylint: disable=use-maxsplit-arg
|
||||
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
||||
+ self._configure_webapp(domain.upper())
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], principal, self.ad_user_password
|
||||
+ )
|
||||
+ self._assert_curl_ok(
|
||||
+ msg=f"Expected webapp content for AD user {aduser}"
|
||||
+ )
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ tasks.kinit_as_user(self.clients[0], "ipahttpuser1", "Passw0rd1")
|
||||
+ self._assert_curl_GSSAPI_access_denied(
|
||||
+ msg=f"Expected 401 for IPA user after AD user {aduser}"
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_nouser(self):
|
||||
+ """
|
||||
+ Test User cannot access http without kerberos ticket via valid user.
|
||||
+
|
||||
+ This test verifies that an user without a valid Kerberos ticket
|
||||
+ is denied access to an HTTP resource protected by GSSAPI
|
||||
+ authentication, receiving a 401 Unauthorized error.
|
||||
+ """
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+
|
||||
+ self._assert_curl_GSSAPI_access_denied()
|
||||
--
|
||||
2.53.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,543 +0,0 @@
|
||||
From 9a8553fc297987b006b28ade2098d613f9b5360f Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Thu, 12 Mar 2026 19:03:24 +0530
|
||||
Subject: [PATCH] ipatests: Add user-principal ipa-getkeytab and ipa-rmkeytab
|
||||
cmdline tests
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Port bash acceptance tests (getkeytab_001–getkeytab_007, rmkeytab_001-rmkeytab_003) from downstream to cmdline tests in test_ipagetkeytab.py:
|
||||
|
||||
test_getkeytab_users:
|
||||
getkeytab_001: quiet mode, insufficient access, empty ccache
|
||||
getkeytab_002: --server / -s with valid and invalid hostnames, DNS-based server discovery when -s is omitted
|
||||
getkeytab_003: --principal / -p with unknown, invalid-realm,
|
||||
bare, and realm-qualified principals
|
||||
getkeytab_004: --keytab / -k creation, enctype content, invalid path
|
||||
getkeytab_005: -e single enctype filtering and kinit validation
|
||||
getkeytab_006: --password / -P key rotation
|
||||
getkeytab_007: -D / -w bind DN error cases (empty DN, wrong
|
||||
password, missing password)
|
||||
|
||||
test_rmkeytab_cmd:
|
||||
rmkeytab_001: removal by principal (valid and invalid)
|
||||
rmkeytab_002: removal by realm
|
||||
rmkeytab_003: invalid keytab path
|
||||
|
||||
Tests that duplicate existing coverage are skipped with comments referencing the original tests. Parametrized variants are used where flags accept both short and long forms.
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9957
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_cmdline/test_ipagetkeytab.py | 459 +++++++++++++++++++++
|
||||
1 file changed, 459 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
index c4678a76d3ad6b1d75e8d54eff194a34d62f4906..9ec9efae60f7a546978a53e5f9f1afb5e7ff1b5c 100755
|
||||
--- a/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
+++ b/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
@@ -22,6 +22,7 @@ Test `ipa-getkeytab`
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import configparser
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
@@ -40,6 +41,28 @@ from ipatests.test_xmlrpc.tracker import host_plugin, service_plugin
|
||||
from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_digits, add_oc
|
||||
from contextlib import contextmanager
|
||||
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+from ipatests.util import unlock_principal_password
|
||||
+
|
||||
+GK_USER1 = 'gkuser1'
|
||||
+GK_USER2 = 'gkuser2'
|
||||
+GK_INIT_PW = 'TempSecret123!'
|
||||
+GK_USER_PW = 'Secret123'
|
||||
+
|
||||
+CRYPTO_POLICIES_KRB5 = '/etc/krb5.conf.d/crypto-policies'
|
||||
+
|
||||
+
|
||||
+def get_permitted_enctypes():
|
||||
+ """Read permitted encryption types from the system crypto-policies.
|
||||
+
|
||||
+ Returns a list of enctype name strings, e.g.
|
||||
+ ['aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96', ...].
|
||||
+ """
|
||||
+ cfg = configparser.ConfigParser()
|
||||
+ cfg.read(CRYPTO_POLICIES_KRB5)
|
||||
+ raw = cfg.get('libdefaults', 'permitted_enctypes', fallback='')
|
||||
+ return [e for e in raw.split() if e]
|
||||
+
|
||||
|
||||
@contextmanager
|
||||
def use_keytab(principal, keytab):
|
||||
@@ -63,6 +86,57 @@ def use_keytab(principal, keytab):
|
||||
setattr(context, 'principal', old_principal)
|
||||
|
||||
|
||||
+@contextmanager
|
||||
+def kinit_as_user(principal, password):
|
||||
+ """Kinit as *principal* into a private ccache; yield its path.
|
||||
+
|
||||
+ private_ccache() already sets KRB5CCNAME in os.environ, so callers
|
||||
+ (and ipautil.run) inherit it automatically.
|
||||
+ """
|
||||
+ with private_ccache() as ccache_path:
|
||||
+ ipautil.run(
|
||||
+ ['kinit', principal],
|
||||
+ stdin=password + '\n',
|
||||
+ raiseonerr=True,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+ yield ccache_path
|
||||
+
|
||||
+
|
||||
+def run_getkeytab(args, env=None, stdin=None):
|
||||
+ """Run ipa-getkeytab with arbitrary arguments."""
|
||||
+ return ipautil.run(
|
||||
+ [paths.IPA_GETKEYTAB] + list(args),
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ stdin=stdin,
|
||||
+ env=env,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def run_rmkeytab(args, env=None):
|
||||
+ """Run ipa-rmkeytab with arbitrary arguments."""
|
||||
+ return ipautil.run(
|
||||
+ [paths.IPA_RMKEYTAB] + list(args),
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ env=env,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def klist_keytab(keytab_path):
|
||||
+ """Run ``klist -ekt`` and return the result object."""
|
||||
+ return ipautil.run(
|
||||
+ ['klist', '-ekt', keytab_path],
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
@pytest.fixture(scope='class')
|
||||
def test_host(request):
|
||||
host_tracker = host_plugin.HostTracker(u'test-host')
|
||||
@@ -76,6 +150,20 @@ def test_service(request, test_host, keytab_retrieval_setup):
|
||||
return service_tracker.make_fixture(request)
|
||||
|
||||
|
||||
+@pytest.fixture(scope='class')
|
||||
+def gk_users(request, keytab_retrieval_setup):
|
||||
+ """Create GK_USER1 and GK_USER2; delete them after the class."""
|
||||
+ for uid in (GK_USER1, GK_USER2):
|
||||
+ tracker = UserTracker(
|
||||
+ name=uid, givenname='Test', sn='GKUser',
|
||||
+ userpassword=GK_INIT_PW,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(uid, GK_INIT_PW, GK_USER_PW)
|
||||
+
|
||||
+
|
||||
@pytest.mark.needs_ipaapi
|
||||
class KeytabRetrievalTest(cmdline_test):
|
||||
"""
|
||||
@@ -134,6 +222,22 @@ class KeytabRetrievalTest(cmdline_test):
|
||||
rc = result.returncode
|
||||
assert rc == retcode
|
||||
|
||||
+ def _get_user1_keytab(self, keytab_path=None):
|
||||
+ """Populate *keytab_path* (default self.keytabname) with GK_USER1's
|
||||
+ keytab."""
|
||||
+ keytab = keytab_path or self.keytabname
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, '-k', keytab]
|
||||
+ )
|
||||
+
|
||||
+ def _assert_getkeytab_succeeds(self, result):
|
||||
+ """Assert ipa-getkeytab returned success with the expected message."""
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Keytab successfully retrieved and stored in:'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
|
||||
@pytest.mark.tier0
|
||||
class test_ipagetkeytab(KeytabRetrievalTest):
|
||||
@@ -495,3 +599,358 @@ class test_smb_service(KeytabRetrievalTest):
|
||||
ipanthash = entry.single_value.get('ipanthash')
|
||||
conn.disconnect()
|
||||
assert ipanthash != b'MagicRegen', 'LDBM backend entry corruption'
|
||||
+
|
||||
+
|
||||
+# -----------------------------------------------------------------------
|
||||
+# User-principal tests ported from bash acceptance tests
|
||||
+# (t.ipa-get-rm-keytabs.sh: getkeytab_001 – getkeytab_006)
|
||||
+# -----------------------------------------------------------------------
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('gk_users')
|
||||
+class test_getkeytab_users(KeytabRetrievalTest):
|
||||
+ """
|
||||
+ User-principal ipa-getkeytab tests (bash getkeytab_001 – getkeytab_006).
|
||||
+ """
|
||||
+ command = paths.IPA_GETKEYTAB
|
||||
+
|
||||
+ def _ensure_keytab_absent(self, keytab_path=None):
|
||||
+ try:
|
||||
+ os.unlink(keytab_path or self.keytabname)
|
||||
+ except OSError:
|
||||
+ pass
|
||||
+
|
||||
+ # --- getkeytab_001: quiet mode, access rights, missing ccache ---
|
||||
+
|
||||
+ def test_insufficient_access_as_other_user(self):
|
||||
+ """Retrieving another user's keytab without admin rights fails."""
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ principal2 = '{}@{}'.format(GK_USER2, api.env.realm)
|
||||
+ with kinit_as_user(principal1, GK_USER_PW):
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host,
|
||||
+ '-p', principal2,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert (
|
||||
+ 'Failed to parse result: Insufficient access rights'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ def test_empty_ccache_returns_exit6(self):
|
||||
+ """An empty ccache (simulating kdestroy) returns exit 6."""
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ with private_ccache():
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', principal1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 6
|
||||
+ assert (
|
||||
+ 'Kerberos User Principal not found. '
|
||||
+ 'Do you have a valid Credential Cache?'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ def test_quiet_suppresses_success_message(self):
|
||||
+ """-q must suppress the keytab-stored success message.
|
||||
+
|
||||
+ Note: test_6_quiet_mode checks -q returncode with a service
|
||||
+ principal but does not verify the message is absent.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ ['-q', '-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert result.error_output == '', (
|
||||
+ f"Expected no output with -q, got: {result.error_output!r}"
|
||||
+ )
|
||||
+
|
||||
+ # Skipped: test_normal_mode_shows_success_message
|
||||
+ # — covered by test_6_quiet_mode which verifies the success message
|
||||
+ # appears without -q.
|
||||
+
|
||||
+ # --- getkeytab_002: --server / -s ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--server', '-s'])
|
||||
+ def test_invalid_server_fails(self, flag):
|
||||
+ """An invalid server hostname returns exit 9 with bind error."""
|
||||
+ result = run_getkeytab(
|
||||
+ [flag, 'invalid.ipaserver.com',
|
||||
+ '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert 'Failed to bind to server' in result.error_output
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--server', '-s'])
|
||||
+ def test_valid_server_succeeds(self, flag):
|
||||
+ """A valid server hostname retrieves the keytab successfully.
|
||||
+
|
||||
+ Note: -s case is also covered by test_7_server_name_check
|
||||
+ with a service principal.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ [flag, api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ def test_dns_discovery_succeeds(self):
|
||||
+ """ipa-getkeytab discovers the server via DNS when -s is omitted."""
|
||||
+ result = run_getkeytab(
|
||||
+ ['-p', GK_USER1, '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ # --- getkeytab_003: --principal / -p ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--principal', '-p'])
|
||||
+ @pytest.mark.parametrize('principal', [
|
||||
+ pytest.param('unknownuser', id='unknown'),
|
||||
+ pytest.param(
|
||||
+ '{}@INVALID.IPASERVER.REALM.COM'.format(GK_USER1),
|
||||
+ id='invalid-realm',
|
||||
+ ),
|
||||
+ ])
|
||||
+ def test_principal_not_found(self, flag, principal):
|
||||
+ """Unresolvable principals return exit 9 with not-found error.
|
||||
+
|
||||
+ Note: test_1_run tests a similar scenario with a non-existent
|
||||
+ service principal.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, flag, principal,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert (
|
||||
+ 'Failed to parse result: PrincipalName not found.'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--principal', '-p'])
|
||||
+ @pytest.mark.parametrize('with_realm', [False, True],
|
||||
+ ids=['bare', 'with-realm'])
|
||||
+ def test_valid_principal_succeeds(self, flag, with_realm):
|
||||
+ """Both bare and realm-qualified principals retrieve the keytab."""
|
||||
+ principal = ('{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ if with_realm else GK_USER1)
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, flag, principal,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ # --- getkeytab_004: --keytab / -k ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_creates_keytab_when_absent(self, flag):
|
||||
+ """The keytab file is created when it does not previously exist."""
|
||||
+ self._ensure_keytab_absent()
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+ assert os.path.isfile(self.keytabname)
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_keytab_contains_aes_enctypes(self, flag):
|
||||
+ """The created keytab contains both aes256 and aes128 entries."""
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, self.keytabname]
|
||||
+ )
|
||||
+ klist_result = klist_keytab(self.keytabname)
|
||||
+ assert api.env.realm in klist_result.output
|
||||
+ assert 'aes128' in klist_result.output
|
||||
+ assert 'aes256' in klist_result.output
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_text_file_path_returns_exit11(self, flag):
|
||||
+ """Writing to a pre-existing plain-text file returns exit 11."""
|
||||
+ txtfd, txt_path = tempfile.mkstemp(suffix='.txt')
|
||||
+ os.close(txtfd)
|
||||
+ try:
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, txt_path],
|
||||
+ )
|
||||
+ assert result.returncode == 11
|
||||
+ assert 'Failed to add key to the keytab' in result.error_output
|
||||
+ finally:
|
||||
+ try:
|
||||
+ os.unlink(txt_path)
|
||||
+ except OSError:
|
||||
+ pass
|
||||
+
|
||||
+ # --- getkeytab_005: -e encryption types ---
|
||||
+
|
||||
+ def test_system_keytab_has_default_enctypes(self):
|
||||
+ """The system keytab must contain both aes256 and aes128."""
|
||||
+ klist_result = klist_keytab('/etc/krb5.keytab')
|
||||
+ assert '(aes256-cts-hmac-sha1-96)' in klist_result.output
|
||||
+ assert '(aes128-cts-hmac-sha1-96)' in klist_result.output
|
||||
+
|
||||
+ @pytest.mark.parametrize('enctype', get_permitted_enctypes())
|
||||
+ def test_single_enctype(self, enctype):
|
||||
+ """With -e <enctype> only that enctype appears and kinit works.
|
||||
+
|
||||
+ Note: test_8_keytab_encryption_check tests -e with multiple
|
||||
+ enctypes but only asserts success, not klist content.
|
||||
+ """
|
||||
+ expected = f"({enctype})"
|
||||
+ self._ensure_keytab_absent()
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, '-e', enctype],
|
||||
+ )
|
||||
+ klist_result = klist_keytab(self.keytabname)
|
||||
+ assert expected in klist_result.output
|
||||
+ for enc in get_permitted_enctypes():
|
||||
+ if enc != enctype:
|
||||
+ assert f"({enc})" not in klist_result.output
|
||||
+ assert '(des3-cbc-sha1)' not in klist_result.output
|
||||
+ assert '(arcfour-hmac)' not in klist_result.output
|
||||
+ with private_ccache():
|
||||
+ ipautil.run(
|
||||
+ ['kinit', '-k', '-t', self.keytabname,
|
||||
+ '{}@{}'.format(GK_USER1, api.env.realm)],
|
||||
+ raiseonerr=True,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+
|
||||
+ def test_invalid_enctype_returns_exit8(self):
|
||||
+ """An invalid -e value returns exit 8 and creates no keytab."""
|
||||
+ self._ensure_keytab_absent()
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, '-e', 'invalid'],
|
||||
+ )
|
||||
+ assert result.returncode == 8
|
||||
+ assert 'Warning unrecognized encryption type' in result.error_output
|
||||
+ assert not os.path.isfile(self.keytabname)
|
||||
+
|
||||
+ # --- getkeytab_006: --password / -P ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--password', '-P'])
|
||||
+ def test_password_flag_rotates_keys(self, flag):
|
||||
+ """After --password and a random-key reset, the original password
|
||||
+ will no longer authenticate.
|
||||
+
|
||||
+ Sequence: admin sets password-derived keys -> user kinits -> user
|
||||
+ regenerates random keys -> kinit with the original password must fail.
|
||||
+ """
|
||||
+ self._ensure_keytab_absent()
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ stdin = '{pw}\n{pw}\n'.format(pw=GK_USER_PW)
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, flag],
|
||||
+ stdin=stdin,
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ with kinit_as_user(principal1, GK_USER_PW):
|
||||
+ regen = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert regen.returncode == 0
|
||||
+ with private_ccache():
|
||||
+ kinit_result = ipautil.run(
|
||||
+ ['kinit', principal1],
|
||||
+ stdin=GK_USER_PW + '\n',
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+ assert kinit_result.returncode != 0
|
||||
+ assert (
|
||||
+ 'Password incorrect' in kinit_result.error_output
|
||||
+ or 'Preauthentication failed' in kinit_result.error_output
|
||||
+ )
|
||||
+
|
||||
+ # --- getkeytab_007: -D / -w bind DN error cases ---
|
||||
+
|
||||
+ # Skipped: test_no_ccache_returns_exit6
|
||||
+ # — identical to test_empty_ccache_returns_exit6 above.
|
||||
+ # Skipped: test_valid_dm_credentials_succeed
|
||||
+ # — covered by TestBindMethods.test_retrieval_with_dm_creds.
|
||||
+
|
||||
+ @pytest.mark.parametrize('extra_args,exitcode,message', [
|
||||
+ (['-D', ' ', '-w', GK_USER_PW],
|
||||
+ 9, 'Anonymous Binds are not allowed'),
|
||||
+ (['-D', 'cn=Directory Manager', '-w', ' '],
|
||||
+ 9, 'Simple bind failed'),
|
||||
+ (['-D', 'cn=Directory Manager'],
|
||||
+ 10, 'Bind password required when using a bind DN'),
|
||||
+ ], ids=['empty-dn', 'wrong-password', 'missing-password'])
|
||||
+ def test_binddn_error_cases(self, extra_args, exitcode, message):
|
||||
+ """Bind DN error cases return appropriate exit codes."""
|
||||
+ result = run_getkeytab(
|
||||
+ ['--server', 'localhost',
|
||||
+ '-p', GK_USER1,
|
||||
+ '-k', self.keytabname] + extra_args,
|
||||
+ )
|
||||
+ assert result.returncode == exitcode
|
||||
+ assert message in result.error_output
|
||||
+
|
||||
+
|
||||
+# -----------------------------------------------------------------------
|
||||
+# ipa-rmkeytab tests (bash rmkeytab_001 – rmkeytab_003)
|
||||
+# -----------------------------------------------------------------------
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('gk_users')
|
||||
+class test_rmkeytab_cmd(KeytabRetrievalTest):
|
||||
+ """
|
||||
+ ipa-rmkeytab tests (bash rmkeytab_001 – rmkeytab_003).
|
||||
+ """
|
||||
+ command = paths.IPA_RMKEYTAB
|
||||
+
|
||||
+ # --- rmkeytab_001: -p removes a named principal ---
|
||||
+
|
||||
+ def test_invalid_principal_returns_exit5(self):
|
||||
+ """A non-existent principal name returns exit 5."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-p', 'invalidprinc', '-k', self.keytabname])
|
||||
+ assert result.returncode == 5
|
||||
+ assert 'principal not found' in result.error_output
|
||||
+
|
||||
+ def test_valid_principal_removed(self):
|
||||
+ """A present principal is removed and absent from klist."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-p', GK_USER1, '-k', self.keytabname])
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Removing principal {}'.format(GK_USER1)
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+ assert GK_USER1 not in klist_keytab(self.keytabname).output
|
||||
+
|
||||
+ # --- rmkeytab_002: -r removes all principals of the given realm ---
|
||||
+
|
||||
+ def test_realm_removes_all_principals(self):
|
||||
+ """All principals of the realm are removed and absent from klist."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-r', api.env.realm, '-k', self.keytabname])
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Removing principal {}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+ assert api.env.realm not in klist_keytab(self.keytabname).output
|
||||
+
|
||||
+ # --- rmkeytab_003: -k with a non-existent path ---
|
||||
+
|
||||
+ def test_invalid_keytab_path_returns_exit7(self):
|
||||
+ """A non-existent keytab path returns exit 7."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(
|
||||
+ ['-p', GK_USER1, '-k', '/opt/invalid.keytab']
|
||||
+ )
|
||||
+ assert result.returncode == 7
|
||||
+ assert 'Failed to set cursor' in result.error_output
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,276 +0,0 @@
|
||||
From b6d50197882dd62b5416948cb232c77c0349c0d3 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Thu, 5 Mar 2026 19:29:26 +0530
|
||||
Subject: [PATCH] ipatests: Additional tests for ipa-ipa migration testsuite
|
||||
|
||||
Covers tests scenarios for sshpubkey.
|
||||
|
||||
1. Test to check sshpubkey is migrated for regular ipa user
|
||||
2. Test to check sshpubkey is migrated for staged user
|
||||
3. Test to check sshpubkey is migrated for preserved user
|
||||
4. Test to check idoverides is migrated.
|
||||
|
||||
Covers test scenarios for TestIPAMigrationDNSRecords
|
||||
|
||||
1. Test to check dns zone is migrated
|
||||
2. Test to check dynamic update value is preserved when migrated
|
||||
3. Test to check dnsforwardzone is migrated
|
||||
4. Test to check A record is migrated
|
||||
|
||||
Covers test scenarios for IPA Privieleges and Permissions
|
||||
|
||||
1. Test to check that IPA role and its privilege are migrated in Prod mode
|
||||
2. Test to check that PBAC Permissions are migrated in Prod mode
|
||||
3. Test to check that sysaccounts are migrated.
|
||||
4. Test to check selinuxusermap is migrated
|
||||
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9964
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Sudhir Menon <sumenon@redhat.com>
|
||||
---
|
||||
.../test_ipa_ipa_migration.py | 155 ++++++++++++++++--
|
||||
1 file changed, 146 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
index a550e8f208dc8e225cd73b58e6d71e1905174420..f759b15f2a9516550b7935897d8368d3ce3ad853 100644
|
||||
--- a/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
@@ -201,6 +201,12 @@ def prepare_ipa_server(master):
|
||||
master.run_command(
|
||||
["ipa", "dnszone-mod", "example.test", "--dynamic-update=TRUE"]
|
||||
)
|
||||
+ master.run_command(
|
||||
+ [
|
||||
+ "ipa", "dnsrecord-add", "example.test", "migratetest",
|
||||
+ "--a-rec", "192.0.2.100",
|
||||
+ ]
|
||||
+ )
|
||||
|
||||
# Add hbac rule
|
||||
master.run_command(["ipa", "hbacrule-add", "--usercat=all", "test1"])
|
||||
@@ -221,7 +227,7 @@ def prepare_ipa_server(master):
|
||||
"dnsforwardzone-add",
|
||||
"forwardzone.test",
|
||||
"--forwarder",
|
||||
- "10.11.12.13",
|
||||
+ "192.168.124.10",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -293,6 +299,11 @@ def prepare_ipa_server(master):
|
||||
]
|
||||
)
|
||||
|
||||
+ # Add sysaccount
|
||||
+ master.run_command(
|
||||
+ ["ipa", "sysaccount-add", "migrate-test-sysaccount", "--random"]
|
||||
+ )
|
||||
+
|
||||
|
||||
def run_migrate(
|
||||
host, mode, remote_host, bind_dn=None, bind_pwd=None, extra_args=None
|
||||
@@ -671,8 +682,8 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
objectClass: krbrealmcontainer
|
||||
"""
|
||||
).format(
|
||||
- realm_name=self.master.domain.realm,
|
||||
- base_dn=str(self.master.domain.basedn),
|
||||
+ realm_name=realm_name,
|
||||
+ base_dn=base_dn,
|
||||
)
|
||||
self.replicas[0].put_file_contents(ldif_file_path, dse_ldif)
|
||||
result = run_migrate(
|
||||
@@ -731,7 +742,6 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
base_dn = str(self.master.domain.basedn)
|
||||
subtree = 'cn=security,{}'.format(base_dn)
|
||||
params = ['-s', subtree, '-n', '-x']
|
||||
- base_dn = str(self.master.domain.basedn)
|
||||
CUSTOM_SUBTREE_LOG = (
|
||||
"Add db entry 'cn=security,{} - custom'"
|
||||
).format(base_dn)
|
||||
@@ -853,7 +863,7 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
def test_ipa_migrate_stage_mode_with_cert(self):
|
||||
"""
|
||||
This testcase checks that ipa-migrate command
|
||||
- works without the 'ValuerError'
|
||||
+ works without the 'ValueError'
|
||||
when -Z <cert> option is used with valid cert
|
||||
"""
|
||||
cert_file = '/tmp/ipa.crt'
|
||||
@@ -878,7 +888,7 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
error when invalid cert is specified with
|
||||
-Z option
|
||||
"""
|
||||
- cert_file = '/tmp/invaid_cert.crt'
|
||||
+ cert_file = '/tmp/invalid_cert.crt'
|
||||
invalid_cert = (
|
||||
b'-----BEGIN CERTIFICATE-----\n'
|
||||
b'MIIFazCCDQYJKoZIhvcNAQELBQAw\n'
|
||||
@@ -1026,6 +1036,59 @@ class TestIPAMigrationProdMode(MigrationTest):
|
||||
assert 'Rule name: readfiles\n' in cmd1.stdout_text
|
||||
assert 'Sudo Command: /usr/bin/less\n' in cmd2.stdout_text
|
||||
|
||||
+ def test_ipa_migrate_prod_mode_roles_privileges(self):
|
||||
+ """
|
||||
+ Test that IPA roles are migrated from remote to local server
|
||||
+ """
|
||||
+ role_name = "junioradmin"
|
||||
+ privilege_name = "User Administrators"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "role-show", role_name]
|
||||
+ )
|
||||
+ assert "Role name: {}".format(role_name) in result.stdout_text
|
||||
+ assert "Privileges: {}".format(privilege_name) in result.stdout_text
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_permission(self):
|
||||
+ """
|
||||
+ Test that PBAC permission is migrated
|
||||
+ """
|
||||
+ permission_name = "Add Users"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "permission-show", permission_name]
|
||||
+ )
|
||||
+ assert (f"Permission name: {permission_name}" in
|
||||
+ result.stdout_text)
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_sysaccounts(self):
|
||||
+ """
|
||||
+ Test that system accounts (sysaccounts) are migrated
|
||||
+ from remote server to local server in prod mode.
|
||||
+ """
|
||||
+ sysaccount_name = "migrate-test-sysaccount"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "sysaccount-show", sysaccount_name]
|
||||
+ )
|
||||
+ assert sysaccount_name in result.stdout_text
|
||||
+ assert (f"System account ID: {sysaccount_name}" in
|
||||
+ result.stdout_text)
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_selinuxusermap(self):
|
||||
+ """
|
||||
+ Test that SELinux usermap is migrated from remote
|
||||
+ to local server.
|
||||
+ """
|
||||
+ usermap_name = "test1"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "selinuxusermap-show", usermap_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert f"Rule name: {usermap_name}" in result.stdout_text
|
||||
+ assert "xguest_u:s0" in result.stdout_text
|
||||
+
|
||||
def test_ipa_migrate_prod_mode_new_user_sid(self):
|
||||
"""
|
||||
This testcase checks that in prod-mode uid/gid of the
|
||||
@@ -1390,6 +1251,89 @@ class TestIPAMigrationProdMode(MigrationTest):
|
||||
assert DEBUG_LOG in install_msg
|
||||
|
||||
|
||||
+class TestIPAMigrationDNSRecords(MigrationTest):
|
||||
+ """
|
||||
+ Tests to verify all DNS zones, forward zones, and DNS records
|
||||
+ are migrated when ipa-migrate is run with --migrate-dns (-B).
|
||||
+ "By default all DNS entries are migrated" / -B to migrate DNS.
|
||||
+ """
|
||||
+ num_replicas = 1
|
||||
+ num_clients = 1
|
||||
+ topology = "line"
|
||||
+
|
||||
+ @pytest.fixture(autouse=True)
|
||||
+ def run_migration_with_dns(self):
|
||||
+ """
|
||||
+ Run full prod-mode migration with -B so that DNS is migrated
|
||||
+ to the local server. All tests in this class assume DNS has
|
||||
+ been migrated.
|
||||
+ """
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ run_migrate(
|
||||
+ self.replicas[0],
|
||||
+ "prod-mode",
|
||||
+ self.master.hostname,
|
||||
+ "cn=Directory Manager",
|
||||
+ self.master.config.admin_password,
|
||||
+ extra_args=["-B", "-n"],
|
||||
+ )
|
||||
+
|
||||
+ def test_dns_zone_example_test_migrated(self):
|
||||
+ """
|
||||
+ Check that DNS zone example.test (from prepare_ipa_server)
|
||||
+ is migrated to the local server.
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnszone-show", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert "Zone name: {}".format(zone_name) in result.stdout_text
|
||||
+
|
||||
+ def test_dns_zone_dynamic_update_preserved(self):
|
||||
+ """
|
||||
+ Check that zone attribute dynamic update is preserved
|
||||
+ (prepare_ipa_server sets dynamic-update=TRUE for example.test).
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnszone-show", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert "Dynamic update: True" in result.stdout_text
|
||||
+
|
||||
+ def test_dns_zone_has_system_records(self):
|
||||
+ """
|
||||
+ Check that migrated zone has system records (NS/SOA).
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnsrecord-find", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ # Zone should have records (e.g. NS, SOA, or record list)
|
||||
+ assert (
|
||||
+ "NS record" in result.stdout_text
|
||||
+ or "SOA record" in result.stdout_text
|
||||
+ or "Record name" in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ def test_dns_record_a_migrated(self):
|
||||
+ """
|
||||
+ Verify that the A record added in prepare_ipa_server is
|
||||
+ migrated to the local server.
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ record_name = "migratetest"
|
||||
+ record_value = "192.0.2.100"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnsrecord-show", zone_name, record_name]
|
||||
+ )
|
||||
+ assert record_name in result.stdout_text
|
||||
+ assert record_value in result.stdout_text
|
||||
+
|
||||
+
|
||||
class TestIPAMigrationWithADtrust(IntegrationTest):
|
||||
"""
|
||||
Test for ipa-migrate tool with IPA Master having trust setup
|
||||
@@ -1319,9 +1465,9 @@ class TestIPAMigratewithBackupRestore(IntegrationTest):
|
||||
DB_LDIF_FILE = '{}-userRoot.ldif'.format(
|
||||
dashed_domain_name
|
||||
)
|
||||
- SCHEMA_LDIF_FILE = '{}''/config_files/schema/99user.ldif'.format(
|
||||
+ SCHEMA_LDIF_FILE = "{}/config_files/schema/99user.ldif".format(
|
||||
dashed_domain_name)
|
||||
- CONFIG_LDIF_FILE = '{}''/config_files/dse.ldif'.format(
|
||||
+ CONFIG_LDIF_FILE = "{}/config_files/dse.ldif".format(
|
||||
dashed_domain_name)
|
||||
param = [
|
||||
'-n', '-g', CONFIG_LDIF_FILE, '-m', SCHEMA_LDIF_FILE,
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,289 +0,0 @@
|
||||
From 8f2bbb02ae64f322fb34f073d7a7cdb00a69aea0 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Thu, 19 Mar 2026 17:12:12 +0530
|
||||
Subject: [PATCH] ipatests: ipa-migrate-ds test scenarios
|
||||
|
||||
This patch tests ipa-migrated-ds related sceanrios
|
||||
|
||||
1. 389-ds instance is setup on client system.
|
||||
2. Attached sample instance1.ldif file in data/ds_migration folder.
|
||||
3. Attempt ipa-migrate-ds with configuration disabled.
|
||||
3. Attempt ipa-migrate-ds over ldaps.
|
||||
|
||||
Assisted by: claude-4.6-opus-high <noreply@anthropic.com>
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/setup.py | 2 +-
|
||||
.../data/ds_migration/instance1.ldif | 48 +++++
|
||||
.../test_integration/test_ds_migration.py | 189 ++++++++++++++++++
|
||||
3 files changed, 238 insertions(+), 1 deletion(-)
|
||||
create mode 100644 ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
create mode 100644 ipatests/test_integration/test_ds_migration.py
|
||||
|
||||
diff --git a/ipatests/setup.py b/ipatests/setup.py
|
||||
index 0aec4a70dbd75d416e4288e1204130daf46bda94..07595a501b5ff7b30fbd6025b7d3d0b3eb253337 100644
|
||||
--- a/ipatests/setup.py
|
||||
+++ b/ipatests/setup.py
|
||||
@@ -56,7 +56,7 @@ if __name__ == '__main__':
|
||||
'ipatests': ['prci_definitions/*'],
|
||||
'ipatests.test_custodia': ['*.conf', 'empty.conf.d/*.conf'],
|
||||
'ipatests.test_install': ['*.update'],
|
||||
- 'ipatests.test_integration': ['scripts/*'],
|
||||
+ 'ipatests.test_integration': ['data/*/*.ldif'],
|
||||
'ipatests.test_ipaclient': ['data/*/*/*'],
|
||||
'ipatests.test_ipalib': ['data/*'],
|
||||
'ipatests.test_ipaplatform': ['data/*'],
|
||||
diff --git a/ipatests/test_integration/data/ds_migration/instance1.ldif b/ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..a148b52c8c55134c36089d076b71745dbd30aeed
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
@@ -0,0 +1,48 @@
|
||||
+# Minimal DS migration test data: base OUs and sample user/group
|
||||
+# Used when setting up 389-ds on the client for migrate-ds tests.
|
||||
+# Base suffix dc=testrealm,dc=test is created by dscreate.
|
||||
+
|
||||
+dn: dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: domain
|
||||
+objectClass: dcObject
|
||||
+dc: testrealm
|
||||
+
|
||||
+dn: ou=People,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: organizationalUnit
|
||||
+ou: People
|
||||
+
|
||||
+dn: ou=Groups,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: organizationalUnit
|
||||
+ou: Groups
|
||||
+
|
||||
+dn: cn=Directory Administrators, ou=Groups, dc=testrealm,dc=test
|
||||
+cn: Directory Administrators
|
||||
+objectclass: top
|
||||
+objectclass: groupofuniquenames
|
||||
+ou: Groups
|
||||
+uniquemember: uid=ldapuser_0001, ou=People, dc=testrealm,dc=test
|
||||
+
|
||||
+dn: uid=ldapuser_0001,ou=People,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: person
|
||||
+objectClass: posixAccount
|
||||
+uid: ldapuser_0001
|
||||
+cn: LDAP User 1
|
||||
+sn: User 1
|
||||
+uidNumber: 1001
|
||||
+gidNumber: 1001
|
||||
+homeDirectory: /home/ldapuser_0001
|
||||
+userPassword: fo0m4nchU
|
||||
+telephonenumber: +1 123 444 555
|
||||
+
|
||||
+dn: cn=ldapgroup_0001,ou=Groups,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: groupOfNames
|
||||
+objectClass: posixGroup
|
||||
+cn: ldapgroup_0001
|
||||
+gidNumber: 1001
|
||||
+member: uid=ldapuser_0001,ou=People,dc=testrealm,dc=test
|
||||
+
|
||||
diff --git a/ipatests/test_integration/test_ds_migration.py b/ipatests/test_integration/test_ds_migration.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..f16759b374ff3d47c304b100a18be0e016d9e7a7
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/test_ds_migration.py
|
||||
@@ -0,0 +1,189 @@
|
||||
+#
|
||||
+# Copyright (C) 2026 FreeIPA Contributors see COPYING for license
|
||||
+#
|
||||
+
|
||||
+"""
|
||||
+ipa-migrate-ds migration acceptance tests.
|
||||
+"""
|
||||
+
|
||||
+from __future__ import absolute_import
|
||||
+
|
||||
+import os
|
||||
+import textwrap
|
||||
+
|
||||
+from ipatests.test_integration.base import IntegrationTest
|
||||
+from ipatests.pytest_ipa.integration import tasks
|
||||
+
|
||||
+# 389-ds instance name and base DN on the client
|
||||
+DS_INSTANCE_NAME = "dsinstance_01"
|
||||
+DS_BASEDN = "dc=testrealm,dc=test"
|
||||
+DS_PORT = 389
|
||||
+DS_SECURE_PORT = 636
|
||||
+
|
||||
+
|
||||
+def _setup_389ds_on_client(client, admin_password):
|
||||
+ """
|
||||
+ Install 389 Directory Server on the client and load migration
|
||||
+ test data from instance1.ldif i.e (ou=People, ou=groups,
|
||||
+ sample user/group).
|
||||
+ """
|
||||
+ tasks.install_packages(client, ["389-ds-base"])
|
||||
+
|
||||
+ # Create instance via dscreate
|
||||
+ inf_content = textwrap.dedent("""\
|
||||
+ [general]
|
||||
+ full_machine_name = {hostname}
|
||||
+ [slapd]
|
||||
+ instance_name = {instance}
|
||||
+ port = {port}
|
||||
+ secure_port = {secure_port}
|
||||
+ root_dn = cn=Directory Manager
|
||||
+ root_password = {password}
|
||||
+ [backend-userroot]
|
||||
+ sample_entries = no
|
||||
+ suffix = {basedn}
|
||||
+ """).format(
|
||||
+ hostname=client.hostname,
|
||||
+ instance=DS_INSTANCE_NAME,
|
||||
+ port=DS_PORT,
|
||||
+ secure_port=DS_SECURE_PORT,
|
||||
+ password=admin_password,
|
||||
+ basedn=DS_BASEDN,
|
||||
+ )
|
||||
+ client.put_file_contents("/tmp/ds-instance.inf", inf_content)
|
||||
+ client.run_command(
|
||||
+ ["dscreate", "from-file", "/tmp/ds-instance.inf"]
|
||||
+ )
|
||||
+
|
||||
+ # Load migration test data from instance1.ldif
|
||||
+ test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
+ ldif_path = os.path.join(
|
||||
+ test_dir, "data", "ds_migration", "instance1.ldif"
|
||||
+ )
|
||||
+ with open(ldif_path) as f:
|
||||
+ ldif_content = f.read()
|
||||
+ client.put_file_contents("/tmp/instance1.ldif", ldif_content)
|
||||
+ client.run_command(
|
||||
+ [
|
||||
+ "/usr/bin/ldapmodify",
|
||||
+ "-a", "-x", "-H", "ldap://localhost:{}".format(DS_PORT),
|
||||
+ "-D", "cn=Directory Manager", "-w", admin_password,
|
||||
+ "-f", "/tmp/instance1.ldif",
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+class TestDSMigrationConfig(IntegrationTest):
|
||||
+ """
|
||||
+ Test ipa migrate-ds related scenarios.
|
||||
+
|
||||
+ Uses a client host with 389-ds populated from instance1.ldif
|
||||
+ (ou=People, ou=groups, dc=testrealm,dc=test) for migration tests.
|
||||
+ """
|
||||
+
|
||||
+ topology = "line"
|
||||
+ num_replicas = 0
|
||||
+ num_clients = 1
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ # Install master and IPA client (full topology)
|
||||
+ super(TestDSMigrationConfig, cls).install(mh)
|
||||
+ # On the client host, set up 389-ds with migration test data
|
||||
+ _setup_389ds_on_client(
|
||||
+ cls.clients[0],
|
||||
+ cls.master.config.admin_password,
|
||||
+ )
|
||||
+
|
||||
+ def test_attempt_migration_with_configuration_false(self):
|
||||
+ """
|
||||
+ Test attempts ipa-migrate-ds with migration disabled.
|
||||
+ """
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ # Ensure migration is disabled
|
||||
+ cmd = ["ipa", "config-mod", "--enable-migration", "FALSE"]
|
||||
+ error_msg = "ipa: ERROR: no modifications to be performed"
|
||||
+ result = self.master.run_command(cmd, raiseonerr=False)
|
||||
+ assert result.returncode != 0
|
||||
+ tasks.assert_error(result, error_msg)
|
||||
+ client_host = self.clients[0].hostname
|
||||
+ ldap_uri = "ldap://{}:{}".format(client_host, DS_PORT)
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "migrate-ds",
|
||||
+ "--user-container=ou=People",
|
||||
+ "--group-container=ou=groups",
|
||||
+ ldap_uri,
|
||||
+ ],
|
||||
+ stdin_text=self.master.config.admin_password,
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert (
|
||||
+ result.returncode != 0
|
||||
+ ), "migrate-ds should fail when migration is disabled"
|
||||
+ assert "migration mode is disabled" in (
|
||||
+ result.stdout_text + result.stderr_text
|
||||
+ ).lower()
|
||||
+
|
||||
+ def test_migration_over_ldaps(self):
|
||||
+ """
|
||||
+ Migrate from the client's 389-ds over LDAPS (port 636).
|
||||
+ """
|
||||
+ ca_cert_file = "/etc/ipa/remoteds.crt"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "config-mod", "--enable-migration", "TRUE"],
|
||||
+ )
|
||||
+
|
||||
+ # Copy 389-ds CA cert from client to master for LDAPS verification
|
||||
+ client = self.clients[0]
|
||||
+ ds_cert_dir = "/etc/dirsrv/slapd-{}".format(DS_INSTANCE_NAME)
|
||||
+ cert_result = client.run_command(
|
||||
+ [
|
||||
+ "certutil", "-d", ds_cert_dir, "-L", "-n",
|
||||
+ "Self-Signed-CA", "-a",
|
||||
+ ],
|
||||
+ )
|
||||
+ self.master.put_file_contents(
|
||||
+ ca_cert_file, cert_result.stdout_text
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["restorecon", ca_cert_file], raiseonerr=False
|
||||
+ )
|
||||
+
|
||||
+ client_host = client.hostname
|
||||
+ ldaps_uri = "ldaps://{}:{}".format(client_host, DS_SECURE_PORT)
|
||||
+ user_container = "ou=People,{}".format(DS_BASEDN)
|
||||
+ group_container = "ou=groups,{}".format(DS_BASEDN)
|
||||
+
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "migrate-ds",
|
||||
+ "--with-compat",
|
||||
+ "--user-container",
|
||||
+ user_container,
|
||||
+ "--group-container",
|
||||
+ group_container,
|
||||
+ ldaps_uri,
|
||||
+ "--ca-cert-file",
|
||||
+ ca_cert_file,
|
||||
+ ],
|
||||
+ stdin_text=self.master.config.admin_password,
|
||||
+ )
|
||||
+ # Verify migrated user and group from instance1.ldif
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-show", "ldapuser_0001"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "group-show", "ldapgroup_0001"]
|
||||
+ )
|
||||
+
|
||||
+ # Clean up migrated user and group
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-del", "ldapuser_0001"], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "group-del", "ldapgroup_0001"], raiseonerr=False
|
||||
+ )
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,506 +0,0 @@
|
||||
From 51e669d46f912d39841bc40560682f9f414f68fe Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Mon, 16 Mar 2026 15:03:17 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa selfservice-show and selfservice-mod CLI
|
||||
tests to xmlrpc
|
||||
|
||||
Add two new Declarative test classes to test_selfservice_plugin.py covering the selfservice-show and selfservice-mod CLI command options, converted from the legacy bash test suite (selfservice_show_1001-1003 and selfservice_mod_1002-1008).
|
||||
|
||||
test_selfservice_show_cli covers:
|
||||
- selfservice-show --all
|
||||
- selfservice-show --all --raw
|
||||
- selfservice-show --raw
|
||||
|
||||
test_selfservice_mod_cli covers:
|
||||
- selfservice-mod with invalid attrs (negative)
|
||||
- selfservice-mod with invalid permissions (negative)
|
||||
- selfservice-mod with no changes / EmptyModlist (negative)
|
||||
- selfservice-mod with bad attrs must not delete the entry (BZ 747741)
|
||||
- selfservice-mod changing and expanding attrs (positive)
|
||||
- selfservice-mod with invalid/same permissions (negative)
|
||||
- selfservice-mod changing and expanding permissions (positive)
|
||||
|
||||
Tests for selfservice_mod_1001 and selfservice_mod_1009, which require interactive input and are not valid in a non-interactive context, are covered as integration tests in test_commands.py by test_selfservice_mod_no_attrs_or_permissions_all and test_selfservice_mod_no_attrs_or_permissions_raw. Tests for BZ 772675 (mod --raw) and BZ 747741 (bad attrs) cross-reference existing coverage in test_selfservice_misc.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_commands.py | 54 +++
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 396 ++++++++++++++++++
|
||||
2 files changed, 450 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
|
||||
index 6d7ee8f2d9b2d9b6e73436b6815c27414a098e60..5021747b67e0b97d322b13261f7e90e688f50cfe 100644
|
||||
--- a/ipatests/test_integration/test_commands.py
|
||||
+++ b/ipatests/test_integration/test_commands.py
|
||||
@@ -391,6 +391,60 @@ class TestIPACommand(IntegrationTest):
|
||||
assert result.returncode == 1
|
||||
assert "Number of permissions added 0" in result.stdout_text
|
||||
|
||||
+ def test_selfservice_mod_no_attrs_or_permissions_all(self):
|
||||
+ """selfservice-mod --all with no --attrs or --permissions fails.
|
||||
+
|
||||
+ When selfservice-mod is invoked with only --all and neither --attrs
|
||||
+ nor --permissions is supplied, the CLI has no modifications to apply
|
||||
+ and must return a non-zero exit code with an appropriate error message.
|
||||
+ """
|
||||
+ entry = 'test_selfservice_mod_all'
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-add', entry,
|
||||
+ '--attrs=l', '--permissions=write']
|
||||
+ )
|
||||
+ try:
|
||||
+ result = self.master.run_command(
|
||||
+ ['ipa', 'selfservice-mod', entry, '--all'],
|
||||
+ stdin_text='\n',
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ assert 'no modifications to be performed' in result.stderr_text
|
||||
+ finally:
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-del', entry],
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+
|
||||
+ def test_selfservice_mod_no_attrs_or_permissions_raw(self):
|
||||
+ """selfservice-mod --raw with no --attrs or --permissions fails.
|
||||
+
|
||||
+ When selfservice-mod is invoked with only --raw and neither --attrs
|
||||
+ nor --permissions is supplied, the CLI has no modifications to apply
|
||||
+ and must return a non-zero exit code with an appropriate error message.
|
||||
+ """
|
||||
+ entry = 'test_selfservice_mod_raw'
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-add', entry,
|
||||
+ '--attrs=l', '--permissions=write']
|
||||
+ )
|
||||
+ try:
|
||||
+ result = self.master.run_command(
|
||||
+ ['ipa', 'selfservice-mod', entry, '--raw'],
|
||||
+ stdin_text='\n',
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ assert 'no modifications to be performed' in result.stderr_text
|
||||
+ finally:
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-del', entry],
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+
|
||||
def test_change_sysaccount_password_issue7561(self):
|
||||
sysuser = 'system'
|
||||
original_passwd = 'Secret123'
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 8f2307a20cbbaa5e17a1425aea4d254382779a6a..48dfd7cc30e4519c3cbc7f3b97ea73539bf4e500 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -942,3 +942,399 @@ class test_selfservice_cli_add_del(Declarative):
|
||||
),
|
||||
|
||||
]
|
||||
+
|
||||
+
|
||||
+# selfservice-show & selfservice-mod CLI test rule names
|
||||
+
|
||||
+SS_CLI_SHOW = 'SELFSERVICE_SHOW_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_show_cli(Declarative):
|
||||
+ """Test selfservice-show CLI options."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_SHOW], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ dict(
|
||||
+ desc='Create %r for show tests' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_SHOW,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --all (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --all' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'all': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --all and --raw (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --all and --raw' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'all': True, 'raw': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result={
|
||||
+ 'aci': '(targetattr = "l")'
|
||||
+ '(version 3.0;acl '
|
||||
+ '"selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_SHOW,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --raw (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --raw' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'raw': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result={
|
||||
+ 'aci': '(targetattr = "l")'
|
||||
+ '(version 3.0;acl '
|
||||
+ '"selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_SHOW,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ dict(
|
||||
+ desc='Delete %r' % SS_CLI_SHOW,
|
||||
+ command=('selfservice_del', [SS_CLI_SHOW], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
+
|
||||
+
|
||||
+SS_CLI_MOD = 'SELFSERVICE_MOD_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_mod_cli(Declarative):
|
||||
+ """Test selfservice-mod CLI options."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_MOD], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ dict(
|
||||
+ desc='Create %r for mod tests' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_all and
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_raw are covered in
|
||||
+ # integration tests in test_commands.py since they are
|
||||
+ # interactive tests.
|
||||
+
|
||||
+ # Modify with --all --attrs=badattr --permissions=write --raw
|
||||
+ # (negative test - invalid attr value)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with invalid attrs' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['badattr'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=(
|
||||
+ r'targetattr "badattr" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattr" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattr\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:%s\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)'
|
||||
+ ) % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --all --attrs=l --permissions=badperm --raw
|
||||
+ # (negative test - invalid permission value)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with invalid permissions'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='badperm',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with same attrs and perms already set
|
||||
+ # (negative test - no modifications error)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with no changes' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.EmptyModlist(),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs=badattrs (negative test,
|
||||
+ # BZ 747741 - a bad attrs mod must not delete the entry).
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with bad attrs (BZ 747741)'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=(
|
||||
+ r'targetattr "badattrs" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattrs" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattrs\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:%s\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)'
|
||||
+ ) % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Verify entry still exists after failed mod (BZ 747741)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Verify %r still exists after failed mod'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=('selfservice_show', [SS_CLI_MOD], {}),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary=None,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs=mobile (positive test - change attr)
|
||||
+ dict(
|
||||
+ desc='Modify %r attrs to mobile' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['mobile']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs={mobile,l} (positive test - add attr)
|
||||
+ dict(
|
||||
+ desc='Modify %r attrs to mobile and l' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['mobile', 'l']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=badperm
|
||||
+ # (negative test - invalid permission string)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with invalid permissions'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='badperm'),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=write
|
||||
+ # (negative test - same perm already set, no modification)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with same permissions' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='write'),
|
||||
+ ),
|
||||
+ expected=errors.EmptyModlist(),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=read (positive test - change perm)
|
||||
+ dict(
|
||||
+ desc='Modify %r permissions to read' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='read'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['read'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions={read,write} (positive test - add perm)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Modify %r permissions to read and write'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions=['read', 'write']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['read', 'write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_all and
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_raw are covered in
|
||||
+ # integration tests in test_commands.py since they are
|
||||
+ # interactive tests.
|
||||
+
|
||||
+ dict(
|
||||
+ desc='Delete %r' % SS_CLI_MOD,
|
||||
+ command=('selfservice_del', [SS_CLI_MOD], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,509 +0,0 @@
|
||||
From c294159878de52fa5762025ee4be893f280c5320 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Tue, 31 Mar 2026 15:38:36 +0200
|
||||
Subject: [PATCH] ipatests: Add selfservice-find cli tests to xmlrpc Add a new
|
||||
Declarative test class test_selfservice_cli_find covering CLI-level behaviour
|
||||
of the selfservice-find command:
|
||||
|
||||
- find with --all succeeds and returns the matching rule
|
||||
- non-existent or wrong attrs filter returns zero results
|
||||
- bad --name filter returns zero results with valid and invalid
|
||||
positional arg
|
||||
- bad permissions filter with --all --raw returns zero results,
|
||||
no internal error (BZ 747693)
|
||||
- all valid params with --all --raw succeeds and returns the raw
|
||||
ACI string (BZ 747693)
|
||||
- wrong or non-existent attrs filter returns zero results with and
|
||||
without name arg
|
||||
- valid --attrs filter succeeds with positional name arg and with
|
||||
--name option
|
||||
- valid --name filter succeeds and returns the matching rule
|
||||
- bad permissions filter returns zero results
|
||||
- valid --permissions filter succeeds with positional name arg and
|
||||
with --name option
|
||||
- --raw only succeeds and returns the raw ACI string, no internal
|
||||
error (BZ 747693)
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 456 ++++++++++++++++++
|
||||
1 file changed, 456 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 48dfd7cc30e4519c3cbc7f3b97ea73539bf4e500..9e921156a592a2ec59ba7dfea3e38211cbb14ec8 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -944,6 +944,462 @@ class test_selfservice_cli_add_del(Declarative):
|
||||
]
|
||||
|
||||
|
||||
+# selfservice-find CLI test rule name
|
||||
+SS_CLI_FIND = 'SELFSERVICE_FIND_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_cli_find(Declarative):
|
||||
+ """Tests for the selfservice-find CLI command."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_FIND], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+
|
||||
+ # Setup: create the rule used by all find tests
|
||||
+ dict(
|
||||
+ desc='Setup: create %r' % SS_CLI_FIND,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['l'], permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_FIND,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_FIND,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Find with --all returns the parsed result
|
||||
+ dict(
|
||||
+ desc='Search for %r with --all' % SS_CLI_FIND,
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(all=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad attrs filter -- aci_find does pure string
|
||||
+ # comparison; no schema validation in find.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Non-existent attr with all filters'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['badattrs'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Wrong attr for this rule (has 'l', not 'mobile')
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Wrong attr for rule with all filters'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['mobile'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad --name filter with --all --raw
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Valid name arg with bad --name filter'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname='badname',
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad name arg also set to 'badname'
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Bad name arg with bad --name filter'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ ['badname'],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname='badname',
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad permissions with --all --raw (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ # aci_find treats permissions as a plain string filter (no
|
||||
+ # validation), so 'badperm' simply matches nothing.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Bad permissions with --all --raw'
|
||||
+ ' returns no match (BZ 747693)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='badperm',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # All valid params with --all --raw (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'All valid params with --all --raw'
|
||||
+ ' returns raw ACI (BZ 747693)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "l")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_FIND
|
||||
+ ),
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad attrs filter without --all --raw
|
||||
+ dict(
|
||||
+ desc='Wrong attr filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['mobile']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Non-existent attr in filter
|
||||
+ dict(
|
||||
+ desc='Non-existent attr filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Non-existent attr without name arg
|
||||
+ dict(
|
||||
+ desc='Non-existent attr without name arg returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid attrs filter
|
||||
+ dict(
|
||||
+ desc='Valid attrs filter with name arg returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['l']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Without positional name arg but with --name
|
||||
+ # filter to get a deterministic result.
|
||||
+ dict(
|
||||
+ desc='Valid attrs filter with --name option returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(attrs=['l'], aciname=SS_CLI_FIND),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad --name filter
|
||||
+ dict(
|
||||
+ desc='Valid name arg with bad --name filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(aciname='badname'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad name arg also set to 'badname'
|
||||
+ dict(
|
||||
+ desc='Bad name arg with bad --name filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ ['badname'],
|
||||
+ dict(aciname='badname'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid --name filter
|
||||
+ dict(
|
||||
+ desc='Valid --name filter returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(aciname=SS_CLI_FIND),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad permissions filter -- aci_find treats permissions
|
||||
+ # as a plain string filter; 'badperm' matches nothing.
|
||||
+ dict(
|
||||
+ desc='Bad permissions filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(permissions='badperm'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid permissions filter
|
||||
+ dict(
|
||||
+ desc='Valid permissions filter with name arg returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Without positional name arg but with --name
|
||||
+ # filter to get a deterministic result.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Valid permissions filter with --name'
|
||||
+ ' option returns match'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(
|
||||
+ permissions='write',
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Raw output only (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ dict(
|
||||
+ desc='Raw output returns ACI string without error (BZ 747693)',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "l")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_FIND
|
||||
+ ),
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
+
|
||||
+
|
||||
# selfservice-show & selfservice-mod CLI test rule names
|
||||
|
||||
SS_CLI_SHOW = 'SELFSERVICE_SHOW_TEST'
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
From 0bf2a549a8a858c393ef59487bc1d395e5535c07 Mon Sep 17 00:00:00 2001
|
||||
From: Stanislav Levin <slev@altlinux.org>
|
||||
Date: Tue, 24 Feb 2026 19:44:51 +0300
|
||||
Subject: [PATCH] ipatest: make tests compatible with Pytest 9
|
||||
|
||||
https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function
|
||||
|
||||
> Applying a mark to a fixture function never had any effect, but it is a common user error.
|
||||
|
||||
Move marks from the fixture to test class.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9950
|
||||
Signed-off-by: Stanislav Levin <slev@altlinux.org>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_ipapython/test_ldap_cache.py | 3 +--
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_ipapython/test_ldap_cache.py b/ipatests/test_ipapython/test_ldap_cache.py
|
||||
index c960db027e1cc377687490105e04fb8880cd35d0..49724ad2fc5aaa083f21422de7eec22009e97f15 100644
|
||||
--- a/ipatests/test_ipapython/test_ldap_cache.py
|
||||
+++ b/ipatests/test_ipapython/test_ldap_cache.py
|
||||
@@ -20,8 +20,6 @@ def hits_and_misses(cache, hits, misses):
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
-@pytest.mark.tier1
|
||||
-@pytest.mark.needs_ipaapi
|
||||
def class_cache(request):
|
||||
cache = ipaldap.LDAPCache(api.env.ldap_uri)
|
||||
hits_and_misses(cache, 0, 0)
|
||||
@@ -56,6 +54,7 @@ def class_cache(request):
|
||||
@pytest.mark.usefixtures('class_cache')
|
||||
@pytest.mark.skip_ipaclient_unittest
|
||||
@pytest.mark.needs_ipaapi
|
||||
+@pytest.mark.tier1
|
||||
class TestLDAPCache:
|
||||
|
||||
def test_one(self):
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
From 628404016d7ab8bcfc6f940e7ef40928f375168e Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Thu, 9 Apr 2026 12:49:18 +0200
|
||||
Subject: [PATCH] ipatests: fix the method add_a_record
|
||||
|
||||
The method add_a_record first checks if a DNS record exists.
|
||||
If it finds one, it assumes there is already a DNS A record for
|
||||
the host but this assumption is wrong. The dnsrecord-show command
|
||||
may return another type of DNS record (for instance an SSHFP record).
|
||||
|
||||
Create the A record if dnsrecord-show fails or if it doesn't return
|
||||
any A record.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9972
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Anuja More <amore@redhat.com>
|
||||
---
|
||||
ipatests/pytest_ipa/integration/tasks.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
|
||||
index ee2befd6998da3b8035e4e015d7528b5a9676a7b..5ce18483d6c1049104afc1cef6524a7a19fc9047 100755
|
||||
--- a/ipatests/pytest_ipa/integration/tasks.py
|
||||
+++ b/ipatests/pytest_ipa/integration/tasks.py
|
||||
@@ -1620,7 +1620,7 @@ def add_a_record(master, host):
|
||||
raiseonerr=False)
|
||||
|
||||
# If not, add it
|
||||
- if cmd.returncode != 0:
|
||||
+ if cmd.returncode != 0 or 'A record' not in cmd.stdout_text:
|
||||
master.run_command(['ipa',
|
||||
'dnsrecord-add',
|
||||
master.domain.name,
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
From 3d5fe37d41332cdca486c9ca42089934e5ed7f89 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 30 Mar 2026 10:39:51 +0200
|
||||
Subject: [PATCH] ipatests: fix migration test
|
||||
|
||||
A previous commit modified the DNS zone forwarder's IP address
|
||||
but forgot to change the expected value in the test.
|
||||
Use a constant in the test setup and in the test expectation.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9964
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Sudhir Menon <sumenon@redhat.com>
|
||||
Reviewed-By: Sudhir Menon <sumenon@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_ipa_ipa_migration.py | 5 +++--
|
||||
1 file changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
index e997f3683c70079af7633b578d5523b646c0f36c..952109239792d34283b282cf16c35e495cb64d1e 100644
|
||||
--- a/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
@@ -15,6 +15,7 @@ import pytest
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
+TEST_ZONE_FORWARDER = "10.11.12.13"
|
||||
|
||||
def prepare_ipa_server(master):
|
||||
"""
|
||||
@@ -227,7 +228,7 @@ def prepare_ipa_server(master):
|
||||
"dnsforwardzone-add",
|
||||
"forwardzone.test",
|
||||
"--forwarder",
|
||||
- "192.168.124.10",
|
||||
+ TEST_ZONE_FORWARDER,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -827,7 +828,7 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
)
|
||||
assert 'Zone name: {}'.format(zone_name) in result.stdout_text
|
||||
assert 'Active zone: True' in result.stdout_text
|
||||
- assert 'Zone forwarders: 10.11.12.13' in result.stdout_text
|
||||
+ assert f'Zone forwarders: {TEST_ZONE_FORWARDER}' in result.stdout_text
|
||||
assert 'Forward policy: first' in result.stdout_text
|
||||
|
||||
def test_ipa_migrate_version_option(self):
|
||||
--
|
||||
2.53.0
|
||||
|
||||
94
freeipa.spec
94
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.24.3
|
||||
%global samba_version 4.24.3-100
|
||||
%global slapi_nis_version 0.70.0
|
||||
%global python_ldap_version 3.1.0-1
|
||||
# version supporting Allow Uniqueness plugin to search uniqueness attributes
|
||||
@ -110,16 +110,10 @@
|
||||
%global python_netaddr_version 0.7.16
|
||||
# Require 4.7.0 which brings Python 3 bindings
|
||||
# Require 4.12 which has DsRGetForestTrustInformation access rights fixes
|
||||
%if 0%{?fedora} <= 40
|
||||
%global samba_version 2:4.12.10
|
||||
%elif 0%{?fedora} == 41
|
||||
# Require 4.20.0 or later for libndr4, latest F41 is 4.21.7 already
|
||||
%global samba_version 2:4.21.7
|
||||
%elif 0%{?fedora} == 42
|
||||
%global samba_version 2:4.22.0
|
||||
%if 0%{?fedora} < 44
|
||||
%global samba_version 2:4.23.8
|
||||
%else
|
||||
# Require 4.23 for passdb ABI bump
|
||||
%global samba_version 2:4.23.0
|
||||
%global samba_version 2:4.24.3
|
||||
%endif
|
||||
|
||||
# 38.28 or later includes passkey-related fixes
|
||||
@ -221,7 +215,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 @
|
||||
@ -234,7 +228,7 @@
|
||||
|
||||
Name: %{package_name}
|
||||
Version: %{IPA_VERSION}
|
||||
Release: 4%{?rc_version:.%rc_version}%{?dist}
|
||||
Release: 1%{?rc_version:.%rc_version}%{?dist}
|
||||
Summary: The Identity, Policy and Audit system
|
||||
|
||||
License: GPL-3.0-or-later
|
||||
@ -262,41 +256,6 @@ 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
|
||||
Patch0014: 0014-ipatests-Add-DNS-bugzilla-integration-tests.patch
|
||||
Patch0015: 0015-Avoid-int-overflow-with-pwpolicy-minlife.patch
|
||||
Patch0016: 0016-ipatests-fix-install-method-for-BasePWpolicy.patch
|
||||
Patch0017: 0017-webui-tests-update-expected-max-value-for-krbminpwdl.patch
|
||||
Patch0018: 0018-ipatests-Add-DNS-integration-tests.patch
|
||||
Patch0019: 0019-ipatests-Add-ipa-selfservice-BZ-tests-to-xmlrpc.patch
|
||||
Patch0020: 0020-Allow-32bit-gid.patch
|
||||
Patch0021: 0021-ipatests-Add-ipa-selfservice-users-tests-to-xmlrpc.patch
|
||||
Patch0022: 0022-ipatests-Fix-test_allow_query_transfer_ipv6-when-IPv.patch
|
||||
Patch0023: 0023-ipatests-Add-XML-RPC-tests-for-i18n-user-attributes.patch
|
||||
Patch0024: 0024-ipatests-Add-selfservice-add-and-selfservice-del-cli.patch
|
||||
Patch0025: 0025-ipatests-Additional-tests-for-32BitIdranges.patch
|
||||
Patch0026: 0026-ipatests-add-HTTP-GSSAPI-Kerberos-authentication-tes.patch
|
||||
Patch0027: 0027-ipatests-Extend-netgroup-test-coverage.patch
|
||||
Patch0028: 0028-ipatests-Add-user-principal-ipa-getkeytab-and-ipa-rm.patch
|
||||
Patch0029: 0029-ipatests-Additional-tests-for-ipa-ipa-migration-test.patch
|
||||
Patch0030: 0030-ipatests-ipa-migrate-ds-test-scenarios.patch
|
||||
Patch0031: 0031-ipatests-Add-ipa-selfservice-show-and-selfservice-mo.patch
|
||||
Patch0032: 0032-ipatests-Add-selfservice-find-cli-tests-to-xmlrpc-Ad.patch
|
||||
Patch0033: 0033-ipatest-make-tests-compatible-with-Pytest-9.patch
|
||||
Patch0034: 0034-ipatests-fix-the-method-add_a_record.patch
|
||||
Patch0035: 0035-ipatests-fix-migration-test.patch
|
||||
Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch
|
||||
%endif
|
||||
%endif
|
||||
@ -352,7 +311,7 @@ BuildRequires: libsss_nss_idmap-devel >= %{sssd_version}
|
||||
%if 0%{?fedora} >= 41 || 0%{?rhel} >= 10
|
||||
# on fedora 41+, stick to nodejs22
|
||||
# Do not use nodejs22 on fedora < 41, https://pagure.io/freeipa/issue/9643
|
||||
BuildRequires: nodejs(abi) == 127
|
||||
BuildRequires: nodejs(abi) == 127, /usr/bin/node, /usr/bin/npm
|
||||
%elif 0%{?fedora} >= 39
|
||||
# Do not use nodejs20 on fedora < 39, https://pagure.io/freeipa/issue/9374
|
||||
BuildRequires: nodejs(abi) < 127
|
||||
@ -1434,8 +1393,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
|
||||
@ -1507,9 +1466,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
|
||||
@ -1528,8 +1487,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
|
||||
@ -1762,6 +1721,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
|
||||
@ -1792,8 +1753,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
|
||||
@ -1806,7 +1765,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
|
||||
@ -1828,6 +1786,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
|
||||
@ -2006,6 +1967,25 @@ fi
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Thu Jul 16 2026 Florence Blanc-Renaud <flo@redhat.com> - 4.13.2-1
|
||||
- Resolves: RHEL-178898 Rebase ipa to latest version for RHEL 10.3
|
||||
- Resolves: RHEL-179217 ipa-migrate tool is renaming host records & host info in automount information
|
||||
- Resolves: RHEL-178538 [Cursor Automated] Include latest fixes in python3-ipatests package
|
||||
- Resolves: RHEL-174346 Release Modern UI progress to RHEL 10.3
|
||||
- Resolves: RHEL-153857 ipa-migrate doesn't properly handle replication conflicts
|
||||
- Resolves: RHEL-153852 ipa-migrate doesn't migrate IPA locations
|
||||
- Resolves: RHEL-151578 Extend SELinux policy to allow use of SSSD authentication helpers on non-enrolled hosts
|
||||
- Resolves: RHEL-151326 freeipa-client %triggerin and %post can fail (rpm scriptlets should never fail, by policy)
|
||||
- Resolves: RHEL-147173 ipa-migrate doesn't process users' sshpublickeys
|
||||
- Resolves: RHEL-107899 [RFE] X-Content-Type-Options header in IdM WebUI
|
||||
- Resolves: RHEL-107897 [RFE] Implement Referrer-Policy on IdM WebUI
|
||||
- Resolves: RHEL-107045 [RFE] CSP not implemented in IdM WebUI
|
||||
- Resolves: RHEL-105139 IPA compatibility with tomcat 10
|
||||
- Resolves: RHEL-103492 ipalockout_postop is incorrectly warning about locked user
|
||||
- Resolves: RHEL-70229 To add "SameSite" flag to ipa_session cookies
|
||||
- Resolves: RHEL-29029 Webui 3007 Requirement error "userCertificate" is required (not allowing certificate manage
|
||||
- Resolves: RHEL-154589 FreeIPA JSON-RPC CSRF Referer Validation Uses Naive Prefix Match
|
||||
|
||||
* Tue Jun 02 2026 Florence Blanc-Renaud <flo@redhat.com> - 4.13.1-4
|
||||
- Related: RHEL-175123 Rebuilt for samba
|
||||
|
||||
|
||||
4
sources
4
sources
@ -1,2 +1,2 @@
|
||||
SHA512 (freeipa-4.13.1.tar.gz) = 78d7675aa49e9a3323b36fd225e0f2aebfb0df58bff4f232cce96933dbe250a90bf062d64d1838b2ab84cb0764537646a7441c7c643672e2757a5501871d6311
|
||||
SHA512 (freeipa-4.13.1.tar.gz.asc) = ddde77e1bd04440f3aeb0fb1f28b017e8374f586c9287b6805fa482161e44bed7fc502a7dbb38bbf6dc7ac5013c353c950ba27148ffe915b88a7a746fb4e4f95
|
||||
SHA512 (freeipa-4.13.2.tar.gz) = 11fa7de4facbfe0b0fb278114d92a9d7b7750754d9194942e8967ecaf1fff3d5a61d5f409e4a50fbc3f65a14addb8eb865a16e7361632fcaf314464c5320ba73
|
||||
SHA512 (freeipa-4.13.2.tar.gz.asc) = 5ed637e30198ab527ff74f1f5940c5409bd6395a8b91668ed01221f49e61010c709206d74863dce40b60022bdb77bd608140d6cfda7ff5eee0662fca1a604964
|
||||
|
||||
Loading…
Reference in New Issue
Block a user