ipa-4.12.2-25
- Resolves: RHEL-128238 [RFE] Support storing LWCA private keys on an HSM [rhel-9] - Resolves: RHEL-126515 RFE: Enable external password reset agents to use ipa_pwd_extop in RHEL IdM [rhel-9] - Resolves: RHEL-73399 RFE: Update IdM password policy configurations to meet M-22-09 by restricting spaces and require number character class - Resolves: RHEL-128241 ATTR_NAME_BY_OID is missing OID 2.5.4.97, organizationIdentifier [rhel-9] - Resolves: RHEL-126514 [RFE] ipa-client-automount should have an option to include domain of the machine. [rhel-9] - Resolves: RHEL-124171 Include latest fixes in python3-ipatests package - Resolves: RHEL-120514 Include fixes in python3-ipatests [rhel-9.8] - Resolves: RHEL-118609 test_cacert_manage fails due to expired Let's Encrypt R3 certificate
This commit is contained in:
parent
6d0d7136f5
commit
445b79154e
@ -0,0 +1,97 @@
|
||||
From b394ac6f7ad62d021412d34364a22ea0dc5a6362 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Tue, 6 May 2025 09:52:35 -0400
|
||||
Subject: [PATCH] Add --domain option to ipa-client-automount for DNS discovery
|
||||
|
||||
If the client machine is not in the IPA DNS domain then discovery
|
||||
will not find a server. Add a --domain option so that the set of
|
||||
servers can be discovered.
|
||||
|
||||
Note that --domain is initialized to "" rather than None to match
|
||||
the behavior in ipa-client-install.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9780
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
client/man/ipa-client-automount.1 | 3 +++
|
||||
ipaclient/install/ipa_client_automount.py | 8 +++++++-
|
||||
ipatests/test_integration/test_nfs.py | 24 +++++++++++++++++++++++
|
||||
3 files changed, 34 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/client/man/ipa-client-automount.1 b/client/man/ipa-client-automount.1
|
||||
index d55ca994d1a8c413134a3031918bc88ce431adb1..1a061e84b7e499c573d2efc9377a5edf0b7b5f0e 100644
|
||||
--- a/client/man/ipa-client-automount.1
|
||||
+++ b/client/man/ipa-client-automount.1
|
||||
@@ -49,6 +49,9 @@ NFSv4 is also configured. The rpc.gssd and rpc.idmapd are started on clients to
|
||||
\fB\-\-server\fR=\fISERVER\fR
|
||||
Set the FQDN of the IPA server to connect to.
|
||||
.TP
|
||||
+\fB\-\-domain\fR=\fIDOMAIN\fR
|
||||
+Primary DNS domain of the IPA deployment to be used for server discovery.
|
||||
+.TP
|
||||
\fB\-\-location\fR=\fILOCATION\fR
|
||||
Automount location.
|
||||
.TP
|
||||
diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py
|
||||
index 9f49ff9edeee2648d2be1dea6b09806ba0b5e041..035eefb1c85a33534fdd2640c8f329aa6fb212bf 100644
|
||||
--- a/ipaclient/install/ipa_client_automount.py
|
||||
+++ b/ipaclient/install/ipa_client_automount.py
|
||||
@@ -61,6 +61,12 @@ def parse_options():
|
||||
usage = "%prog [options]\n"
|
||||
parser = IPAOptionParser(usage=usage)
|
||||
parser.add_option("--server", dest="server", help="FQDN of IPA server")
|
||||
+ parser.add_option(
|
||||
+ "--domain",
|
||||
+ dest="domain",
|
||||
+ default="",
|
||||
+ help="Primary DNS domain of the IPA deployment"
|
||||
+ )
|
||||
parser.add_option(
|
||||
"--location",
|
||||
dest="location",
|
||||
@@ -387,7 +393,7 @@ def configure_automount():
|
||||
ds = discovery.IPADiscovery()
|
||||
if not options.server:
|
||||
print("Searching for IPA server...")
|
||||
- ret = ds.search(ca_cert_path=ca_cert_path)
|
||||
+ ret = ds.search(domain=options.domain, ca_cert_path=ca_cert_path)
|
||||
logger.debug('Executing DNS discovery')
|
||||
if ret == discovery.NO_LDAP_SERVER:
|
||||
logger.debug('Autodiscovery did not find LDAP server')
|
||||
diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
|
||||
index 68cba2988d2c2bd95a60846b0ae999b76fc4c289..32d107b718aecd0944eaeb790af201c17b0ab56a 100644
|
||||
--- a/ipatests/test_integration/test_nfs.py
|
||||
+++ b/ipatests/test_integration/test_nfs.py
|
||||
@@ -355,3 +355,27 @@ class TestIpaClientAutomountFileRestore(IntegrationTest):
|
||||
|
||||
def test_nsswitch_backup_restore_sssd(self):
|
||||
self.nsswitch_backup_restore()
|
||||
+
|
||||
+
|
||||
+class TestIpaClientAutomountDiscovery(IntegrationTest):
|
||||
+
|
||||
+ num_clients = 1
|
||||
+ topology = 'line'
|
||||
+
|
||||
+ def test_automount_invalid_domain(self):
|
||||
+ """Validate that the --domain option is passed into
|
||||
+ Discovery. This is expected to fail discovery.
|
||||
+ """
|
||||
+ testdomain = "client.test"
|
||||
+ msg1 = f"Search for LDAP SRV record in {testdomain}"
|
||||
+ msg2 = f"Search DNS for SRV record of _ldap._tcp.{testdomain}"
|
||||
+ msg3 = "Autodiscovery did not find LDAP server"
|
||||
+
|
||||
+ client = self.clients[0]
|
||||
+ result = client.run_command([
|
||||
+ 'ipa-client-automount', '--domain', 'client.test',
|
||||
+ '--debug'
|
||||
+ ], stdin_text="n", raiseonerr=False)
|
||||
+ assert msg1 in result.stderr_text
|
||||
+ assert msg2 in result.stderr_text
|
||||
+ assert msg3 in result.stderr_text
|
||||
--
|
||||
2.51.1
|
||||
|
||||
378
0119-ipatests-Update-ipatests-to-test-topology-with-multi.patch
Normal file
378
0119-ipatests-Update-ipatests-to-test-topology-with-multi.patch
Normal file
@ -0,0 +1,378 @@
|
||||
From 1f915d4b2a1793a0a3e536604643f80aa76b6b0c Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Fri, 23 Aug 2024 15:08:57 +0530
|
||||
Subject: [PATCH] ipatests: Update ipatests to test topology with multiple
|
||||
domain.
|
||||
|
||||
Added changes in ipatests so that ipa server-replica-client
|
||||
can be installed with two domain - ipa.test and trustedipa.test
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9657
|
||||
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/pytest_ipa/integration/__init__.py | 34 ++++++++-
|
||||
ipatests/pytest_ipa/integration/config.py | 30 +++++++-
|
||||
ipatests/test_integration/base.py | 71 +++++++++++++++++++
|
||||
.../test_integration/test_multidomain_ipa.py | 54 ++++++++++++++
|
||||
pylint_plugins.py | 12 ++++
|
||||
5 files changed, 198 insertions(+), 3 deletions(-)
|
||||
create mode 100644 ipatests/test_integration/test_multidomain_ipa.py
|
||||
|
||||
diff --git a/ipatests/pytest_ipa/integration/__init__.py b/ipatests/pytest_ipa/integration/__init__.py
|
||||
index eb032cd72d2aa2a5ed4c476e3cb04dc77f607eaa..31e909160a0b34b5bb2dd418a20677e4883d318d 100644
|
||||
--- a/ipatests/pytest_ipa/integration/__init__.py
|
||||
+++ b/ipatests/pytest_ipa/integration/__init__.py
|
||||
@@ -415,6 +415,16 @@ def mh(request, class_integration_logs):
|
||||
'type': 'AD_TREEDOMAIN',
|
||||
'hosts': {'ad_treedomain': 1}
|
||||
})
|
||||
+ for _i in range(cls.num_trusted_domains):
|
||||
+ domain_descriptions.append({
|
||||
+ 'type': 'TRUSTED_IPA',
|
||||
+ 'hosts':
|
||||
+ {
|
||||
+ 'master': 1,
|
||||
+ 'replica': cls.num_trusted_replicas,
|
||||
+ 'client': cls.num_trusted_clients,
|
||||
+ }
|
||||
+ })
|
||||
|
||||
mh = make_multihost_fixture(
|
||||
request,
|
||||
@@ -423,10 +433,20 @@ def mh(request, class_integration_logs):
|
||||
_config=get_global_config(),
|
||||
)
|
||||
|
||||
- mh.domain = mh.config.domains[0]
|
||||
+ for domain in mh.config.domains:
|
||||
+ if domain.type == 'IPA':
|
||||
+ mh.domain = domain
|
||||
+ elif domain.type == 'TRUSTED_IPA':
|
||||
+ mh.trusted_domain = domain
|
||||
+
|
||||
[mh.master] = mh.domain.hosts_by_role('master')
|
||||
mh.replicas = mh.domain.hosts_by_role('replica')
|
||||
mh.clients = mh.domain.hosts_by_role('client')
|
||||
+ if mh.config.trusted_domains:
|
||||
+ [mh.trusted_master] = mh.trusted_domain.hosts_by_role('master')
|
||||
+ mh.trusted_replicas = mh.trusted_domain.hosts_by_role('replica')
|
||||
+ mh.trusted_clients = mh.trusted_domain.hosts_by_role('client')
|
||||
+
|
||||
ad_domains = mh.config.ad_domains
|
||||
if ad_domains:
|
||||
mh.ads = []
|
||||
@@ -489,6 +509,12 @@ def add_compat_attrs(cls, mh):
|
||||
cls.ad_subdomains = mh.ad_subdomains
|
||||
cls.ad_treedomains = mh.ad_treedomains
|
||||
|
||||
+ cls.trusted_domains = mh.config.trusted_domains
|
||||
+ if cls.trusted_domains:
|
||||
+ cls.trusted_master = mh.trusted_master
|
||||
+ cls.trusted_replicas = mh.trusted_replicas
|
||||
+ cls.trusted_clients = mh.trusted_clients
|
||||
+
|
||||
|
||||
def del_compat_attrs(cls):
|
||||
"""Remove convenience attributes from the test class
|
||||
@@ -506,6 +532,12 @@ def del_compat_attrs(cls):
|
||||
del cls.ad_treedomains
|
||||
del cls.ad_domains
|
||||
|
||||
+ if cls.trusted_domains:
|
||||
+ del cls.trusted_master
|
||||
+ del cls.trusted_replicas
|
||||
+ del cls.trusted_clients
|
||||
+ del cls.trusted_domains
|
||||
+
|
||||
|
||||
def skip_if_fips(reason='Not supported in FIPS mode', host='master'):
|
||||
if callable(reason):
|
||||
diff --git a/ipatests/pytest_ipa/integration/config.py b/ipatests/pytest_ipa/integration/config.py
|
||||
index 1f4dff7f6526953ea8a98604aabd90d80a3c3403..0fb2313db0aa739a1a57f00bd6a0c6675f1616a6 100644
|
||||
--- a/ipatests/pytest_ipa/integration/config.py
|
||||
+++ b/ipatests/pytest_ipa/integration/config.py
|
||||
@@ -88,13 +88,19 @@ class Config(pytest_multihost.config.Config):
|
||||
def ad_domains(self):
|
||||
return [d for d in self.domains if d.is_ad_type]
|
||||
|
||||
+ @property
|
||||
+ def trusted_domains(self):
|
||||
+ return [d for d in self.domains if d.is_trusted_ipa_type]
|
||||
+
|
||||
def get_all_hosts(self):
|
||||
for domain in self.domains:
|
||||
for host in domain.hosts:
|
||||
yield host
|
||||
|
||||
def get_all_ipa_hosts(self):
|
||||
- for ipa_domain in (d for d in self.domains if d.is_ipa_type):
|
||||
+ for ipa_domain in (d for d in self.domains
|
||||
+ if d.is_ipa_type or d.is_trusted_ipa_type
|
||||
+ ):
|
||||
for ipa_host in ipa_domain.hosts:
|
||||
yield ipa_host
|
||||
|
||||
@@ -135,7 +141,7 @@ class Domain(pytest_multihost.config.Domain):
|
||||
self.name = str(name)
|
||||
self.hosts = []
|
||||
|
||||
- assert self.is_ipa_type or self.is_ad_type
|
||||
+ assert self.is_ipa_type or self.is_ad_type or self.is_trusted_ipa_type
|
||||
self.realm = self.name.upper()
|
||||
self.basedn = DN(*(('dc', p) for p in name.split('.')))
|
||||
|
||||
@@ -143,6 +149,10 @@ class Domain(pytest_multihost.config.Domain):
|
||||
def is_ipa_type(self):
|
||||
return self.type == 'IPA'
|
||||
|
||||
+ @property
|
||||
+ def is_trusted_ipa_type(self):
|
||||
+ return self.type == 'TRUSTED_IPA'
|
||||
+
|
||||
@property
|
||||
def is_ad_type(self):
|
||||
return self.type == 'AD' or self.type.startswith('AD_')
|
||||
@@ -158,6 +168,8 @@ class Domain(pytest_multihost.config.Domain):
|
||||
return ('ad_subdomain',)
|
||||
elif self.type == 'AD_TREEDOMAIN':
|
||||
return ('ad_treedomain',)
|
||||
+ elif self.type == 'TRUSTED_IPA':
|
||||
+ return ('trusted_master', 'trusted_replica', 'trusted_client')
|
||||
else:
|
||||
raise LookupError(self.type)
|
||||
|
||||
@@ -168,6 +180,8 @@ class Domain(pytest_multihost.config.Domain):
|
||||
return Host
|
||||
elif self.is_ad_type:
|
||||
return WinHost
|
||||
+ elif self.is_trusted_ipa_type:
|
||||
+ return Host
|
||||
else:
|
||||
raise LookupError(self.type)
|
||||
|
||||
@@ -175,6 +189,10 @@ class Domain(pytest_multihost.config.Domain):
|
||||
def master(self):
|
||||
return self.host_by_role('master')
|
||||
|
||||
+ @property
|
||||
+ def trusted_master(self):
|
||||
+ return self.host_by_role('trusted_master')
|
||||
+
|
||||
@property
|
||||
def masters(self):
|
||||
return self.hosts_by_role('master')
|
||||
@@ -183,10 +201,18 @@ class Domain(pytest_multihost.config.Domain):
|
||||
def replicas(self):
|
||||
return self.hosts_by_role('replica')
|
||||
|
||||
+ @property
|
||||
+ def trusted_replicas(self):
|
||||
+ return self.hosts_by_role('replica')
|
||||
+
|
||||
@property
|
||||
def clients(self):
|
||||
return self.hosts_by_role('client')
|
||||
|
||||
+ @property
|
||||
+ def trusted_clients(self):
|
||||
+ return self.hosts_by_role('client')
|
||||
+
|
||||
@property
|
||||
def ads(self):
|
||||
return self.hosts_by_role('ad')
|
||||
diff --git a/ipatests/test_integration/base.py b/ipatests/test_integration/base.py
|
||||
index 4717667cb0c48790f77064e6dc300e673d2bb58b..cb34067d4d35cd22a83b34aa265d2df7fb47d371 100644
|
||||
--- a/ipatests/test_integration/base.py
|
||||
+++ b/ipatests/test_integration/base.py
|
||||
@@ -33,6 +33,7 @@ class IntegrationTest:
|
||||
num_replicas = 0
|
||||
num_clients = 0
|
||||
num_ad_domains = 0
|
||||
+ num_trusted_domains = 0
|
||||
num_ad_subdomains = 0
|
||||
num_ad_treedomains = 0
|
||||
required_extra_roles = []
|
||||
@@ -95,6 +96,7 @@ class IntegrationTest:
|
||||
cls.clients, domain_level,
|
||||
random_serial=cls.random_serial,
|
||||
extra_args=extra_args,)
|
||||
+
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
for replica in cls.replicas:
|
||||
@@ -112,3 +114,72 @@ class IntegrationTest:
|
||||
tasks.uninstall_client(client)
|
||||
if cls.fips_mode:
|
||||
cls.disable_fips_mode()
|
||||
+
|
||||
+
|
||||
+@ordered
|
||||
+@pytest.mark.usefixtures('mh')
|
||||
+@pytest.mark.usefixtures('integration_logs')
|
||||
+class MultiDomainIntegrationTest(IntegrationTest):
|
||||
+ num_trusted_domains = 1
|
||||
+ num_trusted_replicas = 0
|
||||
+ num_trusted_clients = 0
|
||||
+
|
||||
+ @classmethod
|
||||
+ def get_domains(cls):
|
||||
+ return super(MultiDomainIntegrationTest, cls
|
||||
+ ).get_domains() + cls.trusted_domains
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ super(MultiDomainIntegrationTest, cls).install(mh)
|
||||
+ extra_args = []
|
||||
+ if cls.topology is None:
|
||||
+ return
|
||||
+ else:
|
||||
+ if cls.token_password:
|
||||
+ extra_args.extend(('--token-password', cls.token_password,))
|
||||
+ tasks.install_topo(cls.topology,
|
||||
+ cls.trusted_master, cls.trusted_replicas,
|
||||
+ cls.trusted_clients, 1,
|
||||
+ random_serial=cls.random_serial,
|
||||
+ extra_args=extra_args,)
|
||||
+ tasks.kinit_admin(cls.master)
|
||||
+ tasks.kinit_admin(cls.trusted_master)
|
||||
+ # Now enable dnssec on the zones
|
||||
+ cls.master.run_command([
|
||||
+ "ipa-dns-install",
|
||||
+ "--dnssec-master",
|
||||
+ "--forwarder", cls.master.config.dns_forwarder,
|
||||
+ "-U",
|
||||
+ ])
|
||||
+ cls.master.run_command([
|
||||
+ "ipa", "dnszone-mod", cls.master.domain.name,
|
||||
+ "--dnssec=True"
|
||||
+ ])
|
||||
+ cls.trusted_master.run_command([
|
||||
+ "ipa-dns-install",
|
||||
+ "--dnssec-master",
|
||||
+ "--forwarder", cls.trusted_master.config.dns_forwarder,
|
||||
+ "-U",
|
||||
+ ])
|
||||
+ cls.trusted_master.run_command([
|
||||
+ "ipa", "dnszone-mod", cls.trusted_master.domain.name,
|
||||
+ "--dnssec=True"
|
||||
+ ])
|
||||
+
|
||||
+ @classmethod
|
||||
+ def uninstall(cls, mh):
|
||||
+ super(MultiDomainIntegrationTest, cls).uninstall(mh)
|
||||
+ for trustedreplica in cls.trusted_replicas:
|
||||
+ try:
|
||||
+ tasks.run_server_del(
|
||||
+ cls.trusted_master, trustedreplica.hostname, force=True,
|
||||
+ ignore_topology_disconnect=True, ignore_last_of_role=True)
|
||||
+ except subprocess.CalledProcessError:
|
||||
+ # If the master has already been uninstalled,
|
||||
+ # this call may fail
|
||||
+ pass
|
||||
+ tasks.uninstall_master(trustedreplica)
|
||||
+ tasks.uninstall_master(cls.trusted_master)
|
||||
+ for client in cls.trusted_clients:
|
||||
+ tasks.uninstall_client(client)
|
||||
diff --git a/ipatests/test_integration/test_multidomain_ipa.py b/ipatests/test_integration/test_multidomain_ipa.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..b1a39a072f0e936d0a7f0f100f9f8ebba729d57c
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/test_multidomain_ipa.py
|
||||
@@ -0,0 +1,54 @@
|
||||
+from ipatests.pytest_ipa.integration import tasks
|
||||
+from ipatests.test_integration.base import MultiDomainIntegrationTest
|
||||
+
|
||||
+
|
||||
+class TestMultidomain(MultiDomainIntegrationTest):
|
||||
+ num_clients = 1
|
||||
+ num_replicas = 1
|
||||
+ num_trusted_clients = 1
|
||||
+ num_trusted_replicas = 1
|
||||
+ topology = 'line'
|
||||
+
|
||||
+ def test_multidomain_trust(self):
|
||||
+ """
|
||||
+ Test services on multidomain topology.
|
||||
+ """
|
||||
+
|
||||
+ for host in (self.master, self.replicas[0],
|
||||
+ self.trusted_master, self.trusted_replicas[0]
|
||||
+ ):
|
||||
+ tasks.start_ipa_server(host)
|
||||
+
|
||||
+ for host in (self.master, self.trusted_master):
|
||||
+ tasks.disable_dnssec_validation(host)
|
||||
+ tasks.restart_named(host)
|
||||
+
|
||||
+ for host in (self.master, self.replicas[0],
|
||||
+ self.trusted_master, self.trusted_replicas[0],
|
||||
+ self.clients[0], self.trusted_clients[0]
|
||||
+ ):
|
||||
+ tasks.kinit_admin(host)
|
||||
+
|
||||
+ # Add DNS forwarder to trusted domain on ipa domain
|
||||
+ self.master.run_command([
|
||||
+ "ipa", "dnsforwardzone-add", self.trusted_master.domain.name,
|
||||
+ "--forwarder", self.trusted_master.ip,
|
||||
+ "--forward-policy=only"
|
||||
+ ])
|
||||
+ self.trusted_master.run_command([
|
||||
+ "ipa", "dnsforwardzone-add", self.master.domain.name,
|
||||
+ "--forwarder", self.master.ip,
|
||||
+ "--forward-policy=only"
|
||||
+ ])
|
||||
+
|
||||
+ tasks.install_adtrust(self.master)
|
||||
+ tasks.install_adtrust(self.trusted_master)
|
||||
+
|
||||
+ # Establish trust
|
||||
+ # self.master.run_command([
|
||||
+ # "ipa", "trust-add", "--type=ipa",
|
||||
+ # "--admin", "admin@{}".format(self.trusted_master.domain.realm),
|
||||
+ # "--range-type=ipa-ad-trust-posix",
|
||||
+ # "--password", "--two-way=true",
|
||||
+ # self.trusted_master.domain.name
|
||||
+ # ], stdin_text=self.trusted_master.config.admin_password)
|
||||
diff --git a/pylint_plugins.py b/pylint_plugins.py
|
||||
index d75da3f4aae9e7bb5083a0618c8d242ce8117f64..75d65f016fde70a783b24689c9bb2c88a7e193de 100644
|
||||
--- a/pylint_plugins.py
|
||||
+++ b/pylint_plugins.py
|
||||
@@ -566,6 +566,7 @@ AstroidBuilder(MANAGER).string_build(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
+ from ipatests.test_integration.base import MultiDomainIntegrationTest
|
||||
from ipatests.pytest_ipa.integration.host import Host, WinHost
|
||||
from ipatests.pytest_ipa.integration.config import Config, Domain
|
||||
|
||||
@@ -584,6 +585,9 @@ AstroidBuilder(MANAGER).string_build(
|
||||
def __getitem__(self, key):
|
||||
return Domain()
|
||||
|
||||
+ class PylintTrustedDomains:
|
||||
+ def __getitem__(self, key):
|
||||
+ return Domain()
|
||||
|
||||
Host.config = Config()
|
||||
Host.domain = Domain()
|
||||
@@ -596,6 +600,14 @@ AstroidBuilder(MANAGER).string_build(
|
||||
IntegrationTest.ad_treedomains = PylintWinHosts()
|
||||
IntegrationTest.ad_subdomains = PylintWinHosts()
|
||||
IntegrationTest.ad_domains = PylintADDomains()
|
||||
+ MultiDomainIntegrationTest.domain = Domain()
|
||||
+ MultiDomainIntegrationTest.master = Host()
|
||||
+ MultiDomainIntegrationTest.replicas = PylintIPAHosts()
|
||||
+ MultiDomainIntegrationTest.clients = PylintIPAHosts()
|
||||
+ MultiDomainIntegrationTest.trusted_master = Host()
|
||||
+ MultiDomainIntegrationTest.trusted_replicas = PylintIPAHosts()
|
||||
+ MultiDomainIntegrationTest.trusted_clients = PylintIPAHosts()
|
||||
+ MultiDomainIntegrationTest.trusted_domains = PylintTrustedDomains()
|
||||
"""
|
||||
)
|
||||
)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
404
0120-ipatests-Add-comprehensive-tests-for-ipa-client-auto.patch
Normal file
404
0120-ipatests-Add-comprehensive-tests-for-ipa-client-auto.patch
Normal file
@ -0,0 +1,404 @@
|
||||
From 902dbeb67e0574dca4c761d058b43af3ac2cef6a Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Mon, 26 May 2025 20:27:02 +0530
|
||||
Subject: [PATCH] ipatests: Add comprehensive tests for ipa-client-automount
|
||||
--domain option
|
||||
|
||||
- Add parametrized test for domain validation covering valid/invalid formats
|
||||
- Add cross-domain discovery test showing --domain enables discovery when
|
||||
client is in different domain than IPA domain
|
||||
- Validate configuration in sssd.conf after successful automount setup
|
||||
|
||||
The new tests ensure --domain option works correctly and provides proper
|
||||
hints for DNS discovery in cross-domain scenarios, reducing user friction
|
||||
compared to requiring --server specification.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9780
|
||||
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
.../nightly_ipa-4-12_latest.yaml | 17 ++
|
||||
.../nightly_ipa-4-12_latest_selinux.yaml | 18 ++
|
||||
ipatests/prci_definitions/temp_commit.yaml | 4 +
|
||||
ipatests/test_integration/test_nfs.py | 215 ++++++++++++++++--
|
||||
ipatests/test_ipalib/test_util.py | 21 +-
|
||||
5 files changed, 255 insertions(+), 20 deletions(-)
|
||||
|
||||
diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
|
||||
index bc2a10de47bb136e13bf99869fc4f41101e863cb..198c6acec368f2dc11197b55066a042473b27201 100644
|
||||
--- a/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
|
||||
+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest.yaml
|
||||
@@ -43,6 +43,10 @@ topologies:
|
||||
name: ad_master_1repl_1client
|
||||
cpu: 6
|
||||
memory: 12096
|
||||
+ ipa_ipa_trust: &ipa_ipa_trust
|
||||
+ name: ipa_ipa_trust
|
||||
+ cpu: 7
|
||||
+ memory: 14750
|
||||
|
||||
jobs:
|
||||
fedora-latest-ipa-4-12/build:
|
||||
@@ -59,6 +63,19 @@ jobs:
|
||||
timeout: 1800
|
||||
topology: *build
|
||||
|
||||
+ fedora-latest-ipa-4-12/nfs_automountdiscovery:
|
||||
+ requires: [fedora-latest-ipa-4-12/build]
|
||||
+ priority: 50
|
||||
+ job:
|
||||
+ class: RunPytest
|
||||
+ args:
|
||||
+ build_url: '{fedora-latest-ipa-4-12/build_url}'
|
||||
+ test_suite: test_integration/test_nfs.py::TestIpaClientAutomountDiscovery
|
||||
+ trusted_domain: True
|
||||
+ template: *ci-ipa-4-12-latest
|
||||
+ timeout: 3600
|
||||
+ topology: *ipa_ipa_trust
|
||||
+
|
||||
fedora-latest-ipa-4-12/simple_replication:
|
||||
requires: [fedora-latest-ipa-4-12/build]
|
||||
priority: 50
|
||||
diff --git a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
|
||||
index fc31186dfa4dcf863220044a2a5881304b39e76d..8f01bb84126e7f6b2b8e79e8e45475f85a0d8469 100644
|
||||
--- a/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
|
||||
+++ b/ipatests/prci_definitions/nightly_ipa-4-12_latest_selinux.yaml
|
||||
@@ -43,6 +43,10 @@ topologies:
|
||||
name: ad_master_1repl_1client
|
||||
cpu: 6
|
||||
memory: 12096
|
||||
+ ipa_ipa_trust: &ipa_ipa_trust
|
||||
+ name: ipa_ipa_trust
|
||||
+ cpu: 7
|
||||
+ memory: 14750
|
||||
|
||||
jobs:
|
||||
fedora-latest-ipa-4-12/build:
|
||||
@@ -59,6 +63,20 @@ jobs:
|
||||
timeout: 1800
|
||||
topology: *build
|
||||
|
||||
+ fedora-latest-ipa-4-12/nfs_automountdiscovery:
|
||||
+ requires: [fedora-latest-ipa-4-12/build]
|
||||
+ priority: 50
|
||||
+ job:
|
||||
+ class: RunPytest
|
||||
+ args:
|
||||
+ build_url: '{fedora-latest-ipa-4-12/build_url}'
|
||||
+ selinux_enforcing: True
|
||||
+ test_suite: test_integration/test_nfs.py::TestIpaClientAutomountDiscovery
|
||||
+ trusted_domain: True
|
||||
+ template: *ci-ipa-4-12-latest
|
||||
+ timeout: 3600
|
||||
+ topology: *ipa_ipa_trust
|
||||
+
|
||||
fedora-latest-ipa-4-12/simple_replication:
|
||||
requires: [fedora-latest-ipa-4-12/build]
|
||||
priority: 50
|
||||
diff --git a/ipatests/prci_definitions/temp_commit.yaml b/ipatests/prci_definitions/temp_commit.yaml
|
||||
index 24b7b4c48f31522421a7d7a099702a7e92cfbd27..036a1f495f85d6dd9a414bd7ea94446f780711e7 100644
|
||||
--- a/ipatests/prci_definitions/temp_commit.yaml
|
||||
+++ b/ipatests/prci_definitions/temp_commit.yaml
|
||||
@@ -49,6 +49,10 @@ topologies:
|
||||
name: ad_master_1repl_1client
|
||||
cpu: 6
|
||||
memory: 12096
|
||||
+ ipa_ipa_trust: &ipa_ipa_trust
|
||||
+ name: ipa_ipa_trust
|
||||
+ cpu: 7
|
||||
+ memory: 14750
|
||||
|
||||
jobs:
|
||||
fedora-latest-ipa-4-12/build:
|
||||
diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
|
||||
index 32d107b718aecd0944eaeb790af201c17b0ab56a..49a86fc8ef7441d26a1307e52cac0f6aa8962fcf 100644
|
||||
--- a/ipatests/test_integration/test_nfs.py
|
||||
+++ b/ipatests/test_integration/test_nfs.py
|
||||
@@ -21,15 +21,35 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
-from ipatests.test_integration.base import IntegrationTest
|
||||
+from ipaplatform.paths import paths
|
||||
+from ipatests.test_integration.base import (
|
||||
+ IntegrationTest, MultiDomainIntegrationTest)
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
-
|
||||
# give some time for units to stabilize
|
||||
# otherwise we get transient errors
|
||||
WAIT_AFTER_INSTALL = 5
|
||||
WAIT_AFTER_UNINSTALL = WAIT_AFTER_INSTALL
|
||||
|
||||
|
||||
+def remove_automount(host):
|
||||
+ time.sleep(WAIT_AFTER_INSTALL)
|
||||
+ host.run_command([
|
||||
+ 'ipa-client-automount', '--uninstall', '-U'
|
||||
+ ], raiseonerr=False)
|
||||
+ time.sleep(WAIT_AFTER_UNINSTALL)
|
||||
+
|
||||
+
|
||||
+def add_automount(host, extra_args, raiseonerr=False):
|
||||
+ time.sleep(WAIT_AFTER_UNINSTALL)
|
||||
+ args = [
|
||||
+ "ipa-client-automount",
|
||||
+ ]
|
||||
+ args.extend(extra_args)
|
||||
+ ret = host.run_command(args, raiseonerr=raiseonerr)
|
||||
+ time.sleep(WAIT_AFTER_INSTALL)
|
||||
+ return ret
|
||||
+
|
||||
+
|
||||
class TestNFS(IntegrationTest):
|
||||
|
||||
num_clients = 3
|
||||
@@ -357,25 +377,184 @@ class TestIpaClientAutomountFileRestore(IntegrationTest):
|
||||
self.nsswitch_backup_restore()
|
||||
|
||||
|
||||
-class TestIpaClientAutomountDiscovery(IntegrationTest):
|
||||
+class TestIpaClientAutomountDiscovery(MultiDomainIntegrationTest):
|
||||
|
||||
+ num_replicas = 0
|
||||
+ num_trusted_replicas = 0
|
||||
num_clients = 1
|
||||
- topology = 'line'
|
||||
+ num_trusted_clients = 1
|
||||
+ topology = "line"
|
||||
+
|
||||
+ def test_automount_valid_domain(self):
|
||||
+ """Test that --domain option controls which domain is used
|
||||
+ for DNS SRV record lookup.
|
||||
|
||||
- def test_automount_invalid_domain(self):
|
||||
- """Validate that the --domain option is passed into
|
||||
- Discovery. This is expected to fail discovery.
|
||||
+ Without --domain: client searches in its local domain (fails if no
|
||||
+ IPA records) With --domain: client searches in the specified
|
||||
+ domain (succeeds for IPA domain)
|
||||
"""
|
||||
- testdomain = "client.test"
|
||||
- msg1 = f"Search for LDAP SRV record in {testdomain}"
|
||||
- msg2 = f"Search DNS for SRV record of _ldap._tcp.{testdomain}"
|
||||
- msg3 = "Autodiscovery did not find LDAP server"
|
||||
+ testdomain1 = self.master.domain.name
|
||||
+ client2 = self.trusted_clients[0]
|
||||
+ tasks.uninstall_client(client2)
|
||||
+ client2.run_command(["ipa-client-install", "--domain", testdomain1,
|
||||
+ "--realm", self.master.domain.realm,
|
||||
+ "--server", self.master.hostname,
|
||||
+ "-p", client2.config.admin_name, "-w",
|
||||
+ client2.config.admin_password, "-U"]
|
||||
+ )
|
||||
+ result = add_automount(
|
||||
+ client2, extra_args=['--debug', '-U']
|
||||
+ )
|
||||
+ msg = "Search DNS for SRV record of _ldap._tcp.{0}"
|
||||
+ assert msg.format(client2.domain.name) in result.stderr_text
|
||||
+ remove_automount(client2)
|
||||
+ result2 = add_automount(
|
||||
+ client2, extra_args=['--debug', '--domain', testdomain1, '-U']
|
||||
+ )
|
||||
+ assert msg.format(testdomain1) in result2.stderr_text
|
||||
|
||||
+ @pytest.mark.parametrize(
|
||||
+ "domain_input,expected_success,test_description", [
|
||||
+ ("{ipa_domain}", True, "valid IPA domain should succeed"),
|
||||
+ ("client.test", False, "non-IPA domain should fail discovery"),
|
||||
+ (" example.com ", True, "whitespace should be trimmed"),
|
||||
+ ("EXAMPLE.COM", True, "uppercase should work"),
|
||||
+
|
||||
+ ])
|
||||
+ def test_automount_domain_option_integration(
|
||||
+ self, domain_input, expected_success, test_description):
|
||||
+ """Test for --domain affects DNS discovery and system integration.
|
||||
+
|
||||
+ This test verifies that the --domain option actually changes the DNS
|
||||
+ discovery behavior and system configuration, not just input validation.
|
||||
+
|
||||
+ Test cases:
|
||||
+ - Valid IPA domain: Should successfully discover IPA services and
|
||||
+ configure automount correctly
|
||||
+ - Non-IPA domain: Should fail discovery since no IPA DNS records
|
||||
+ exist in that domain
|
||||
+ """
|
||||
+ client = self.clients[0]
|
||||
+ ipa_domain = self.master.domain.name
|
||||
+
|
||||
+ # Replace placeholders in domain_input
|
||||
+ domain_to_test = domain_input.format(ipa_domain=ipa_domain)
|
||||
+
|
||||
+ if expected_success:
|
||||
+ # Should succeed
|
||||
+ add_automount(
|
||||
+ client, extra_args=['--domain', domain_to_test,
|
||||
+ '--debug', '-U']
|
||||
+ )
|
||||
+ # Verify configuration if successful
|
||||
+ sssd_conf = client.get_file_contents(
|
||||
+ paths.SSSD_CONF).decode()
|
||||
+ assert "autofs_provider = ipa" in sssd_conf, \
|
||||
+ "Autofs provider should be set to ipa"
|
||||
+ # Clean up
|
||||
+ remove_automount(client)
|
||||
+ else:
|
||||
+ # Should fail
|
||||
+ result = client.run_command([
|
||||
+ 'ipa-client-automount', '--domain', domain_to_test,
|
||||
+ '--debug'
|
||||
+ ], stdin_text="n", raiseonerr=False)
|
||||
+ assert (
|
||||
+ result.returncode != 0
|
||||
+ or "Autodiscovery did not find LDAP server" in
|
||||
+ result.stderr_text
|
||||
+ or "Invalid domain" in result.stderr_text
|
||||
+ ), f"Should have failed: {test_description}"
|
||||
+
|
||||
+ def test_automount_domain_option_overrides_discovery(self):
|
||||
+ """Test that explicit --domain option overrides automatic discovery."""
|
||||
client = self.clients[0]
|
||||
- result = client.run_command([
|
||||
- 'ipa-client-automount', '--domain', 'client.test',
|
||||
- '--debug'
|
||||
- ], stdin_text="n", raiseonerr=False)
|
||||
- assert msg1 in result.stderr_text
|
||||
- assert msg2 in result.stderr_text
|
||||
- assert msg3 in result.stderr_text
|
||||
+ ipa_domain = self.master.domain.name
|
||||
+
|
||||
+ # First install without domain to establish baseline
|
||||
+ result_auto = add_automount(client, extra_args=['--debug', '-U'])
|
||||
+ assert "Search DNS for SRV record" in result_auto.stderr_text
|
||||
+ remove_automount(client)
|
||||
+
|
||||
+ # Now with explicit domain
|
||||
+ result_explicit = add_automount(
|
||||
+ client, extra_args=['--domain', ipa_domain, '--debug', '-U']
|
||||
+ )
|
||||
+ explicit_domain_msg = (
|
||||
+ f"Using domain '{ipa_domain}'" in result_explicit.stderr_text
|
||||
+ or f"Search DNS for SRV record of _ldap._tcp.{ipa_domain}"
|
||||
+ in result_explicit.stderr_text
|
||||
+ )
|
||||
+
|
||||
+ assert explicit_domain_msg, \
|
||||
+ "Explicit domain should override automatic discovery"
|
||||
+
|
||||
+ # Final cleanup
|
||||
+ remove_automount(client)
|
||||
+
|
||||
+ def test_automount_domain_hint_for_cross_domain_discovery(self):
|
||||
+ """Test that --domain option enables discovery when client is in
|
||||
+ a different domain than the IPA domain.
|
||||
+ """
|
||||
+ client = self.clients[0]
|
||||
+ other_domain = self.trusted_master.domain.name
|
||||
+ tasks.uninstall_client(client)
|
||||
+ # Add DNS forwarder
|
||||
+ self.master.run_command([
|
||||
+ "ipa", "dnsforwardzone-add", self.trusted_master.domain.name,
|
||||
+ "--forwarder", self.trusted_master.ip,
|
||||
+ "--forward-policy=only"
|
||||
+ ])
|
||||
+ self.trusted_master.run_command([
|
||||
+ "ipa", "dnsforwardzone-add", self.master.domain.name,
|
||||
+ "--forwarder", self.master.ip,
|
||||
+ "--forward-policy=only"
|
||||
+ ])
|
||||
+ # Backup original resolv.conf
|
||||
+ tasks.backup_file(client, paths.RESOLV_CONF)
|
||||
+
|
||||
+ try:
|
||||
+ # Install client in a domain other than the IPA domain
|
||||
+ non_ipa_resolv_conf = f"""search {other_domain}
|
||||
+nameserver {self.trusted_master.ip}
|
||||
+"""
|
||||
+ client.put_file_contents(paths.RESOLV_CONF, non_ipa_resolv_conf)
|
||||
+
|
||||
+ # Ensure client is installed for the test
|
||||
+ tasks.uninstall_client(client)
|
||||
+ client.run_command(["ipa-client-install", "--domain", other_domain,
|
||||
+ "--realm", self.trusted_master.domain.realm,
|
||||
+ "--server", self.trusted_master.hostname,
|
||||
+ "-p", client.config.admin_name, "-w",
|
||||
+ client.config.admin_password, "-U"]
|
||||
+ )
|
||||
+ # Verify DNS discovery will fail when client is in Non-IPA domain
|
||||
+ # Attempt automount with --domain hint (should succeed)
|
||||
+ nodomain = add_automount(
|
||||
+ client, extra_args=['--debug', '-U']
|
||||
+ )
|
||||
+ # Verify discovery fails.
|
||||
+ assert "DNS record not found" in nodomain.stderr_text
|
||||
+ remove_automount(client)
|
||||
+
|
||||
+ # Attempt automount with --domain hint (should succeed)
|
||||
+ withdomain = add_automount(
|
||||
+ client, extra_args=['--domain', other_domain,
|
||||
+ '--debug', '-U']
|
||||
+ )
|
||||
+ # Verify discovery finds a IPA server.
|
||||
+ assert "DNS record found" in withdomain.stderr_text
|
||||
+ ipa_discovery = (
|
||||
+ f"Validated servers: {self.trusted_master.hostname}"
|
||||
+ in withdomain.stderr_text
|
||||
+ )
|
||||
+ assert ipa_discovery, \
|
||||
+ "Autodiscovery success"
|
||||
+ # Verify configuration was applied correctly
|
||||
+ sssd_conf = client.get_file_contents(paths.SSSD_CONF).decode()
|
||||
+ assert "autofs_provider = ipa" in sssd_conf, \
|
||||
+ "Autofs provider should be configured"
|
||||
+ finally:
|
||||
+ # Cleanup: restore original resolv.conf and uninstall
|
||||
+ tasks.restore_files(client)
|
||||
+ remove_automount(client)
|
||||
diff --git a/ipatests/test_ipalib/test_util.py b/ipatests/test_ipalib/test_util.py
|
||||
index 74a32b72c08aef6e01396b26efe6d71f570754cc..ba1b9f120d96b998c6effd55f12c7c9ba80a2565 100644
|
||||
--- a/ipatests/test_ipalib/test_util.py
|
||||
+++ b/ipatests/test_ipalib/test_util.py
|
||||
@@ -11,8 +11,10 @@ from unittest import mock
|
||||
import pytest
|
||||
|
||||
from ipalib.util import (
|
||||
- get_pager, create_https_connection, get_proper_tls_version_span
|
||||
+ get_pager, create_https_connection, get_proper_tls_version_span,
|
||||
+ validate_domain_name
|
||||
)
|
||||
+
|
||||
from ipaplatform.constants import constants
|
||||
|
||||
|
||||
@@ -27,7 +29,7 @@ from ipaplatform.constants import constants
|
||||
def test_get_pager(pager, expected_result):
|
||||
with mock.patch.dict(os.environ, {'PAGER': pager}):
|
||||
pager = get_pager()
|
||||
- assert(pager == expected_result or pager.endswith(expected_result))
|
||||
+ assert (pager == expected_result or pager.endswith(expected_result))
|
||||
|
||||
|
||||
BASE_CTX = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
@@ -75,3 +77,18 @@ def test_tls_version_span(minver, maxver, opt, expected):
|
||||
ctx = getattr(conn, "_context")
|
||||
assert ctx.options == BASE_OPT | opt
|
||||
assert ctx.get_ciphers() == BASE_CTX.get_ciphers()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("domain_input,expected_valid,description", [
|
||||
+ ("invalid..domain", False, "double dots should be rejected"),
|
||||
+ (".invalid.domain", False, "leading dot should be rejected"),
|
||||
+ ("invalid domain with spaces", False, "spaces should be rejected"),
|
||||
+ ("toolong" + "x" * 250 + ".domain", False, "overly long domain rejected"),
|
||||
+ ("", False, "empty string should be rejected"),
|
||||
+ ("single", False, "single label should be rejected"),
|
||||
+])
|
||||
+def test_validate_domain_name(domain_input, expected_valid, description):
|
||||
+ """Test domain name validation logic in ipalib.util.validate_domain_name"""
|
||||
+
|
||||
+ with pytest.raises((ValueError, TypeError)):
|
||||
+ validate_domain_name(domain_input)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
37
0121-ipatests-remove-xfail-for-PKI-11.7.patch
Normal file
37
0121-ipatests-remove-xfail-for-PKI-11.7.patch
Normal file
@ -0,0 +1,37 @@
|
||||
From b923355ff04dd88b1530d0bb2e032280afc5d315 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Tue, 26 Aug 2025 09:00:48 +0200
|
||||
Subject: [PATCH] ipatests: remove xfail for PKI 11.7
|
||||
|
||||
The test test_ca_show_error_handling is green with PKI 11.7
|
||||
because the PKI regression has been fixed.
|
||||
Update the xfail condition to 11.5 <= version < 11.7.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9606
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_cert.py | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
|
||||
index 05b20b910b249af24039a497538f96dad07162aa..84adf2ceafe013e6cfc973fb2cb650c40f36971d 100644
|
||||
--- a/ipatests/test_integration/test_cert.py
|
||||
+++ b/ipatests/test_integration/test_cert.py
|
||||
@@ -558,8 +558,10 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
)
|
||||
error_msg = 'ipa: ERROR: The certificate for ' \
|
||||
'{} is not available on this server.'.format(lwca)
|
||||
- bad_version = (tasks.get_pki_version(self.master)
|
||||
- >= tasks.parse_version('11.5.0'))
|
||||
+ pki_version = tasks.get_pki_version(self.master)
|
||||
+ # The regression was introduced in 11.5 and fixed in 11.7
|
||||
+ bad_version = (tasks.parse_version('11.5.0') <= pki_version
|
||||
+ < tasks.parse_version('11.7.0'))
|
||||
with xfail_context(bad_version,
|
||||
reason="https://pagure.io/freeipa/issue/9606"):
|
||||
assert error_msg in result.stderr_text
|
||||
--
|
||||
2.51.1
|
||||
|
||||
42
0122-ipatests-fix-test_otp.patch
Normal file
42
0122-ipatests-fix-test_otp.patch
Normal file
@ -0,0 +1,42 @@
|
||||
From 9b631f80720fe1f2492d1a30bb1c2410af5eb587 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Wed, 3 Sep 2025 14:57:52 +0200
|
||||
Subject: [PATCH] ipatests: fix test_otp
|
||||
|
||||
The test is performing ssh from the runner to the master
|
||||
but is using the external_hostname and randomly fails.
|
||||
|
||||
Make sure to use the configured hostname instead.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9849
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_otp.py | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_otp.py b/ipatests/test_integration/test_otp.py
|
||||
index 0babb45897c6107bf354477dbb0d3a805a3116f5..a4adeee69b068b0f889165882ab4ab6c5e2a97f8 100644
|
||||
--- a/ipatests/test_integration/test_otp.py
|
||||
+++ b/ipatests/test_integration/test_otp.py
|
||||
@@ -504,7 +504,7 @@ class TestOTPToken(IntegrationTest):
|
||||
)
|
||||
with xfail_context(rhel_fail or fedora_fail, reason=github_ticket):
|
||||
result = ssh_2fa_with_cmd(master,
|
||||
- self.master.external_hostname,
|
||||
+ self.master.hostname,
|
||||
USER3, PASSWORD, otpvalue=otpvalue,
|
||||
command="klist")
|
||||
print(result.stdout_text)
|
||||
@@ -552,7 +552,7 @@ class TestOTPToken(IntegrationTest):
|
||||
otpvalue = totp.generate(int(time.time())).decode('ascii')
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
result = ssh_2fa_with_cmd(master,
|
||||
- self.master.external_hostname,
|
||||
+ self.master.hostname,
|
||||
USER4, PASSWORD, otpvalue=otpvalue,
|
||||
command="klist")
|
||||
print(result.stdout_text)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
115
0123-ipatests-update-the-Let-s-Encrypt-cert-chain.patch
Normal file
115
0123-ipatests-update-the-Let-s-Encrypt-cert-chain.patch
Normal file
@ -0,0 +1,115 @@
|
||||
From 94493640e10547cd4aff82b017391916149822e5 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Wed, 17 Sep 2025 10:13:44 +0200
|
||||
Subject: [PATCH] ipatests: update the Let's Encrypt cert chain
|
||||
|
||||
The test TestIPACommand::test_cacert_manage is using
|
||||
Let's Encrypt chain to check the ipa-cacert-manage install
|
||||
command.
|
||||
The chain isrgrootx1 > r3 must be replaced with
|
||||
isrgrootx1 > r12 because r3 expired Sep 15.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9857
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_commands.py | 63 +++++++++++-----------
|
||||
1 file changed, 31 insertions(+), 32 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
|
||||
index ad97affe62e15c68442239d669032f0c84e7f5c9..fcf347ee068729d1b28d215b242569f02e9a549c 100644
|
||||
--- a/ipatests/test_integration/test_commands.py
|
||||
+++ b/ipatests/test_integration/test_commands.py
|
||||
@@ -88,41 +88,40 @@ isrgrootx1 = (
|
||||
)
|
||||
isrgrootx1_nick = 'CN=ISRG Root X1,O=Internet Security Research Group,C=US'
|
||||
|
||||
-# This sub-CA expires on Sep 15, 2025 and will need to be replaced
|
||||
+# This sub-CA expires on March 12, 2027 and will need to be replaced
|
||||
# after this date. Otherwise TestIPACommand::test_cacert_manage fails.
|
||||
-letsencryptauthorityr3 = (
|
||||
+letsencryptauthorityr12 = (
|
||||
b'-----BEGIN CERTIFICATE-----\n'
|
||||
- b'MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw\n'
|
||||
+ b'MIIFBjCCAu6gAwIBAgIRAMISMktwqbSRcdxA9+KFJjwwDQYJKoZIhvcNAQELBQAw\n'
|
||||
b'TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n'
|
||||
- b'cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw\n'
|
||||
- b'WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n'
|
||||
- b'RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n'
|
||||
- b'AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP\n'
|
||||
- b'R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx\n'
|
||||
- b'sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm\n'
|
||||
- b'NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg\n'
|
||||
- b'Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG\n'
|
||||
- b'/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC\n'
|
||||
- b'AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB\n'
|
||||
- b'Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA\n'
|
||||
- b'FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw\n'
|
||||
- b'AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw\n'
|
||||
- b'Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB\n'
|
||||
- b'gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W\n'
|
||||
- b'PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl\n'
|
||||
- b'ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz\n'
|
||||
- b'CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm\n'
|
||||
- b'lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4\n'
|
||||
- b'avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2\n'
|
||||
- b'yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O\n'
|
||||
- b'yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids\n'
|
||||
- b'hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+\n'
|
||||
- b'HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv\n'
|
||||
- b'MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX\n'
|
||||
- b'nLRbwHOoq7hHwg==\n'
|
||||
+ b'cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\n'
|
||||
+ b'WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n'
|
||||
+ b'RW5jcnlwdDEMMAoGA1UEAxMDUjEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n'
|
||||
+ b'CgKCAQEA2pgodK2+lP474B7i5Ut1qywSf+2nAzJ+Npfs6DGPpRONC5kuHs0BUT1M\n'
|
||||
+ b'5ShuCVUxqqUiXXL0LQfCTUA83wEjuXg39RplMjTmhnGdBO+ECFu9AhqZ66YBAJpz\n'
|
||||
+ b'kG2Pogeg0JfT2kVhgTU9FPnEwF9q3AuWGrCf4yrqvSrWmMebcas7dA8827JgvlpL\n'
|
||||
+ b'Thjp2ypzXIlhZZ7+7Tymy05v5J75AEaz/xlNKmOzjmbGGIVwx1Blbzt05UiDDwhY\n'
|
||||
+ b'XS0jnV6j/ujbAKHS9OMZTfLuevYnnuXNnC2i8n+cF63vEzc50bTILEHWhsDp7CH4\n'
|
||||
+ b'WRt/uTp8n1wBnWIEwii9Cq08yhDsGwIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB\n'
|
||||
+ b'hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB\n'
|
||||
+ b'/wIBADAdBgNVHQ4EFgQUALUp8i2ObzHom0yteD763OkM0dIwHwYDVR0jBBgwFoAU\n'
|
||||
+ b'ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC\n'
|
||||
+ b'hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG\n'
|
||||
+ b'A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN\n'
|
||||
+ b'AQELBQADggIBAI910AnPanZIZTKS3rVEyIV29BWEjAK/duuz8eL5boSoVpHhkkv3\n'
|
||||
+ b'4eoAeEiPdZLj5EZ7G2ArIK+gzhTlRQ1q4FKGpPPaFBSpqV/xbUb5UlAXQOnkHn3m\n'
|
||||
+ b'FVj+qYv87/WeY+Bm4sN3Ox8BhyaU7UAQ3LeZ7N1X01xxQe4wIAAE3JVLUCiHmZL+\n'
|
||||
+ b'qoCUtgYIFPgcg350QMUIWgxPXNGEncT921ne7nluI02V8pLUmClqXOsCwULw+PVO\n'
|
||||
+ b'ZCB7qOMxxMBoCUeL2Ll4oMpOSr5pJCpLN3tRA2s6P1KLs9TSrVhOk+7LX28NMUlI\n'
|
||||
+ b'usQ/nxLJID0RhAeFtPjyOCOscQBA53+NRjSCak7P4A5jX7ppmkcJECL+S0i3kXVU\n'
|
||||
+ b'y5Me5BbrU8973jZNv/ax6+ZK6TM8jWmimL6of6OrX7ZU6E2WqazzsFrLG3o2kySb\n'
|
||||
+ b'zlhSgJ81Cl4tv3SbYiYXnJExKQvzf83DYotox3f0fwv7xln1A2ZLplCb0O+l/AK0\n'
|
||||
+ b'YE0DS2FPxSAHi0iwMfW2nNHJrXcY3LLHD77gRgje4Eveubi2xxa+Nmk/hmhLdIET\n'
|
||||
+ b'iVDFanoCrMVIpQ59XWHkzdFmoHXHBV7oibVjGSO7ULSQ7MJ1Nz51phuDJSgAIU7A\n'
|
||||
+ b'0zrLnOrAj/dfrlEWRhCvAgbuwLZX1A2sjNjXoPOHbsPiy+lO1KF8/XY7\n'
|
||||
b'-----END CERTIFICATE-----\n'
|
||||
)
|
||||
-le_r3_nick = "CN=R3,O=Let's Encrypt,C=US"
|
||||
+le_r12_nick = "CN=R12,O=Let's Encrypt,C=US"
|
||||
|
||||
# Certificates for reproducing duplicate ipaCertSubject values.
|
||||
# The trick to creating the second intermediate is for the validity
|
||||
@@ -1230,7 +1229,7 @@ class TestIPACommand(IntegrationTest):
|
||||
result.stderr_text
|
||||
|
||||
# Install 3rd party CA's, Let's Encrypt in this case
|
||||
- for cert in (isrgrootx1, letsencryptauthorityr3):
|
||||
+ for cert in (isrgrootx1, letsencryptauthorityr12):
|
||||
certfile = os.path.join(self.master.config.test_dir, 'cert.pem')
|
||||
self.master.put_file_contents(certfile, cert)
|
||||
result = self.master.run_command(
|
||||
@@ -1257,7 +1256,7 @@ class TestIPACommand(IntegrationTest):
|
||||
|
||||
# deletion of a subca
|
||||
result = self.master.run_command(
|
||||
- ['ipa-cacert-manage', 'delete', le_r3_nick],
|
||||
+ ['ipa-cacert-manage', 'delete', le_r12_nick],
|
||||
raiseonerr=False
|
||||
)
|
||||
assert result.returncode == 0
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
From 325108c7134db2a4ea631ee4b31fc2e1b70580ff Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Fri, 19 Sep 2025 16:31:36 +0200
|
||||
Subject: [PATCH] ipatests: fix TestIPAMigratewithBackupRestore setup
|
||||
|
||||
The test is installing a first master with DNS enabled, then a
|
||||
second master (same domain name, with DNS enabled) in order to
|
||||
perform migration.
|
||||
Add --allow-zone-overlap to the 2nd master installation.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9858
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Sudhir Menon <sumenon@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_ipa_ipa_migration.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
index c6247e772b257748aa0c0f58bd04b53d3756125c..d076510f90aa65d37cfe72b52e915504155aa2e4 100644
|
||||
--- a/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
@@ -1283,7 +1283,8 @@ class TestIPAMigratewithBackupRestore(IntegrationTest):
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
||||
prepare_ipa_server(cls.master)
|
||||
- tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True)
|
||||
+ tasks.install_master(cls.replicas[0], setup_dns=True, setup_kra=True,
|
||||
+ extra_args=['--allow-zone-overlap'])
|
||||
tasks.install_replica(cls.master, cls.replicas[1],
|
||||
setup_dns=True, setup_kra=True)
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
1115
0125-Add-support-for-libpwpolicy-credit-to-password-polic.patch
Normal file
1115
0125-Add-support-for-libpwpolicy-credit-to-password-polic.patch
Normal file
File diff suppressed because it is too large
Load Diff
376
0126-ipatests-Refactor-and-port-trust-functional-HBAC-tes.patch
Normal file
376
0126-ipatests-Refactor-and-port-trust-functional-HBAC-tes.patch
Normal file
@ -0,0 +1,376 @@
|
||||
From 13ab328e519ba3e8e22bcade7680bc060a11d4a1 Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Tue, 29 Jul 2025 20:46:37 +0530
|
||||
Subject: [PATCH] ipatests: Refactor and port trust functional HBAC tests.
|
||||
|
||||
- Tests to cover both root domain and subdomain users:
|
||||
- Test that adding AD users/groups without the external group to
|
||||
HBAC rules fails.
|
||||
- Test HBAC rule denies SSH access for AD users.
|
||||
- Test HBAC rule allows SSH access for AD users in external group.
|
||||
- Test HBAC rule denies sudo access for AD users when rule doesn't
|
||||
include them.
|
||||
- Test HBAC rule allows sudo access for AD users in external group.
|
||||
|
||||
Related : https://pagure.io/freeipa/issue/9845
|
||||
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_trust.py | 11 +-
|
||||
.../test_integration/test_trust_functional.py | 306 ++++++++++++++++++
|
||||
2 files changed, 315 insertions(+), 2 deletions(-)
|
||||
create mode 100644 ipatests/test_integration/test_trust_functional.py
|
||||
|
||||
diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
|
||||
index 4086cb30ac5d52ee595c1ecdbe86a8d511cbb704..7bb74e2f5821719ffe2ceaf2bdcd8e7d46a6cd1f 100644
|
||||
--- a/ipatests/test_integration/test_trust.py
|
||||
+++ b/ipatests/test_integration/test_trust.py
|
||||
@@ -66,6 +66,10 @@ class BaseTestTrust(IntegrationTest):
|
||||
if cls.num_ad_subdomains > 0:
|
||||
cls.child_ad = cls.ad_subdomains[0]
|
||||
cls.ad_subdomain = cls.child_ad.domain.name
|
||||
+ cls.subaduser = f"subdomaintestuser@{cls.ad_subdomain}"
|
||||
+ cls.subaduser2 = f"subdomaindisabledadu@{cls.ad_subdomain}"
|
||||
+ cls.ad_sub_group = f"subdomaintestgroup@{cls.ad_subdomain}"
|
||||
+
|
||||
if cls.num_ad_treedomains > 0:
|
||||
cls.tree_ad = cls.ad_treedomains[0]
|
||||
cls.ad_treedomain = cls.tree_ad.domain.name
|
||||
@@ -74,6 +78,9 @@ class BaseTestTrust(IntegrationTest):
|
||||
cls.srv_gc_record_name = \
|
||||
'_ldap._tcp.Default-First-Site-Name._sites.gc._msdcs'
|
||||
cls.srv_gc_record_value = '0 100 389 {}.'.format(cls.master.hostname)
|
||||
+ cls.aduser = f"nonposixuser@{cls.ad_domain}"
|
||||
+ cls.aduser2 = f"nonposixuser1@{cls.ad_domain}"
|
||||
+ cls.ad_group = f"testgroup@{cls.ad_domain}"
|
||||
|
||||
@classmethod
|
||||
def check_sid_generation(cls):
|
||||
@@ -1303,8 +1310,8 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
|
||||
self.gid_override):
|
||||
self.mod_idrange_auto_private_group(type)
|
||||
(uid, gid) = self.get_user_id(self.clients[0], posixuser)
|
||||
- assert(uid == self.uid_override
|
||||
- and gid == self.gid_override)
|
||||
+ assert (uid == self.uid_override
|
||||
+ 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")
|
||||
diff --git a/ipatests/test_integration/test_trust_functional.py b/ipatests/test_integration/test_trust_functional.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..b40bf9675ed5cddaf51624417356ddab28e870a5
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/test_trust_functional.py
|
||||
@@ -0,0 +1,306 @@
|
||||
+# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
+
|
||||
+from __future__ import absolute_import
|
||||
+
|
||||
+from ipaplatform.paths import paths
|
||||
+from ipatests.pytest_ipa.integration import tasks
|
||||
+from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
+
|
||||
+
|
||||
+class TestTrustFunctionalHbac(BaseTestTrust):
|
||||
+ topology = 'line'
|
||||
+ num_ad_treedomains = 0
|
||||
+
|
||||
+ def _add_hbacrule_with_service(self, rule_name, service_name):
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "hbacrule-add", rule_name, "--hostcat=all"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "hbacrule-add-service",
|
||||
+ rule_name,
|
||||
+ f"--hbacsvcs={service_name}",
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ def _disable_allow_all_and_wait(self):
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(["ipa", "hbacrule-disable", "allow_all"])
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.master)
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.clients[0])
|
||||
+
|
||||
+ def _cleanup_hrule_allow_all_and_wait(self, hrule):
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(["ipa", "hbacrule-del", hrule])
|
||||
+ self.master.run_command(["ipa", "hbacrule-enable", "allow_all"])
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.master)
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.clients[0])
|
||||
+
|
||||
+ def _ssh_with_password(
|
||||
+ self,
|
||||
+ login,
|
||||
+ host,
|
||||
+ password,
|
||||
+ success_expected=False
|
||||
+ ):
|
||||
+ result = self.clients[0].run_command(
|
||||
+ ['sshpass', '-p', password,
|
||||
+ 'ssh', '-v', '-o', 'StrictHostKeyChecking=no',
|
||||
+ '-l', login, host, "id"],
|
||||
+ raiseonerr=success_expected
|
||||
+ )
|
||||
+ output = f"{result.stdout_text}{result.stderr_text}"
|
||||
+ return output
|
||||
+
|
||||
+ def _get_log_tail(self, host, log_path, start_offset):
|
||||
+ return host.get_file_contents(log_path)[start_offset:]
|
||||
+
|
||||
+ def test_setup(self):
|
||||
+ tasks.configure_dns_for_trust(self.master, self.ad)
|
||||
+ tasks.establish_trust_with_ad(
|
||||
+ self.master, self.ad_domain,
|
||||
+ extra_args=['--range-type', 'ipa-ad-trust'])
|
||||
+ tasks.kdestroy_all(self.master)
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ tasks.group_add(
|
||||
+ self.master,
|
||||
+ groupname="hbacgroup_external",
|
||||
+ extra_args=["--external"],
|
||||
+ )
|
||||
+ tasks.group_add(self.master, groupname="hbacgroup")
|
||||
+ tasks.group_add_member(
|
||||
+ self.master,
|
||||
+ groupname="hbacgroup",
|
||||
+ extra_args=['--groups=hbacgroup_external'],
|
||||
+ )
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', '-n', 'group-add-member', '--external',
|
||||
+ self.aduser, 'hbacgroup_external',
|
||||
+ ])
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', '-n', 'group-add-member', '--external',
|
||||
+ self.subaduser, 'hbacgroup_external',
|
||||
+ ])
|
||||
+
|
||||
+ def test_ipa_trust_func_hbac_0001(self):
|
||||
+ """
|
||||
+ Test that adding AD users/groups without the external group to
|
||||
+ HBAC rules fails.
|
||||
+
|
||||
+ This test verifies that when attempting to add AD users or
|
||||
+ groups directly to HABC rules, the operation fails with
|
||||
+ a "no such entry" error.
|
||||
+ """
|
||||
+ hrule = "hbacrule_hbac_0001"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ try:
|
||||
+ self._add_hbacrule_with_service(hrule, 'sudo')
|
||||
+ for arg in [
|
||||
+ f"--users={self.aduser}", f"--users={self.subaduser}",
|
||||
+ f"--groups={self.ad_group}", f"--groups={self.ad_sub_group}"
|
||||
+ ]:
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "hbacrule-add-user", hrule, arg],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ output = f"{result.stdout_text}{result.stderr_text}"
|
||||
+ assert result.returncode != 0
|
||||
+ assert "no such entry" in output
|
||||
+ finally:
|
||||
+ self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
+
|
||||
+ def test_ipa_trust_func_hbac_0002(self):
|
||||
+ """
|
||||
+ Test HBAC rule denies SSH access for AD users.
|
||||
+
|
||||
+ This test creates an HBAC rule that allows SSH access only for admin
|
||||
+ users/groups, then verifies that AD users from the trusted domain are
|
||||
+ denied access. The test confirms that the denial is logged with
|
||||
+ "Access denied by HBAC rules" message.
|
||||
+ """
|
||||
+ hrule = "hbacrule_hbac_0002"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ log_file = '{0}/sssd_{1}.log'.format(
|
||||
+ paths.VAR_LOG_SSSD_DIR, self.master.domain.name)
|
||||
+ try:
|
||||
+ self._add_hbacrule_with_service(hrule, 'sshd')
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'hbacrule-add-user', hrule,
|
||||
+ '--users=admin', '--groups=admins'
|
||||
+ ]
|
||||
+ )
|
||||
+ self._disable_allow_all_and_wait()
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ logsize = tasks.get_logsize(
|
||||
+ self.clients[0], log_file
|
||||
+ )
|
||||
+ self._ssh_with_password(
|
||||
+ user,
|
||||
+ self.clients[0].hostname,
|
||||
+ 'Secret123',
|
||||
+ success_expected=False,
|
||||
+ )
|
||||
+ sssd_logs = self._get_log_tail(
|
||||
+ self.clients[0], log_file, logsize
|
||||
+ )
|
||||
+ assert b"Access denied by HBAC rules" in sssd_logs
|
||||
+ finally:
|
||||
+ self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
+
|
||||
+ def test_ipa_trust_func_hbac_0005(self):
|
||||
+ """
|
||||
+ Test HBAC rule allows SSH access for AD users in external group.
|
||||
+
|
||||
+ This test creates an HBAC rule that allows SSH access for members of
|
||||
+ the hbacgroup (which includes AD users via external group membership).
|
||||
+ It verifies that AD users who are members can successfully SSH, while
|
||||
+ AD users who are not members are denied access.
|
||||
+ """
|
||||
+ hrule = "hbacrule_hbac_0005"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ try:
|
||||
+ self._add_hbacrule_with_service(hrule, 'sshd')
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "hbacrule-add-user",
|
||||
+ hrule,
|
||||
+ "--groups=hbacgroup",
|
||||
+ ]
|
||||
+ )
|
||||
+ self._disable_allow_all_and_wait()
|
||||
+ tasks.kinit_admin(self.clients[0])
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ tasks.kinit_admin(self.clients[0])
|
||||
+ self.clients[0].run_command(
|
||||
+ ["ipa", "hbactest", f"--user={user}", "--service=sshd",
|
||||
+ f"--host={self.clients[0].hostname}",
|
||||
+ ]
|
||||
+ )
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ output = self._ssh_with_password(
|
||||
+ user,
|
||||
+ self.clients[0].hostname,
|
||||
+ 'Secret123',
|
||||
+ success_expected=True,
|
||||
+ )
|
||||
+ assert "domain users" in output
|
||||
+
|
||||
+ for user2 in [self.aduser2, self.subaduser2]:
|
||||
+ self._ssh_with_password(
|
||||
+ user2,
|
||||
+ self.clients[0].hostname,
|
||||
+ 'Secret123',
|
||||
+ success_expected=False,
|
||||
+ )
|
||||
+ finally:
|
||||
+ self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
+
|
||||
+ def test_ipa_trust_func_hbac_0008(self):
|
||||
+ """
|
||||
+ Test HBAC rule denies sudo access for AD users when rule doesn't
|
||||
+ include them.
|
||||
+
|
||||
+ This test creates an HBAC rule for sudo service that only allows
|
||||
+ admin users, and a sudo rule that allows admin users to run all
|
||||
+ commands. It then verifies that AD users are denied sudo access
|
||||
+ due to HBAC restrictions, with the denial being logged as
|
||||
+ "user NOT authorized on host".
|
||||
+ """
|
||||
+ hrule = "hbacrule_hbac_0008"
|
||||
+ srule = "sudorule_hbac_0008"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ try:
|
||||
+ self._add_hbacrule_with_service(hrule, 'sudo')
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "hbacrule-add-user", hrule, "--users=admin",
|
||||
+ "--groups=admins"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "sudorule-add",
|
||||
+ srule,
|
||||
+ "--hostcat=all",
|
||||
+ "--cmdcat=all",
|
||||
+ ]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "sudorule-add-user",
|
||||
+ srule,
|
||||
+ "--users=admin",
|
||||
+ "--groups=admins"
|
||||
+ ]
|
||||
+ )
|
||||
+ tasks.clear_sssd_cache(self.clients[0])
|
||||
+ self._disable_allow_all_and_wait()
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ test_sudo = "su {0} -c 'sudo -S id'".format(user)
|
||||
+ result = self.clients[0].run_command(
|
||||
+ test_sudo,
|
||||
+ stdin_text='Secret123',
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ output = f"{result.stdout_text}{result.stderr_text}"
|
||||
+ assert (
|
||||
+ "sudo: PAM account management error: Permission denied"
|
||||
+ in output
|
||||
+ )
|
||||
+ finally:
|
||||
+ self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
+ self.master.run_command(["ipa", "sudorule-del", srule])
|
||||
+
|
||||
+ def test_ipa_trust_func_hbac_0011(self):
|
||||
+ """
|
||||
+ Test HBAC rule allows sudo access for AD users in external group.
|
||||
+
|
||||
+ This test creates an HBAC rule for sudo service that allows members of
|
||||
+ the hbacgroup (which includes AD users via external group membership),
|
||||
+ and a sudo rule that allows hbacgroup members to run all commands.
|
||||
+ It verifies that AD users who are members of the external group can
|
||||
+ successfully use sudo and gain root privileges.
|
||||
+ """
|
||||
+ hrule = "ipa_trust_func_hbac_0011"
|
||||
+ srule = "ipa_trust_func_hbac_0011"
|
||||
+ tasks.clear_sssd_cache(self.master)
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ try:
|
||||
+ self._add_hbacrule_with_service(hrule, 'sudo')
|
||||
+
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "hbacrule-add-user", hrule, "--groups=hbacgroup"]
|
||||
+ )
|
||||
+ self.master.run_command(["ipa", "hbacrule-disable", "allow_all"])
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=hbacgroup"]
|
||||
+ )
|
||||
+ tasks.clear_sssd_cache(self.master)
|
||||
+ tasks.clear_sssd_cache(self.clients[0])
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.master)
|
||||
+ test_sudo = "su {user} -c 'sudo -S id'"
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ with self.clients[0].spawn_expect(
|
||||
+ test_sudo.format(user=user)) as e:
|
||||
+ e.sendline('Secret123')
|
||||
+ e.expect_exit(ignore_remaining_output=True, timeout=60)
|
||||
+ output = e.get_last_output()
|
||||
+ assert 'uid=0(root)' in output
|
||||
+ for user in [self.aduser2, self.subaduser2]:
|
||||
+ test_sudo = "su {0} -c 'sudo -S id'".format(user)
|
||||
+ result = self.clients[0].run_command(
|
||||
+ test_sudo,
|
||||
+ stdin_text='Secret123',
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ finally:
|
||||
+ self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
+ self.master.run_command(["ipa", "sudorule-del", srule])
|
||||
--
|
||||
2.51.1
|
||||
|
||||
55
0127-ipatests-skip-encrypted-dns-tests-on-fedora-41.patch
Normal file
55
0127-ipatests-skip-encrypted-dns-tests-on-fedora-41.patch
Normal file
@ -0,0 +1,55 @@
|
||||
From 5b10d0eebffe0aaec7e7cb7974b8299905d289e9 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 2 Jun 2025 15:03:40 +0200
|
||||
Subject: [PATCH] ipatests: skip encrypted dns tests on fedora 41
|
||||
|
||||
The package ipa-server-encrypted-dns is not available on fedora 41
|
||||
as it requires a more recent bind version.
|
||||
Skip the tests that require this package in f41.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9799
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_edns.py | 8 ++++++++
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
|
||||
index dd046f226926d09074d8d6ce536999c5d452fcc4..1f843c7bcc5f8420740175ca03bcdc1ddf59ce09 100644
|
||||
--- a/ipatests/test_integration/test_edns.py
|
||||
+++ b/ipatests/test_integration/test_edns.py
|
||||
@@ -4,15 +4,20 @@
|
||||
"""This covers tests for DNS over TLS related feature"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
+import pytest
|
||||
import textwrap
|
||||
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
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
|
||||
|
||||
|
||||
+@pytest.mark.skipif(
|
||||
+ osinfo.id == 'fedora' and osinfo.version_number == (41,),
|
||||
+ reason='Encrypted DNS not supported in fedora 41')
|
||||
class TestDNSOverTLS(IntegrationTest):
|
||||
"""Tests for DNS over TLS feature."""
|
||||
|
||||
@@ -246,6 +251,9 @@ class TestDNSOverTLS(IntegrationTest):
|
||||
assert '''--dns-over-tls Configure DNS over TLS''' in cmdout.stdout_text # noqa: E501
|
||||
|
||||
|
||||
+@pytest.mark.skipif(
|
||||
+ osinfo.id == 'fedora' and osinfo.version_number == (41,),
|
||||
+ reason='Encrypted DNS not supported in fedora 41')
|
||||
class TestDNS_DoT(TestDNS):
|
||||
|
||||
@classmethod
|
||||
--
|
||||
2.51.1
|
||||
|
||||
367
0128-Extended-eDNS-testsuite-with-Relaxed-policy-testcase.patch
Normal file
367
0128-Extended-eDNS-testsuite-with-Relaxed-policy-testcase.patch
Normal file
@ -0,0 +1,367 @@
|
||||
From 79eaa672eeed6f6eb2f92ec97150fb154e963eb4 Mon Sep 17 00:00:00 2001
|
||||
From: PRANAV THUBE <pthube@redhat.com>
|
||||
Date: Wed, 20 Aug 2025 15:55:55 +0530
|
||||
Subject: [PATCH] Extended eDNS testsuite with Relaxed policy testcases. 1.
|
||||
Relaxed policy without certs and including --no-dnssec-validation 2. Relaxed
|
||||
policy with external CA and including --no-dnssec-validation
|
||||
|
||||
Automated with Cursor+Claude
|
||||
Related: https://issues.redhat.com/browse/IDM-2894
|
||||
|
||||
Signed-off-by: PRANAV THUBE <pthube@redhat.com>
|
||||
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Reviewed-By: Antonio Torres <antorres@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_edns.py | 315 ++++++++++++++++---------
|
||||
1 file changed, 202 insertions(+), 113 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
|
||||
index 1f843c7bcc5f8420740175ca03bcdc1ddf59ce09..6556c46ce2040ea5ce94bebe69cff06e727af64a 100644
|
||||
--- a/ipatests/test_integration/test_edns.py
|
||||
+++ b/ipatests/test_integration/test_edns.py
|
||||
@@ -6,7 +6,9 @@
|
||||
from __future__ import absolute_import
|
||||
import pytest
|
||||
import textwrap
|
||||
-
|
||||
+import os
|
||||
+from ipatests.test_integration.test_caless import ExternalCA
|
||||
+from cryptography.hazmat.primitives import serialization
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.test_integration.test_dns import TestDNS
|
||||
@@ -15,6 +17,54 @@ from ipaplatform.osinfo import osinfo
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
|
||||
+def verify_queries_encrypted(master, replicas, clients,
|
||||
+ forwarder="1.1.1.1#853",
|
||||
+ dns_hostname="freeipa.org"):
|
||||
+ """
|
||||
+ Helper function to verify that queries are encrypted and
|
||||
+ routed to the specified forwarder.
|
||||
+ """
|
||||
+ unbound_log_cfg = textwrap.dedent("""
|
||||
+ server:
|
||||
+ verbosity: 3
|
||||
+ log-queries: yes
|
||||
+ cache-min-ttl: 0
|
||||
+ cache-max-ttl: 0
|
||||
+ """)
|
||||
+
|
||||
+ for server in [master] + replicas:
|
||||
+ server.put_file_contents(
|
||||
+ os.path.join(paths.UNBOUND_CONFIG_DIR, "log.conf"),
|
||||
+ unbound_log_cfg,
|
||||
+ )
|
||||
+ server.run_command(["systemctl", "restart", "unbound"])
|
||||
+ server.run_command(
|
||||
+ ["journalctl", "--flush", "--rotate", "--vacuum-time=1s"]
|
||||
+ )
|
||||
+ server.run_command(["dig", dns_hostname])
|
||||
+ log_output = server.run_command(
|
||||
+ ["journalctl", "-u", "unbound", "--grep", forwarder]
|
||||
+ )
|
||||
+ assert forwarder in log_output.stdout_text, (
|
||||
+ f"Forwarder {forwarder} not found in logs on "
|
||||
+ f"{server.hostname}"
|
||||
+ )
|
||||
+ server.run_command(
|
||||
+ ["journalctl", "--flush", "--rotate", "--vacuum-time=1s"]
|
||||
+ )
|
||||
+
|
||||
+ for client in clients:
|
||||
+ client.run_command(["dig", dns_hostname])
|
||||
+ log_output = master.run_command(
|
||||
+ ["journalctl", "-u", "unbound", "--grep", forwarder]
|
||||
+ )
|
||||
+
|
||||
+ assert forwarder in log_output.stdout_text, (
|
||||
+ f"Forwarder {forwarder} not found in logs on master for "
|
||||
+ f"client {client.hostname}"
|
||||
+ )
|
||||
+
|
||||
+
|
||||
@pytest.mark.skipif(
|
||||
osinfo.id == 'fedora' and osinfo.version_number == (41,),
|
||||
reason='Encrypted DNS not supported in fedora 41')
|
||||
@@ -83,118 +133,6 @@ class TestDNSOverTLS(IntegrationTest):
|
||||
"--setup-dns, ignoring") in res.stdout_text
|
||||
tasks.uninstall_master(self.master)
|
||||
|
||||
- def test_install_dnsovertls_master(self):
|
||||
- """
|
||||
- This tests installs IPA server with --dns-over-tls option.
|
||||
- """
|
||||
- args = [
|
||||
- "--dns-over-tls",
|
||||
- "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
- ]
|
||||
- return tasks.install_master(self.master, extra_args=args)
|
||||
-
|
||||
- def test_install_dnsovertls_client(self):
|
||||
- """
|
||||
- This tests installs IPA client with --dns-over-tls option.
|
||||
- """
|
||||
- self.clients[0].put_file_contents(
|
||||
- paths.RESOLV_CONF,
|
||||
- "nameserver %s" % self.master.ip
|
||||
- )
|
||||
- args = [
|
||||
- "--dns-over-tls"
|
||||
- ]
|
||||
- return tasks.install_client(self.master,
|
||||
- self.clients[0],
|
||||
- nameservers=None,
|
||||
- extra_args=args)
|
||||
-
|
||||
- def test_install_dnsovertls_replica(self):
|
||||
- """
|
||||
- This tests installs IPA replica with --dns-over-tls option.
|
||||
- """
|
||||
- args = [
|
||||
- "--dns-over-tls",
|
||||
- "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
- ]
|
||||
- return tasks.install_replica(self.master, self.replicas[0],
|
||||
- setup_dns=True, extra_args=args)
|
||||
-
|
||||
- def test_queries_encrypted(self):
|
||||
- """
|
||||
- This test performs queries from each of the hosts
|
||||
- and ensures they were routed to 1.1.1.1#853 (eDNS).
|
||||
- """
|
||||
- unbound_log_cfg = textwrap.dedent("""
|
||||
- server:
|
||||
- verbosity: 3
|
||||
- log-queries: yes
|
||||
- """)
|
||||
- # Test servers first (querying to local Unbound)
|
||||
- for server in [self.master, self.replicas[0]]:
|
||||
- server.put_file_contents("/etc/unbound/conf.d/log.conf",
|
||||
- unbound_log_cfg)
|
||||
- server.run_command(["systemctl", "restart", "unbound"])
|
||||
- server.run_command(["journalctl", "--flush", "--rotate",
|
||||
- "--vacuum-time=1s"])
|
||||
- server.run_command(["dig", "freeipa.org"])
|
||||
- server.run_command(["journalctl", "-u", "unbound",
|
||||
- "--grep=1.1.1.1#853"])
|
||||
- server.run_command(["journalctl", "--flush", "--rotate",
|
||||
- "--vacuum-time=1s"])
|
||||
- # Now, test the client (redirects query to master)
|
||||
- self.clients[0].run_command(["dig", "redhat.com"])
|
||||
- self.master.run_command(["journalctl", "-u", "unbound",
|
||||
- "--grep=1.1.1.1#853"])
|
||||
-
|
||||
- def test_uninstall_all(self):
|
||||
- """
|
||||
- This test ensures that all hosts can be uninstalled correctly.
|
||||
- """
|
||||
- tasks.uninstall_client(self.clients[0])
|
||||
- tasks.uninstall_replica(self.master, self.replicas[0])
|
||||
- tasks.uninstall_master(self.master)
|
||||
-
|
||||
- def test_install_dnsovertls_master_external_ca(self):
|
||||
- """
|
||||
- This test ensures that IPA server can be installed
|
||||
- with DoT using an external CA.
|
||||
- """
|
||||
- self.master.run_command(["openssl", "req", "-newkey", "rsa:2048",
|
||||
- "-nodes", "-keyout",
|
||||
- "/etc/pki/tls/certs/privkey.pem", "-x509",
|
||||
- "-days", "36500", "-out",
|
||||
- "/etc/pki/tls/certs/certificate.pem", "-subj",
|
||||
- ("/C=ES/ST=Andalucia/L=Sevilla/O=CompanyName/"
|
||||
- "OU=IT/CN={}/"
|
||||
- "emailAddress=email@example.com")
|
||||
- .format(self.master.hostname)])
|
||||
- self.master.run_command(["chown", "named:named",
|
||||
- "/etc/pki/tls/certs/privkey.pem",
|
||||
- "/etc/pki/tls/certs/certificate.pem"])
|
||||
- args = [
|
||||
- "--dns-over-tls",
|
||||
- "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
- "--dns-over-tls-cert", "/etc/pki/tls/certs/certificate.pem",
|
||||
- "--dns-over-tls-key", "/etc/pki/tls/certs/privkey.pem"
|
||||
- ]
|
||||
- return tasks.install_master(self.master, extra_args=args)
|
||||
-
|
||||
- def test_enrollments_external_ca(self):
|
||||
- """
|
||||
- Test that replicas and clients can be deployed when the master
|
||||
- uses an external CA.
|
||||
- """
|
||||
- tasks.copy_files(self.master, self.clients[0],
|
||||
- ["/etc/pki/tls/certs/certificate.pem"])
|
||||
- self.clients[0].run_command(["mv",
|
||||
- "/etc/pki/tls/certs/certificate.pem",
|
||||
- "/etc/pki/ca-trust/source/anchors/"])
|
||||
- self.clients[0].run_command(["update-ca-trust", "extract"])
|
||||
- self.test_install_dnsovertls_client()
|
||||
- self.test_install_dnsovertls_replica()
|
||||
- self.test_queries_encrypted()
|
||||
-
|
||||
def test_install_dnsovertls_with_invalid_ipaddress_master(self):
|
||||
"""
|
||||
This test installs an IPA server using the --dns-over-tls
|
||||
@@ -251,6 +189,157 @@ class TestDNSOverTLS(IntegrationTest):
|
||||
assert '''--dns-over-tls Configure DNS over TLS''' in cmdout.stdout_text # noqa: E501
|
||||
|
||||
|
||||
+@pytest.mark.skipif(
|
||||
+ osinfo.id == 'fedora' and osinfo.version_number < (42,),
|
||||
+ reason='Encrypted DNS not supported in Fedora < 42')
|
||||
+class TestDNSOverTLS_RelaxedPolicy(IntegrationTest):
|
||||
+ """Tests for DNS over TLS feature."""
|
||||
+
|
||||
+ topology = 'line'
|
||||
+ num_replicas = 1
|
||||
+ num_clients = 1
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ Firewall(cls.master).enable_service("dns-over-tls")
|
||||
+ Firewall(cls.replicas[0]).enable_service("dns-over-tls")
|
||||
+ tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
|
||||
+ tasks.install_packages(cls.replicas[0], ['*ipa-server-encrypted-dns'])
|
||||
+ tasks.install_packages(cls.clients[0], ['*ipa-client-encrypted-dns'])
|
||||
+
|
||||
+ def test_dot_relaxed_dns_policy_with_IPA_CA(self):
|
||||
+ """
|
||||
+ This test installs IPA server, replica, and client with
|
||||
+ --no-dnssec-validation option, relaxed DNS policy, and
|
||||
+ with IPA CA, ensuring all queries are encrypted.
|
||||
+ """
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
+ "--no-dnssec-validation",
|
||||
+ "--dns-policy", "relaxed"
|
||||
+ ]
|
||||
+ tasks.install_master(self.master, extra_args=args)
|
||||
+
|
||||
+ self.clients[0].put_file_contents(
|
||||
+ paths.RESOLV_CONF,
|
||||
+ "nameserver %s" % self.master.ip
|
||||
+ )
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--no-dnssec-validation"
|
||||
+ ]
|
||||
+ tasks.install_client(
|
||||
+ self.master,
|
||||
+ self.clients[0],
|
||||
+ nameservers=None,
|
||||
+ extra_args=args
|
||||
+ )
|
||||
+
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
+ "--no-dnssec-validation",
|
||||
+ "--dns-policy", "relaxed"
|
||||
+ ]
|
||||
+ tasks.install_replica(
|
||||
+ self.master,
|
||||
+ self.replicas[0],
|
||||
+ setup_dns=True,
|
||||
+ extra_args=args
|
||||
+ )
|
||||
+ verify_queries_encrypted(
|
||||
+ self.master,
|
||||
+ [self.replicas[0]],
|
||||
+ [self.clients[0]]
|
||||
+ )
|
||||
+
|
||||
+ def test_uninstall_all(self):
|
||||
+ """
|
||||
+ This test ensures that all hosts can be uninstalled correctly.
|
||||
+ """
|
||||
+ tasks.uninstall_client(self.clients[0])
|
||||
+ tasks.uninstall_replica(self.master, self.replicas[0])
|
||||
+ tasks.uninstall_master(self.master)
|
||||
+
|
||||
+ def test_dot_relaxed_dns_policy_with_external_ca(self):
|
||||
+ """
|
||||
+ This test installs IPA server, replica, and client with
|
||||
+ --no-dnssec-validation option, relaxed DNS policy, and
|
||||
+ with external CA, ensuring all queries are encrypted.
|
||||
+ """
|
||||
+ # Install Master with external CA
|
||||
+ # Create external CA cert + key.
|
||||
+ external_ca = ExternalCA(days=36500)
|
||||
+ cert_pem = external_ca.create_ca()
|
||||
+ key_pem = external_ca.ca_key.private_bytes(
|
||||
+ encoding=serialization.Encoding.PEM,
|
||||
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
+ encryption_algorithm=serialization.NoEncryption(),
|
||||
+ )
|
||||
+ cert_dest = "/etc/pki/tls/certs/certificate.pem"
|
||||
+ key_dest = "/etc/pki/tls/certs/privkey.pem"
|
||||
+
|
||||
+ self.master.put_file_contents(cert_dest, cert_pem)
|
||||
+ self.master.put_file_contents(key_dest, key_pem)
|
||||
+
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
+ "--dns-over-tls-cert", cert_dest,
|
||||
+ "--dns-over-tls-key", key_dest,
|
||||
+ "--no-dnssec-validation",
|
||||
+ "--dns-policy", "relaxed"
|
||||
+ ]
|
||||
+ tasks.install_master(self.master, extra_args=args)
|
||||
+
|
||||
+ # Install Client with external CA
|
||||
+ self.clients[0].put_file_contents(
|
||||
+ paths.RESOLV_CONF,
|
||||
+ "nameserver %s" % self.master.ip
|
||||
+ )
|
||||
+ dest_file = "/etc/pki/ca-trust/source/anchors/certificate.pem"
|
||||
+ data = self.master.get_file_contents(cert_dest)
|
||||
+ self.clients[0].transport.put_file_contents(dest_file, data)
|
||||
+ self.clients[0].run_command(["update-ca-trust", "extract"])
|
||||
+
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--no-dnssec-validation"
|
||||
+ ]
|
||||
+ tasks.install_client(
|
||||
+ self.master,
|
||||
+ self.clients[0],
|
||||
+ nameservers=None,
|
||||
+ extra_args=args
|
||||
+ )
|
||||
+
|
||||
+ # Install Replica with external CA
|
||||
+ dest_file = "/etc/pki/ca-trust/source/anchors/certificate.pem"
|
||||
+ data = self.master.get_file_contents(cert_dest)
|
||||
+ self.clients[0].transport.put_file_contents(dest_file, data)
|
||||
+
|
||||
+ self.replicas[0].run_command(["update-ca-trust", "extract"])
|
||||
+ args = [
|
||||
+ "--dns-over-tls",
|
||||
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
|
||||
+ "--no-dnssec-validation",
|
||||
+ "--dns-policy", "relaxed"
|
||||
+ ]
|
||||
+ tasks.install_replica(
|
||||
+ self.master,
|
||||
+ self.replicas[0],
|
||||
+ setup_dns=True,
|
||||
+ extra_args=args
|
||||
+ )
|
||||
+
|
||||
+ verify_queries_encrypted(
|
||||
+ self.master,
|
||||
+ [self.replicas[0]],
|
||||
+ [self.clients[0]]
|
||||
+ )
|
||||
+
|
||||
+
|
||||
@pytest.mark.skipif(
|
||||
osinfo.id == 'fedora' and osinfo.version_number == (41,),
|
||||
reason='Encrypted DNS not supported in fedora 41')
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
From 883f69db280071cf8003eff977f6f061651c7a7d Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Tue, 11 Mar 2025 18:32:43 +0100
|
||||
Subject: [PATCH] ipatest: make test_cert more robust to replication delays
|
||||
|
||||
The test TestCAShowErrorHandling::test_ca_show_error_handling is
|
||||
adding a subca on the replica, then checks the entry is present on the
|
||||
master.
|
||||
If the replication is a bit slow, the call on the master may fail to
|
||||
return the newly created subca.
|
||||
The test should wait for replication to complete before calling
|
||||
ipa ca-find.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9762
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_cert.py | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
|
||||
index 91598b655a8cd6ff92c1a0cf2166c6548a7af758..c642caaf03dfb980e956cf8105911440a5ec8539 100644
|
||||
--- a/ipatests/test_integration/test_cert.py
|
||||
+++ b/ipatests/test_integration/test_cert.py
|
||||
@@ -548,6 +548,8 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA 1'
|
||||
])
|
||||
assert 'Created CA "{}"'.format(lwca) in result.stdout_text
|
||||
+ # wait for replication to propagate the change
|
||||
+ tasks.wait_for_replication(self.replicas[0].ldap_connect())
|
||||
result = self.master.run_command(['ipa', 'ca-find'])
|
||||
assert 'Name: {}'.format(lwca) in result.stdout_text
|
||||
result = self.master.run_command(
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
From 9c416a61b72b288212e03724cff9bd169390cbfe Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Wed, 8 Oct 2025 14:26:12 +0200
|
||||
Subject: [PATCH] test_cert: adapt the expect error message to PKI 11.7.0-5
|
||||
|
||||
The error message returned by ipa ca-show has changed with PKI 11.7.
|
||||
Adapt the test to succeed with old and new versions.
|
||||
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_cert.py | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
|
||||
index 3e1e8fd1fa14278f3868e6e206a979e11b70a848..21568c2421c21855df06bcf5fbb4d52b3651a523 100644
|
||||
--- a/ipatests/test_integration/test_cert.py
|
||||
+++ b/ipatests/test_integration/test_cert.py
|
||||
@@ -575,6 +575,9 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA 1'
|
||||
])
|
||||
assert 'Created CA "{}"'.format(lwca) in result.stdout_text
|
||||
+ match = re.search(r'Authority ID: (?P<id>.*)', result.stdout_text)
|
||||
+ id = match.group('id')
|
||||
+
|
||||
# wait for replication to propagate the change
|
||||
tasks.wait_for_replication(self.replicas[0].ldap_connect())
|
||||
result = self.master.run_command(['ipa', 'ca-find'])
|
||||
@@ -585,13 +588,16 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
)
|
||||
error_msg = 'ipa: ERROR: The certificate for ' \
|
||||
'{} is not available on this server.'.format(lwca)
|
||||
+ new_error_msg = 'ipa: ERROR: Certificate for CA ' \
|
||||
+ '"{}" not available'.format(id)
|
||||
pki_version = tasks.get_pki_version(self.master)
|
||||
# The regression was introduced in 11.5 and fixed in 11.7
|
||||
bad_version = (tasks.parse_version('11.5.0') <= pki_version
|
||||
< tasks.parse_version('11.7.0'))
|
||||
with xfail_context(bad_version,
|
||||
reason="https://pagure.io/freeipa/issue/9606"):
|
||||
- assert error_msg in result.stderr_text
|
||||
+ assert (error_msg in result.stderr_text
|
||||
+ or new_error_msg in result.stderr_text)
|
||||
|
||||
def test_certmonger_empty_cert_not_segfault(self):
|
||||
"""Test empty cert request doesn't force certmonger to segfault
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
From 4a2e912d2386dfeb9765e32dd244b32b03cbf9a5 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Tue, 2 Sep 2025 10:05:31 +0200
|
||||
Subject: [PATCH] ipatests: fix test_certmonger_ipa_responder_jsonrpc
|
||||
|
||||
Test scenario:
|
||||
- install IPA server and client
|
||||
- store the start date
|
||||
- request a certificate on the client using ipa-getcert
|
||||
- check in the journal after start date that the request was done using the
|
||||
https://.../ipa/json URI
|
||||
|
||||
The test obtains the start date on the runner. As a consequence, if the runner
|
||||
is late compared to the client, it may miss the message in the journal.
|
||||
The date should rather be obtained on the client.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9848
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_cert.py | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
|
||||
index 84adf2ceafe013e6cfc973fb2cb650c40f36971d..ddc4e089a365b89a3dd26881845228ca60558bf7 100644
|
||||
--- a/ipatests/test_integration/test_cert.py
|
||||
+++ b/ipatests/test_integration/test_cert.py
|
||||
@@ -13,7 +13,6 @@ import pytest
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
-import time
|
||||
import textwrap
|
||||
|
||||
from ipaplatform.paths import paths
|
||||
@@ -70,9 +69,10 @@ class TestInstallMasterClient(IntegrationTest):
|
||||
def install(cls, mh):
|
||||
super().install(mh)
|
||||
|
||||
- # time to look into journal logs in
|
||||
+ # store the start time to look into journal logs in
|
||||
# test_certmonger_ipa_responder_jsonrpc
|
||||
- cls.since = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
+ result = cls.clients[0].run_command(['date', '+%Y-%m-%d %H:%M:%S'])
|
||||
+ cls.since = result.stdout_text.strip()
|
||||
|
||||
def test_cacert_file_appear_with_option_F(self):
|
||||
"""Test if getcert creates cacert file with -F option
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
From 9c416a61b72b288212e03724cff9bd169390cbfe Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Wed, 8 Oct 2025 14:26:12 +0200
|
||||
Subject: [PATCH] test_cert: adapt the expect error message to PKI 11.7.0-5
|
||||
|
||||
The error message returned by ipa ca-show has changed with PKI 11.7.
|
||||
Adapt the test to succeed with old and new versions.
|
||||
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_cert.py | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
|
||||
index 3e1e8fd1fa14278f3868e6e206a979e11b70a848..21568c2421c21855df06bcf5fbb4d52b3651a523 100644
|
||||
--- a/ipatests/test_integration/test_cert.py
|
||||
+++ b/ipatests/test_integration/test_cert.py
|
||||
@@ -575,6 +575,9 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA 1'
|
||||
])
|
||||
assert 'Created CA "{}"'.format(lwca) in result.stdout_text
|
||||
+ match = re.search(r'Authority ID: (?P<id>.*)', result.stdout_text)
|
||||
+ id = match.group('id')
|
||||
+
|
||||
# wait for replication to propagate the change
|
||||
tasks.wait_for_replication(self.replicas[0].ldap_connect())
|
||||
result = self.master.run_command(['ipa', 'ca-find'])
|
||||
@@ -585,13 +588,16 @@ class TestCAShowErrorHandling(IntegrationTest):
|
||||
)
|
||||
error_msg = 'ipa: ERROR: The certificate for ' \
|
||||
'{} is not available on this server.'.format(lwca)
|
||||
+ new_error_msg = 'ipa: ERROR: Certificate for CA ' \
|
||||
+ '"{}" not available'.format(id)
|
||||
pki_version = tasks.get_pki_version(self.master)
|
||||
# The regression was introduced in 11.5 and fixed in 11.7
|
||||
bad_version = (tasks.parse_version('11.5.0') <= pki_version
|
||||
< tasks.parse_version('11.7.0'))
|
||||
with xfail_context(bad_version,
|
||||
reason="https://pagure.io/freeipa/issue/9606"):
|
||||
- assert error_msg in result.stderr_text
|
||||
+ assert (error_msg in result.stderr_text
|
||||
+ or new_error_msg in result.stderr_text)
|
||||
|
||||
def test_certmonger_empty_cert_not_segfault(self):
|
||||
"""Test empty cert request doesn't force certmonger to segfault
|
||||
--
|
||||
2.51.1
|
||||
|
||||
286
0132-Include-the-HSM-token-name-when-creating-LWCAs.patch
Normal file
286
0132-Include-the-HSM-token-name-when-creating-LWCAs.patch
Normal file
@ -0,0 +1,286 @@
|
||||
From 12dd94e61a245ac8645789423aa9fc47b3cc14d0 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Fri, 10 Oct 2025 19:41:15 +0000
|
||||
Subject: [PATCH] Include the HSM token name when creating LWCAs
|
||||
|
||||
In order to generate the private key for a a LWCA (subca)
|
||||
on an HSM the name of the subca needs to be the HSM token
|
||||
name : name_of_subca, e.g. ipa_token:test.
|
||||
|
||||
This works fine now without any code changes but it requires
|
||||
that admins always remember to include the prefix which is
|
||||
unlikely (everyone makes mistakes). So do it for them if
|
||||
an HSM is present and a token is not provided. We only support
|
||||
one token at a time in IPA so for now this is sufficient.
|
||||
|
||||
One can also mix-and-match including the token and not.
|
||||
For example you can run:
|
||||
|
||||
$ ipa ca-add test --subject cn=test
|
||||
$ ipa ca-show ipa_token:test
|
||||
|
||||
It shouldn't be an issue if a lwca name contains a colon.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9865
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
---
|
||||
ipaserver/plugins/ca.py | 42 +++++++-
|
||||
ipatests/test_integration/test_hsm.py | 140 ++++++++++++++++++++++++++
|
||||
2 files changed, 181 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py
|
||||
index d35275cc6f625c4834f43a537b29da32a909326b..1eeab4048091bd990c4b27e0f7976940da734acc 100644
|
||||
--- a/ipaserver/plugins/ca.py
|
||||
+++ b/ipaserver/plugins/ca.py
|
||||
@@ -10,7 +10,7 @@ from ipalib import api, errors, messages, output
|
||||
from ipalib import Bytes, DNParam, Flag, Str, Int
|
||||
from ipalib.constants import IPA_CA_CN
|
||||
from ipalib.plugable import Registry
|
||||
-from ipapython.dn import ATTR_NAME_BY_OID
|
||||
+from ipapython.dn import ATTR_NAME_BY_OID, DN
|
||||
from ipaserver.plugins.baseldap import (
|
||||
LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete,
|
||||
LDAPUpdate, LDAPRetrieve, LDAPQuery, pkey_to_value)
|
||||
@@ -174,6 +174,26 @@ class ca(LDAPObject):
|
||||
},
|
||||
}
|
||||
|
||||
+ # LWCA are supported on HSMs but the key will only be generated
|
||||
+ # there if the LWCA name is prefixed by the token name. So do
|
||||
+ # that automatically for users to hide that complexity.
|
||||
+
|
||||
+ def add_token_key(self, *keys):
|
||||
+ if len(keys) == 0 or keys[0] == IPA_CA_CN:
|
||||
+ return keys
|
||||
+ config = api.Command['config_show']()['result']
|
||||
+ if 'hsm_token_name' in config and not keys[-1].startswith(
|
||||
+ config['hsm_token_name']
|
||||
+ ):
|
||||
+ keys = (f"{config['hsm_token_name']}:{keys[-1]}",)
|
||||
+ return keys
|
||||
+
|
||||
+ def remove_token_key(self, *keys):
|
||||
+ config = api.Command['config_show']()['result']
|
||||
+ if 'hsm_token_name' in config and ':' in keys[-1]:
|
||||
+ keys = (keys[-1].split(':', 1)[1],)
|
||||
+ return keys
|
||||
+
|
||||
|
||||
def set_certificate_attrs(entry, options, want_cert=True):
|
||||
"""
|
||||
@@ -233,6 +253,10 @@ class ca_find(LDAPSearch):
|
||||
|
||||
def execute(self, *keys, **options):
|
||||
ca_enabled_check(self.api)
|
||||
+ keys = self.obj.add_token_key(*keys)
|
||||
+ if 'cn' in options:
|
||||
+ new = self.obj.add_token_key(options['cn'])
|
||||
+ options['cn'] = new[0]
|
||||
result = super(ca_find, self).execute(*keys, **options)
|
||||
if not options.get('pkey_only', False):
|
||||
for entry in result['result']:
|
||||
@@ -259,6 +283,7 @@ class ca_show(LDAPRetrieve):
|
||||
|
||||
def execute(self, *keys, **options):
|
||||
ca_enabled_check(self.api)
|
||||
+ keys = self.obj.add_token_key(*keys)
|
||||
result = super(ca_show, self).execute(*keys, **options)
|
||||
msg = set_certificate_attrs(result['result'], options)
|
||||
if msg:
|
||||
@@ -301,6 +326,7 @@ class ca_add(LDAPCreate):
|
||||
|
||||
# check for name collision before creating CA in Dogtag
|
||||
try:
|
||||
+ keys = self.obj.remove_token_key(*keys)
|
||||
api.Object.ca.get_dn_if_exists(keys[-1])
|
||||
self.obj.handle_duplicate_entry(*keys)
|
||||
except errors.NotFound:
|
||||
@@ -325,6 +351,10 @@ class ca_add(LDAPCreate):
|
||||
entry['ipacasubjectdn'] = [resp['dn']]
|
||||
return dn
|
||||
|
||||
+ def execute(self, *keys, **options):
|
||||
+ keys = self.obj.add_token_key(*keys)
|
||||
+ return super(ca_add, self).execute(*keys, **options)
|
||||
+
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
msg = set_certificate_attrs(entry_attrs, options)
|
||||
if msg:
|
||||
@@ -341,6 +371,12 @@ class ca_del(LDAPDelete):
|
||||
def pre_callback(self, ldap, dn, *keys, **options):
|
||||
ca_enabled_check(self.api)
|
||||
|
||||
+ # Handle an HSM-stored LWCA that is referenced without the
|
||||
+ # token name. On a non-HSM install real_* will be unchanged
|
||||
+ # from keys and dn.
|
||||
+ real_keys = self.obj.add_token_key(*keys)
|
||||
+ dn = DN(('cn', real_keys[0]), self.obj.container_dn,
|
||||
+ api.env.basedn)
|
||||
# ensure operator has permission to delete CA
|
||||
# before contacting Dogtag
|
||||
if not ldap.can_delete(dn):
|
||||
@@ -386,6 +422,10 @@ class ca_mod(LDAPUpdate):
|
||||
|
||||
return dn
|
||||
|
||||
+ def execute(self, *keys, **options):
|
||||
+ keys = self.obj.add_token_key(*keys)
|
||||
+ return super(ca_mod, self).execute(*keys, **options)
|
||||
+
|
||||
|
||||
class CAQuery(LDAPQuery):
|
||||
has_output = output.standard_value
|
||||
diff --git a/ipatests/test_integration/test_hsm.py b/ipatests/test_integration/test_hsm.py
|
||||
index 42895fcd60a7c02d3b6103c2f6751a367da30b2f..159ead2fcc79982c7289f316c57aaeb2b812004e 100644
|
||||
--- a/ipatests/test_integration/test_hsm.py
|
||||
+++ b/ipatests/test_integration/test_hsm.py
|
||||
@@ -1316,3 +1316,143 @@ class TestHSMVault(BaseHSMTest):
|
||||
vault_name,
|
||||
"--password", vault_password,
|
||||
])
|
||||
+
|
||||
+
|
||||
+class TestHSMLWCA(BaseHSMTest):
|
||||
+ """Test that managing a LWCA on an HSM-installed system installs
|
||||
+ the keys onto the HSM and not the local NSS database.
|
||||
+
|
||||
+ Also verify that the ca-* operations can handle hiding the
|
||||
+ complexity of the token name prefix without specifying it
|
||||
+ (but also allowing it).
|
||||
+ """
|
||||
+
|
||||
+ num_replicas = 0
|
||||
+
|
||||
+ def remove_lwca(self, lwca, othername=None):
|
||||
+ """Save a lot of duplicate code disabling and removing a lwca"""
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', 'ca-disable', lwca,
|
||||
+ ])
|
||||
+
|
||||
+ name = othername or lwca
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', 'ca-del', name,
|
||||
+ ])
|
||||
+
|
||||
+ def test_hsm_add_lwca(self):
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ """First test add/show/disable/delete without specifying
|
||||
+ the token. So hiding the complexity of including the
|
||||
+ token name except the stored Name (cn) value.
|
||||
+ """
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA',
|
||||
+ ])
|
||||
+ assert f'Name: {lwca_fullname}' in result.stdout_text
|
||||
+
|
||||
+ self.remove_lwca(lwca)
|
||||
+
|
||||
+ def test_hsm_token_add_lwca(self):
|
||||
+ """Now test add/show/disable/delete with specifying
|
||||
+ the token with the name. So not hiding the complexity of
|
||||
+ including the token name.
|
||||
+ """
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', lwca_fullname, '--subject', 'CN=LWCA',
|
||||
+ ])
|
||||
+ assert f'Name: {lwca_fullname}' in result.stdout_text
|
||||
+
|
||||
+ self.remove_lwca(lwca_fullname)
|
||||
+
|
||||
+ def test_duplicate_token_add_lwca(self):
|
||||
+ """Test that adding a duplicate name by adding with and
|
||||
+ without the token included"""
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', 'ca-add', lwca_fullname, '--subject', 'CN=LWCA',
|
||||
+ ])
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', lwca, '--subject', 'CN=LWCA',
|
||||
+ ], raiseonerr=False)
|
||||
+ assert 'Subject DN is already used' in result.stderr_text
|
||||
+
|
||||
+ # we can also mix and match the name in the cleanup
|
||||
+ self.remove_lwca(lwca, lwca_fullname)
|
||||
+
|
||||
+ def test_colon_in_lwca(self):
|
||||
+ """
|
||||
+ Trying to add a CA using an unknown or mis-typed token
|
||||
+ name will result in the real token + whatever the name.
|
||||
+ """
|
||||
+ lwca = "lwca"
|
||||
+ colonname = "undefined:{}".format(lwca)
|
||||
+ colonfullname = "{}:undefined:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', colonname, '--subject', 'CN=LWCA',
|
||||
+ ])
|
||||
+ assert 'Name: {}'.format(colonfullname) in result.stdout_text
|
||||
+
|
||||
+ self.remove_lwca(colonfullname, colonname)
|
||||
+
|
||||
+ def test_show_lwca(self):
|
||||
+ """Show a lwca using with and without the token name"""
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ for name in (lwca, lwca_fullname,):
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', name, '--subject', f'CN={name}',
|
||||
+ ])
|
||||
+ assert f'Name: {lwca_fullname}' in result.stdout_text
|
||||
+ self.master.run_command(['ipa', 'ca-show', name])
|
||||
+ self.remove_lwca(name)
|
||||
+
|
||||
+ def test_find_lwca(self):
|
||||
+ """Find a lwca using with and without the token name"""
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ for name in (lwca, lwca_fullname,):
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', name, '--subject', f'CN={name}',
|
||||
+ ])
|
||||
+ assert f'Name: {lwca_fullname}' in result.stdout_text
|
||||
+ self.master.run_command(['ipa', 'ca-find', '--name', name])
|
||||
+ self.remove_lwca(name)
|
||||
+
|
||||
+ def test_mod_lwca(self):
|
||||
+ """Modify a lwca using with and without the token name"""
|
||||
+ lwca = "lwca"
|
||||
+ lwca_fullname = "{}:{}".format(self.token_name, lwca)
|
||||
+
|
||||
+ check_version(self.master)
|
||||
+
|
||||
+ for name in (lwca, lwca_fullname,):
|
||||
+ result = self.master.run_command([
|
||||
+ 'ipa', 'ca-add', name, '--subject', f'CN={name}',
|
||||
+ ])
|
||||
+ assert f'Name: {lwca_fullname}' in result.stdout_text
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'ca-mod', name, '--desc', name])
|
||||
+ self.remove_lwca(name)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
403
0133-ipatests-Refactor-and-port-trust-functional-SUDO-tes.patch
Normal file
403
0133-ipatests-Refactor-and-port-trust-functional-SUDO-tes.patch
Normal file
@ -0,0 +1,403 @@
|
||||
From 9d144b89ce743805e6e2d19791436ca5ecee172f Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Wed, 8 Oct 2025 13:18:56 +0530
|
||||
Subject: [PATCH] ipatests: Refactor and port trust functional SUDO tests.
|
||||
|
||||
- Test scenarios :
|
||||
- AD users running commands as root via external groups
|
||||
- AD users switching to other AD user accounts
|
||||
- IPA users running commands as AD users
|
||||
- Sudo rule disable/enable functionality
|
||||
- Command allow/deny restrictions
|
||||
- Access denial for users not in sudo rules
|
||||
- Automated with Cursor+Claude
|
||||
|
||||
Related : https://pagure.io/freeipa/issue/9845
|
||||
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_integration/test_trust_functional.py | 356 +++++++++++++++++-
|
||||
1 file changed, 355 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_trust_functional.py b/ipatests/test_integration/test_trust_functional.py
|
||||
index b40bf9675ed5cddaf51624417356ddab28e870a5..a85f21e96463757b9a446df666d5361e65ba686c 100644
|
||||
--- a/ipatests/test_integration/test_trust_functional.py
|
||||
+++ b/ipatests/test_integration/test_trust_functional.py
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import time
|
||||
+
|
||||
from ipaplatform.paths import paths
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
@@ -290,7 +292,9 @@ class TestTrustFunctionalHbac(BaseTestTrust):
|
||||
with self.clients[0].spawn_expect(
|
||||
test_sudo.format(user=user)) as e:
|
||||
e.sendline('Secret123')
|
||||
- e.expect_exit(ignore_remaining_output=True, timeout=60)
|
||||
+ e.sendline('exit')
|
||||
+ e.expect_exit(
|
||||
+ ignore_remaining_output=True, raiseonerr=False)
|
||||
output = e.get_last_output()
|
||||
assert 'uid=0(root)' in output
|
||||
for user in [self.aduser2, self.subaduser2]:
|
||||
@@ -304,3 +308,353 @@ class TestTrustFunctionalHbac(BaseTestTrust):
|
||||
finally:
|
||||
self._cleanup_hrule_allow_all_and_wait(hrule)
|
||||
self.master.run_command(["ipa", "sudorule-del", srule])
|
||||
+
|
||||
+
|
||||
+class TestTrustFunctionalSudo(BaseTestTrust):
|
||||
+ topology = 'line'
|
||||
+ num_ad_treedomains = 0
|
||||
+
|
||||
+ def cache_reset(self):
|
||||
+ tasks.clear_sssd_cache(self.master)
|
||||
+ tasks.clear_sssd_cache(self.clients[0])
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.master)
|
||||
+ tasks.wait_for_sssd_domain_status_online(self.clients[0])
|
||||
+ # give time to SSSD to retrieve new records
|
||||
+ time.sleep(30)
|
||||
+
|
||||
+ def _cleanup_srule(self, srule):
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(["ipa", "sudorule-del", srule])
|
||||
+ self.cache_reset()
|
||||
+
|
||||
+ def _run_sudo_command(self, host, command, username, password='Secret123',
|
||||
+ expected_output=None, raiseonerr=True, timeout=60):
|
||||
+ """
|
||||
+ Run a sudo command using spawn_expect with proper error handling.
|
||||
+
|
||||
+ Args:
|
||||
+ host: Host to run the command on
|
||||
+ command: Command to execute
|
||||
+ username: Username for password prompt
|
||||
+ password: Password to send
|
||||
+ expected_output: Expected string in output (for assertion)
|
||||
+ raiseonerr: Whether to raise on error
|
||||
+ timeout: Timeout for expect_exit
|
||||
+
|
||||
+ Returns:
|
||||
+ Output from the command
|
||||
+ """
|
||||
+ with host.spawn_expect(command) as e:
|
||||
+ e.expect(r'(?i).*Password for {}.*:'.format(username))
|
||||
+ e.sendline(password)
|
||||
+ e.expect_exit(ignore_remaining_output=True,
|
||||
+ raiseonerr=raiseonerr, timeout=timeout)
|
||||
+ output = e.get_last_output()
|
||||
+
|
||||
+ if expected_output:
|
||||
+ assert expected_output in output, (
|
||||
+ f"Expected '{expected_output}' in output, got: {output}"
|
||||
+ )
|
||||
+
|
||||
+ return output
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_setup(self):
|
||||
+ tasks.configure_dns_for_trust(self.master, self.ad)
|
||||
+ tasks.establish_trust_with_ad(
|
||||
+ self.master, self.ad_domain,
|
||||
+ extra_args=['--range-type', 'ipa-ad-trust'])
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ for i in range(1, 3):
|
||||
+ external_group = f"sudogroup_external{i}"
|
||||
+ internal_group = f"sudogroup{i}"
|
||||
+ tasks.group_add(self.master,
|
||||
+ groupname=external_group,
|
||||
+ extra_args=["--external"]
|
||||
+ )
|
||||
+ tasks.group_add(self.master,
|
||||
+ groupname=internal_group
|
||||
+ )
|
||||
+ tasks.group_add_member(self.master,
|
||||
+ groupname=internal_group,
|
||||
+ extra_args=[f"--groups={external_group}"]
|
||||
+ )
|
||||
+
|
||||
+ group_members = {
|
||||
+ "sudogroup_external1": [self.aduser, self.subaduser],
|
||||
+ "sudogroup_external2": [self.aduser2, self.subaduser2],
|
||||
+ }
|
||||
+
|
||||
+ for group, members in group_members.items():
|
||||
+ for member in members:
|
||||
+ self.master.run_command([
|
||||
+ 'ipa', '-n', 'group-add-member', '--external',
|
||||
+ member, group,
|
||||
+ ])
|
||||
+ for user in ['sudouser1', 'sudouser2', 'ipauser1']:
|
||||
+ tasks.create_active_user(
|
||||
+ self.master, user, password='Secret123'
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0001(self):
|
||||
+ """
|
||||
+ Test sudo rule allow AD user in external group to run commands as root.
|
||||
+
|
||||
+ This test creates a sudo rule that allows members of sudogroup1 (which
|
||||
+ includes AD users via external group membership) to run all commands as
|
||||
+ root. It verifies that AD users who are members of the external group
|
||||
+ can successfully use sudo to gain root privileges.
|
||||
+ """
|
||||
+ srule = "sudorule_01"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=sudogroup1"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ test_sudo = f"su {user} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(
|
||||
+ self.clients[0], test_sudo, user,
|
||||
+ expected_output='uid=0(root)'
|
||||
+ )
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0002(self):
|
||||
+ """
|
||||
+ Test sudo rule allows AD users to run commands as other AD users.
|
||||
+
|
||||
+ This test creates a sudo rule that allows members of sudogroup1 to run
|
||||
+ commands as members of sudogroup2. It verifies that AD users can
|
||||
+ successfully use sudo to switch to other AD user accounts when they
|
||||
+ have the appropriate sudo permissions.
|
||||
+ """
|
||||
+ srule = "sudorule_02"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=sudogroup1"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-runasuser", srule, "--groups=sudogroup2"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ test_sudo = "su {0} -c 'sudo -S -u {1} id'".format(
|
||||
+ self.aduser, self.aduser2
|
||||
+ )
|
||||
+ self._run_sudo_command(self.clients[0], test_sudo, self.aduser,
|
||||
+ expected_output=self.aduser2
|
||||
+ )
|
||||
+
|
||||
+ test_sudo = "su {0} -c 'sudo -S -u {1} id'".format(
|
||||
+ self.subaduser, self.subaduser2
|
||||
+ )
|
||||
+ self._run_sudo_command(self.clients[0], test_sudo, self.subaduser,
|
||||
+ expected_output=self.subaduser2
|
||||
+ )
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0004(self):
|
||||
+ """
|
||||
+ Test sudo rule allows IPA users to run commands as AD users.
|
||||
+
|
||||
+ This test creates a sudo rule that allows IPA users to run commands
|
||||
+ as AD users who are members of external groups. It verifies that
|
||||
+ IPA users can successfully use sudo to switch to AD user accounts
|
||||
+ when they have the appropriate sudo permissions.
|
||||
+ """
|
||||
+ srule = "sudorule_04"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--users=ipauser1"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-runasuser", srule, "--groups=sudogroup1"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-show", srule, "--all"]
|
||||
+ )
|
||||
+ self.master.run_command(['ipa', 'group-show', 'sudogroup1'])
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'group-show', 'sudogroup_external1']
|
||||
+ )
|
||||
+ for user in [self.aduser, self.subaduser]:
|
||||
+ tasks.clear_sssd_cache(self.master)
|
||||
+ test_sudo = f"su ipauser1 -c 'sudo -S -u {user} id'"
|
||||
+ self._run_sudo_command(self.master, test_sudo, 'ipauser1',
|
||||
+ expected_output=user)
|
||||
+
|
||||
+ for user in [self.aduser2, self.subaduser2]:
|
||||
+ tasks.clear_sssd_cache(self.master)
|
||||
+ test_sudo = f"su ipauser1 -c 'sudo -S -u {user} id'"
|
||||
+ self._run_sudo_command(self.master, test_sudo, 'ipauser1',
|
||||
+ expected_output="not allowed to",
|
||||
+ raiseonerr=False)
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0005(self):
|
||||
+ """
|
||||
+ Test sudo rule disable/enable functionality for AD users.
|
||||
+
|
||||
+ Test creates a sudo rule that allows AD users to run commands as root,
|
||||
+ then tests the disable/enable functionality. It verifies that:
|
||||
+ 1. AD users can sudo as root when the rule is enabled
|
||||
+ 2. AD users are denied sudo access when the rule is disabled
|
||||
+ 3. AD users can sudo as root again when the rule is re-enabled
|
||||
+ """
|
||||
+ srule = "sudorule_05"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=sudogroup1"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ for aduser in [self.aduser, self.subaduser]:
|
||||
+ # First check that user can sudo as root
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output='uid=0(root)')
|
||||
+
|
||||
+ # disable sudorule
|
||||
+ self.master.run_command(["ipa", "sudorule-disable", srule])
|
||||
+ self.cache_reset()
|
||||
+
|
||||
+ # now make sure user cannot sudo as root
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output="is not allowed to",
|
||||
+ raiseonerr=False)
|
||||
+
|
||||
+ # now reenable rule
|
||||
+ self.master.run_command(["ipa", "sudorule-enable", srule])
|
||||
+ self.cache_reset()
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output='uid=0(root)')
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0007(self):
|
||||
+ """
|
||||
+ Test sudo rule with allow/deny command restrictions for AD users.
|
||||
+
|
||||
+ Test creates a sudo rule that allows AD users to run commands as root,
|
||||
+ but with specific command restrictions. It verifies that:
|
||||
+ 1. AD users are denied access to commands in the deny list
|
||||
+ 2. AD users are allowed access to commands in the allow list
|
||||
+
|
||||
+ The test uses /usr/bin/id as a denied command and /usr/bin/whoami as an
|
||||
+ allowed command to demonstrate the allow/deny functionality.
|
||||
+ """
|
||||
+ srule = "sudorule_07"
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all"]
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=sudogroup1"]
|
||||
+ )
|
||||
+ self.master.run_command(['ipa', 'sudocmd-add', '/usr/bin/id'])
|
||||
+ self.master.run_command(['ipa', 'sudocmd-add', '/usr/bin/whoami'])
|
||||
+ self.master.run_command(['ipa', 'sudorule-add-deny-command', srule,
|
||||
+ '--sudocmds', '/usr/bin/id']
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'sudorule-add-allow-command', srule,
|
||||
+ '--sudocmds', '/usr/bin/whoami']
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ for aduser in [self.aduser, self.subaduser]:
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output="is not allowed to",
|
||||
+ raiseonerr=False)
|
||||
+ for aduser in [self.aduser, self.subaduser]:
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S whoami'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output='root')
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+ self.master.run_command(['ipa', 'sudocmd-del', '/usr/bin/id'])
|
||||
+ self.master.run_command(['ipa', 'sudocmd-del', '/usr/bin/whoami'])
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0009(self):
|
||||
+ """
|
||||
+ Test sudo rule denies AD users access when they are not in the rule.
|
||||
+
|
||||
+ This test creates a sudo rule that only allows members of sudogroup2
|
||||
+ to run commands as other members of sudogroup2. It verifies that
|
||||
+ AD users who are not members of the allowed group are denied access
|
||||
+ when attempting to use sudo to switch to other user accounts.
|
||||
+ """
|
||||
+ srule = "sudorule_09"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--groups=sudogroup2"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-runasuser", srule, "--groups=sudogroup2"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+ test_sudo = "su {0} -c 'sudo -S -u {1} id'".format(
|
||||
+ self.aduser, self.aduser2
|
||||
+ )
|
||||
+ self._run_sudo_command(self.clients[0], test_sudo, self.aduser,
|
||||
+ expected_output='not allowed to run sudo',
|
||||
+ raiseonerr=False)
|
||||
+
|
||||
+ test_sudo = "su {0} -c 'sudo -S -u {1} id'".format(
|
||||
+ self.subaduser, self.subaduser2
|
||||
+ )
|
||||
+ self._run_sudo_command(self.clients[0], test_sudo, self.subaduser,
|
||||
+ expected_output='not allowed to run sudo',
|
||||
+ raiseonerr=False)
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
+
|
||||
+ def test_ipa_trust_func_sudo_0010(self):
|
||||
+ """
|
||||
+ Test sudo rule denies IPA users access to AD users not in the rule.
|
||||
+
|
||||
+ This test creates a sudo rule that allows IPA users to run commands as
|
||||
+ members of sudogroup2 (which includes aduser2/subaduser2), but not as
|
||||
+ members of sudogroup1 (which includes aduser1/subaduser1). It verifies
|
||||
+ that IPA users are denied access when attempting to use sudo to switch
|
||||
+ to AD user accounts that are not in the allowed runasuser group.
|
||||
+ """
|
||||
+ srule = "sudorule_10"
|
||||
+ cmd = ["ipa", "sudorule-add", srule, "--hostcat=all", "--cmdcat=all"]
|
||||
+ try:
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(cmd)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-user", srule, "--users=ipauser1"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sudorule-add-runasuser", srule, "--groups=sudogroup2"]
|
||||
+ )
|
||||
+ self.cache_reset()
|
||||
+
|
||||
+ for aduser in [self.aduser, self.subaduser]:
|
||||
+ sudo_cmd = f"su - {aduser} -c 'sudo -S id'"
|
||||
+ self._run_sudo_command(self.clients[0], sudo_cmd, aduser,
|
||||
+ expected_output="is not allowed to",
|
||||
+ raiseonerr=False)
|
||||
+ finally:
|
||||
+ self._cleanup_srule(srule)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
158
0134-ipa-pwd-extop-add-SysAcctManagersDNs-support.patch
Normal file
158
0134-ipa-pwd-extop-add-SysAcctManagersDNs-support.patch
Normal file
@ -0,0 +1,158 @@
|
||||
From 5550efd2c4fe9e71544747ca23a99544b0f43274 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Tue, 16 Sep 2025 14:48:59 +0300
|
||||
Subject: [PATCH] ipa-pwd-extop: add SysAcctManagersDNs support
|
||||
|
||||
Add new attribute, SysAcctManagersDNs, to store list of DNs allowed to
|
||||
reset user passwords without forcing the users to change them
|
||||
afterwards.
|
||||
|
||||
This list will differ from the use of PassSyncManagersDNs by the fact
|
||||
that password policy checks will still apply to those password changes.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9842
|
||||
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: Thomas Woerner <twoerner@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
.../ipa-slapi-plugins/ipa-pwd-extop/common.c | 15 ++++++++++----
|
||||
.../ipa-pwd-extop/ipa_pwd_extop.c | 9 +++++----
|
||||
.../ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h | 3 ++-
|
||||
.../ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 20 +++++++++++--------
|
||||
4 files changed, 30 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
index c85795c1e1c4fa42bde80829861333168ea193e6..114d20417d053ad7e822bd474eedf794b2c316d6 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
@@ -225,7 +225,10 @@ static struct ipapwd_krbcfg *ipapwd_getConfig(void)
|
||||
goto free_and_error;
|
||||
}
|
||||
config->passsync_mgrs =
|
||||
- slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
|
||||
+ slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
|
||||
+ config->sysacct_mgrs =
|
||||
+ slapi_entry_attr_get_charray(config_entry, "SysAcctManagersDNs");
|
||||
+
|
||||
/* now add Directory Manager, it is always added by default */
|
||||
tmpstr = slapi_ch_strdup("cn=Directory Manager");
|
||||
slapi_ch_array_add(&config->passsync_mgrs, tmpstr);
|
||||
@@ -233,8 +236,6 @@ static struct ipapwd_krbcfg *ipapwd_getConfig(void)
|
||||
LOG_OOM();
|
||||
goto free_and_error;
|
||||
}
|
||||
- for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
|
||||
- config->num_passsync_mgrs = i;
|
||||
|
||||
slapi_entry_free(config_entry);
|
||||
|
||||
@@ -289,6 +290,7 @@ free_and_error:
|
||||
free(config->pref_encsalts);
|
||||
free(config->supp_encsalts);
|
||||
slapi_ch_array_free(config->passsync_mgrs);
|
||||
+ slapi_ch_array_free(config->sysacct_mgrs);
|
||||
free(config);
|
||||
}
|
||||
slapi_entry_free(config_entry);
|
||||
@@ -614,6 +616,12 @@ int ipapwd_CheckPolicy(struct ipapwd_data *data)
|
||||
|
||||
switch(data->changetype) {
|
||||
case IPA_CHANGETYPE_NORMAL:
|
||||
+ case IPA_CHANGETYPE_SYSACCT:
|
||||
+ /*
|
||||
+ * Treat a system account-initiated password change as the user's
|
||||
+ * initiated one as well, to force password quality checks on them.
|
||||
+ */
|
||||
+
|
||||
/* Find the entry with the password policy */
|
||||
ret = ipapwd_getPolicy(data->dn, data->target, &pol);
|
||||
if (ret) {
|
||||
@@ -1143,4 +1151,3 @@ int ipapwd_check_max_pwd_len(size_t len, char **errMesg) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
-
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
||||
index 43c31becae45c1c91c7c2adf498aedbd05af9a69..ca48a12a68ffeca8dcb3f0ed46d789973aab2192 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
||||
@@ -583,10 +583,11 @@ parse_req_done:
|
||||
(strcasecmp(ipa_changepw_principal_dn, bindDN) != 0)) {
|
||||
pwdata.changetype = IPA_CHANGETYPE_ADMIN;
|
||||
|
||||
- for (size_t i = 0; i < krbcfg->num_passsync_mgrs; i++) {
|
||||
- if (strcasecmp(krbcfg->passsync_mgrs[i], bindDN) == 0) {
|
||||
- pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
- break;
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->passsync_mgrs, bindDN) == 1) {
|
||||
+ pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
+ } else {
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->sysacct_mgrs, bindDN) == 1) {
|
||||
+ pwdata.changetype = IPA_CHANGETYPE_SYSACCT;
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
||||
index 97697000674d8fbbe3a924af63261482db173852..c2682a7ba06962147414636484cc425850398cc4 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
||||
@@ -75,6 +75,7 @@
|
||||
#define IPA_CHANGETYPE_NORMAL 0
|
||||
#define IPA_CHANGETYPE_ADMIN 1
|
||||
#define IPA_CHANGETYPE_DSMGR 2
|
||||
+#define IPA_CHANGETYPE_SYSACCT 3
|
||||
|
||||
struct ipapwd_data {
|
||||
Slapi_Entry *target;
|
||||
@@ -108,7 +109,7 @@ struct ipapwd_krbcfg {
|
||||
int num_pref_encsalts;
|
||||
krb5_key_salt_tuple *pref_encsalts;
|
||||
char **passsync_mgrs;
|
||||
- int num_passsync_mgrs;
|
||||
+ char **sysacct_mgrs;
|
||||
bool allow_nt_hash;
|
||||
bool enforce_ldap_otp;
|
||||
};
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
||||
index 42e880fd0a5c8b4708b145b340209eb218f60c4e..0fdb7840bbe3d800270f60c58c1438a2d8267ba2 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
||||
@@ -368,10 +368,12 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
|
||||
slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
|
||||
|
||||
/* if it is a passsync manager we also need to skip resets */
|
||||
- for (size_t i = 0; i < krbcfg->num_passsync_mgrs; i++) {
|
||||
- if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
|
||||
- pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
- break;
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->passsync_mgrs, binddn) == 1) {
|
||||
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
+ } else {
|
||||
+ /* if it is a system account allowed to skip resets, mark it so */
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->sysacct_mgrs, binddn) == 1) {
|
||||
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_SYSACCT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -861,10 +863,12 @@ static int ipapwd_pre_mod(Slapi_PBlock *pb)
|
||||
pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
|
||||
|
||||
/* if it is a passsync manager we also need to skip resets */
|
||||
- for (size_t i = 0; i < krbcfg->num_passsync_mgrs; i++) {
|
||||
- if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
|
||||
- pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
- break;
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->passsync_mgrs, binddn) == 1) {
|
||||
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
||||
+ } else {
|
||||
+ /* if it is a system account allowed to skip resets, mark it so */
|
||||
+ if (slapi_ch_array_utf8_inlist(krbcfg->sysacct_mgrs, binddn) == 1) {
|
||||
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_SYSACCT;
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.51.1
|
||||
|
||||
1890
0135-Add-system-accounts-sysaccounts.patch
Normal file
1890
0135-Add-system-accounts-sysaccounts.patch
Normal file
File diff suppressed because it is too large
Load Diff
462
0136-sysaccounts-add-integration-test.patch
Normal file
462
0136-sysaccounts-add-integration-test.patch
Normal file
@ -0,0 +1,462 @@
|
||||
From ce907c2d805632e7d1aeb46363e37efd81b6ad04 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Thu, 18 Sep 2025 19:31:17 +0300
|
||||
Subject: [PATCH] sysaccounts: add integration test
|
||||
|
||||
The integration test was created with the help of claude.ai by
|
||||
providing the following prompt:
|
||||
|
||||
>> given the design document in doc/designs/sysaccounts.md, write an
|
||||
>> integration test for sysaccounts module
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9842
|
||||
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: Thomas Woerner <twoerner@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_sysaccounts.py | 429 ++++++++++++++++++
|
||||
1 files changed, 429 insertions(+)
|
||||
create mode 100644 ipatests/test_integration/test_sysaccounts.py
|
||||
|
||||
diff --git a/ipatests/test_integration/test_sysaccounts.py b/ipatests/test_integration/test_sysaccounts.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e19b1ae18633b95b14ccd6f94f863f4224ebde64
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/test_sysaccounts.py
|
||||
@@ -0,0 +1,429 @@
|
||||
+# Copyright (C) 2025 Red Hat
|
||||
+# see file 'COPYING' for use and warranty information
|
||||
+"""Tests for FreeIPA system accounts functionality"""
|
||||
+
|
||||
+import time
|
||||
+from ipatests.test_integration.base import IntegrationTest
|
||||
+from ipatests.pytest_ipa.integration import tasks
|
||||
+from ipapython.ipautil import ipa_generate_password
|
||||
+
|
||||
+
|
||||
+def extract_password_from_result(result):
|
||||
+ # Extract password from creation output
|
||||
+ password_line = [
|
||||
+ line
|
||||
+ for line in result.stdout_text.split("\n")
|
||||
+ if "Random password:" in line
|
||||
+ ][0]
|
||||
+ return password_line.split("Random password:")[1].strip()
|
||||
+
|
||||
+
|
||||
+class TestSystemAccounts(IntegrationTest):
|
||||
+ """Integration tests for system accounts functionality"""
|
||||
+
|
||||
+ topology = "line"
|
||||
+ num_clients = 0
|
||||
+ num_replicas = 0
|
||||
+
|
||||
+ def test_system_account_lifecycle(self):
|
||||
+ """Test basic system account lifecycle operations"""
|
||||
+ sysaccount_name = "test-app"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ # Test creating a system account with random password
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+ assert (
|
||||
+ f'Added system account "{sysaccount_name}"' in result.stdout_text
|
||||
+ )
|
||||
+ assert "Random password:" in result.stdout_text
|
||||
+
|
||||
+ # Test finding system accounts
|
||||
+ result = self.master.run_command(["ipa", "sysaccount-find"])
|
||||
+ assert sysaccount_name in result.stdout_text
|
||||
+
|
||||
+ # Test showing system account details
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-show", sysaccount_name]
|
||||
+ )
|
||||
+ assert f"System account ID: {sysaccount_name}" in result.stdout_text
|
||||
+
|
||||
+ # Test disabling system account
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-disable", sysaccount_name]
|
||||
+ )
|
||||
+ assert (
|
||||
+ f'Disabled system account "{sysaccount_name}"'
|
||||
+ in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ # Test enabling system account
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-enable", sysaccount_name]
|
||||
+ )
|
||||
+ assert (
|
||||
+ f'Enabled system account "{sysaccount_name}"' in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ # Clean up
|
||||
+ self.master.run_command(["ipa", "sysaccount-del", sysaccount_name])
|
||||
+
|
||||
+ # Verify deletion
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-show", sysaccount_name], raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+
|
||||
+ def test_system_account_ldap_bind(self):
|
||||
+ """Test LDAP bind functionality with system accounts"""
|
||||
+ sysaccount_name = "ldap-test-app"
|
||||
+ basedn = str(self.master.domain.basedn)
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ # Create system account
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+
|
||||
+ # Extract password from creation output
|
||||
+ password = extract_password_from_result(result)
|
||||
+
|
||||
+ bind_dn = f"uid={sysaccount_name},cn=sysaccounts,cn=etc,{basedn}"
|
||||
+
|
||||
+ # Test LDAP bind with the system account
|
||||
+ ldap_uri = f"ldap://{self.master.hostname}"
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ldapsearch",
|
||||
+ "-D", bind_dn,
|
||||
+ "-w", password,
|
||||
+ "-H", ldap_uri,
|
||||
+ "-b", basedn,
|
||||
+ "-s", "base",
|
||||
+ "(objectclass=*)",
|
||||
+ "dn",
|
||||
+ ]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert basedn in result.stdout_text
|
||||
+
|
||||
+ # Clean up
|
||||
+ self.master.run_command(["ipa", "sysaccount-del", sysaccount_name])
|
||||
+
|
||||
+ def test_system_account_password_management_without_reset(self):
|
||||
+ """Test system account password management without triggering reset"""
|
||||
+ sysaccount_name = "password-mgmt-app"
|
||||
+ test_user = "sysaccount-test-user"
|
||||
+ dashed_domain = self.master.domain.realm.replace(".", "-")
|
||||
+ basedn = str(self.master.domain.basedn)
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ try:
|
||||
+ # Create test user
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "user-add", test_user,
|
||||
+ "--first", "Test",
|
||||
+ "--last", "User",
|
||||
+ "--random",
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ # Create system account
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+
|
||||
+ # Extract password from creation output
|
||||
+ sysaccount_password = extract_password_from_result(result)
|
||||
+
|
||||
+ # Create privilege for password changes
|
||||
+ privilege_name = f"{sysaccount_name}-password-privilege"
|
||||
+ self.master.run_command(["ipa", "privilege-add", privilege_name])
|
||||
+
|
||||
+ # Add permission to privilege
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "privilege-add-permission", privilege_name,
|
||||
+ "--permission", "System: Change User password",
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ # Create role
|
||||
+ role_name = f"{sysaccount_name}-role"
|
||||
+ self.master.run_command(["ipa", "role-add", role_name])
|
||||
+
|
||||
+ # Add privilege to role
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "role-add-privilege", role_name,
|
||||
+ "--privilege", privilege_name,
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ # Add system account to role
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "role-add-member", role_name,
|
||||
+ "--sysaccounts", sysaccount_name,
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ # Enable privileged password changes for the system account
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "sysaccount-policy", sysaccount_name,
|
||||
+ "--privileged=true",
|
||||
+ ]
|
||||
+ )
|
||||
+ assert (
|
||||
+ "Restart the Directory Server services" in result.stderr_text
|
||||
+ )
|
||||
+
|
||||
+ # Restart directory server to apply changes
|
||||
+ self.master.run_command(
|
||||
+ ["systemctl", "restart", f"dirsrv@{dashed_domain}"]
|
||||
+ )
|
||||
+
|
||||
+ # Wait for the service to restart
|
||||
+ time.sleep(15)
|
||||
+
|
||||
+ # Set user password to a known value first
|
||||
+ initial_password = ipa_generate_password()
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-mod", test_user, "--password"],
|
||||
+ stdin_text=f"{initial_password}\n{initial_password}\n",
|
||||
+ )
|
||||
+
|
||||
+ # Change user password using system account via ldappasswd
|
||||
+ new_password = ipa_generate_password()
|
||||
+ sysaccount_dn = (
|
||||
+ f"uid={sysaccount_name},cn=sysaccounts,cn=etc,{basedn}"
|
||||
+ )
|
||||
+ user_dn = (
|
||||
+ f"uid={test_user},cn=users,cn=accounts,{basedn}"
|
||||
+ )
|
||||
+
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ldappasswd",
|
||||
+ "-D", sysaccount_dn,
|
||||
+ "-w", sysaccount_password,
|
||||
+ "-a", initial_password,
|
||||
+ "-s", new_password,
|
||||
+ "-x",
|
||||
+ "-ZZ",
|
||||
+ "-H",
|
||||
+ f"ldap://{self.master.hostname}",
|
||||
+ user_dn,
|
||||
+ ]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Verify password was changed and no reset is required
|
||||
+ user_details = self.master.run_command(
|
||||
+ ["ipa", "user-show", test_user, "--all", "--raw"]
|
||||
+ )
|
||||
+
|
||||
+ # The key test:
|
||||
+ # verify that krbLastPwdChange != krbPasswordExpiration
|
||||
+ # If they are equal, it means password reset is required
|
||||
+ krb_last_pwd_change = None
|
||||
+ krb_pwd_expiration = None
|
||||
+
|
||||
+ for line in user_details.stdout_text.split("\n"):
|
||||
+ if "krbLastPwdChange:" in line:
|
||||
+ krb_last_pwd_change = line.split(":", 1)[1].strip()
|
||||
+ elif "krbPasswordExpiration:" in line:
|
||||
+ krb_pwd_expiration = line.split(":", 1)[1].strip()
|
||||
+
|
||||
+ # If system account privileged password change worked,
|
||||
+ # these values should be different
|
||||
+ if krb_last_pwd_change and krb_pwd_expiration:
|
||||
+ assert krb_last_pwd_change != krb_pwd_expiration, (
|
||||
+ "Password reset was not bypassed - "
|
||||
+ "krbLastPwdChange equals krbPasswordExpiration"
|
||||
+ )
|
||||
+
|
||||
+ # Test user can authenticate with new password
|
||||
+ self.master.run_command(["kdestroy", "-A"])
|
||||
+ result = self.master.run_command(
|
||||
+ ["kinit", test_user], stdin_text=f"{new_password}\n"
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ finally:
|
||||
+ # Clean up
|
||||
+ self.master.run_command(["kdestroy", "-A"])
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ # Remove system account policy
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "sysaccount-policy", sysaccount_name,
|
||||
+ "--privileged=false",
|
||||
+ ],
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+
|
||||
+ # Clean up entities in reverse order
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "role-del", role_name], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "privilege-del", privilege_name], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-del", sysaccount_name], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-del", test_user], raiseonerr=False
|
||||
+ )
|
||||
+
|
||||
+ # Restart directory server to apply changes
|
||||
+ self.master.run_command(
|
||||
+ ["systemctl", "restart", f"dirsrv@{dashed_domain}"]
|
||||
+ )
|
||||
+
|
||||
+ # Wait for the service to restart
|
||||
+ time.sleep(15)
|
||||
+
|
||||
+ def test_system_account_role_membership(self):
|
||||
+ """Test system account membership in roles"""
|
||||
+ sysaccount_name = "role-test-app"
|
||||
+ role_name = "test-sysaccount-role"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ try:
|
||||
+ # Create system account
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+
|
||||
+ # Create a test role
|
||||
+ self.master.run_command(["ipa", "role-add", role_name])
|
||||
+
|
||||
+ # Add system account to role
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "role-add-member", role_name,
|
||||
+ "--sysaccounts", sysaccount_name,
|
||||
+ ]
|
||||
+ )
|
||||
+ assert "Number of members added 1" in result.stdout_text
|
||||
+
|
||||
+ # Verify membership
|
||||
+ result = self.master.run_command(["ipa", "role-show", role_name])
|
||||
+ assert sysaccount_name in result.stdout_text
|
||||
+
|
||||
+ # Verify from system account perspective
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-show", sysaccount_name]
|
||||
+ )
|
||||
+ assert role_name in result.stdout_text
|
||||
+
|
||||
+ # Remove system account from role
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "role-remove-member", role_name,
|
||||
+ "--sysaccounts", sysaccount_name,
|
||||
+ ]
|
||||
+ )
|
||||
+ assert "Number of members removed 1" in result.stdout_text
|
||||
+
|
||||
+ finally:
|
||||
+ # Clean up
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "role-del", role_name], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-del", sysaccount_name], raiseonerr=False
|
||||
+ )
|
||||
+
|
||||
+ def test_required_system_accounts_protection(self):
|
||||
+ """Test that required system accounts cannot be deleted"""
|
||||
+ required_accounts = ["passsync", "sudo"]
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ for account in required_accounts:
|
||||
+ # Try to delete a required system account
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-del", account], raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ assert "is required by the IPA master" in result.stderr_text
|
||||
+
|
||||
+ def test_system_account_password_validation(self):
|
||||
+ """Test system account password validation"""
|
||||
+ sysaccount_name = "password-validation-test"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ # Test creation with explicit password
|
||||
+ test_password = ipa_generate_password()
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--password"],
|
||||
+ stdin_text=f"{test_password}\n{test_password}\n",
|
||||
+ )
|
||||
+ assert (
|
||||
+ f'Added system account "{sysaccount_name}"' in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ # Clean up
|
||||
+ self.master.run_command(["ipa", "sysaccount-del", sysaccount_name])
|
||||
+
|
||||
+ def test_system_account_implicit_password(self):
|
||||
+ """Test system account password validation"""
|
||||
+ sysaccount_name = "password-implicit-validation-test"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ # Test creation with implicitly asked password
|
||||
+ test_password = ipa_generate_password()
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name],
|
||||
+ stdin_text=f"{test_password}\n{test_password}\n",
|
||||
+ )
|
||||
+ assert (
|
||||
+ f'Added system account "{sysaccount_name}"' in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ # Clean up
|
||||
+ self.master.run_command(["ipa", "sysaccount-del", sysaccount_name])
|
||||
+
|
||||
+ def test_system_account_modify_password(self):
|
||||
+ """Test modifying system account password"""
|
||||
+ sysaccount_name = "modify-password-test"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ try:
|
||||
+ # Create system account
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-add", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+
|
||||
+ # Modify with new random password
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "sysaccount-mod", sysaccount_name, "--random"]
|
||||
+ )
|
||||
+ assert "Random password:" in result.stdout_text
|
||||
+
|
||||
+ # Modify with explicit password
|
||||
+ new_password = ipa_generate_password()
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-mod", sysaccount_name, "--password"],
|
||||
+ stdin_text=f"{new_password}\n{new_password}\n",
|
||||
+ )
|
||||
+
|
||||
+ finally:
|
||||
+ # Clean up
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "sysaccount-del", sysaccount_name], raiseonerr=False
|
||||
+ )
|
||||
--
|
||||
2.51.1
|
||||
|
||||
1331
0137-Port-bash-sudo-tests.patch
Normal file
1331
0137-Port-bash-sudo-tests.patch
Normal file
File diff suppressed because it is too large
Load Diff
101
0138-sysaccount-make-sure-nsaccountlock-is-always-present.patch
Normal file
101
0138-sysaccount-make-sure-nsaccountlock-is-always-present.patch
Normal file
@ -0,0 +1,101 @@
|
||||
From 517fe4cfee29da7531728b0a508c6162935e5597 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Wed, 5 Nov 2025 16:16:21 +0200
|
||||
Subject: [PATCH] sysaccount: make sure nsaccountlock is always present
|
||||
|
||||
Commands sysaccount-enable/sysaccount-disable allow to lock/unlock the
|
||||
account. This is exposed via operational attribute `nsAccountLock`
|
||||
which has to be explicitly requested to query the state. However, if
|
||||
account wasn't explicitly disabled before, `nsAccountLock` will not be
|
||||
returned by the LDAP server.
|
||||
|
||||
Ensure the nsaccountlock attribute is always retrieved, validated, and
|
||||
normalized across sysaccount operations.
|
||||
|
||||
- Invoke validate_nsaccountlock in create and modify pre-callbacks to
|
||||
enforce valid values
|
||||
|
||||
- Invoke convert_nsaccountlock in create, modify, list, reset, and
|
||||
policy post-callbacks to normalize the nsaccountlock attribute and
|
||||
default to False in case it is absent.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9842
|
||||
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Thomas Woerner <twoerner@redhat.com>
|
||||
---
|
||||
ipaserver/plugins/sysaccounts.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
diff --git a/ipaserver/plugins/sysaccounts.py b/ipaserver/plugins/sysaccounts.py
|
||||
index e2dd820efc2e83ca79f8a2badbca2013b1d40c5d..a67d8b2fb17a9bd1590f95bf661d8f1b0453c807 100644
|
||||
--- a/ipaserver/plugins/sysaccounts.py
|
||||
+++ b/ipaserver/plugins/sysaccounts.py
|
||||
@@ -15,6 +15,7 @@ from .baseldap import (
|
||||
LDAPSearch,
|
||||
LDAPRetrieve,
|
||||
LDAPQuery)
|
||||
+from .baseuser import validate_nsaccountlock, convert_nsaccountlock
|
||||
from ipalib import _, ngettext
|
||||
from ipalib import constants
|
||||
from ipalib import output
|
||||
@@ -285,6 +286,7 @@ class sysaccount_add(LDAPCreate):
|
||||
error=_('Either --password or --random is required')
|
||||
)
|
||||
check_userpassword(entry_attrs, **options)
|
||||
+ validate_nsaccountlock(entry_attrs)
|
||||
return dn
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
@@ -296,6 +298,7 @@ class sysaccount_add(LDAPCreate):
|
||||
except errors.NotGroupMember:
|
||||
pass
|
||||
self.add_message(SystemAccountUsage(uid=keys[0], dn=dn))
|
||||
+ convert_nsaccountlock(entry_attrs)
|
||||
return dn
|
||||
|
||||
|
||||
@@ -353,12 +356,15 @@ class sysaccount_mod(LDAPUpdate):
|
||||
if 'privileged' not in options:
|
||||
self.allow_empty_update = False
|
||||
|
||||
+ validate_nsaccountlock(entry_attrs)
|
||||
+
|
||||
return dn
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
fill_randompassword(entry_attrs, **options)
|
||||
entry_attrs['privileged'] = getattr(context, 'privileged')
|
||||
+ convert_nsaccountlock(entry_attrs)
|
||||
return dn
|
||||
|
||||
|
||||
@@ -382,6 +388,7 @@ class sysaccount_find(LDAPSearch):
|
||||
self.obj.get_password_attributes(ldap, entry_attrs.dn, entry_attrs)
|
||||
self.obj.handle_reset(self, self,
|
||||
ldap, entry_attrs.dn, entry_attrs, **options)
|
||||
+ convert_nsaccountlock(entry_attrs)
|
||||
|
||||
return truncated
|
||||
|
||||
@@ -398,6 +405,7 @@ class sysaccount_show(LDAPRetrieve):
|
||||
self.obj.get_password_attributes(ldap, dn, entry_attrs)
|
||||
self.obj.handle_reset(self, self,
|
||||
ldap, dn, entry_attrs, **options)
|
||||
+ convert_nsaccountlock(entry_attrs)
|
||||
|
||||
return dn
|
||||
|
||||
@@ -413,6 +421,7 @@ class sysaccount_policy(LDAPRetrieve):
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
self.obj.handle_reset(self, self, ldap, dn, entry_attrs, **options)
|
||||
+ convert_nsaccountlock(entry_attrs)
|
||||
return dn
|
||||
|
||||
|
||||
--
|
||||
2.51.1
|
||||
|
||||
146
0139-test_sudo-do-not-clean-the-cache-for-offline-cache-t.patch
Normal file
146
0139-test_sudo-do-not-clean-the-cache-for-offline-cache-t.patch
Normal file
@ -0,0 +1,146 @@
|
||||
From 8e9e516299883e5f4f820c3a3c444513b896a36a Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 10 Nov 2025 17:59:22 +0100
|
||||
Subject: [PATCH] test_sudo: do not clean the cache for offline cache tests
|
||||
|
||||
The tests for offline caching should not clear SSSD cache
|
||||
before shutting down the IPA server. Currently most of them
|
||||
follow the same steps:
|
||||
- create the rule
|
||||
- clear the cache
|
||||
- call run_as_sudo_user (which clears the cache)
|
||||
- call list_sudo_commands (which clears the cache)
|
||||
- stop the master
|
||||
- call run_as_sudo_user with skip_sssd_cache_clear
|
||||
- call list_sudo_commands with skip_sssd_cache_clear
|
||||
|
||||
The scenario is wrong as skip_sssd_cache_clear should also be
|
||||
added on the calls before the master is stopped.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9874
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: PRANAV THUBE <pthube@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_sudo.py | 38 +++++++++++++++++---------
|
||||
1 file changed, 25 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_sudo.py b/ipatests/test_integration/test_sudo.py
|
||||
index ce4aafd12961c8e66373e0d6c6200799c0642302..639f3e6b6fef0842eefb9fc5c5fda445682d5baa 100644
|
||||
--- a/ipatests/test_integration/test_sudo.py
|
||||
+++ b/ipatests/test_integration/test_sudo.py
|
||||
@@ -1343,13 +1343,15 @@ class TestSudo_Functional(IntegrationTest):
|
||||
clear_sssd_cache(master)
|
||||
clear_sssd_cache(self.client)
|
||||
result = self.run_as_sudo_user(
|
||||
- "date", sudo_user="root", su_user=self.USER_1)
|
||||
+ "date", sudo_user="root", su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
assert sys_day in result.stdout_text
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) /bin/date" in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
@@ -1383,9 +1385,11 @@ class TestSudo_Functional(IntegrationTest):
|
||||
clear_sssd_cache(self.client)
|
||||
clear_sssd_cache(master)
|
||||
result = self.run_as_sudo_user(
|
||||
- "uname", sudo_user="root", su_user=self.USER_1)
|
||||
+ "uname", sudo_user="root", su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) !/bin/uname" in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
@@ -1420,7 +1424,8 @@ class TestSudo_Functional(IntegrationTest):
|
||||
clear_sssd_cache(master)
|
||||
clear_sssd_cache(self.client)
|
||||
result = self.run_as_sudo_user(
|
||||
- "date", sudo_user=self.USER_2, su_user=self.USER_1)
|
||||
+ "date", sudo_user=self.USER_2, su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
@@ -1485,13 +1490,15 @@ class TestSudo_Functional(IntegrationTest):
|
||||
)
|
||||
clear_sssd_cache(self.client)
|
||||
result = self.run_as_sudo_user(
|
||||
- "date", sudo_user="root", su_user=self.USER_1)
|
||||
+ "date", sudo_user="root", su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
assert sys_day in result.stdout_text
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) /bin/date" in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
@@ -1554,13 +1561,15 @@ class TestSudo_Functional(IntegrationTest):
|
||||
clear_sssd_cache(self.client)
|
||||
|
||||
result = self.run_as_sudo_user(
|
||||
- "date", sudo_user="root", su_user=self.USER_1)
|
||||
+ "date", sudo_user="root", su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
assert sys_day in result.stdout_text
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) /bin/date" in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
@@ -1613,13 +1622,14 @@ class TestSudo_Functional(IntegrationTest):
|
||||
clear_sssd_cache(self.client)
|
||||
result = self.run_as_sudo_user(
|
||||
"date", sudo_user="root", su_user=self.USER_1,
|
||||
- skip_password=True)
|
||||
+ skip_password=True, skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
assert sys_day in result.stdout_text
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) NOPASSWD: /bin/date" in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
@@ -1660,13 +1670,15 @@ class TestSudo_Functional(IntegrationTest):
|
||||
master.run_command(["ipa", "sudorule-disable", self.SUDO_RULE])
|
||||
clear_sssd_cache(self.client)
|
||||
result = self.run_as_sudo_user(
|
||||
- "date", sudo_user="root", su_user=self.USER_1)
|
||||
+ "date", sudo_user="root", su_user=self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
|
||||
sys_day = self.client.run_command(
|
||||
["date", "+%a"]).stdout_text.strip()
|
||||
assert sys_day not in result.stdout_text
|
||||
|
||||
- result = self.list_sudo_commands(self.USER_1)
|
||||
+ result = self.list_sudo_commands(self.USER_1,
|
||||
+ skip_sssd_cache_clear=True)
|
||||
assert "(root) /bin/date" not in result.stdout_text
|
||||
|
||||
stop_ipa_server(master)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
From 5e87614ef408cfa89093bc2186242f1b99c0b251 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Wed, 12 Nov 2025 13:38:14 +0200
|
||||
Subject: [PATCH] sysaccounts: extend permissions to include description and
|
||||
account lock
|
||||
|
||||
Security Architect role was supposed to manage sysaccount objects. But
|
||||
since description attribute is missing from the list of the managed
|
||||
permissions, the role is unable to modify 'description' field. Same for
|
||||
nsAccountLock which needs an explicit ACI.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9875
|
||||
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
---
|
||||
ACI.txt | 4 ++--
|
||||
ipaserver/plugins/sysaccounts.py | 5 +++--
|
||||
2 files changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ACI.txt b/ACI.txt
|
||||
index 8db1634bc25ff616f67cc4bc7ccfa385c2d77c53..b3c87bac9fa50ae153eee8d2a0271c2585a8bf75 100644
|
||||
--- a/ACI.txt
|
||||
+++ b/ACI.txt
|
||||
@@ -379,9 +379,9 @@ aci: (targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "perm
|
||||
dn: cn=sysaccounts,cn=etc,dc=ipa,dc=example
|
||||
aci: (targetattr = "userpassword")(targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Check System Accounts passwords";allow (search) groupdn = "ldap:///cn=System: Check System Accounts passwords,cn=permissions,cn=pbac,dc=ipa,dc=example";)
|
||||
dn: cn=sysaccounts,cn=etc,dc=ipa,dc=example
|
||||
-aci: (targetattr = "userpassword")(targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Modify System Accounts";allow (write) groupdn = "ldap:///cn=System: Modify System Accounts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
|
||||
+aci: (targetattr = "description || nsaccountlock || userpassword")(targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Modify System Accounts";allow (write) groupdn = "ldap:///cn=System: Modify System Accounts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
|
||||
dn: cn=sysaccounts,cn=etc,dc=ipa,dc=example
|
||||
-aci: (targetattr = "createtimestamp || entryusn || memberof || modifytimestamp || objectclass || uid")(targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Read System Accounts";allow (compare,read,search) userdn = "ldap:///all";)
|
||||
+aci: (targetattr = "createtimestamp || description || entryusn || memberof || modifytimestamp || nsaccountlock || objectclass || uid")(targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Read System Accounts";allow (compare,read,search) userdn = "ldap:///all";)
|
||||
dn: cn=sysaccounts,cn=etc,dc=ipa,dc=example
|
||||
aci: (targetfilter = "(objectclass=simplesecurityobject)")(version 3.0;acl "permission:System: Remove System Accounts";allow (delete) groupdn = "ldap:///cn=System: Remove System Accounts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
|
||||
dn: cn=topology,cn=ipa,cn=etc,dc=ipa,dc=example
|
||||
diff --git a/ipaserver/plugins/sysaccounts.py b/ipaserver/plugins/sysaccounts.py
|
||||
index a67d8b2fb17a9bd1590f95bf661d8f1b0453c807..ad579eeed4521d75a274a07ea70dcd6bf96d589b 100644
|
||||
--- a/ipaserver/plugins/sysaccounts.py
|
||||
+++ b/ipaserver/plugins/sysaccounts.py
|
||||
@@ -138,7 +138,7 @@ class sysaccount(LDAPObject):
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'objectclass',
|
||||
- 'uid', 'memberof'
|
||||
+ 'uid', 'memberof', 'nsaccountlock', 'description'
|
||||
},
|
||||
},
|
||||
'System: Check System Accounts passwords': {
|
||||
@@ -152,7 +152,8 @@ class sysaccount(LDAPObject):
|
||||
},
|
||||
'System: Modify System Accounts': {
|
||||
'ipapermright': {'write'},
|
||||
- 'ipapermdefaultattr': {'userpassword'},
|
||||
+ 'ipapermdefaultattr': {'userpassword', 'description',
|
||||
+ 'nsaccountlock'},
|
||||
'default_privileges': {'System Accounts Administrators'},
|
||||
},
|
||||
'System: Remove System Accounts': {
|
||||
--
|
||||
2.51.1
|
||||
|
||||
464
0141-Correctly-recognize-OID-2.5.4.97-organizationIdentif.patch
Normal file
464
0141-Correctly-recognize-OID-2.5.4.97-organizationIdentif.patch
Normal file
@ -0,0 +1,464 @@
|
||||
From 8905bbf7a9ad7aefc9ddad9ccdbfb050c8429863 Mon Sep 17 00:00:00 2001
|
||||
From: Aleksandr Sharov <asharov@redhat.com>
|
||||
Date: Mon, 20 Oct 2025 21:41:19 +0200
|
||||
Subject: [PATCH] Correctly recognize OID 2.5.4.97, organizationIdentifier as a
|
||||
subject/issuer DN of the CA certificate
|
||||
|
||||
OID 2.5.4.97 added to the ATTR_NAME_BY_OID list, and cainstance during
|
||||
2-step installation is re-fetching CA certificate and re-parsing OIDs
|
||||
based on the list. Example:
|
||||
|
||||
cert issuer: <Name(C=CZ,2.5.4.97=LLCCZ-123456789,O=Corp,CN=ROOT)>
|
||||
DN(cert issuer): CN=ROOT,O=Corp,organizationIdentifier=LLCCZ-123456789,C=CZ
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9866
|
||||
|
||||
Signed-off-by: Aleksandr Sharov <asharov@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipapython/dn.py | 1 +
|
||||
ipaserver/install/cainstance.py | 10 +-
|
||||
ipatests/test_integration/test_external_ca.py | 372 ++++++++++++++++++
|
||||
3 files changed, 381 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipapython/dn.py b/ipapython/dn.py
|
||||
index 8974b420cb2c2d823f8ea45ee05cfd1f0c1a3954..12870d59143e89ecb729aa059218a40c368efd64 100644
|
||||
--- a/ipapython/dn.py
|
||||
+++ b/ipapython/dn.py
|
||||
@@ -1462,4 +1462,5 @@ ATTR_NAME_BY_OID = {
|
||||
cryptography.x509.ObjectIdentifier('2.5.4.9'): 'STREET',
|
||||
cryptography.x509.ObjectIdentifier('2.5.4.17'): 'postalCode',
|
||||
cryptography.x509.ObjectIdentifier('0.9.2342.19200300.100.1.1'): 'UID',
|
||||
+ cryptography.x509.ObjectIdentifier('2.5.4.97'): 'organizationIdentifier',
|
||||
}
|
||||
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
|
||||
index b3cc0b262e8c7c770b521b25ffed2b1fe97b81b5..b2a0a76a402ca88d873d2e1dd423f637686b0fba 100644
|
||||
--- a/ipaserver/install/cainstance.py
|
||||
+++ b/ipaserver/install/cainstance.py
|
||||
@@ -2352,10 +2352,16 @@ def ensure_ipa_authority_entry():
|
||||
api.Backend.ra_lightweight_ca.override_port = 8443
|
||||
with api.Backend.ra_lightweight_ca as lwca:
|
||||
data = lwca.read_ca('host-authority')
|
||||
+ # Loading certificate to properly re-parse issuer and subject DNs in
|
||||
+ # case CA doesn't recognize some of the OIDs. DN class will re-access
|
||||
+ # the OIDs based on ATTR_NAME_BY_OID list.
|
||||
+ cert_data = lwca.read_ca_cert('host-authority')
|
||||
+ cert = x509.load_der_x509_certificate(cert_data)
|
||||
+
|
||||
attrs = dict(
|
||||
ipacaid=data['id'],
|
||||
- ipacaissuerdn=data['issuerDN'],
|
||||
- ipacasubjectdn=data['dn'],
|
||||
+ ipacaissuerdn=DN(cert.issuer),
|
||||
+ ipacasubjectdn=DN(cert.subject),
|
||||
)
|
||||
api.Backend.ra_lightweight_ca.override_port = None
|
||||
|
||||
diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py
|
||||
index aeb08aae725af080dcd7807857b94c23094c3155..a6a088537dbfbb389658f89f9b07fa5323a05ed8 100644
|
||||
--- a/ipatests/test_integration/test_external_ca.py
|
||||
+++ b/ipatests/test_integration/test_external_ca.py
|
||||
@@ -23,6 +23,8 @@ import time
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
+from cryptography.x509.oid import ObjectIdentifier, NameOID
|
||||
+from cryptography.hazmat.primitives import hashes, serialization
|
||||
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
@@ -108,6 +110,142 @@ def check_ipaca_issuerDN(host, expected_dn):
|
||||
assert "Issuer DN: {}".format(expected_dn) in result.stdout_text
|
||||
|
||||
|
||||
+def create_external_ca_with_subject(subject_attrs):
|
||||
+ """
|
||||
+ Create an external CA with custom subject attributes including non-standard
|
||||
+ OIDs.
|
||||
+
|
||||
+ :param subject_attrs: List of x509.NameAttribute objects to include in
|
||||
+ subject
|
||||
+ :return: Tuple of (ExternalCA object, root CA certificate as PEM bytes)
|
||||
+
|
||||
+ Example:
|
||||
+ subj_attrs = [
|
||||
+ x509.NameAttribute(NameOID.COMMON_NAME, 'My CA'),
|
||||
+ x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'),
|
||||
+ x509.NameAttribute(ObjectIdentifier('2.5.4.97'), 'VATEU-123456789')
|
||||
+ ]
|
||||
+ external_ca, root_ca_pem = create_external_ca_with_subject(subj_attrs)
|
||||
+ signed_cert = external_ca.sign_csr(csr_data)
|
||||
+ """
|
||||
+ external_ca = ExternalCA()
|
||||
+ external_ca.create_ca_key()
|
||||
+
|
||||
+ # Create the custom subject
|
||||
+ subject = x509.Name(subject_attrs)
|
||||
+ external_ca.issuer = subject
|
||||
+
|
||||
+ # Build the root CA certificate
|
||||
+ builder = x509.CertificateBuilder()
|
||||
+ builder = builder.subject_name(subject)
|
||||
+ builder = builder.issuer_name(subject) # self-signed
|
||||
+ builder = builder.public_key(external_ca.ca_public_key)
|
||||
+ builder = builder.serial_number(x509.random_serial_number())
|
||||
+ builder = builder.not_valid_before(external_ca.now)
|
||||
+ builder = builder.not_valid_after(external_ca.now + external_ca.delta)
|
||||
+
|
||||
+ # Add required extensions for a CA certificate
|
||||
+ builder = builder.add_extension(
|
||||
+ x509.KeyUsage(
|
||||
+ digital_signature=False,
|
||||
+ content_commitment=False,
|
||||
+ key_encipherment=False,
|
||||
+ data_encipherment=False,
|
||||
+ key_agreement=False,
|
||||
+ key_cert_sign=True,
|
||||
+ crl_sign=True,
|
||||
+ encipher_only=False,
|
||||
+ decipher_only=False,
|
||||
+ ),
|
||||
+ critical=True,
|
||||
+ )
|
||||
+
|
||||
+ builder = builder.add_extension(
|
||||
+ x509.BasicConstraints(ca=True, path_length=None),
|
||||
+ critical=True,
|
||||
+ )
|
||||
+
|
||||
+ builder = builder.add_extension(
|
||||
+ x509.SubjectKeyIdentifier.from_public_key(
|
||||
+ external_ca.ca_public_key
|
||||
+ ),
|
||||
+ critical=False,
|
||||
+ )
|
||||
+
|
||||
+ builder = builder.add_extension(
|
||||
+ x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
||||
+ external_ca.ca_public_key
|
||||
+ ),
|
||||
+ critical=False,
|
||||
+ )
|
||||
+
|
||||
+ # Sign the certificate
|
||||
+ root_ca_cert = builder.sign(
|
||||
+ external_ca.ca_key, hashes.SHA256(), default_backend()
|
||||
+ )
|
||||
+ root_ca_pem = root_ca_cert.public_bytes(serialization.Encoding.PEM)
|
||||
+
|
||||
+ return external_ca, root_ca_pem
|
||||
+
|
||||
+
|
||||
+def find_cert_in_chain(cert_chain, subject_attrs=None, issuer_attrs=None):
|
||||
+ """
|
||||
+ Retrieves a certificate from a provided chain that matches specified
|
||||
+ criteria. The search can be filtered using dictionaries of subject
|
||||
+ attributes, issuer attributes, or a combination of both.
|
||||
+
|
||||
+ :param cert_chain: List of certificates to search through
|
||||
+ :param subject_attrs: Dict of OID -> expected value for subject attributes
|
||||
+ :param issuer_attrs: Dict of OID -> expected value for issuer attributes
|
||||
+ :return: The matching certificate or None if not found
|
||||
+
|
||||
+ Example:
|
||||
+ from cryptography.x509.oid import NameOID, ObjectIdentifier
|
||||
+ org_id_oid = ObjectIdentifier("2.5.4.97")
|
||||
+
|
||||
+ # Find IPA CA cert with specific subject and issuer
|
||||
+ cert = find_cert_in_chain(
|
||||
+ ca_chain,
|
||||
+ subject_attrs={
|
||||
+ NameOID.COMMON_NAME: "Certificate Authority",
|
||||
+ NameOID.ORGANIZATION_NAME: "EXAMPLE.TEST"
|
||||
+ },
|
||||
+ issuer_attrs={
|
||||
+ org_id_oid: "VATEU-123456789"
|
||||
+ }
|
||||
+ )
|
||||
+ """
|
||||
+ for cert in cert_chain:
|
||||
+ # Check subject attributes if provided
|
||||
+ if subject_attrs:
|
||||
+ subject_match = True
|
||||
+ for oid, expected_value in subject_attrs.items():
|
||||
+ attrs = [attr for attr in cert.subject if attr.oid == oid]
|
||||
+ if not any(attr.value == expected_value for attr in attrs):
|
||||
+ # This cert doesn't match, move to next cert
|
||||
+ subject_match = False
|
||||
+ break
|
||||
+ if not subject_match:
|
||||
+ continue
|
||||
+
|
||||
+ # Check issuer attributes if provided
|
||||
+ if issuer_attrs:
|
||||
+ issuer_match = True
|
||||
+ for oid, expected_value in issuer_attrs.items():
|
||||
+ attrs = [attr for attr in cert.issuer if attr.oid == oid]
|
||||
+ if not any(attr.value == expected_value for attr in attrs):
|
||||
+ # This cert doesn't match, move to next cert
|
||||
+ issuer_match = False
|
||||
+ break
|
||||
+ if not issuer_match:
|
||||
+ continue
|
||||
+
|
||||
+ # All specified attributes match, return this cert
|
||||
+ return cert
|
||||
+
|
||||
+ return None
|
||||
+
|
||||
+
|
||||
def check_mscs_extension(ipa_csr, template):
|
||||
csr = x509.load_pem_x509_csr(ipa_csr, default_backend())
|
||||
extensions = [
|
||||
@@ -243,6 +381,133 @@ class TestExternalCAConstraints(IntegrationTest):
|
||||
self.master.run_command(['ipa', 'ping'])
|
||||
|
||||
|
||||
+class TestExternalCAInstallWithOrgId(IntegrationTest):
|
||||
+ """Test 2-step installation with external CA containing
|
||||
+ organizationIdentifier.
|
||||
+
|
||||
+ This test verifies that FreeIPA can successfully install with a 2-step
|
||||
+ external CA process when the external CA certificate contains the
|
||||
+ organizationIdentifier attribute (OID 2.5.4.97) in its issuer DN.
|
||||
+
|
||||
+ This tests the fix for DN parsing in ensure_ipa_authority_entry in
|
||||
+ cainstance.py where the issuer DN must be properly parsed against
|
||||
+ ATTR_NAME_BY_OID to recognize all OIDs including organizationIdentifier.
|
||||
+ """
|
||||
+ num_replicas = 0
|
||||
+ num_clients = 0
|
||||
+
|
||||
+ def test_external_ca_install_with_organization_identifier(self):
|
||||
+ """Test 2-step installation with organizationIdentifier (OID 2.5.4.97)
|
||||
+
|
||||
+ Verify that FreeIPA can successfully complete a 2-step installation
|
||||
+ with an external CA that contains organizationIdentifier (OID 2.5.4.97)
|
||||
+ in the issuer DN. The issuer DN should be properly parsed and stored
|
||||
+ in LDAP during the ensure_ipa_authority_entry process.
|
||||
+ """
|
||||
+
|
||||
+ # Test parameters
|
||||
+ org_id_value = "VATEU-123456789"
|
||||
+ org_id_oid = ObjectIdentifier("2.5.4.97") # organizationIdentifier OID
|
||||
+ external_ca_cn = "External CA with OrgID"
|
||||
+
|
||||
+ # Step 1 of ipa-server-install
|
||||
+ result = install_server_external_ca_step1(self.master)
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Get the CSR generated by step 1
|
||||
+ ipa_csr = self.master.get_file_contents(paths.ROOT_IPA_CSR)
|
||||
+
|
||||
+ # Create an external CA with organizationIdentifier in the subject
|
||||
+ subject_attrs = [
|
||||
+ x509.NameAttribute(NameOID.COMMON_NAME, external_ca_cn),
|
||||
+ x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'),
|
||||
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'Test Organization'),
|
||||
+ x509.NameAttribute(org_id_oid, org_id_value),
|
||||
+ ]
|
||||
+ external_ca, root_ca = create_external_ca_with_subject(subject_attrs)
|
||||
+
|
||||
+ # Sign the IPA CSR with the external CA that has organizationIdentifier
|
||||
+ ipa_ca = external_ca.sign_csr(ipa_csr)
|
||||
+
|
||||
+ # Write certificates to files
|
||||
+ root_ca_fname = os.path.join(
|
||||
+ self.master.config.test_dir,
|
||||
+ 'root_ca_with_orgid.crt'
|
||||
+ )
|
||||
+ ipa_ca_fname = os.path.join(
|
||||
+ self.master.config.test_dir,
|
||||
+ 'ipa_ca_signed_with_orgid.crt'
|
||||
+ )
|
||||
+
|
||||
+ # Transport certificates to master
|
||||
+ self.master.put_file_contents(root_ca_fname, root_ca)
|
||||
+ self.master.put_file_contents(ipa_ca_fname, ipa_ca)
|
||||
+
|
||||
+ # Step 2 of ipa-server-install
|
||||
+ # This should succeed despite organizationIdentifier in issuer DN
|
||||
+ result = install_server_external_ca_step2(
|
||||
+ self.master, ipa_ca_fname, root_ca_fname
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Make sure IPA server is working properly
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ result = self.master.run_command(['ipa', 'user-show', 'admin'])
|
||||
+ assert 'User login: admin' in result.stdout_text
|
||||
+
|
||||
+ # Verify IPA is functional
|
||||
+ result = self.master.run_command(['ipa', 'ping'])
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Verify the certificate chain contains the expected certificates
|
||||
+ # Load all certificates from /etc/ipa/ca.crt (the CA chain)
|
||||
+ ca_chain_content = self.master.get_file_contents(paths.IPA_CA_CRT)
|
||||
+ ca_chain = ipa_x509.load_certificate_list(ca_chain_content)
|
||||
+
|
||||
+ # 1. Find and verify the IPA CA certificate
|
||||
+ # It should have subject O=REALM, CN=Certificate Authority
|
||||
+ # and issuer with organizationIdentifier
|
||||
+ ipa_ca_cert = find_cert_in_chain(
|
||||
+ ca_chain,
|
||||
+ subject_attrs={
|
||||
+ NameOID.COMMON_NAME: "Certificate Authority",
|
||||
+ NameOID.ORGANIZATION_NAME: self.master.domain.realm
|
||||
+ },
|
||||
+ issuer_attrs={
|
||||
+ org_id_oid: org_id_value,
|
||||
+ NameOID.COMMON_NAME: external_ca_cn
|
||||
+ }
|
||||
+ )
|
||||
+ assert ipa_ca_cert is not None, \
|
||||
+ f"Did not find IPA CA certificate with subject " \
|
||||
+ f"O={self.master.domain.realm}, CN=Certificate Authority " \
|
||||
+ f"and issuer with organizationIdentifier={org_id_value}"
|
||||
+
|
||||
+ # 2. Find and verify the external root CA certificate
|
||||
+ # It should be self-signed with organizationIdentifier in subject
|
||||
+ external_ca_cert = find_cert_in_chain(
|
||||
+ ca_chain,
|
||||
+ subject_attrs={
|
||||
+ NameOID.COMMON_NAME: external_ca_cn,
|
||||
+ org_id_oid: org_id_value
|
||||
+ },
|
||||
+ issuer_attrs={
|
||||
+ NameOID.COMMON_NAME: external_ca_cn,
|
||||
+ org_id_oid: org_id_value
|
||||
+ }
|
||||
+ )
|
||||
+ assert external_ca_cert is not None, \
|
||||
+ f"Did not find external root CA certificate (CN={external_ca_cn})" \
|
||||
+ f" with organizationIdentifier={org_id_value} in subject"
|
||||
+
|
||||
+ # 3. Verify the issuer DN is correctly stored in LDAP
|
||||
+ # The issuer DN should contain organizationIdentifier
|
||||
+ # Note: The order in the DN string representation matters
|
||||
+ result = self.master.run_command(['ipa', 'ca-show', 'ipa'])
|
||||
+ assert f"organizationIdentifier={org_id_value}" in result.stdout_text, \
|
||||
+ "organizationIdentifier not found in IPA CA issuer DN"
|
||||
+
|
||||
+
|
||||
def verify_caentry(host, cert):
|
||||
"""
|
||||
Verify the content of cn=DOMAIN IPA CA,cn=certificates,cn=ipa,cn=etc,basedn
|
||||
@@ -475,6 +740,113 @@ class TestExternalCAInstall(IntegrationTest):
|
||||
self.master.run_command([paths.IPA_CACERT_MANAGE, 'install',
|
||||
root_ca_fname])
|
||||
|
||||
+ def test_renew_external_ca_with_organization_identifier(self):
|
||||
+ """Test CA renewal with organizationIdentifier (OID 2.5.4.97)
|
||||
+
|
||||
+ Verify that FreeIPA can successfully renew CA with an external CA
|
||||
+ that contains organizationIdentifier (OID 2.5.4.97) in the issuer DN.
|
||||
+ The IPA CA will be signed by this external CA, and the issuer DN
|
||||
+ will contain the organizationIdentifier attribute.
|
||||
+ """
|
||||
+
|
||||
+ # Test parameters
|
||||
+ org_id_value = "VATEU-123456789"
|
||||
+ org_id_oid = ObjectIdentifier("2.5.4.97") # organizationIdentifier OID
|
||||
+ external_ca_cn = "External CA with OrgID"
|
||||
+
|
||||
+ # Initiate CA renewal with external CA
|
||||
+ result = self.master.run_command([paths.IPA_CACERT_MANAGE, 'renew',
|
||||
+ '--external-ca'])
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Get the CSR generated by the renewal process
|
||||
+ ipa_csr = self.master.get_file_contents(paths.IPA_CA_CSR)
|
||||
+
|
||||
+ # Create an external CA with organizationIdentifier in the subject
|
||||
+ subject_attrs = [
|
||||
+ x509.NameAttribute(NameOID.COMMON_NAME, external_ca_cn),
|
||||
+ x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'),
|
||||
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'Test Organization'),
|
||||
+ x509.NameAttribute(org_id_oid, org_id_value),
|
||||
+ ]
|
||||
+ external_ca, root_ca = create_external_ca_with_subject(subject_attrs)
|
||||
+
|
||||
+ # Sign the IPA CSR with the external CA that has organizationIdentifier
|
||||
+ ipa_ca = external_ca.sign_csr(ipa_csr)
|
||||
+
|
||||
+ # Write certificates to files
|
||||
+ root_ca_fname = os.path.join(
|
||||
+ self.master.config.test_dir,
|
||||
+ 'root_ca_with_orgid.crt'
|
||||
+ )
|
||||
+ ipa_ca_fname = os.path.join(
|
||||
+ self.master.config.test_dir,
|
||||
+ 'ipa_ca_signed_with_orgid.crt'
|
||||
+ )
|
||||
+
|
||||
+ # Transport certificates to master
|
||||
+ self.master.put_file_contents(root_ca_fname, root_ca)
|
||||
+ self.master.put_file_contents(ipa_ca_fname, ipa_ca)
|
||||
+
|
||||
+ # Complete the renewal with the signed certificates
|
||||
+ # This should succeed despite organizationIdentifier in issuer DN
|
||||
+ result = self.master.run_command([
|
||||
+ paths.IPA_CACERT_MANAGE, 'renew',
|
||||
+ '--external-cert-file', ipa_ca_fname,
|
||||
+ '--external-cert-file', root_ca_fname
|
||||
+ ])
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Verify the CA was properly installed
|
||||
+ result = self.master.run_command([paths.IPA_CERTUPDATE])
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Verify IPA is still functional
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ result = self.master.run_command(['ipa', 'ping'])
|
||||
+ assert result.returncode == 0
|
||||
+
|
||||
+ # Verify the certificate chain contains the expected certificates
|
||||
+ # Load all certificates from /etc/ipa/ca.crt (the CA chain)
|
||||
+ ca_chain_content = self.master.get_file_contents(paths.IPA_CA_CRT)
|
||||
+ ca_chain = ipa_x509.load_certificate_list(ca_chain_content)
|
||||
+
|
||||
+ # 1. Find and verify the IPA CA certificate
|
||||
+ # It should have subject O=REALM, CN=Certificate Authority
|
||||
+ # and issuer with organizationIdentifier
|
||||
+ ipa_ca_cert = find_cert_in_chain(
|
||||
+ ca_chain,
|
||||
+ subject_attrs={
|
||||
+ NameOID.COMMON_NAME: "Certificate Authority",
|
||||
+ NameOID.ORGANIZATION_NAME: self.master.domain.realm
|
||||
+ },
|
||||
+ issuer_attrs={
|
||||
+ org_id_oid: org_id_value,
|
||||
+ NameOID.COMMON_NAME: external_ca_cn
|
||||
+ }
|
||||
+ )
|
||||
+ assert ipa_ca_cert is not None, \
|
||||
+ f"Did not find IPA CA certificate with subject " \
|
||||
+ f"O={self.master.domain.realm}, CN=Certificate Authority " \
|
||||
+ f"and issuer with organizationIdentifier={org_id_value}"
|
||||
+
|
||||
+ # 2. Find and verify the external root CA certificate
|
||||
+ # It should be self-signed with organizationIdentifier in subject
|
||||
+ external_ca_cert = find_cert_in_chain(
|
||||
+ ca_chain,
|
||||
+ subject_attrs={
|
||||
+ NameOID.COMMON_NAME: external_ca_cn,
|
||||
+ org_id_oid: org_id_value
|
||||
+ },
|
||||
+ issuer_attrs={
|
||||
+ NameOID.COMMON_NAME: external_ca_cn,
|
||||
+ org_id_oid: org_id_value
|
||||
+ }
|
||||
+ )
|
||||
+ assert external_ca_cert is not None, \
|
||||
+ f"Did not find external root CA certificate (CN={external_ca_cn})"\
|
||||
+ f" with organizationIdentifier={org_id_value} in subject"
|
||||
+
|
||||
|
||||
class TestMultipleExternalCA(IntegrationTest):
|
||||
"""Setup externally signed ca1
|
||||
--
|
||||
2.51.1
|
||||
|
||||
36
freeipa.spec
36
freeipa.spec
@ -232,7 +232,7 @@
|
||||
|
||||
Name: %{package_name}
|
||||
Version: %{IPA_VERSION}
|
||||
Release: 24%{?rc_version:.%rc_version}%{?dist}
|
||||
Release: 25%{?rc_version:.%rc_version}%{?dist}
|
||||
Summary: The Identity, Policy and Audit system
|
||||
|
||||
License: GPL-3.0-or-later
|
||||
@ -373,6 +373,30 @@ Patch0114: 0114-ipasam-remove-definitions-which-included-from-ndr_dr.patch
|
||||
Patch0115: 0115-Enforce-uniqueness-across-krbprincipalname-and-krbca.patch
|
||||
Patch0116: 0116-ipa-kdb-enforce-PAC-presence-on-TGT-for-TGS-REQ.patch
|
||||
Patch0117: 0117-ipatests-extend-test-for-unique-krbcanonicalname.patch
|
||||
Patch0118: 0118-Add-domain-option-to-ipa-client-automount-for-DNS-di.patch
|
||||
Patch0119: 0119-ipatests-Update-ipatests-to-test-topology-with-multi.patch
|
||||
Patch0120: 0120-ipatests-Add-comprehensive-tests-for-ipa-client-auto.patch
|
||||
Patch0121: 0121-ipatests-remove-xfail-for-PKI-11.7.patch
|
||||
Patch0122: 0122-ipatests-fix-test_otp.patch
|
||||
Patch0123: 0123-ipatests-update-the-Let-s-Encrypt-cert-chain.patch
|
||||
Patch0124: 0124-ipatests-fix-TestIPAMigratewithBackupRestore-setup.patch
|
||||
Patch0125: 0125-Add-support-for-libpwpolicy-credit-to-password-polic.patch
|
||||
Patch0126: 0126-ipatests-Refactor-and-port-trust-functional-HBAC-tes.patch
|
||||
Patch0127: 0127-ipatests-skip-encrypted-dns-tests-on-fedora-41.patch
|
||||
Patch0128: 0128-Extended-eDNS-testsuite-with-Relaxed-policy-testcase.patch
|
||||
Patch0129: 0129-ipatest-make-test_cert-more-robust-to-replication-de.patch
|
||||
Patch0130: 0130-ipatests-fix-test_certmonger_ipa_responder_jsonrpc.patch
|
||||
Patch0131: 0131-test_cert-adapt-the-expect-error-message-to-PKI-11.7.patch
|
||||
Patch0132: 0132-Include-the-HSM-token-name-when-creating-LWCAs.patch
|
||||
Patch0133: 0133-ipatests-Refactor-and-port-trust-functional-SUDO-tes.patch
|
||||
Patch0134: 0134-ipa-pwd-extop-add-SysAcctManagersDNs-support.patch
|
||||
Patch0135: 0135-Add-system-accounts-sysaccounts.patch
|
||||
Patch0136: 0136-sysaccounts-add-integration-test.patch
|
||||
Patch0137: 0137-Port-bash-sudo-tests.patch
|
||||
Patch0138: 0138-sysaccount-make-sure-nsaccountlock-is-always-present.patch
|
||||
Patch0139: 0139-test_sudo-do-not-clean-the-cache-for-offline-cache-t.patch
|
||||
Patch0140: 0140-sysaccounts-extend-permissions-to-include-descriptio.patch
|
||||
Patch0141: 0141-Correctly-recognize-OID-2.5.4.97-organizationIdentif.patch
|
||||
Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch
|
||||
%endif
|
||||
%endif
|
||||
@ -2026,6 +2050,16 @@ fi
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Wed Nov 19 2025 Florence Blanc-Renaud <flo@redhat.com> - 4.12.2-25
|
||||
- Resolves: RHEL-128238 [RFE] Support storing LWCA private keys on an HSM [rhel-9]
|
||||
- Resolves: RHEL-126515 RFE: Enable external password reset agents to use ipa_pwd_extop in RHEL IdM [rhel-9]
|
||||
- Resolves: RHEL-73399 RFE: Update IdM password policy configurations to meet M-22-09 by restricting spaces and require number character class
|
||||
- Resolves: RHEL-128241 ATTR_NAME_BY_OID is missing OID 2.5.4.97, organizationIdentifier [rhel-9]
|
||||
- Resolves: RHEL-126514 [RFE] ipa-client-automount should have an option to include domain of the machine. [rhel-9]
|
||||
- Resolves: RHEL-124171 Include latest fixes in python3-ipatests package
|
||||
- Resolves: RHEL-120514 Include fixes in python3-ipatests [rhel-9.8]
|
||||
- Resolves: RHEL-118609 test_cacert_manage fails due to expired Let's Encrypt R3 certificate
|
||||
|
||||
* Tue Sep 30 2025 Florence Blanc-Renaud <flo@redhat.com> - 4.12.2-24
|
||||
- Resolves: RHEL-118448 CVE-2025-7493 ipa: Privilege escalation from host to domain admin in FreeIPA
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user