Bump version to 3.2.0-3
- Resolves: RHEL-76835 - Web console doesn't show the sub suffix of ou=foo,ou=people,dc=example,dc=com. - Resolves: RHEL-86320 - [RFE] Support updating/renewing TLS certificate without restarting slapd - Resolves: RHEL-110192 - ns-slapd doesn't support PQC keys - Resolves: RHEL-111220 - RHDS-11.9 dsctl db2index --attr recreates all indexes instead of selected ones - Resolves: RHEL-111931 - Improve error messages for dsconf localpwp list - Resolves: RHEL-121981 - Setting password history count to 0 does not flush history - Resolves: RHEL-122625 - ipa-healthcheck is complaining about missing or incorrectly configured system indexes. - Resolves: RHEL-128906 - Scalability issue of replication online initialization with large database - Resolves: RHEL-137786 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication - Resolves: RHEL-146769 - Remove memberof_del_dn_from_groups from MemberOf plugin
This commit is contained in:
parent
e32f517791
commit
b70343dc0c
924
0006-Issue-6753-Port-ticket-548-test-7101.patch
Normal file
924
0006-Issue-6753-Port-ticket-548-test-7101.patch
Normal file
@ -0,0 +1,924 @@
|
||||
From fb23c9e366f5eafa6bdbb8cd71afd78e3edefde2 Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <mirielka@users.noreply.github.com>
|
||||
Date: Tue, 13 Jan 2026 10:44:15 +0100
|
||||
Subject: [PATCH] Issue 6753 - Port ticket 548 test (#7101)
|
||||
|
||||
Description:
|
||||
Port ticket 548 test into
|
||||
dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
|
||||
Relates: #6753
|
||||
|
||||
Author: Lenka Doudova, aadhikar
|
||||
Assisted by: Cursor
|
||||
Reviewer: @droideck(Thanks!)
|
||||
---
|
||||
.../password/pwdPolicy_attribute_test.py | 464 +++++++++++++++++-
|
||||
dirsrvtests/tests/tickets/ticket548_test.py | 408 ---------------
|
||||
2 files changed, 463 insertions(+), 409 deletions(-)
|
||||
delete mode 100644 dirsrvtests/tests/tickets/ticket548_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
index d0c172f94..d021f4720 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
@@ -9,12 +9,12 @@
|
||||
import pytest
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
-import pdb
|
||||
from lib389.topologies import topology_st
|
||||
from lib389.pwpolicy import PwPolicyManager
|
||||
from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389._constants import (DEFAULT_SUFFIX, DN_DM, PASSWORD)
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -361,6 +361,468 @@ def test_pwdpolicysubentry(topology_st, password_policy):
|
||||
assert 'nsPwPolicyEntry_user' not in pwp_subentry
|
||||
|
||||
|
||||
+@pytest.fixture(scope="function")
|
||||
+def shadowUser(request, topology_st):
|
||||
+ """ Create a user with shadowAccount objectclass """
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ shadowUser = users.create(properties={
|
||||
+ 'objectclass': ['top', 'person', 'organizationalPerson',
|
||||
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
|
||||
+ 'sn': '1',
|
||||
+ 'cn': 'shadowUser',
|
||||
+ 'uid': 'shadowUser',
|
||||
+ 'uidNumber': '1',
|
||||
+ 'gidNumber': '11',
|
||||
+ 'homeDirectory': '/home/shadowUser',
|
||||
+ 'displayName': 'Shadow User',
|
||||
+ 'givenname': 'Shadow',
|
||||
+ 'mail':f'shadowuser@{DEFAULT_SUFFIX}',
|
||||
+ 'userpassword': 'password'
|
||||
+ })
|
||||
+
|
||||
+ def fin():
|
||||
+ if shadowUser.exists():
|
||||
+ shadowUser.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return shadowUser
|
||||
+
|
||||
+
|
||||
+def days_to_secs(days):
|
||||
+ """ Convert days to seconds """
|
||||
+ return days * 86400
|
||||
+
|
||||
+
|
||||
+def check_shadow_attr_value(inst, user_dn, attr_type, expected):
|
||||
+ """ Check that shadowAccount attribute has expected value """
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+ user = UserAccount(inst, user_dn)
|
||||
+ assert user.present(attr_type), f'Entry {user_dn} does not have {attr_type} attribute'
|
||||
+ actual = int(user.get_attr_val_utf8(attr_type))
|
||||
+ assert actual == expected, f'{attr_type} of entry {user_dn} is {actual}, expected {expected}'
|
||||
+ log.info(f'{attr_type} of entry {user_dn} has expected value {actual}')
|
||||
+
|
||||
+
|
||||
+def setup_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None):
|
||||
+ """ Setup password policy """
|
||||
+
|
||||
+ log.info(f'Setting up {policy} password policy for {dn}')
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+
|
||||
+ log.info(f'Configuring {policy} password policy')
|
||||
+
|
||||
+ assert policy == 'global' or dn, 'dn is required for non-global policy'
|
||||
+
|
||||
+ if not policy_props:
|
||||
+ policy_props = {
|
||||
+ 'passwordMinAge': str(days_to_secs(1)),
|
||||
+ 'passwordExp': 'on',
|
||||
+ 'passwordMaxAge': str(days_to_secs(10)),
|
||||
+ 'passwordWarning': str(days_to_secs(3))
|
||||
+ }
|
||||
+
|
||||
+ if policy == 'global':
|
||||
+ pwp_mgr.set_global_policy(policy_props)
|
||||
+ elif policy == 'subtree':
|
||||
+ pwp_mgr.create_subtree_policy(dn, policy_props)
|
||||
+ elif policy == 'user':
|
||||
+ pwp_mgr.create_user_policy(dn, policy_props)
|
||||
+ else:
|
||||
+ raise ValueError(f'Invalid type of password policy: {policy}')
|
||||
+
|
||||
+
|
||||
+def modify_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None):
|
||||
+ """ Modify password policy """
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+
|
||||
+ assert policy == 'global' or dn, 'dn is required for non-global policy'
|
||||
+
|
||||
+ if not policy_props:
|
||||
+ policy_props = {
|
||||
+ 'passwordMinAge': str(days_to_secs(3)),
|
||||
+ 'passwordMaxAge': str(days_to_secs(30)),
|
||||
+ 'passwordWarning': str(days_to_secs(9))
|
||||
+ }
|
||||
+
|
||||
+ if policy == 'global':
|
||||
+ pwp_mgr.set_global_policy(properties=policy_props)
|
||||
+ elif policy in ['subtree', 'user']:
|
||||
+ policy_entry = pwp_mgr.get_pwpolicy_entry(dn)
|
||||
+ policy_entry.replace_many(*policy_props.items())
|
||||
+ else:
|
||||
+ raise ValueError(f'Invalid type of password policy: {policy}')
|
||||
+ log.info(f'Modified {policy} policy with {policy_props}.')
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
|
||||
+def test_shadowaccount_no_policy(topology_st, shadowUser):
|
||||
+ """Check shadowAccount under no password policy
|
||||
+
|
||||
+ :id: a1b2c3d4-5e6f-7890-abcd-ef1234567890
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Add a user with shadowAccount objectclass
|
||||
+ 2. Bind as the user
|
||||
+ 3. Check shadowLastChange attribute is set correctly
|
||||
+ :expectedresults:
|
||||
+ 1. User is added successfully
|
||||
+ 2. Bind is successful
|
||||
+ 3. shadowLastChange is set correctly (days since epoch)
|
||||
+ """
|
||||
+
|
||||
+ edate = int(time.time() / (60 * 60 * 24))
|
||||
+
|
||||
+ log.info(f"Bind as {shadowUser.dn}")
|
||||
+ shadowUser.rebind('password')
|
||||
+ check_shadow_attr_value(topology_st.standalone, shadowUser.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
|
||||
+def test_shadowaccount_global_policy(topology_st, shadowUser, request):
|
||||
+ """Check shadowAccount with global password policy
|
||||
+
|
||||
+ :id: b2c3d4e5-6f7a-8901-bcde-f23456789012
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Set global password policy
|
||||
+ 2. Add a second shadowAccount user
|
||||
+ 3. Bind as each user and check shadowAccount attributes
|
||||
+ 4. Modify global password policy
|
||||
+ 5. Change user password (as the user, not DM)
|
||||
+ 6. Re-bind with new password
|
||||
+ 7. Check shadowAccount attributes are updated
|
||||
+ 8. Clean up - delete second user and reset policy
|
||||
+ :expectedresults:
|
||||
+ 1. Global password policy is set successfully
|
||||
+ 2. Second user is added
|
||||
+ 3. shadowAccount attributes match policy values for both users
|
||||
+ 4. Password policy is modified successfully
|
||||
+ 5. Password is changed successfully
|
||||
+ 6. Re-bind with new password is successful
|
||||
+ 7. shadowAccount attributes are updated to match new policy values
|
||||
+ 8. Cleanup is successful
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ log.info('Create second shadowAccount user')
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ shadowUser2 = users.create(properties={
|
||||
+ 'objectclass': ['top', 'person', 'organizationalPerson',
|
||||
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
|
||||
+ 'sn': '2',
|
||||
+ 'cn': 'shadowUser2',
|
||||
+ 'uid': 'shadowUser2',
|
||||
+ 'uidNumber': '2',
|
||||
+ 'gidNumber': '22',
|
||||
+ 'homeDirectory': '/home/shadowUser2',
|
||||
+ 'displayName': 'Shadow User 2',
|
||||
+ 'givenname': 'Shadow2',
|
||||
+ 'mail': f'shadowuser2@{DEFAULT_SUFFIX}',
|
||||
+ 'userpassword': 'password'
|
||||
+ })
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Clean up - delete second user and reset global policy')
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+ try:
|
||||
+ shadowUser2.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+ inst.config.replace('passwordMinAge', '0')
|
||||
+ inst.config.replace('passwordMaxAge', '8640000')
|
||||
+ inst.config.replace('passwordWarning', '86400')
|
||||
+ inst.config.replace('passwordExp', 'off')
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ log.info('Configure global password policy')
|
||||
+ pwp_mgr = PwPolicyManager(inst)
|
||||
+ setup_pwp(inst, pwp_mgr, 'global')
|
||||
+
|
||||
+ edate = int(time.time() / (60 * 60 * 24))
|
||||
+
|
||||
+ log.info('Verify attributes of shadowUser (user 1)')
|
||||
+ shadowUser.rebind('password')
|
||||
+ check_shadow_attr_value(inst, shadowUser.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+ check_shadow_attr_value(inst, shadowUser.dn,
|
||||
+ 'shadowMin', 1)
|
||||
+ check_shadow_attr_value(inst, shadowUser.dn,
|
||||
+ 'shadowMax', 10)
|
||||
+ check_shadow_attr_value(inst, shadowUser.dn,
|
||||
+ 'shadowWarning', 3)
|
||||
+
|
||||
+ log.info('Verify attributes of shadowUser2 (user 2)')
|
||||
+ shadowUser2.rebind('password')
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowMin', 1)
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowMax', 10)
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowWarning', 3)
|
||||
+
|
||||
+ log.info('Modify global password policy')
|
||||
+ modify_pwp(inst, pwp_mgr, 'global')
|
||||
+
|
||||
+ log.info('Change shadowUser2 password as the user')
|
||||
+ shadowUser2.rebind('password')
|
||||
+ shadowUser2.replace('userpassword', 'password2')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ log.info('Re-bind as shadowUser2 with new password')
|
||||
+ shadowUser2.rebind('password2')
|
||||
+
|
||||
+ log.info('Verify modified shadowUser2 attributes')
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowMin', 3)
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowMax', 30)
|
||||
+ check_shadow_attr_value(inst, shadowUser2.dn,
|
||||
+ 'shadowWarning', 9)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
|
||||
+def test_shadowaccount_subtree_policy(topology_st, request):
|
||||
+ """Check shadowAccount with subtree level password policy
|
||||
+
|
||||
+ :id: c3d4e5f6-7a8b-9012-cdef-345678901234
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create subtree password policy for DEFAULT_SUFFIX with passwordMustChange on
|
||||
+ 2. Add a new shadowAccount user under the subtree
|
||||
+ 3. Check shadowLastChange is 0 (since passwordMustChange is on)
|
||||
+ 4. Verify search as user fails with UNWILLING_TO_PERFORM
|
||||
+ 5. Change user password (as user)
|
||||
+ 6. Re-bind with new password
|
||||
+ 7. Check shadowAccount attributes are updated with correct values
|
||||
+ 8. Clean up subtree password policy
|
||||
+ :expectedresults:
|
||||
+ 1. Subtree password policy is created successfully
|
||||
+ 2. User is added successfully
|
||||
+ 3. shadowLastChange is 0 until password is changed
|
||||
+ 4. Search fails with UNWILLING_TO_PERFORM as expected
|
||||
+ 5. Password is changed successfully
|
||||
+ 6. Re-bind with new password is successful
|
||||
+ 7. shadowAccount attributes are updated to match policy values
|
||||
+ 8. Subtree password policy is deleted successfully
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ subtree_dn = DEFAULT_SUFFIX
|
||||
+
|
||||
+ log.info('Configure subtree password policy with passwordMustChange on')
|
||||
+ properties = {
|
||||
+ 'passwordMustChange': 'on',
|
||||
+ 'passwordExp': 'on',
|
||||
+ 'passwordMinAge': str(days_to_secs(2)),
|
||||
+ 'passwordMaxAge': str(days_to_secs(20)),
|
||||
+ 'passwordWarning': str(days_to_secs(6)),
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordStorageScheme': 'clear'
|
||||
+ }
|
||||
+
|
||||
+ pwp_mgr = PwPolicyManager(inst)
|
||||
+ setup_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=properties)
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Clean up: delete subtree password policy')
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+ try:
|
||||
+ pwp_mgr.delete_local_policy(subtree_dn)
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+ try:
|
||||
+ subtree_user.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ log.info('Add a new shadowAccount user under the subtree')
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ subtree_user = users.create(properties={
|
||||
+ 'objectclass': ['top', 'person', 'organizationalPerson',
|
||||
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
|
||||
+ 'sn': '3',
|
||||
+ 'cn': 'subtreeUser',
|
||||
+ 'uid': 'subtreeUser',
|
||||
+ 'uidNumber': '3',
|
||||
+ 'gidNumber': '33',
|
||||
+ 'homeDirectory': '/home/subtreeUser',
|
||||
+ 'displayName': 'Subtree User',
|
||||
+ 'givenname': 'Subtree',
|
||||
+ 'mail': f'subtreeuser@{DEFAULT_SUFFIX}',
|
||||
+ 'userpassword': 'password'
|
||||
+ })
|
||||
+
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+
|
||||
+ log.info('Verify shadowLastChange is 0 since passwordMustChange is on')
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowLastChange', 0)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowMin', 2)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowMax', 20)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowWarning', 6)
|
||||
+
|
||||
+ log.info(f'Bind as {subtree_user.dn} and verify search fails with UNWILLING_TO_PERFORM')
|
||||
+ subtree_user.rebind('password')
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ subtree_user.exists()
|
||||
+
|
||||
+ log.info('Modify subtree password policy')
|
||||
+ dm.rebind()
|
||||
+ modify_properties = {
|
||||
+ 'passwordMinAge': str(days_to_secs(4)),
|
||||
+ 'passwordMaxAge': str(days_to_secs(40)),
|
||||
+ 'passwordWarning': str(days_to_secs(12))
|
||||
+ }
|
||||
+ modify_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=modify_properties)
|
||||
+
|
||||
+ log.info(f'Change {subtree_user.dn} password as the user')
|
||||
+ subtree_user.rebind('password')
|
||||
+ subtree_user.replace('userpassword', 'password0')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ log.info(f'Re-bind as {subtree_user.dn} with new password')
|
||||
+ subtree_user.rebind('password0')
|
||||
+
|
||||
+ edate = int(time.time() / (60 * 60 * 24))
|
||||
+
|
||||
+ log.info('Verify shadowLastChange is now set to today after password change')
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowMin', 4)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowMax', 40)
|
||||
+ check_shadow_attr_value(inst, subtree_user.dn,
|
||||
+ 'shadowWarning', 12)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
|
||||
+def test_shadowaccount_user_policy(topology_st, request):
|
||||
+ """Check shadowAccount with user level password policy
|
||||
+
|
||||
+ :id: d4e5f6a7-8b9c-0123-def0-456789012345
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create a new shadowAccount user
|
||||
+ 2. Create user password policy
|
||||
+ 3. Verify shadowAccount attributes match policy values
|
||||
+ 4. Modify user password policy
|
||||
+ 5. Change user password
|
||||
+ 6. Re-bind with new password
|
||||
+ 7. Check shadowAccount attributes are updated
|
||||
+ 8. Clean up user password policy
|
||||
+ :expectedresults:
|
||||
+ 1. User is created successfully
|
||||
+ 2. User password policy is created successfully
|
||||
+ 3. shadowAccount attributes match policy values
|
||||
+ 4. Password policy is modified successfully
|
||||
+ 5. Password is changed successfully
|
||||
+ 6. Re-bind with new password is successful
|
||||
+ 7. shadowAccount attributes are updated to match new policy values
|
||||
+ 8. User password policy is deleted successfully
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ log.info('Create a new shadowAccount user for user policy test')
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user_policy_user = users.create(properties={
|
||||
+ 'objectclass': ['top', 'person', 'organizationalPerson',
|
||||
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
|
||||
+ 'sn': '4',
|
||||
+ 'cn': 'userPolicyUser',
|
||||
+ 'uid': 'userPolicyUser',
|
||||
+ 'uidNumber': '4',
|
||||
+ 'gidNumber': '44',
|
||||
+ 'homeDirectory': '/home/userPolicyUser',
|
||||
+ 'displayName': 'User Policy User',
|
||||
+ 'givenname': 'UserPolicy',
|
||||
+ 'mail': f'userpolicyuser@{DEFAULT_SUFFIX}',
|
||||
+ 'userpassword': 'password'
|
||||
+ })
|
||||
+
|
||||
+ pwp_mgr = PwPolicyManager(inst)
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Clean up: delete user password policy and user')
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+ try:
|
||||
+ pwp_mgr.delete_local_policy(user_policy_user.dn)
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+ try:
|
||||
+ user_policy_user.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ log.info('Configure user password policy')
|
||||
+ properties = {
|
||||
+ 'passwordExp': 'on',
|
||||
+ 'passwordMinAge': str(days_to_secs(2)),
|
||||
+ 'passwordMaxAge': str(days_to_secs(20)),
|
||||
+ 'passwordWarning': str(days_to_secs(6)),
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordStorageScheme': 'clear'
|
||||
+ }
|
||||
+ setup_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=properties)
|
||||
+
|
||||
+ edate = int(time.time() / (60 * 60 * 24))
|
||||
+
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.rebind()
|
||||
+
|
||||
+ log.info('Verify shadowAccount attributes match user policy')
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowMin', 2)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowMax', 20)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowWarning', 6)
|
||||
+
|
||||
+ log.info('Modify user password policy')
|
||||
+ modify_properties = {
|
||||
+ 'passwordMinAge': str(days_to_secs(4)),
|
||||
+ 'passwordMaxAge': str(days_to_secs(40)),
|
||||
+ 'passwordWarning': str(days_to_secs(12))
|
||||
+ }
|
||||
+ modify_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=modify_properties)
|
||||
+
|
||||
+ log.info(f'Change {user_policy_user.dn} password as the user')
|
||||
+ user_policy_user.rebind('password')
|
||||
+ user_policy_user.replace('userpassword', 'password0')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ log.info(f'Re-bind as {user_policy_user.dn} with new password')
|
||||
+ user_policy_user.rebind('password0')
|
||||
+
|
||||
+ edate = int(time.time() / (60 * 60 * 24))
|
||||
+
|
||||
+ log.info('Verify shadowAccount attributes are updated after password change')
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowLastChange', edate)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowMin', 4)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowMax', 40)
|
||||
+ check_shadow_attr_value(inst, user_policy_user.dn,
|
||||
+ 'shadowWarning', 12)
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/dirsrvtests/tests/tickets/ticket548_test.py b/dirsrvtests/tests/tickets/ticket548_test.py
|
||||
deleted file mode 100644
|
||||
index cac3cc5f8..000000000
|
||||
--- a/dirsrvtests/tests/tickets/ticket548_test.py
|
||||
+++ /dev/null
|
||||
@@ -1,408 +0,0 @@
|
||||
-# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2016 Red Hat, Inc.
|
||||
-# All rights reserved.
|
||||
-#
|
||||
-# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
-# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
-import pytest
|
||||
-from lib389.tasks import *
|
||||
-from lib389.utils import *
|
||||
-from lib389.topologies import topology_st
|
||||
-
|
||||
-from lib389._constants import DEFAULT_SUFFIX, DN_CONFIG, DN_DM, PASSWORD, DEFAULT_SUFFIX_ESCAPED
|
||||
-
|
||||
-# Skip on older versions
|
||||
-pytestmark = [pytest.mark.tier2,
|
||||
- pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")]
|
||||
-
|
||||
-log = logging.getLogger(__name__)
|
||||
-
|
||||
-# Assuming DEFAULT_SUFFIX is "dc=example,dc=com", otherwise it does not work... :(
|
||||
-SUBTREE_CONTAINER = 'cn=nsPwPolicyContainer,' + DEFAULT_SUFFIX
|
||||
-SUBTREE_PWPDN = 'cn=nsPwPolicyEntry,' + DEFAULT_SUFFIX
|
||||
-SUBTREE_PWP = 'cn=cn\3DnsPwPolicyEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER
|
||||
-SUBTREE_COS_TMPLDN = 'cn=nsPwTemplateEntry,' + DEFAULT_SUFFIX
|
||||
-SUBTREE_COS_TMPL = 'cn=cn\3DnsPwTemplateEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER
|
||||
-SUBTREE_COS_DEF = 'cn=nsPwPolicy_CoS,' + DEFAULT_SUFFIX
|
||||
-
|
||||
-USER1_DN = 'uid=user1,' + DEFAULT_SUFFIX
|
||||
-USER2_DN = 'uid=user2,' + DEFAULT_SUFFIX
|
||||
-USER3_DN = 'uid=user3,' + DEFAULT_SUFFIX
|
||||
-USER_PW = 'password'
|
||||
-
|
||||
-
|
||||
-def days_to_secs(days):
|
||||
- # Value of 60 * 60 * 24
|
||||
- return days * 86400
|
||||
-
|
||||
-
|
||||
-# Values are in days
|
||||
-def set_global_pwpolicy(topology_st, min_=1, max_=10, warn=3):
|
||||
- log.info(" +++++ Enable global password policy +++++\n")
|
||||
- # Enable password policy
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', b'on')])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to set pwpolicy-local: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- # Convert our values to seconds
|
||||
- min_secs = days_to_secs(min_)
|
||||
- max_secs = days_to_secs(max_)
|
||||
- warn_secs = days_to_secs(warn)
|
||||
-
|
||||
- log.info(" Set global password Min Age -- %s day\n" % min_)
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMinAge', ('%s' % min_secs).encode())])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to set passwordMinAge: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info(" Set global password Expiration -- on\n")
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordExp', b'on')])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to set passwordExp: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info(" Set global password Max Age -- %s days\n" % max_)
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMaxAge', ('%s' % max_secs).encode())])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to set passwordMaxAge: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info(" Set global password Warning -- %s days\n" % warn)
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordWarning', ('%s' % warn_secs).encode())])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to set passwordWarning: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
-
|
||||
-def set_subtree_pwpolicy(topology_st, min_=2, max_=20, warn=6):
|
||||
- log.info(" +++++ Enable subtree level password policy +++++\n")
|
||||
-
|
||||
- # Convert our values to seconds
|
||||
- min_secs = days_to_secs(min_)
|
||||
- max_secs = days_to_secs(max_)
|
||||
- warn_secs = days_to_secs(warn)
|
||||
-
|
||||
- log.info(" Add the container")
|
||||
- try:
|
||||
- topology_st.standalone.add_s(Entry((SUBTREE_CONTAINER, {'objectclass': 'top nsContainer'.split(),
|
||||
- 'cn': 'nsPwPolicyContainer'})))
|
||||
- except ldap.ALREADY_EXISTS:
|
||||
- pass
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to add subtree container: error ' + e.message['desc'])
|
||||
- # assert False
|
||||
-
|
||||
- try:
|
||||
- # Purge the old policy
|
||||
- topology_st.standalone.delete_s(SUBTREE_PWP)
|
||||
- except:
|
||||
- pass
|
||||
-
|
||||
- log.info(
|
||||
- " Add the password policy subentry {passwordMustChange: on, passwordMinAge: %s, passwordMaxAge: %s, passwordWarning: %s}" % (
|
||||
- min_, max_, warn))
|
||||
- try:
|
||||
- topology_st.standalone.add_s(Entry((SUBTREE_PWP, {'objectclass': 'top ldapsubentry passwordpolicy'.split(),
|
||||
- 'cn': SUBTREE_PWPDN,
|
||||
- 'passwordMustChange': 'on',
|
||||
- 'passwordExp': 'on',
|
||||
- 'passwordMinAge': '%s' % min_secs,
|
||||
- 'passwordMaxAge': '%s' % max_secs,
|
||||
- 'passwordWarning': '%s' % warn_secs,
|
||||
- 'passwordChange': 'on',
|
||||
- 'passwordStorageScheme': 'clear'})))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to add passwordpolicy: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info(" Add the COS template")
|
||||
- try:
|
||||
- topology_st.standalone.add_s(
|
||||
- Entry((SUBTREE_COS_TMPL, {'objectclass': 'top ldapsubentry costemplate extensibleObject'.split(),
|
||||
- 'cn': SUBTREE_PWPDN,
|
||||
- 'cosPriority': '1',
|
||||
- 'cn': SUBTREE_COS_TMPLDN,
|
||||
- 'pwdpolicysubentry': SUBTREE_PWP})))
|
||||
- except ldap.ALREADY_EXISTS:
|
||||
- pass
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to add COS template: error ' + e.message['desc'])
|
||||
- # assert False
|
||||
-
|
||||
- log.info(" Add the COS definition")
|
||||
- try:
|
||||
- topology_st.standalone.add_s(
|
||||
- Entry((SUBTREE_COS_DEF, {'objectclass': 'top ldapsubentry cosSuperDefinition cosPointerDefinition'.split(),
|
||||
- 'cn': SUBTREE_PWPDN,
|
||||
- 'costemplatedn': SUBTREE_COS_TMPL,
|
||||
- 'cosAttribute': 'pwdpolicysubentry default operational-default'})))
|
||||
- except ldap.ALREADY_EXISTS:
|
||||
- pass
|
||||
- except ldap.LDAPError as e:
|
||||
- log.error('Failed to add COS def: error ' + e.message['desc'])
|
||||
- # assert False
|
||||
-
|
||||
- time.sleep(1)
|
||||
-
|
||||
-
|
||||
-def update_passwd(topology_st, user, passwd, newpasswd):
|
||||
- log.info(" Bind as {%s,%s}" % (user, passwd))
|
||||
- topology_st.standalone.simple_bind_s(user, passwd)
|
||||
- try:
|
||||
- topology_st.standalone.modify_s(user, [(ldap.MOD_REPLACE, 'userpassword', newpasswd.encode())])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to update the password ' + cpw + ' of user ' + user + ': error ' + e.message[
|
||||
- 'desc'])
|
||||
- assert False
|
||||
-
|
||||
- time.sleep(1)
|
||||
-
|
||||
-
|
||||
-def check_shadow_attr_value(entry, attr_type, expected, dn):
|
||||
- if entry.hasAttr(attr_type):
|
||||
- actual = entry.getValue(attr_type)
|
||||
- if int(actual) == expected:
|
||||
- log.info('%s of entry %s has expected value %s' % (attr_type, dn, actual))
|
||||
- assert True
|
||||
- else:
|
||||
- log.fatal('%s %s of entry %s does not have expected value %s' % (attr_type, actual, dn, expected))
|
||||
- assert False
|
||||
- else:
|
||||
- log.fatal('entry %s does not have %s attr' % (dn, attr_type))
|
||||
- assert False
|
||||
-
|
||||
-
|
||||
-def test_ticket548_test_with_no_policy(topology_st):
|
||||
- """
|
||||
- Check shadowAccount under no password policy
|
||||
- """
|
||||
- log.info("Case 1. No password policy")
|
||||
-
|
||||
- log.info("Bind as %s" % DN_DM)
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
-
|
||||
- log.info('Add an entry' + USER1_DN)
|
||||
- try:
|
||||
- topology_st.standalone.add_s(
|
||||
- Entry((USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
|
||||
- 'sn': '1',
|
||||
- 'cn': 'user 1',
|
||||
- 'uid': 'user1',
|
||||
- 'givenname': 'user',
|
||||
- 'mail': 'user1@' + DEFAULT_SUFFIX,
|
||||
- 'userpassword': USER_PW})))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to add user' + USER1_DN + ': error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- edate = int(time.time() / (60 * 60 * 24))
|
||||
- log.info('Search entry %s' % USER1_DN)
|
||||
-
|
||||
- log.info("Bind as %s" % USER1_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW)
|
||||
- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['shadowLastChange'])
|
||||
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN)
|
||||
-
|
||||
- log.info("Check shadowAccount with no policy was successfully verified.")
|
||||
-
|
||||
-
|
||||
-def test_ticket548_test_global_policy(topology_st):
|
||||
- """
|
||||
- Check shadowAccount with global password policy
|
||||
- """
|
||||
-
|
||||
- log.info("Case 2. Check shadowAccount with global password policy")
|
||||
-
|
||||
- log.info("Bind as %s" % DN_DM)
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
-
|
||||
- set_global_pwpolicy(topology_st)
|
||||
-
|
||||
- log.info('Add an entry' + USER2_DN)
|
||||
- try:
|
||||
- topology_st.standalone.add_s(
|
||||
- Entry((USER2_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
|
||||
- 'sn': '2',
|
||||
- 'cn': 'user 2',
|
||||
- 'uid': 'user2',
|
||||
- 'givenname': 'user',
|
||||
- 'mail': 'user2@' + DEFAULT_SUFFIX,
|
||||
- 'userpassword': USER_PW})))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to add user' + USER2_DN + ': error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- edate = int(time.time() / (60 * 60 * 24))
|
||||
-
|
||||
- log.info("Bind as %s" % USER1_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW)
|
||||
-
|
||||
- log.info('Search entry %s' % USER1_DN)
|
||||
- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN)
|
||||
-
|
||||
- # passwordMinAge -- 1 day
|
||||
- check_shadow_attr_value(entry, 'shadowMin', 1, USER1_DN)
|
||||
-
|
||||
- # passwordMaxAge -- 10 days
|
||||
- check_shadow_attr_value(entry, 'shadowMax', 10, USER1_DN)
|
||||
-
|
||||
- # passwordWarning -- 3 days
|
||||
- check_shadow_attr_value(entry, 'shadowWarning', 3, USER1_DN)
|
||||
-
|
||||
- log.info("Bind as %s" % USER2_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW)
|
||||
-
|
||||
- log.info('Search entry %s' % USER2_DN)
|
||||
- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER2_DN)
|
||||
-
|
||||
- # passwordMinAge -- 1 day
|
||||
- check_shadow_attr_value(entry, 'shadowMin', 1, USER2_DN)
|
||||
-
|
||||
- # passwordMaxAge -- 10 days
|
||||
- check_shadow_attr_value(entry, 'shadowMax', 10, USER2_DN)
|
||||
-
|
||||
- # passwordWarning -- 3 days
|
||||
- check_shadow_attr_value(entry, 'shadowWarning', 3, USER2_DN)
|
||||
-
|
||||
- # Bind as DM again, change policy
|
||||
- log.info("Bind as %s" % DN_DM)
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
- set_global_pwpolicy(topology_st, 3, 30, 9)
|
||||
-
|
||||
- # change the user password, then check again.
|
||||
- log.info("Bind as %s" % USER2_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW)
|
||||
-
|
||||
- newpasswd = USER_PW + '2'
|
||||
- update_passwd(topology_st, USER2_DN, USER_PW, newpasswd)
|
||||
-
|
||||
- log.info("Re-bind as %s with new password" % USER2_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER2_DN, newpasswd)
|
||||
-
|
||||
- ## This tests if we update the shadow values on password change.
|
||||
- log.info('Search entry %s' % USER2_DN)
|
||||
- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
-
|
||||
- # passwordMinAge -- 1 day
|
||||
- check_shadow_attr_value(entry, 'shadowMin', 3, USER2_DN)
|
||||
-
|
||||
- # passwordMaxAge -- 10 days
|
||||
- check_shadow_attr_value(entry, 'shadowMax', 30, USER2_DN)
|
||||
-
|
||||
- # passwordWarning -- 3 days
|
||||
- check_shadow_attr_value(entry, 'shadowWarning', 9, USER2_DN)
|
||||
-
|
||||
- log.info("Check shadowAccount with global policy was successfully verified.")
|
||||
-
|
||||
-
|
||||
-def test_ticket548_test_subtree_policy(topology_st):
|
||||
- """
|
||||
- Check shadowAccount with subtree level password policy
|
||||
- """
|
||||
-
|
||||
- log.info("Case 3. Check shadowAccount with subtree level password policy")
|
||||
-
|
||||
- log.info("Bind as %s" % DN_DM)
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
- # Check the global policy values
|
||||
-
|
||||
- set_subtree_pwpolicy(topology_st, 2, 20, 6)
|
||||
-
|
||||
- log.info('Add an entry' + USER3_DN)
|
||||
- try:
|
||||
- topology_st.standalone.add_s(
|
||||
- Entry((USER3_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
|
||||
- 'sn': '3',
|
||||
- 'cn': 'user 3',
|
||||
- 'uid': 'user3',
|
||||
- 'givenname': 'user',
|
||||
- 'mail': 'user3@' + DEFAULT_SUFFIX,
|
||||
- 'userpassword': USER_PW})))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to add user' + USER3_DN + ': error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info('Search entry %s' % USER3_DN)
|
||||
- entry0 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
-
|
||||
- log.info('Expecting shadowLastChange 0 since passwordMustChange is on')
|
||||
- check_shadow_attr_value(entry0, 'shadowLastChange', 0, USER3_DN)
|
||||
-
|
||||
- # passwordMinAge -- 2 day
|
||||
- check_shadow_attr_value(entry0, 'shadowMin', 2, USER3_DN)
|
||||
-
|
||||
- # passwordMaxAge -- 20 days
|
||||
- check_shadow_attr_value(entry0, 'shadowMax', 20, USER3_DN)
|
||||
-
|
||||
- # passwordWarning -- 6 days
|
||||
- check_shadow_attr_value(entry0, 'shadowWarning', 6, USER3_DN)
|
||||
-
|
||||
- log.info("Bind as %s" % USER3_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW)
|
||||
-
|
||||
- log.info('Search entry %s' % USER3_DN)
|
||||
- try:
|
||||
- entry1 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
- except ldap.UNWILLING_TO_PERFORM:
|
||||
- log.info('test_ticket548: Search by' + USER3_DN + ' failed by UNWILLING_TO_PERFORM as expected')
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info("Bind as %s and updating the password with a new one" % USER3_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW)
|
||||
-
|
||||
- # Bind as DM again, change policy
|
||||
- log.info("Bind as %s" % DN_DM)
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
-
|
||||
- set_subtree_pwpolicy(topology_st, 4, 40, 12)
|
||||
-
|
||||
- newpasswd = USER_PW + '0'
|
||||
- update_passwd(topology_st, USER3_DN, USER_PW, newpasswd)
|
||||
-
|
||||
- log.info("Re-bind as %s with new password" % USER3_DN)
|
||||
- topology_st.standalone.simple_bind_s(USER3_DN, newpasswd)
|
||||
-
|
||||
- try:
|
||||
- entry2 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
- edate = int(time.time() / (60 * 60 * 24))
|
||||
-
|
||||
- log.info('Expecting shadowLastChange %d once userPassword is updated', edate)
|
||||
- check_shadow_attr_value(entry2, 'shadowLastChange', edate, USER3_DN)
|
||||
-
|
||||
- log.info('Search entry %s' % USER3_DN)
|
||||
- entry = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
||||
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER3_DN)
|
||||
-
|
||||
- # passwordMinAge -- 1 day
|
||||
- check_shadow_attr_value(entry, 'shadowMin', 4, USER3_DN)
|
||||
-
|
||||
- # passwordMaxAge -- 10 days
|
||||
- check_shadow_attr_value(entry, 'shadowMax', 40, USER3_DN)
|
||||
-
|
||||
- # passwordWarning -- 3 days
|
||||
- check_shadow_attr_value(entry, 'shadowWarning', 12, USER3_DN)
|
||||
-
|
||||
- log.info("Check shadowAccount with subtree level policy was successfully verified.")
|
||||
-
|
||||
-
|
||||
-if __name__ == '__main__':
|
||||
- # Run isolated
|
||||
- # -s for DEBUG mode
|
||||
- CURRENT_FILE = os.path.realpath(__file__)
|
||||
- pytest.main("-s %s" % CURRENT_FILE)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
From 7c8a16c6bed524fb54d18a5b7e93d4bd5bb19d49 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 14 Jan 2026 17:55:29 +0100
|
||||
Subject: [PATCH] Issue 7152 - ns-slapd fails to shutdown when deferred
|
||||
memberof update is in progress (#7187)
|
||||
|
||||
Bug Description:
|
||||
When a deferred memberof update is in progress during shutdown, the
|
||||
backend operations (add, modify, delete, modrdn) wait in a polling loop
|
||||
for the deferred task to complete. However, if the deferred thread exits
|
||||
before clearing the SLAPI_DEFERRED_MEMBEROF flag, the loop becomes
|
||||
infinite, causing the server to hang during shutdown.
|
||||
|
||||
Fix Description:
|
||||
Add additional check to the polling loops so they exit immediately when
|
||||
the server is shutting down.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7152
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 2 +-
|
||||
4 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
index db6024636..90d5abc3d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
@@ -1452,7 +1452,7 @@ common_return:
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
if (deferred) {
|
||||
PRIntervalTime delay = PR_MillisecondsToInterval(100);
|
||||
- while (deferred) {
|
||||
+ while (deferred && !g_get_shutdown()) {
|
||||
DS_Sleep(delay);
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
index 498342f2d..cbfb5bca9 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
@@ -1536,7 +1536,7 @@ diskfull_return:
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
if (deferred) {
|
||||
PRIntervalTime delay = PR_MillisecondsToInterval(100);
|
||||
- while (deferred) {
|
||||
+ while (deferred && !g_get_shutdown()) {
|
||||
DS_Sleep(delay);
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
index ea49a4c56..c57ba43ae 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
@@ -1179,7 +1179,7 @@ common_return:
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
if (deferred) {
|
||||
PRIntervalTime delay = PR_MillisecondsToInterval(100);
|
||||
- while (deferred) {
|
||||
+ while (deferred && !g_get_shutdown()) {
|
||||
DS_Sleep(delay);
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
index 018ad9e49..759edb80d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
@@ -1469,7 +1469,7 @@ common_return:
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
if (deferred) {
|
||||
PRIntervalTime delay = PR_MillisecondsToInterval(100);
|
||||
- while (deferred) {
|
||||
+ while (deferred && !g_get_shutdown()) {
|
||||
DS_Sleep(delay);
|
||||
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
From a84a6a7d8323316bfa4055729a3604a0fcfebaf9 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Fri, 16 Jan 2026 19:35:42 +0530
|
||||
Subject: [PATCH] Issue 7169 - Fix automember_plugin CI test failures (#7181)
|
||||
|
||||
Description: Issue 7053 removed member cleanup from MemberOf plugin,
|
||||
transferring it to Referential Integrity plugin. Enable this plugin
|
||||
in automember tests and clean up groups before rebuild task tests.
|
||||
|
||||
Fixes: #7169
|
||||
|
||||
Reviewed by: @progier389
|
||||
---
|
||||
.../tests/suites/automember_plugin/basic_test.py | 16 ++++++++++++++--
|
||||
1 file changed, 14 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/automember_plugin/basic_test.py b/dirsrvtests/tests/suites/automember_plugin/basic_test.py
|
||||
index 6f2cf3326..f3629c811 100644
|
||||
--- a/dirsrvtests/tests/suites/automember_plugin/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/automember_plugin/basic_test.py
|
||||
@@ -19,7 +19,8 @@ from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389.idm.domain import Domain
|
||||
from lib389.idm.posixgroup import PosixGroups
|
||||
from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions, \
|
||||
- MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin
|
||||
+ MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin, \
|
||||
+ ReferentialIntegrityPlugin
|
||||
from lib389.backend import Backends
|
||||
from lib389.config import Config
|
||||
from lib389._constants import DEFAULT_SUFFIX
|
||||
@@ -196,6 +197,7 @@ def _create_all_entries(topo):
|
||||
auto = AutoMembershipPlugin(topo.ms["supplier1"])
|
||||
auto.add("nsslapd-pluginConfigArea", "cn=autoMembersPlugin,{}".format(BASE_REPL))
|
||||
MemberOfPlugin(topo.ms["supplier1"]).enable()
|
||||
+ ReferentialIntegrityPlugin(topo.ms["supplier1"]).enable()
|
||||
automembers_definitions = AutoMembershipDefinitions(topo.ms["supplier1"])
|
||||
automembers_definitions.create(properties={
|
||||
'cn': 'userGroups',
|
||||
@@ -950,8 +952,18 @@ def _startuptask(topo):
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def _fixture_for_build_task(request, topo):
|
||||
+ supplier = topo.ms['supplier1']
|
||||
+ managers_grp = "cn=Managers,ou=userGroups,{}".format(BASE_SUFF)
|
||||
+ contract_grp = "cn=Contractors,ou=userGroups,{}".format(BASE_SUFF)
|
||||
+
|
||||
+ for grp in (managers_grp, contract_grp):
|
||||
+ group = Group(supplier, grp)
|
||||
+ try:
|
||||
+ group.remove_all('member')
|
||||
+ except ldap.NO_SUCH_ATTRIBUTE:
|
||||
+ pass
|
||||
+
|
||||
def finof():
|
||||
- supplier = topo.ms['supplier1']
|
||||
auto_mem_scope = "ou=TaskEmployees,{}".format(BASE_SUFF)
|
||||
for user in nsAdminGroups(supplier, auto_mem_scope, rdn=None).list():
|
||||
user.delete()
|
||||
--
|
||||
2.52.0
|
||||
|
||||
115
0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch
Normal file
115
0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch
Normal file
@ -0,0 +1,115 @@
|
||||
From 5b32479abcd68f8b37d2fb207c502113a5b4b16c Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Mon, 19 Jan 2026 19:45:29 +0530
|
||||
Subject: [PATCH] Issue 6758 - Use OUIA selectors for WebUI plugin tests
|
||||
(#7182)
|
||||
|
||||
Description:
|
||||
Add ouiaId to plugin NavItems and update tests to use OUIA selectors
|
||||
instead of text matching.
|
||||
|
||||
Relates: #6758
|
||||
|
||||
Reviewed by: @droideck
|
||||
---
|
||||
.../suites/webui/plugins/plugins_test.py | 28 +++++++++----------
|
||||
src/cockpit/389-console/src/plugins.jsx | 2 +-
|
||||
2 files changed, 15 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
|
||||
index e4bd7f039..a849bfb91 100644
|
||||
--- a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
|
||||
+++ b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
|
||||
@@ -168,8 +168,8 @@ def test_linked_attributes_plugin_visibility(topology_st, page, browser_name):
|
||||
|
||||
log.info('Click on Plugins tab, click on Linked Attributes plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('Linked Attributes').wait_for()
|
||||
- frame.get_by_text('Linked Attributes').click()
|
||||
+ frame.locator('[data-ouia-component-id="linkedAttributes"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="linkedAttributes"]').click()
|
||||
frame.get_by_role('button', name='Add Config').wait_for()
|
||||
assert frame.get_by_role('button', name='Add Config').is_visible()
|
||||
|
||||
@@ -254,8 +254,8 @@ def test_ldap_pass_through_auth_plugin_visibility(topology_st, page, browser_nam
|
||||
|
||||
log.info('Click on Plugins tab, click on LDAP Pass Through Auth plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('LDAP Pass Through Auth').wait_for()
|
||||
- frame.get_by_text('LDAP Pass Through Auth').click()
|
||||
+ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').click()
|
||||
frame.get_by_role('button', name='Add URL').wait_for()
|
||||
assert frame.get_by_role('button', name='Add URL').is_visible()
|
||||
|
||||
@@ -280,8 +280,8 @@ def test_pam_pass_through_auth_plugin_visibility(topology_st, page, browser_name
|
||||
|
||||
log.info('Click on Plugins tab, click on PAM Pass Through Auth plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('PAM Pass Through Auth').wait_for()
|
||||
- frame.get_by_text('PAM Pass Through Auth').click()
|
||||
+ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').click()
|
||||
frame.get_by_role('button', name='Add Config').wait_for()
|
||||
assert frame.get_by_role('button', name='Add Config').is_visible()
|
||||
|
||||
@@ -306,8 +306,8 @@ def test_posix_winsync_plugin_visibility(topology_st, page, browser_name):
|
||||
|
||||
log.info('Click on Plugins tab, click on Posix Winsync plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('Posix Winsync').wait_for()
|
||||
- frame.get_by_text('Posix Winsync').click()
|
||||
+ frame.locator('[data-ouia-component-id="winsync"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="winsync"]').click()
|
||||
frame.locator('#posixWinsyncCreateMemberOfTask').wait_for()
|
||||
assert frame.locator('#posixWinsyncCreateMemberOfTask').is_visible()
|
||||
|
||||
@@ -332,8 +332,8 @@ def test_referential_integrity_plugin_visibility(topology_st, page, browser_name
|
||||
|
||||
log.info('Click on Plugins tab, click on Referential Integrity plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('Referential Integrity').wait_for()
|
||||
- frame.get_by_text('Referential Integrity').click()
|
||||
+ frame.locator('[data-ouia-component-id="referentialIntegrity"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="referentialIntegrity"]').click()
|
||||
frame.locator('#entryScope').wait_for()
|
||||
assert frame.locator('#entryScope').is_visible()
|
||||
|
||||
@@ -358,8 +358,8 @@ def test_retro_changelog_plugin_visibility(topology_st, page, browser_name):
|
||||
|
||||
log.info('Click on Plugins tab, click on Retro Changelog plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('Retro Changelog').wait_for()
|
||||
- frame.get_by_text('Retro Changelog').click()
|
||||
+ frame.locator('[data-ouia-component-id="retroChangelog"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="retroChangelog"]').click()
|
||||
frame.locator('#isReplicated').wait_for()
|
||||
assert frame.locator('#isReplicated').is_visible()
|
||||
|
||||
@@ -384,8 +384,8 @@ def test_rootdn_access_control_plugin_visibility(topology_st, page, browser_name
|
||||
|
||||
log.info('Click on Plugins tab, click on RootDN Access Control plugin and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Plugins', exact=True).click()
|
||||
- frame.get_by_text('RootDN Access Control').wait_for()
|
||||
- frame.get_by_text('RootDN Access Control').click()
|
||||
+ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').wait_for()
|
||||
+ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').click()
|
||||
frame.locator('#allowMon').wait_for()
|
||||
assert frame.locator('#allowMon').is_visible()
|
||||
|
||||
diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx
|
||||
index 4124a77ef..e24255c76 100644
|
||||
--- a/src/cockpit/389-console/src/plugins.jsx
|
||||
+++ b/src/cockpit/389-console/src/plugins.jsx
|
||||
@@ -680,7 +680,7 @@ export class Plugins extends React.Component {
|
||||
<Nav key={this.state.pluginTableKey} theme="light" onSelect={(event, item) => this.handleSelect(event, item)}>
|
||||
<NavList>
|
||||
{Object.entries(selectPlugins).map(([id, item]) => (
|
||||
- <NavItem key={item.name} itemId={item.name} isActive={this.state.activePlugin === item.name}>
|
||||
+ <NavItem key={item.name} itemId={item.name} ouiaId={id} isActive={this.state.activePlugin === item.name}>
|
||||
{item.icon}
|
||||
</NavItem>
|
||||
))}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
From 9bd93dc618c261d52222e713c56500abbce4113e Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 19 Jan 2026 17:24:40 +0100
|
||||
Subject: [PATCH] Issue 7196 - DynamicCertificates returns empty DER (#7197)
|
||||
|
||||
Fixing a mistake done while fixing memory leaks.
|
||||
Value was freed before being added in the entry rather than after ...
|
||||
|
||||
Issue: #7196
|
||||
|
||||
Reviewed by: @jchapma (thanks!)
|
||||
---
|
||||
ldap/servers/slapd/dyncerts.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/dyncerts.c b/ldap/servers/slapd/dyncerts.c
|
||||
index 50b92aa5f..efeaa6eb6 100644
|
||||
--- a/ldap/servers/slapd/dyncerts.c
|
||||
+++ b/ldap/servers/slapd/dyncerts.c
|
||||
@@ -786,8 +786,8 @@ dyncerts_cert2entry(CERTCertificate *cert)
|
||||
COND_STR(e, DYCATTR_TYPE, "OBJECT SIGNING CA", cert->nsCertType & NS_CERT_TYPE_OBJECT_SIGNING_CA);
|
||||
slapi_entry_add_string(e, DYCATTR_TOKEN, PK11_GetTokenName(cert->slot));
|
||||
secitemv(&cert->derCert, &tmpv);
|
||||
- value_done(&tmpv);
|
||||
slapi_entry_add_value(e, DYCATTR_CERTDER, &tmpv);
|
||||
+ value_done(&tmpv);
|
||||
tmpstr = secitem2hex(&cert->serialNumber);
|
||||
slapi_entry_add_string(e, DYCATTR_SN, tmpstr);
|
||||
slapi_ch_free_string(&tmpstr);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
235
0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
Normal file
235
0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
Normal file
@ -0,0 +1,235 @@
|
||||
From c6f458b421598b18a545582441472b910b5ba56e Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 20 Jan 2026 09:52:47 +0100
|
||||
Subject: [PATCH] Issue 7189 - DSBLE0007 generates incorrect remediation
|
||||
commands for scan limits
|
||||
|
||||
Bug Description:
|
||||
|
||||
The generated dsconf commands for fixing missing system indexes had two issues:
|
||||
|
||||
1. The --add-scanlimit value was not quoted, causing the shell to interpret
|
||||
"limit=5000 type=eq flags=AND" as multiple arguments instead of a single
|
||||
value, resulting in "unrecognized arguments: type=eq flags=AND" error.
|
||||
|
||||
2. When both matching rule and scanlimit were missing, two separate commands
|
||||
were generated where the second would fail because the matching rule was
|
||||
already added by the first command.
|
||||
|
||||
Fix Description:
|
||||
|
||||
1. Quote the scanlimit value in all remediation commands
|
||||
|
||||
2. Combine matching rule and scanlimit fixes into a single command when
|
||||
both are missing for the same index instead of expected_scanlimit)
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7189
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 126 ++++++++++++++++++
|
||||
src/lib389/lib389/backend.py | 39 +++---
|
||||
2 files changed, 147 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index a977b71d1..486fad44b 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -408,6 +408,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_missing_scanlimit(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
|
||||
+
|
||||
+ :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove nsIndexIDListScanLimit from parentId index
|
||||
+ 3. Use healthcheck without --json option
|
||||
+ 4. Use healthcheck with --json option
|
||||
+ 5. Verify the remediation command has properly quoted scanlimit
|
||||
+ 6. Re-add the scanlimit
|
||||
+ 7. Use healthcheck without --json option
|
||||
+ 8. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 code and related details
|
||||
+ 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 5. The scanlimit value is quoted in the remediation command
|
||||
+ 6. Success
|
||||
+ 7. healthcheck reports no issues found
|
||||
+ 8. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove nsIndexIDListScanLimit from parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+
|
||||
+ # Verify the remediation command has properly quoted scanlimit
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = standalone.serverid
|
||||
+ args.verbose = standalone.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.exclude_check = []
|
||||
+ args.check = ["backends"]
|
||||
+ args.dry_run = False
|
||||
+ args.json = False
|
||||
+ health_check_run(standalone, topology_st.logcap.log, args)
|
||||
+ # Check that the scanlimit is quoted in the output
|
||||
+ assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
+ log.info("Verified scanlimit is properly quoted in remediation command")
|
||||
+ topology_st.logcap.flush()
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info("Re-add the nsIndexIDListScanLimit")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
+def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
|
||||
+ """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
|
||||
+
|
||||
+ :id: af8214ad-5e4c-422a-8f74-3e99227551df
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
|
||||
+ 3. Use healthcheck and verify a single combined command is generated
|
||||
+ 4. Re-add the matching rule and scanlimit
|
||||
+ 5. Use healthcheck without --json option
|
||||
+ 6. Use healthcheck with --json option
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
|
||||
+ 4. Success
|
||||
+ 5. healthcheck reports no issues found
|
||||
+ 6. healthcheck reports no issues found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = "DSBLE0007"
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+
|
||||
+ log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ # Run healthcheck and verify combined command
|
||||
+ args = FakeArgs()
|
||||
+ args.instance = standalone.serverid
|
||||
+ args.verbose = standalone.verbose
|
||||
+ args.list_errors = False
|
||||
+ args.list_checks = False
|
||||
+ args.exclude_check = []
|
||||
+ args.check = ["backends"]
|
||||
+ args.dry_run = False
|
||||
+ args.json = False
|
||||
+ health_check_run(standalone, topology_st.logcap.log, args)
|
||||
+
|
||||
+ # Verify DSBLE0007 is reported
|
||||
+ assert topology_st.logcap.contains(RET_CODE)
|
||||
+ log.info("healthcheck returned code: %s" % RET_CODE)
|
||||
+
|
||||
+ # Verify a single combined command is generated with both --add-mr and --add-scanlimit
|
||||
+ assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
+ log.info("Verified combined command with both --add-mr and --add-scanlimit")
|
||||
+
|
||||
+ topology_st.logcap.flush()
|
||||
+
|
||||
+ log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+
|
||||
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index fba95987b..db464b43a 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -678,7 +678,7 @@ class Backend(DSLdapObject):
|
||||
if expected_config.get('matching_rule'):
|
||||
cmd += f" --matching-rule {expected_config['matching_rule']}"
|
||||
if expected_config.get('scanlimit'):
|
||||
- cmd += f" --add-scanlimit {expected_config['scanlimit']}"
|
||||
+ cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
else:
|
||||
@@ -700,28 +700,31 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
- # Check matching rules
|
||||
+ # Check matching rules and scanlimit together to generate a single combined command
|
||||
expected_mr = expected_config.get('matching_rule')
|
||||
+ expected_scanlimit = expected_config.get('scanlimit')
|
||||
+
|
||||
+ missing_mr = False
|
||||
if expected_mr:
|
||||
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
if expected_mr.lower() not in actual_mrs_lower:
|
||||
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
|
||||
- # Add the missing matching rule
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
-
|
||||
- # Check fine grain definitions for parentid ONLY
|
||||
- expected_scanlimit = expected_config.get('scanlimit')
|
||||
- if (attr_name.lower() == "parentid") and expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_mr}")
|
||||
- # Add the missing scanlimit
|
||||
- if expected_mr:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr} --add-scanlimit {expected_scanlimit}"
|
||||
- else:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-scanlimit {expected_scanlimit}"
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
+ missing_mr = True
|
||||
+
|
||||
+ missing_scanlimit = False
|
||||
+ if expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
+ discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
|
||||
+ missing_scanlimit = True
|
||||
+
|
||||
+ # Generate a single combined command for all missing items
|
||||
+ if missing_mr or missing_scanlimit:
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
|
||||
+ if missing_mr:
|
||||
+ cmd += f" --add-mr {expected_mr}"
|
||||
+ if missing_scanlimit:
|
||||
+ cmd += f" --add-scanlimit \"{expected_scanlimit}\""
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
--
|
||||
2.52.0
|
||||
|
||||
504
0012-Issue-7170-Support-of-PQC-keys-7188.patch
Normal file
504
0012-Issue-7170-Support-of-PQC-keys-7188.patch
Normal file
@ -0,0 +1,504 @@
|
||||
From 6ce33b1bedd2cd13d7e6544692354715f6e613b8 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 20 Jan 2026 19:41:05 +0100
|
||||
Subject: [PATCH] Issue 7170 - Support of PQC keys (#7188)
|
||||
|
||||
Support of Post Quantum Cryptography Keys in certificates:
|
||||
Added support of a new key type: ML_DSA
|
||||
Enable the following policies (that are not enabled by defaut): ML-DSA-44, ML-DSA-65- ML-DSA-87
|
||||
Replaced deprecated function SSL_ConfigSecureServer by SSL_ConfigServerCert
|
||||
Added test case for ML-DSA certificate. That test case rely of openssl command because python cryptography module does not yet support ML-DSA keys
|
||||
|
||||
Issue: #7170
|
||||
|
||||
Reviewed by: @tbordaz, @droideck and @vashirov (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/tls/mldsa_test.py | 322 +++++++++++++++++++++
|
||||
ldap/servers/slapd/ssl.c | 95 +++++-
|
||||
2 files changed, 407 insertions(+), 10 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/tls/mldsa_test.py b/dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
new file mode 100644
|
||||
index 000000000..2c815088b
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
@@ -0,0 +1,322 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import logging
|
||||
+import pytest
|
||||
+import os
|
||||
+import sys
|
||||
+import itertools
|
||||
+import rpm
|
||||
+import socket
|
||||
+import subprocess
|
||||
+from lib389.utils import ds_is_older
|
||||
+from lib389._constants import DN_DM, PW_DM, DEFAULT_SUFFIX
|
||||
+from lib389.config import Encryption, CertmapLegacy
|
||||
+from lib389.idm.user import UserAccount
|
||||
+from lib389.topologies import topology_st as topo
|
||||
+from tempfile import TemporaryDirectory
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+if DEBUGGING:
|
||||
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+else:
|
||||
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+def rpm_is_older(pkg, version):
|
||||
+ ts = rpm.TransactionSet()
|
||||
+ mi = ts.dbMatch('name', pkg)
|
||||
+ for h in mi:
|
||||
+ print(f"{pkg} {h['version']} {version}")
|
||||
+ for n1,n2 in itertools.zip_longest(h['version'].split('.'), version.split('.'), fillvalue=""):
|
||||
+ try:
|
||||
+ if int(n1) < int(n2):
|
||||
+ return True
|
||||
+ except ValueError:
|
||||
+ if n1 < n2:
|
||||
+ return True
|
||||
+ return False
|
||||
+
|
||||
+
|
||||
+script_content="""
|
||||
+#!/bin/bash
|
||||
+set -e # Exit if a command fails
|
||||
+set -x # Log the commands
|
||||
+
|
||||
+cd {dir}
|
||||
+inst={instname}
|
||||
+url={url}
|
||||
+rootdn="{rootdn}"
|
||||
+rootpw="{rootpw}"
|
||||
+
|
||||
+################################
|
||||
+###### GENERATE CA CERT ########
|
||||
+################################
|
||||
+
|
||||
+echo "
|
||||
+[ req ]
|
||||
+distinguished_name = req_distinguished_name
|
||||
+policy = policy_match
|
||||
+x509_extensions = v3_ca
|
||||
+
|
||||
+# For the CA policy
|
||||
+[ policy_match ]
|
||||
+countryName = optional
|
||||
+stateOrProvinceName = optional
|
||||
+organizationName = optional
|
||||
+organizationalUnitName = optional
|
||||
+commonName = supplied
|
||||
+emailAddress = optional
|
||||
+
|
||||
+[ req_distinguished_name ]
|
||||
+countryName = Country Name (2 letter code)
|
||||
+countryName_default = FR
|
||||
+countryName_min = 2
|
||||
+countryName_max = 2
|
||||
+
|
||||
+stateOrProvinceName = State or Province Name (full name)
|
||||
+stateOrProvinceName_default = test
|
||||
+
|
||||
+localityName = Locality Name (eg, city)
|
||||
+
|
||||
+0.organizationName = Organization Name (eg, company)
|
||||
+0.organizationName_default = test-ML-DSA-CA
|
||||
+
|
||||
+organizationalUnitName = Organizational Unit Name (eg, section)
|
||||
+#organizationalUnitName_default =
|
||||
+
|
||||
+commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||
+commonName_max = 64
|
||||
+
|
||||
+
|
||||
+[ v3_ca ]
|
||||
+subjectKeyIdentifier = hash
|
||||
+authorityKeyIdentifier = keyid:always,issuer
|
||||
+basicConstraints = critical,CA:true
|
||||
+#nsComment = "OpenSSL Generated Certificate"
|
||||
+keyUsage=critical, keyCertSign
|
||||
+" >ca.conf
|
||||
+
|
||||
+
|
||||
+openssl genpkey -algorithm ML-DSA-87 -out ca.key
|
||||
+openssl req -x509 -new -sha256 -key ca.key -nodes -days 3650 -config ca.conf -subj "/CN=`hostname`/O=test-ML-DSA-CA/C=FR" -out ca.pem -keyout ca.key
|
||||
+openssl x509 -outform der -in ca.pem -out ca.crt
|
||||
+
|
||||
+openssl x509 -text -in ca.pem
|
||||
+
|
||||
+####################################
|
||||
+###### GENERATE SERVER CERT ########
|
||||
+####################################
|
||||
+
|
||||
+echo "
|
||||
+[ req ]
|
||||
+distinguished_name = req_distinguished_name
|
||||
+policy = policy_match
|
||||
+x509_extensions = v3_cert
|
||||
+
|
||||
+# For the cert policy
|
||||
+[ policy_match ]
|
||||
+countryName = optional
|
||||
+stateOrProvinceName = optional
|
||||
+organizationName = optional
|
||||
+organizationalUnitName = optional
|
||||
+commonName = supplied
|
||||
+emailAddress = optional
|
||||
+
|
||||
+[ req_distinguished_name ]
|
||||
+countryName = Country Name (2 letter code)
|
||||
+countryName_default = FR
|
||||
+countryName_min = 2
|
||||
+countryName_max = 2
|
||||
+
|
||||
+stateOrProvinceName = State or Province Name (full name)
|
||||
+
|
||||
+localityName = Locality Name (eg, city)
|
||||
+
|
||||
+0.organizationName = Organization Name (eg, company)
|
||||
+0.organizationName_default = test-ML-DSA
|
||||
+
|
||||
+organizationalUnitName = Organizational Unit Name (eg, section)
|
||||
+#organizationalUnitName_default =
|
||||
+
|
||||
+commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||
+commonName_max = 64
|
||||
+
|
||||
+
|
||||
+[ v3_cert ]
|
||||
+basicConstraints = critical,CA:false
|
||||
+subjectAltName=DNS:`hostname`
|
||||
+keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
+#nsComment = "OpenSSL Generated Certificate"
|
||||
+extendedKeyUsage=clientAuth, serverAuth
|
||||
+nsCertType=client, server
|
||||
+" >cert.conf
|
||||
+
|
||||
+openssl genpkey -algorithm ML-DSA-65 -out cert.key
|
||||
+openssl req -new -sha256 -key cert.key -nodes -config cert.conf -subj "/CN=`hostname`/O=test-ML-DSA/C=FR" -out cert.csr
|
||||
+openssl x509 -req -sha256 -days 3650 -extensions v3_cert -extfile cert.conf -in cert.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out cert.pem
|
||||
+openssl pkcs12 -export -inkey cert.key -in cert.pem -name mldsacert -out cert.p12 -passout pass:secret12
|
||||
+
|
||||
+openssl x509 -text -in cert.pem
|
||||
+
|
||||
+
|
||||
+
|
||||
+####################################
|
||||
+###### GENERATE CLIENT CERT ########
|
||||
+####################################
|
||||
+
|
||||
+echo "
|
||||
+[ req ]
|
||||
+distinguished_name = req_distinguished_name
|
||||
+policy = policy_match
|
||||
+x509_extensions = v3_cert
|
||||
+
|
||||
+# For the cert policy
|
||||
+[ policy_match ]
|
||||
+countryName = optional
|
||||
+stateOrProvinceName = optional
|
||||
+organizationName = optional
|
||||
+organizationalUnitName = optional
|
||||
+commonName = supplied
|
||||
+emailAddress = optional
|
||||
+
|
||||
+[ req_distinguished_name ]
|
||||
+countryName = Country Name (2 letter code)
|
||||
+countryName_default = FR
|
||||
+countryName_min = 2
|
||||
+countryName_max = 2
|
||||
+
|
||||
+stateOrProvinceName = State or Province Name (full name)
|
||||
+
|
||||
+localityName = Locality Name (eg, city)
|
||||
+
|
||||
+0.organizationName = Organization Name (eg, company)
|
||||
+0.organizationName_default = test-ML-DSA
|
||||
+
|
||||
+organizationalUnitName = Organizational Unit Name (eg, section)
|
||||
+#organizationalUnitName_default =
|
||||
+
|
||||
+commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||
+commonName_max = 64
|
||||
+
|
||||
+
|
||||
+[ v3_cert ]
|
||||
+basicConstraints = critical,CA:false
|
||||
+subjectAltName=DNS:`hostname`
|
||||
+keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
+#nsComment = "OpenSSL Generated Certificate"
|
||||
+extendedKeyUsage=clientAuth
|
||||
+nsCertType=client, server
|
||||
+" >client.conf
|
||||
+
|
||||
+openssl genpkey -algorithm ML-DSA-65 -out client.key
|
||||
+openssl req -new -sha256 -key client.key -nodes -config client.conf -subj "/CN=`hostname`/O=client-test-ML-DSA/C=FR" -out client.csr
|
||||
+openssl x509 -req -sha256 -days 3650 -extensions v3_cert -extfile client.conf -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem
|
||||
+openssl pkcs12 -export -inkey client.key -in client.pem -name mldsacert2 -out client.p12 -passout pass:secret12
|
||||
+
|
||||
+openssl x509 -text -in client.pem
|
||||
+
|
||||
+
|
||||
+#############################
|
||||
+###### INSTALL CERTS ########
|
||||
+#############################
|
||||
+
|
||||
+certdbdir=$PREFIX/etc/dirsrv/slapd-$inst
|
||||
+rm -f $certdbdir/cert9.db $certdbdir/key4.db
|
||||
+certutil -N -d $certdbdir -f $certdbdir/pwdfile.txt
|
||||
+
|
||||
+certutil -A -n Self-Signed-CA -t CT,, -f $certdbdir/pwdfile.txt -d $certdbdir -a -i ca.pem
|
||||
+certutil -A -n Client-Cert -t u,, -f $certdbdir/pwdfile.txt -d $certdbdir -a -i client.pem
|
||||
+
|
||||
+dsctl $inst tls import-server-key-cert cert.pem cert.key
|
||||
+
|
||||
+dsctl $inst restart
|
||||
+
|
||||
+
|
||||
+#########################
|
||||
+###### TEST CERT ########
|
||||
+#########################
|
||||
+export LDAPTLS_CACERT=$PWD/ca.pem
|
||||
+export LDAPTLS_CERT=$PWD/client.pem
|
||||
+export LDAPTLS_KEY=$PWD/client.key
|
||||
+
|
||||
+ldapsearch -x -H $url -D "$rootdn" -w "$rootpw" -b "" -s base
|
||||
+ldapsearch -Y external -H $url -b "" -s base
|
||||
+"""
|
||||
+
|
||||
+@pytest.mark.skipif(rpm_is_older("openssl", "3.5"), reason="OpenSSL too old to support PQC")
|
||||
+@pytest.mark.skipif(rpm_is_older("nss", "3.119.1"), reason="NSS too old to support PQC")
|
||||
+def test_mldsa(topo):
|
||||
+ """Test using ML-DSA Certificate - (PQC)
|
||||
+
|
||||
+ :id: 87fb19ef-672d-4fa7-934d-dfb4397f2312
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure the certmap
|
||||
+ 2. Create user mapped with theclient certificate
|
||||
+ 3. Generate the test script
|
||||
+ 4. Run the test script
|
||||
+ 5. Check that ldapsearch returned the namingcontext
|
||||
+ :expectedresults:
|
||||
+ 1. No error
|
||||
+ 2. No error
|
||||
+ 3. No error
|
||||
+ 4. No error and exit code should be 0
|
||||
+ 5. namingcontext should be in the script output
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ inst.enable_tls()
|
||||
+
|
||||
+ cm = CertmapLegacy(inst)
|
||||
+ certmaps = cm.list()
|
||||
+ certmaps['default'].update({'DNComps': None, 'CmapLdapAttr': 'description'})
|
||||
+ cm.set(certmaps)
|
||||
+
|
||||
+ cert_dn = f'C=FR,O=client-test-ML-DSA,CN={socket.gethostname()}'
|
||||
+ dn = f'uid=test_user,ou=people,{DEFAULT_SUFFIX}'
|
||||
+ UserAccount(inst, dn=dn).create( properties= {
|
||||
+ 'uid': 'test_user',
|
||||
+ 'cn': 'Test user',
|
||||
+ 'sn': 'Test user',
|
||||
+ 'uidNumber': '99998',
|
||||
+ 'gidNumber': '99998',
|
||||
+ 'homeDirectory': '/var/empty',
|
||||
+ 'loginShell': '/bin/false',
|
||||
+ 'description': cert_dn })
|
||||
+
|
||||
+ tmpdir_kwargs = {}
|
||||
+ if sys.version_info >= (3, 12):
|
||||
+ tmpdir_kwargs['delete'] = not DEBUGGING
|
||||
+ with TemporaryDirectory(**tmpdir_kwargs) as dir:
|
||||
+ scriptname = f"{dir}/doit"
|
||||
+ d = {
|
||||
+ 'dir': dir,
|
||||
+ 'instname': inst.serverid,
|
||||
+ 'url': f"ldaps://localhost:{inst.sslport}",
|
||||
+ 'rootdn': DN_DM,
|
||||
+ 'rootpw': PW_DM,
|
||||
+ }
|
||||
+ with open(scriptname, 'w') as f:
|
||||
+ f.write(script_content.format(**d))
|
||||
+ res = subprocess.run(('/bin/bash', scriptname), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8')
|
||||
+ assert res
|
||||
+ log.info(res.stdout)
|
||||
+ res.check_returncode()
|
||||
+ # If ldapsearch is successful then defaultnamingcontext should be in res.stdout
|
||||
+ assert "defaultnamingcontext" in res.stdout
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
|
||||
index a9c17ef42..053db5424 100644
|
||||
--- a/ldap/servers/slapd/ssl.c
|
||||
+++ b/ldap/servers/slapd/ssl.c
|
||||
@@ -169,6 +169,20 @@ PRBool enableTLS1 = PR_TRUE;
|
||||
/* CA cert pem file */
|
||||
static char *CACertPemFile = NULL;
|
||||
|
||||
+static const struct {
|
||||
+ KeyType kt;
|
||||
+ const char *shortname;
|
||||
+ const char *fullname;
|
||||
+} supported_key_types[] = {
|
||||
+ { rsaKey, "RSA", "Rivest–Shamir–Adleman" },
|
||||
+ { ecKey, "EC", "Elliptic Curve" },
|
||||
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
+ { mldsaKey, "ML-DSA", "Module-Lattice-Based Digital Signature Algorithm (post-quantum)" },
|
||||
+#endif
|
||||
+ { 0 }
|
||||
+};
|
||||
+
|
||||
+
|
||||
/* helper functions for openldap update. */
|
||||
static int slapd_extract_cert(Slapi_Entry *entry, int isCA);
|
||||
static int slapd_extract_key(Slapi_Entry *entry, char *token, PK11SlotInfo *slot);
|
||||
@@ -718,9 +732,31 @@ SSLPLCY_Install(void)
|
||||
{
|
||||
|
||||
SECStatus s = 0;
|
||||
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
+ int flags = NSS_USE_ALG_IN_SIGNATURE | NSS_USE_ALG_IN_SSL;
|
||||
+ static const SECOidTag oids[] = {
|
||||
+ SEC_OID_ML_DSA_44,
|
||||
+ SEC_OID_ML_DSA_65,
|
||||
+ SEC_OID_ML_DSA_87,
|
||||
+ };
|
||||
+#endif
|
||||
|
||||
s = NSS_SetDomesticPolicy();
|
||||
|
||||
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
+ /* Should rely on the crypto module policy in FIPS mode */
|
||||
+ if (!slapd_pk11_isFIPS()) {
|
||||
+ /* Set explicitly PQC algorithm policy if it is not set by default */
|
||||
+ for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
|
||||
+ int oflags = 0;
|
||||
+ (void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
|
||||
+ if ((oflags & flags) != flags) {
|
||||
+ s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
return s ? PR_FAILURE : PR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1640,7 +1676,7 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
|
||||
/*
|
||||
* Now, get the complete list of cipher families. Each family
|
||||
* has a token name and personality name which we'll use to find
|
||||
- * appropriate keys and certs, and call SSL_ConfigSecureServer
|
||||
+ * appropriate keys and certs, and call SSL_ConfigServerCert
|
||||
* with.
|
||||
*/
|
||||
|
||||
@@ -1759,8 +1795,6 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
|
||||
}
|
||||
|
||||
if (SECSuccess == rv) {
|
||||
- SSLKEAType certKEA;
|
||||
-
|
||||
/* If we want weak dh params, flag it on the socket now! */
|
||||
rv = SSL_OptionSet(*fd, SSL_ENABLE_SERVER_DHE, PR_TRUE);
|
||||
if (rv != SECSuccess) {
|
||||
@@ -1774,11 +1808,10 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
|
||||
}
|
||||
}
|
||||
|
||||
- certKEA = NSS_FindCertKEAType(cert);
|
||||
- rv = SSL_ConfigSecureServer(*fd, cert, key, certKEA);
|
||||
+ rv = SSL_ConfigServerCert(*fd, cert, key, NULL, 0);
|
||||
if (SECSuccess != rv) {
|
||||
errorCode = PR_GetError();
|
||||
- slapd_SSL_warn("ConfigSecureServer: "
|
||||
+ slapd_SSL_warn("SSL_ConfigServerCert: "
|
||||
"Server key/certificate is "
|
||||
"bad for cert %s of family %s (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
|
||||
cert_name, *family, errorCode,
|
||||
@@ -2663,6 +2696,38 @@ bail:
|
||||
return rv;
|
||||
}
|
||||
|
||||
+/* Helper for get_supported_key_type */
|
||||
+static char *
|
||||
+buf_add_str(char *buf, char *bufend, const char *str)
|
||||
+{
|
||||
+ /* bufend is sizeof(buf)-4 (to avoid overflow with ...) */
|
||||
+ char *ret = buf+strlen(str);
|
||||
+ if (ret > bufend) {
|
||||
+ ret = bufend;
|
||||
+ strcpy(buf, "...");
|
||||
+ } else {
|
||||
+ strcpy(buf, str);
|
||||
+ }
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+get_supported_key_type_names(char *buf, size_t bufsize)
|
||||
+{
|
||||
+ char *bufend = buf + bufsize - 4;
|
||||
+ for (size_t i=0; supported_key_types[i].kt; i++) {
|
||||
+ if (i>0) {
|
||||
+ if (supported_key_types[i+1].kt == 0) {
|
||||
+ /* Last supported key type */
|
||||
+ buf = buf_add_str(buf, bufend, " or ");
|
||||
+ } else {
|
||||
+ buf = buf_add_str(buf, bufend, ", ");
|
||||
+ }
|
||||
+ }
|
||||
+ buf = buf_add_str(buf, bufend, supported_key_types[i].shortname);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Borrowed from keyutil.c (crypto-util)
|
||||
*
|
||||
@@ -2723,10 +2788,20 @@ extractKeysAndSubject(
|
||||
}
|
||||
|
||||
keytype = (*privkey)->keyType;
|
||||
- if (keytype != rsaKey && keytype != ecKey) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "extractKeysAndSubject",
|
||||
- "Unexpected key algorythm in certificate: %s. Only rsa and ec keys are supported.\n", nickname);
|
||||
- goto bail;
|
||||
+ for (size_t i=0; ;i++) {
|
||||
+ KeyType kt = supported_key_types[i].kt;
|
||||
+ if (kt == keytype && keytype != 0) {
|
||||
+ /* Stop looping if the key type is supported */
|
||||
+ break;
|
||||
+ }
|
||||
+ if (kt == 0) {
|
||||
+ /* No supported key type have been found. */
|
||||
+ char sktnames[100] = "";
|
||||
+ get_supported_key_type_names(sktnames, sizeof sktnames);
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "extractKeysAndSubject",
|
||||
+ "Unexpected key algorithm in certificate: %s. Only %s are supported.\n", nickname, sktnames);
|
||||
+ goto bail;
|
||||
+ }
|
||||
}
|
||||
|
||||
*subject = CERT_AsciiToName(cert->subjectName);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
From 4068f68bea77f466f9b3d87c766ea14d2f175b17 Mon Sep 17 00:00:00 2001
|
||||
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
|
||||
Date: Wed, 21 Jan 2026 19:58:46 -0800
|
||||
Subject: [PATCH] Bump lodash from 4.17.21 to 4.17.23 in
|
||||
/src/cockpit/389-console (#7203)
|
||||
|
||||
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
|
||||
- [Release notes](https://github.com/lodash/lodash/releases)
|
||||
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)
|
||||
|
||||
---
|
||||
updated-dependencies:
|
||||
- dependency-name: lodash
|
||||
dependency-version: 4.17.23
|
||||
dependency-type: indirect
|
||||
...
|
||||
|
||||
Signed-off-by: dependabot[bot] <support@github.com>
|
||||
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||
---
|
||||
src/cockpit/389-console/package-lock.json | 12 ++++++------
|
||||
1 file changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/package-lock.json b/src/cockpit/389-console/package-lock.json
|
||||
index 0aa5bbbb9..23faef62f 100644
|
||||
--- a/src/cockpit/389-console/package-lock.json
|
||||
+++ b/src/cockpit/389-console/package-lock.json
|
||||
@@ -4833,9 +4833,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
- "version": "4.17.21",
|
||||
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
+ "version": "4.17.23",
|
||||
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
@@ -11087,9 +11087,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
- "version": "4.17.21",
|
||||
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
+ "version": "4.17.23",
|
||||
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
--
|
||||
2.52.0
|
||||
|
||||
517
0014-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
Normal file
517
0014-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
Normal file
@ -0,0 +1,517 @@
|
||||
From 3ff253af76df07fe0519481795b7ed155fefaa9e Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 23 Jan 2026 17:35:45 -0800
|
||||
Subject: [PATCH] Issue 7198 - Web console doesn't show sub-suffix when
|
||||
parent-suffix points to an entry (#7202)
|
||||
|
||||
Description: The web console doesn't show sub-suffixes when the
|
||||
nsslapd-parent-suffix attribute points to an entry rather than a backend
|
||||
suffix.
|
||||
For example, creating a sub-suffix ou=foo,ou=people,dc=example,dc=com
|
||||
with parent-suffix ou=people,dc=example,dc=com (where ou=people is just an
|
||||
entry, not a suffix) would not appear in the web console tree.
|
||||
|
||||
Fix: In backend_build_tree() and get_sub_suffixes(), the code only matched
|
||||
when nsslapd-parent-suffix exactly equaled an existing backend suffix.
|
||||
Now it also checks if the parent-suffix is an entry under the current
|
||||
suffix (ends with ,suffix) and is not itself a backend suffix. This
|
||||
correctly attaches sub-suffixes to their containing suffix when the
|
||||
parent-suffix points to an intermediate entry.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7198
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../suites/lib389/subsuffix_tree_test.py | 313 ++++++++++++++++++
|
||||
src/lib389/lib389/backend.py | 47 ++-
|
||||
src/lib389/lib389/cli_conf/backend.py | 34 +-
|
||||
3 files changed, 370 insertions(+), 24 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
new file mode 100644
|
||||
index 000000000..fa10ba530
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
|
||||
@@ -0,0 +1,313 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import logging
|
||||
+import os
|
||||
+import pytest
|
||||
+from lib389.topologies import topology_st as topo
|
||||
+from lib389.backend import Backends
|
||||
+from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def setup_subsuffix_with_entry_parent(topo, request):
|
||||
+ """Setup a sub-suffix whose parent-suffix points to an entry, not a suffix."""
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ # Create ou=people entry under the root suffix
|
||||
+ log.info("Creating ou=people,dc=example,dc=com entry")
|
||||
+ ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
|
||||
+ ou_people = ous.get('people')
|
||||
+
|
||||
+ # Create sub-suffix with parent-suffix pointing to the entry
|
||||
+ log.info("Creating sub-suffix ou=foo,ou=people,dc=example,dc=com")
|
||||
+ backends = Backends(inst)
|
||||
+ subsuffix_dn = 'ou=foo,ou=people,dc=example,dc=com'
|
||||
+ parent_suffix_dn = 'ou=people,dc=example,dc=com'
|
||||
+
|
||||
+ foo_backend = backends.create(properties={
|
||||
+ 'cn': 'foo',
|
||||
+ 'nsslapd-suffix': subsuffix_dn,
|
||||
+ 'parent': parent_suffix_dn,
|
||||
+ })
|
||||
+
|
||||
+ # Create the suffix entry
|
||||
+ foo_ous = OrganizationalUnits(inst, parent_suffix_dn)
|
||||
+ foo_ou = foo_ous.create(properties={'ou': 'foo'})
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Cleaning up test backends and entries")
|
||||
+ try:
|
||||
+ foo_ou.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete foo_ou: {e}")
|
||||
+ try:
|
||||
+ foo_backend.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete foo_backend: {e}")
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+
|
||||
+ return {
|
||||
+ 'instance': inst,
|
||||
+ 'backends': backends,
|
||||
+ 'foo_backend': foo_backend,
|
||||
+ 'ou_people': ou_people,
|
||||
+ 'subsuffix_dn': subsuffix_dn,
|
||||
+ 'parent_suffix_dn': parent_suffix_dn,
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_with_entry_parent_in_tree(topo, setup_subsuffix_with_entry_parent):
|
||||
+ """Test that a sub-suffix with parent pointing to an entry is visible in the tree.
|
||||
+
|
||||
+ :id: 256f36f5-76ad-4043-ad8d-1f9e2afc4e1d
|
||||
+ :setup: Standalone instance with sub-suffix whose parent is an entry
|
||||
+ :steps:
|
||||
+ 1. Verify the sub-suffix backend exists
|
||||
+ 2. Get sub-suffixes of the root backend
|
||||
+ 3. Verify the sub-suffix appears in the list
|
||||
+ :expectedresults:
|
||||
+ 1. Backend should exist
|
||||
+ 2. Sub-suffixes should be retrievable
|
||||
+ 3. Sub-suffix should be visible (this is where the bug manifested)
|
||||
+ """
|
||||
+ backends = setup_subsuffix_with_entry_parent['backends']
|
||||
+ foo_backend = setup_subsuffix_with_entry_parent['foo_backend']
|
||||
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
|
||||
+
|
||||
+ # Step 1: Verify the sub-suffix backend exists
|
||||
+ assert foo_backend.exists(), "The foo backend should exist"
|
||||
+
|
||||
+ # Step 2: Get sub-suffixes of the root backend
|
||||
+ root_backend = backends.get(DEFAULT_SUFFIX)
|
||||
+ sub_suffixes = root_backend.get_sub_suffixes()
|
||||
+ log.info(f"Sub-suffixes found: {[s.get_attr_val_utf8('nsslapd-suffix') for s in sub_suffixes]}")
|
||||
+
|
||||
+ # Step 3: Verify sub-suffix is in the list
|
||||
+ sub_suffix_found = any(
|
||||
+ s.get_attr_val_utf8_l('nsslapd-suffix') == subsuffix_dn.lower()
|
||||
+ for s in sub_suffixes
|
||||
+ )
|
||||
+
|
||||
+ assert sub_suffix_found, (
|
||||
+ f"Sub-suffix {subsuffix_dn} should be visible in get_sub_suffixes(). "
|
||||
+ "The parent-suffix points to an entry, not a backend suffix."
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_in_backend_list(topo, setup_subsuffix_with_entry_parent):
|
||||
+ """Test that the sub-suffix appears in the backend list.
|
||||
+
|
||||
+ :id: 0ccc49af-91bb-4e8f-b0e1-1bd0b75c041b
|
||||
+ :setup: Standalone instance with sub-suffix configuration
|
||||
+ :steps:
|
||||
+ 1. Get all backends
|
||||
+ 2. Verify both root suffix and sub-suffix are present
|
||||
+ :expectedresults:
|
||||
+ 1. Should retrieve all backends
|
||||
+ 2. Both suffixes should be listed
|
||||
+ """
|
||||
+ backends = setup_subsuffix_with_entry_parent['backends']
|
||||
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
|
||||
+
|
||||
+ be_list = backends.list()
|
||||
+ suffixes = [be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_list]
|
||||
+
|
||||
+ assert DEFAULT_SUFFIX.lower() in suffixes, \
|
||||
+ f"Root suffix {DEFAULT_SUFFIX} should be in the list"
|
||||
+ assert subsuffix_dn.lower() in suffixes, \
|
||||
+ f"Sub-suffix {subsuffix_dn} should be in the list"
|
||||
+
|
||||
+
|
||||
+def test_subsuffix_dn_boundary_matching():
|
||||
+ """Test that suffix matching respects DN component boundaries.
|
||||
+
|
||||
+ :id: 0b856e26-c394-4c36-b9ba-d7894aa2ed11
|
||||
+ :setup: None (unit test)
|
||||
+ :steps:
|
||||
+ 1. Test exact suffix match
|
||||
+ 2. Test proper DN ancestor match (ends with ,suffix)
|
||||
+ 3. Test that partial string matches are rejected
|
||||
+ :expectedresults:
|
||||
+ 1. Exact match should return True
|
||||
+ 2. Proper ancestor should return True
|
||||
+ 3. Partial string match should return False
|
||||
+ """
|
||||
+ from lib389.backend import is_subsuffix_of
|
||||
+
|
||||
+ all_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=dept,dc=example,dc=com'}
|
||||
+
|
||||
+ # Test 1: Exact match
|
||||
+ assert is_subsuffix_of('dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Exact match should return True"
|
||||
+
|
||||
+ # Test 2: Parent is an entry under the suffix (not itself a suffix)
|
||||
+ assert is_subsuffix_of('ou=people,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Parent entry under suffix should return True"
|
||||
+
|
||||
+ # Test 3: Parent IS a suffix - should return False (handled separately)
|
||||
+ assert not is_subsuffix_of('ou=dept,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
|
||||
+ "Parent that is itself a suffix should return False"
|
||||
+
|
||||
+ # Test 4: Edge case - wrong DN boundary (string ends with suffix but wrong boundary)
|
||||
+ edge_suffixes = {'dc=com', 'st,dc=com'}
|
||||
+ assert is_subsuffix_of('dc=test,dc=com', 'dc=com', edge_suffixes), \
|
||||
+ "dc=test,dc=com should match dc=com"
|
||||
+ assert not is_subsuffix_of('dc=test,dc=com', 'st,dc=com', edge_suffixes), \
|
||||
+ "dc=test,dc=com should NOT match st,dc=com (wrong DN boundary)"
|
||||
+
|
||||
+ # Test 5: None input
|
||||
+ assert not is_subsuffix_of(None, 'dc=com', all_suffixes), \
|
||||
+ "None parent should return False"
|
||||
+
|
||||
+ # Test 6: Closest ancestor - should only match the nearest suffix
|
||||
+ # Hierarchy: dc=com -> dc=example,dc=com -> ou=branch,dc=example,dc=com (suffix)
|
||||
+ # -> ou=dept,ou=branch,dc=example,dc=com (entry) -> subsuffix
|
||||
+ # The subsuffix should only appear under ou=branch, not under dc=example,dc=com
|
||||
+ nested_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=branch,dc=example,dc=com'}
|
||||
+ entry_parent = 'ou=dept,ou=branch,dc=example,dc=com'
|
||||
+ # Should match ou=branch (closest)
|
||||
+ assert is_subsuffix_of(entry_parent, 'ou=branch,dc=example,dc=com', nested_suffixes), \
|
||||
+ "Should match closest ancestor suffix (ou=branch)"
|
||||
+ # Should NOT match dc=example,dc=com (not closest)
|
||||
+ assert not is_subsuffix_of(entry_parent, 'dc=example,dc=com', nested_suffixes), \
|
||||
+ "Should NOT match distant ancestor (dc=example) - ou=branch is closer"
|
||||
+ # Should NOT match dc=com (not closest)
|
||||
+ assert not is_subsuffix_of(entry_parent, 'dc=com', nested_suffixes), \
|
||||
+ "Should NOT match distant ancestor (dc=com) - ou=branch is closer"
|
||||
+
|
||||
+ log.info("All DN boundary edge cases passed")
|
||||
+
|
||||
+
|
||||
+def test_deep_suffix_hierarchy(topo, request):
|
||||
+ """Test complex hierarchy: suffix -> suffix -> entry -> suffix -> suffix.
|
||||
+
|
||||
+ :id: fd06491a-defa-4780-8472-78c077febdfb
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create sub-suffix ou=branch (parent=dc=example,dc=com - a suffix)
|
||||
+ 2. Create entry ou=dept,ou=branch (not a suffix)
|
||||
+ 3. Create sub-suffix ou=team,ou=dept,ou=branch (parent=ou=dept - an entry)
|
||||
+ 4. Create sub-suffix ou=sub,ou=team,ou=dept,ou=branch (parent=ou=team - a suffix)
|
||||
+ 5. Verify all sub-suffixes are correctly placed in the tree
|
||||
+ :expectedresults:
|
||||
+ 1. Sub-suffix created successfully
|
||||
+ 2. Entry created successfully
|
||||
+ 3. Sub-suffix with entry parent created successfully
|
||||
+ 4. Sub-suffix with suffix parent created successfully
|
||||
+ 5. Tree hierarchy is correct
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ backends = Backends(inst)
|
||||
+
|
||||
+ # Define the hierarchy
|
||||
+ branch_suffix = f'ou=branch,{DEFAULT_SUFFIX}'
|
||||
+ dept_entry = f'ou=dept,{branch_suffix}' # This is an ENTRY, not a suffix
|
||||
+ team_suffix = f'ou=team,{dept_entry}'
|
||||
+ sub_suffix = f'ou=sub,{team_suffix}'
|
||||
+
|
||||
+ created_backends = []
|
||||
+ created_entries = []
|
||||
+
|
||||
+ def cleanup():
|
||||
+ log.info("Cleaning up deep hierarchy test")
|
||||
+ for entry in reversed(created_entries):
|
||||
+ try:
|
||||
+ entry.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete entry: {e}")
|
||||
+ for be in reversed(created_backends):
|
||||
+ try:
|
||||
+ be.delete()
|
||||
+ except Exception as e:
|
||||
+ log.warning(f"Failed to delete backend: {e}")
|
||||
+
|
||||
+ request.addfinalizer(cleanup)
|
||||
+
|
||||
+ # Step 1: Create ou=branch sub-suffix (parent is root suffix)
|
||||
+ log.info(f"Creating sub-suffix {branch_suffix}")
|
||||
+ branch_be = backends.create(properties={
|
||||
+ 'cn': 'branch',
|
||||
+ 'nsslapd-suffix': branch_suffix,
|
||||
+ 'parent': DEFAULT_SUFFIX,
|
||||
+ })
|
||||
+ created_backends.append(branch_be)
|
||||
+ branch_ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
|
||||
+ branch_ou = branch_ous.create(properties={'ou': 'branch'})
|
||||
+ created_entries.append(branch_ou)
|
||||
+
|
||||
+ # Step 2: Create ou=dept entry under branch (NOT a suffix)
|
||||
+ log.info(f"Creating entry {dept_entry}")
|
||||
+ dept_ous = OrganizationalUnits(inst, branch_suffix)
|
||||
+ dept_ou = dept_ous.create(properties={'ou': 'dept'})
|
||||
+ created_entries.append(dept_ou)
|
||||
+
|
||||
+ # Step 3: Create ou=team sub-suffix (parent is dept ENTRY, not a suffix)
|
||||
+ log.info(f"Creating sub-suffix {team_suffix} with entry parent {dept_entry}")
|
||||
+ team_be = backends.create(properties={
|
||||
+ 'cn': 'team',
|
||||
+ 'nsslapd-suffix': team_suffix,
|
||||
+ 'parent': dept_entry, # Parent is an ENTRY!
|
||||
+ })
|
||||
+ created_backends.append(team_be)
|
||||
+ team_ous = OrganizationalUnits(inst, dept_entry)
|
||||
+ team_ou = team_ous.create(properties={'ou': 'team'})
|
||||
+ created_entries.append(team_ou)
|
||||
+
|
||||
+ # Step 4: Create ou=sub sub-suffix (parent is team suffix)
|
||||
+ log.info(f"Creating sub-suffix {sub_suffix} with suffix parent {team_suffix}")
|
||||
+ sub_be = backends.create(properties={
|
||||
+ 'cn': 'sub',
|
||||
+ 'nsslapd-suffix': sub_suffix,
|
||||
+ 'parent': team_suffix, # Parent is a SUFFIX
|
||||
+ })
|
||||
+ created_backends.append(sub_be)
|
||||
+ sub_ous = OrganizationalUnits(inst, team_suffix)
|
||||
+ sub_ou = sub_ous.create(properties={'ou': 'sub'})
|
||||
+ created_entries.append(sub_ou)
|
||||
+
|
||||
+ # Step 5: Verify the tree hierarchy
|
||||
+ log.info("Verifying tree hierarchy...")
|
||||
+
|
||||
+ # Root should have branch as sub-suffix
|
||||
+ root_be = backends.get(DEFAULT_SUFFIX)
|
||||
+ root_subs = root_be.get_sub_suffixes()
|
||||
+ root_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in root_subs]
|
||||
+ log.info(f"Root sub-suffixes: {root_sub_suffixes}")
|
||||
+ assert branch_suffix.lower() in root_sub_suffixes, \
|
||||
+ f"branch should be under root suffix"
|
||||
+
|
||||
+ # Branch should have team as sub-suffix (even though team's parent is an entry)
|
||||
+ branch_be_obj = backends.get(branch_suffix)
|
||||
+ branch_subs = branch_be_obj.get_sub_suffixes()
|
||||
+ branch_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in branch_subs]
|
||||
+ log.info(f"Branch sub-suffixes: {branch_sub_suffixes}")
|
||||
+ assert team_suffix.lower() in branch_sub_suffixes, \
|
||||
+ f"team should be under branch suffix (parent is entry under branch)"
|
||||
+
|
||||
+ # Team should have sub as sub-suffix
|
||||
+ team_be_obj = backends.get(team_suffix)
|
||||
+ team_subs = team_be_obj.get_sub_suffixes()
|
||||
+ team_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in team_subs]
|
||||
+ log.info(f"Team sub-suffixes: {team_sub_suffixes}")
|
||||
+ assert sub_suffix.lower() in team_sub_suffixes, \
|
||||
+ f"sub should be under team suffix"
|
||||
+
|
||||
+ log.info("Deep hierarchy test passed!")
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index db464b43a..274d45abe 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -38,6 +38,36 @@ from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, D
|
||||
from lib389.plugins import USNPlugin
|
||||
|
||||
|
||||
+def is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
|
||||
+ """Check if sub_parent indicates this is a sub-suffix of be_suffix.
|
||||
+
|
||||
+ Returns True only if be_suffix is the CLOSEST ancestor suffix of sub_parent.
|
||||
+ This prevents a sub-suffix from appearing under multiple ancestors.
|
||||
+
|
||||
+ :param sub_parent: The nsslapd-parent-suffix value (lowercase)
|
||||
+ :param be_suffix: The suffix to check against (lowercase)
|
||||
+ :param all_suffixes: Set of all backend suffixes (lowercase)
|
||||
+ :returns: True if be_suffix is the closest ancestor suffix
|
||||
+ """
|
||||
+ if not sub_parent:
|
||||
+ return False
|
||||
+ if sub_parent == be_suffix:
|
||||
+ return True
|
||||
+ if sub_parent in all_suffixes:
|
||||
+ # sub_parent is itself a suffix, will be handled separately
|
||||
+ return False
|
||||
+ if not sub_parent.endswith(',' + be_suffix):
|
||||
+ return False
|
||||
+ # Find the closest (longest) matching suffix for this parent
|
||||
+ best_match = None
|
||||
+ for sfx in all_suffixes:
|
||||
+ if sub_parent == sfx or sub_parent.endswith(',' + sfx):
|
||||
+ if best_match is None or len(sfx) > len(best_match):
|
||||
+ best_match = sfx
|
||||
+ # Only return True if be_suffix is the closest match
|
||||
+ return best_match == be_suffix
|
||||
+
|
||||
+
|
||||
class BackendLegacy(object):
|
||||
proxied_methods = 'search_s getEntry'.split()
|
||||
|
||||
@@ -1104,22 +1134,27 @@ class Backend(DSLdapObject):
|
||||
vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn)
|
||||
|
||||
def get_sub_suffixes(self):
|
||||
- """Return a list of Backend's
|
||||
- returns: a List of subsuffix entries
|
||||
+ """Return a list of Backend's that are sub-suffixes of this backend.
|
||||
+ :returns: A list of Backend instances that are sub-suffixes
|
||||
"""
|
||||
subsuffixes = []
|
||||
top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
+ if not top_be_suffix:
|
||||
+ return subsuffixes
|
||||
+
|
||||
mts = self._mts.list()
|
||||
+ be_insts = Backends(self._instance).list()
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
+
|
||||
for mt in mts:
|
||||
parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
if parent_suffix is None:
|
||||
continue
|
||||
- if parent_suffix == top_be_suffix:
|
||||
+
|
||||
+ if is_subsuffix_of(parent_suffix, top_be_suffix, all_suffixes):
|
||||
child_suffix = mt.get_attr_val_utf8_l('cn')
|
||||
- be_insts = Backends(self._instance).list()
|
||||
for be in be_insts:
|
||||
- be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
- if child_suffix == be_suffix:
|
||||
+ if child_suffix == be.get_attr_val_utf8_l('nsslapd-suffix'):
|
||||
subsuffixes.append(be)
|
||||
break
|
||||
return subsuffixes
|
||||
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
|
||||
index d0ec4bd9e..9772e39d4 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -7,7 +7,7 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
-from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView
|
||||
+from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView, is_subsuffix_of
|
||||
from lib389.configurations.sample import (
|
||||
create_base_domain,
|
||||
create_base_org,
|
||||
@@ -338,6 +338,7 @@ def is_db_replicated(inst, suffix):
|
||||
def backend_get_subsuffixes(inst, basedn, log, args):
|
||||
subsuffixes = []
|
||||
be_insts = MANY(inst).list()
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
for be in be_insts:
|
||||
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
if be_suffix == args.be_name.lower():
|
||||
@@ -347,7 +348,7 @@ def backend_get_subsuffixes(inst, basedn, log, args):
|
||||
db_type = "suffix"
|
||||
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
|
||||
- if sub == be_suffix:
|
||||
+ if is_subsuffix_of(sub, be_suffix, all_suffixes):
|
||||
# We have a subsuffix (maybe a db link?)
|
||||
if is_db_link(inst, sub_be):
|
||||
db_type = "link"
|
||||
@@ -399,38 +400,34 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
|
||||
}
|
||||
|
||||
|
||||
-def backend_build_tree(inst, be_insts, nodes):
|
||||
- """Recursively build the tree
|
||||
- """
|
||||
- if len(nodes) == 0:
|
||||
- # Done
|
||||
+def backend_build_tree(inst, be_insts, nodes, all_suffixes):
|
||||
+ """Recursively build the tree."""
|
||||
+ if not nodes:
|
||||
return
|
||||
|
||||
for node in nodes:
|
||||
- node_suffix = node['id']
|
||||
+ node_suffix = node['id'].lower()
|
||||
# Get sub suffixes and chaining of node
|
||||
for be in be_insts:
|
||||
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
- if be_suffix == node_suffix.lower():
|
||||
+ if be_suffix == node_suffix:
|
||||
# We have our parent, now find the children
|
||||
mts = be._mts.list()
|
||||
-
|
||||
for mt in mts:
|
||||
sub_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
|
||||
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
|
||||
sub_suffix = mt.get_attr_val_utf8_l('cn')
|
||||
- if sub_parent == be_suffix:
|
||||
+ if is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
|
||||
# We have a subsuffix (maybe a db link?)
|
||||
link = is_db_link(inst, sub_be)
|
||||
replicated = is_db_replicated(inst, sub_suffix)
|
||||
node['children'].append(build_node(sub_suffix,
|
||||
- sub_be,
|
||||
- subsuf=True,
|
||||
- link=link,
|
||||
- replicated=replicated))
|
||||
-
|
||||
+ sub_be,
|
||||
+ subsuf=True,
|
||||
+ link=link,
|
||||
+ replicated=replicated))
|
||||
# Recurse over the new subsuffixes
|
||||
- backend_build_tree(inst, be_insts, node['children'])
|
||||
+ backend_build_tree(inst, be_insts, node['children'], all_suffixes)
|
||||
break
|
||||
|
||||
|
||||
@@ -471,7 +468,8 @@ def backend_get_tree(inst, basedn, log, args):
|
||||
else:
|
||||
# Build the tree
|
||||
be_insts = Backends(inst).list()
|
||||
- backend_build_tree(inst, be_insts, nodes)
|
||||
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
|
||||
+ backend_build_tree(inst, be_insts, nodes, all_suffixes)
|
||||
|
||||
# Done
|
||||
if args.json:
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
From aa24c00d9ed1ea9730c0e8c5dccc1bbb5b61e312 Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <lryznaro@redhat.com>
|
||||
Date: Mon, 26 Jan 2026 16:21:23 +0100
|
||||
Subject: [PATCH] Issue 7014 - memberOf - ignored deferred updates with LMDB
|
||||
|
||||
Description:
|
||||
Fix typo in pytest marker reason.
|
||||
|
||||
Relates: #7014
|
||||
|
||||
Author: Lenka Doudova
|
||||
Reviewed by: ???
|
||||
---
|
||||
.../suites/memberof_plugin/memberof_deferred_lmdb_test.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
index 0d9f793c1..12fcfa3ec 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
|
||||
@@ -1,4 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
@@ -28,7 +29,7 @@ else:
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
|
||||
|
||||
-@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="Not supported over mdb")
|
||||
+@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="Not supported over bdb")
|
||||
def test_memberof_deferred_update_lmdb_rejection(topo):
|
||||
"""Test that memberOf plugin rejects deferred update configuration with LMDB backend
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
From 4a73a31e6c91e507e5aa2cba1e5bd55d1d07894d Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 12 Jan 2026 13:53:05 -0500
|
||||
Subject: [PATCH] Issue 7184 - argparse.HelpFormatter _format_actions_usage()
|
||||
is deprecated
|
||||
|
||||
Description:
|
||||
|
||||
_format_actions_usage() was removed in python 3.15. Instead we can use
|
||||
_get_actions_usage_parts() but it also behaves differently between
|
||||
python 3.14 and 3.15 so we need special handling.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7184
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_base/__init__.py | 15 ++++++++++++++-
|
||||
1 file changed, 14 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
|
||||
index 06b8f9964..f1055aadc 100644
|
||||
--- a/src/lib389/lib389/cli_base/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_base/__init__.py
|
||||
@@ -413,7 +413,20 @@ class CustomHelpFormatter(argparse.HelpFormatter):
|
||||
|
||||
def _format_usage(self, usage, actions, groups, prefix):
|
||||
usage = super(CustomHelpFormatter, self)._format_usage(usage, actions, groups, prefix)
|
||||
- formatted_options = self._format_actions_usage(parent_arguments, [])
|
||||
+
|
||||
+ if sys.version_info < (3, 13):
|
||||
+ # Use _format_actions_usage() for Python 3.12 and earlier
|
||||
+ formatted_options = self._format_actions_usage(parent_arguments, [])
|
||||
+ else:
|
||||
+ # Use _get_actions_usage_parts() for Python 3.13 and later
|
||||
+ action_parts = self._get_actions_usage_parts(parent_arguments, [])
|
||||
+ if sys.version_info >= (3, 15):
|
||||
+ # Python 3.15 returns a tuple (list of actions, count of actions)
|
||||
+ formatted_options = ' '.join(action_parts[0])
|
||||
+ else:
|
||||
+ # Python 3.13 and 3.14 return a list of actions
|
||||
+ formatted_options = ' '.join(action_parts)
|
||||
+
|
||||
# If formatted_options already in usage - remove them
|
||||
if formatted_options in usage:
|
||||
usage = usage.replace(f' {formatted_options}', '')
|
||||
--
|
||||
2.52.0
|
||||
|
||||
174
0017-Issue-6947-Revise-time-skew-check-in-healthcheck-too.patch
Normal file
174
0017-Issue-6947-Revise-time-skew-check-in-healthcheck-too.patch
Normal file
@ -0,0 +1,174 @@
|
||||
From bc28406778534a77064a26b4f0467dffecde33ea Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 27 Jan 2026 09:40:12 +0100
|
||||
Subject: [PATCH] Issue 6947 - Revise time skew check in healthcheck tool - add
|
||||
tests (#7208)
|
||||
|
||||
Description:
|
||||
Add tests for DSSKEWLE0003 and DSSKEWLE0004 checks.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6947
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
---
|
||||
.../suites/healthcheck/health_skew_test.py | 148 ++++++++++++++++++
|
||||
1 file changed, 148 insertions(+)
|
||||
create mode 100644 dirsrvtests/tests/suites/healthcheck/health_skew_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_skew_test.py b/dirsrvtests/tests/suites/healthcheck/health_skew_test.py
|
||||
new file mode 100644
|
||||
index 000000000..9ee070364
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_skew_test.py
|
||||
@@ -0,0 +1,148 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+
|
||||
+import logging
|
||||
+import os
|
||||
+import pytest
|
||||
+import subprocess
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.dseldif import DSEldif
|
||||
+from lib389.replica import ReplicationManager
|
||||
+from lib389.topologies import topology_m2
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+CMD_OUTPUT = 'No issues found.'
|
||||
+JSON_OUTPUT = '[]'
|
||||
+
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+
|
||||
+def run_healthcheck_and_check_result(instance, searched_code, json=False, isnot=False):
|
||||
+ """Run healthcheck and verify the expected code is in the output"""
|
||||
+ cmd = ['dsctl']
|
||||
+ if json:
|
||||
+ cmd.append('--json')
|
||||
+ if searched_code == CMD_OUTPUT:
|
||||
+ searched_code = JSON_OUTPUT
|
||||
+ cmd.append(instance.serverid)
|
||||
+ cmd.extend(['healthcheck', '--check', 'dseldif:nsstate'])
|
||||
+
|
||||
+ result = subprocess.run(cmd, capture_output=True, universal_newlines=True)
|
||||
+ log.info(f'Running: {cmd}')
|
||||
+ log.info(f'Stdout: {result.stdout}')
|
||||
+ log.info(f'Stderr: {result.stderr}')
|
||||
+ log.info(f'Return code: {result.returncode}')
|
||||
+ stdout = result.stdout
|
||||
+
|
||||
+ # stdout should not be empty
|
||||
+ assert stdout is not None
|
||||
+ assert len(stdout) > 0
|
||||
+
|
||||
+ if isnot:
|
||||
+ assert searched_code not in stdout, \
|
||||
+ f'{searched_code} should NOT be in healthcheck output but was found'
|
||||
+ log.info(f'Verified {searched_code} is NOT in healthcheck output')
|
||||
+ else:
|
||||
+ assert searched_code in stdout, \
|
||||
+ f'{searched_code} should be in healthcheck output but was not found'
|
||||
+ log.info(f'Verified {searched_code} is in healthcheck output')
|
||||
+
|
||||
+
|
||||
+def test_healthcheck_time_skew_extensive(topology_m2):
|
||||
+ """Check if HealthCheck returns DSSKEWLE0003 and DSSKEWLE0004 codes for extensive time skew
|
||||
+
|
||||
+ :id: 7591cd2b-8d66-4d33-9f8d-babff0571086
|
||||
+ :setup: Two suppliers replication topology
|
||||
+ :steps:
|
||||
+ 1. Create a replicated topology
|
||||
+ 2. Stop supplier1
|
||||
+ 3. Increase time skew on supplier1 to over 24 hours
|
||||
+ 4. Start supplier1
|
||||
+ 5. Use HealthCheck and verify DSSKEWLE0003 is reported
|
||||
+ 6. Set nsslapd-ignore-time-skew to on
|
||||
+ 7. Use HealthCheck and verify DSSKEWLE0003 is NOT reported
|
||||
+ 8. Stop supplier1
|
||||
+ 9. Increase time skew on supplier1 to over 365 days
|
||||
+ 10. Start supplier1
|
||||
+ 11. Use HealthCheck and verify only DSSKEWLE0004 is reported
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Healthcheck reports DSSKEWLE0003 code
|
||||
+ 6. Success
|
||||
+ 7. Healthcheck does not report DSSKEWLE0003 code
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Healthcheck reports DSSKEWLE0004 code and not DSSKEWLE0003
|
||||
+ """
|
||||
+
|
||||
+ M1 = topology_m2.ms['supplier1']
|
||||
+ M2 = topology_m2.ms['supplier2']
|
||||
+
|
||||
+ # Ensure replication is working first
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+ repl.wait_for_replication(M1, M2)
|
||||
+
|
||||
+ # Step 2-4: Stop supplier1, increase time skew to over 24 hours, start supplier1
|
||||
+ log.info('Stop supplier1 to modify dse.ldif')
|
||||
+ M1.stop()
|
||||
+
|
||||
+ # Set time skew to over 24 hours (86400 seconds)
|
||||
+ # Add a margin to be safely over the threshold
|
||||
+ time_skew_24h = 86400 + 3600 # 25 hours
|
||||
+ log.info(f'Increase time skew on supplier1 by {time_skew_24h} seconds')
|
||||
+ DSEldif(M1)._increaseTimeSkew(DEFAULT_SUFFIX, time_skew_24h)
|
||||
+
|
||||
+ log.info('Start supplier1')
|
||||
+ M1.start()
|
||||
+
|
||||
+ # Step 5: Verify DSSKEWLE0003 is reported
|
||||
+ log.info('Run healthcheck and verify DSSKEWLE0003 is reported')
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False)
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True)
|
||||
+
|
||||
+ # Step 6: Set nsslapd-ignore-time-skew to on
|
||||
+ log.info('Set nsslapd-ignore-time-skew to on')
|
||||
+ M1.config.set('nsslapd-ignore-time-skew', 'on')
|
||||
+
|
||||
+ # Step 7: Verify DSSKEWLE0003 is NOT reported when ignoring time skew
|
||||
+ log.info('Run healthcheck and verify DSSKEWLE0003 is NOT reported')
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False, isnot=True)
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True, isnot=True)
|
||||
+
|
||||
+ # Step 8-10: Stop supplier1, increase time skew to over 365 days, start supplier1
|
||||
+ log.info('Stop supplier1 to modify dse.ldif')
|
||||
+ M1.stop()
|
||||
+
|
||||
+ # Increase time skew to over 365 days (31536000 seconds)
|
||||
+ # We need to add enough to go from current ~25 hours to over 365 days
|
||||
+ time_skew_year = (86400 * 365) + 86400 # 366 days total additional
|
||||
+ log.info(f'Increase time skew on supplier1 by {time_skew_year} seconds')
|
||||
+ DSEldif(M1)._increaseTimeSkew(DEFAULT_SUFFIX, time_skew_year)
|
||||
+
|
||||
+ log.info('Start supplier1')
|
||||
+ M1.start()
|
||||
+
|
||||
+ # Step 11: Verify only DSSKEWLE0004 is reported (not DSSKEWLE0003)
|
||||
+ log.info('Run healthcheck and verify only DSSKEWLE0004 is reported')
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0004', json=False)
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0004', json=True)
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False, isnot=True)
|
||||
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True, isnot=True)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
215
0018-Issue-7201-Syscall-overhead-in-LMDB-import-writer-th.patch
Normal file
215
0018-Issue-7201-Syscall-overhead-in-LMDB-import-writer-th.patch
Normal file
@ -0,0 +1,215 @@
|
||||
From a53c0e4dea5c35fef196500b2a36c92bc8f07a51 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 27 Jan 2026 09:49:16 +0100
|
||||
Subject: [PATCH] Issue 7201 - Syscall overhead in LMDB import writer thread
|
||||
(#7204)
|
||||
|
||||
Bug Description:
|
||||
ldif2db import is slower with LMDB than with BDB (~3500 vs ~4500
|
||||
entries/s) due to 2 issues:
|
||||
1. The MDB_STAT_STEP macro calls `clock_gettime()` to collect
|
||||
performance statistics. This was called on every single operation inside
|
||||
the writer loop, resulting in ~3 syscalls per write or ~6000 syscalls
|
||||
per transaction (with 2000 as the default batch size).
|
||||
|
||||
2. In `dbmdb_import_workerq_push()`, after copying work to a worker
|
||||
slot, the condition variable was never signaled. This caused workers to
|
||||
spend up to 100ms in `safe_cond_wait()` before checking for new work,
|
||||
severely limiting import throughput.
|
||||
|
||||
Fix Description:
|
||||
1. Add a new config parameter `nsslapd-mdb-import-stats` (default: off)
|
||||
to control whether performance statistics collection is enabled.
|
||||
2. Add `pthread_cond_broadcast()` immediately after copying data to wake
|
||||
the workers.
|
||||
|
||||
After applying these fixes ldif2db import rate is about ~10000 entries/s.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7201
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../slapd/back-ldbm/db-mdb/mdb_config.c | 23 +++++++++++
|
||||
.../back-ldbm/db-mdb/mdb_import_threads.c | 40 ++++++++++---------
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.h | 2 +
|
||||
3 files changed, 46 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
|
||||
index 96295ed5b..ade42b1d7 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
|
||||
@@ -477,6 +477,28 @@ dbmdb_ctx_t_db_durable_transactions_set(void *arg, void *value, char *errorbuf _
|
||||
return retval;
|
||||
}
|
||||
|
||||
+static void *
|
||||
+dbmdb_ctx_t_db_import_stats_get(void *arg)
|
||||
+{
|
||||
+ struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
+
|
||||
+ return (void *)((uintptr_t)(MDB_CONFIG(li)->dsecfg.import_stats));
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+dbmdb_ctx_t_db_import_stats_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
|
||||
+{
|
||||
+ struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
+ int retval = LDAP_SUCCESS;
|
||||
+ int val = (int)((uintptr_t)value);
|
||||
+
|
||||
+ if (apply) {
|
||||
+ MDB_CONFIG(li)->dsecfg.import_stats = val;
|
||||
+ }
|
||||
+
|
||||
+ return retval;
|
||||
+}
|
||||
+
|
||||
static int
|
||||
dbmdb_ctx_t_set_bypass_filter_test(void *arg,
|
||||
void *value,
|
||||
@@ -589,6 +611,7 @@ static config_info dbmdb_ctx_t_param[] = {
|
||||
{CONFIG_MDB_MAX_DBS, CONFIG_TYPE_INT, "512", &dbmdb_ctx_t_db_max_dbs_get, &dbmdb_ctx_t_db_max_dbs_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &dbmdb_ctx_t_maxpassbeforemerge_get, &dbmdb_ctx_t_maxpassbeforemerge_set, 0},
|
||||
{CONFIG_DB_DURABLE_TRANSACTIONS, CONFIG_TYPE_ONOFF, "on", &dbmdb_ctx_t_db_durable_transactions_get, &dbmdb_ctx_t_db_durable_transactions_set, CONFIG_FLAG_ALWAYS_SHOW},
|
||||
+ {CONFIG_MDB_IMPORT_STATS, CONFIG_TYPE_ONOFF, "off", &dbmdb_ctx_t_db_import_stats_get, &dbmdb_ctx_t_db_import_stats_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_BYPASS_FILTER_TEST, CONFIG_TYPE_STRING, "on", &dbmdb_ctx_t_get_bypass_filter_test, &dbmdb_ctx_t_set_bypass_filter_test, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_SERIAL_LOCK, CONFIG_TYPE_ONOFF, "on", &dbmdb_ctx_t_serial_lock_get, &dbmdb_ctx_t_serial_lock_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "25", &mdb_config_cache_autosize_get, &mdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
index 2270abd69..65b29343e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
|
||||
@@ -59,9 +59,9 @@
|
||||
|
||||
|
||||
/* import thread usage statistics */
|
||||
-#define MDB_STAT_INIT(stats) { mdb_stat_collect(&stats, MDB_STAT_RUN, 1); }
|
||||
-#define MDB_STAT_END(stats) { mdb_stat_collect(&stats, MDB_STAT_RUN, 0); }
|
||||
-#define MDB_STAT_STEP(stats, step) { mdb_stat_collect(&stats, (step), 0); }
|
||||
+#define MDB_STAT_INIT(stats, enabled) { if (enabled) mdb_stat_collect(&stats, MDB_STAT_RUN, 1); }
|
||||
+#define MDB_STAT_END(stats, enabled) { if (enabled) mdb_stat_collect(&stats, MDB_STAT_RUN, 0); }
|
||||
+#define MDB_STAT_STEP(stats, step, enabled) { if (enabled) mdb_stat_collect(&stats, (step), 0); }
|
||||
|
||||
typedef enum {
|
||||
MDB_STAT_RUN,
|
||||
@@ -424,6 +424,7 @@ dbmdb_import_workerq_push(ImportQueue_t *q, WorkerQueueData_t *data)
|
||||
return -1;
|
||||
}
|
||||
dbmdb_dup_worker_slot(q, data, slot);
|
||||
+ pthread_cond_broadcast(&q->cv);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return 0;
|
||||
}
|
||||
@@ -3914,14 +3915,15 @@ dbmdb_import_writer(void*param)
|
||||
int count = 0;
|
||||
int rc = 0;
|
||||
mdb_stat_info_t stats = {0};
|
||||
+ int stats_enabled = ctx->ctx->dsecfg.import_stats;
|
||||
|
||||
- MDB_STAT_INIT(stats);
|
||||
+ MDB_STAT_INIT(stats, stats_enabled);
|
||||
while (!rc && !info_is_finished(info)) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_PAUSE);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_PAUSE, stats_enabled);
|
||||
wait_for_starting(info);
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_READ);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_READ, stats_enabled);
|
||||
slot = dbmdb_import_q_getall(&ctx->writerq);
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
|
||||
if (info_is_finished(info)) {
|
||||
dbmdb_import_q_flush(&ctx->writerq);
|
||||
break;
|
||||
@@ -3932,14 +3934,14 @@ dbmdb_import_writer(void*param)
|
||||
|
||||
for (; slot; slot = nextslot) {
|
||||
if (!txn) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTART);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTART, stats_enabled);
|
||||
rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
|
||||
}
|
||||
if (!rc) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_WRITE);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_WRITE, stats_enabled);
|
||||
rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
|
||||
}
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
|
||||
nextslot = slot->next;
|
||||
slapi_ch_free((void**)&slot);
|
||||
}
|
||||
@@ -3947,9 +3949,9 @@ dbmdb_import_writer(void*param)
|
||||
break;
|
||||
}
|
||||
if (count++ >= WRITER_MAX_OPS_IN_TXN) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
|
||||
rc = TXN_COMMIT(txn);
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
|
||||
if (rc) {
|
||||
break;
|
||||
}
|
||||
@@ -3958,32 +3960,32 @@ dbmdb_import_writer(void*param)
|
||||
}
|
||||
}
|
||||
if (txn && !rc) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
|
||||
rc = TXN_COMMIT(txn);
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
|
||||
if (!rc) {
|
||||
txn = NULL;
|
||||
}
|
||||
}
|
||||
if (txn) {
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
|
||||
TXN_ABORT(txn);
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
|
||||
txn = NULL;
|
||||
}
|
||||
- MDB_STAT_STEP(stats, MDB_STAT_WRITE);
|
||||
+ MDB_STAT_STEP(stats, MDB_STAT_WRITE, stats_enabled);
|
||||
if (!rc) {
|
||||
/* Ensure that all data are written on disk */
|
||||
rc = mdb_env_sync(ctx->ctx->env, 1);
|
||||
}
|
||||
- MDB_STAT_END(stats);
|
||||
+ MDB_STAT_END(stats, stats_enabled);
|
||||
|
||||
if (rc) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
|
||||
"Failed to write in the database. Error is 0x%x: %s.\n",
|
||||
rc, mdb_strerror(rc));
|
||||
thread_abort(info);
|
||||
- } else {
|
||||
+ } else if (stats_enabled) {
|
||||
char buf[200];
|
||||
char *summary = mdb_stat_summarize(&stats, buf, sizeof buf);
|
||||
if (summary) {
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
|
||||
index 647d00db9..cf6d00dd4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
|
||||
@@ -41,6 +41,7 @@
|
||||
#define CONFIG_MDB_MAX_SIZE "nsslapd-mdb-max-size"
|
||||
#define CONFIG_MDB_MAX_READERS "nsslapd-mdb-max-readers"
|
||||
#define CONFIG_MDB_MAX_DBS "nsslapd-mdb-max-dbs"
|
||||
+#define CONFIG_MDB_IMPORT_STATS "nsslapd-mdb-import-stats"
|
||||
|
||||
#define DBMDB_DB_MINSIZE ( 4LL * MEGABYTE )
|
||||
#define DBMDB_DISK_RESERVE(disksize) ((disksize)*2ULL/1000ULL)
|
||||
@@ -76,6 +77,7 @@ typedef struct
|
||||
int max_readers;
|
||||
int max_dbs;
|
||||
uint64_t max_size;
|
||||
+ int import_stats;
|
||||
} dbmdb_cfg_t;
|
||||
|
||||
/* config parameters limits */
|
||||
--
|
||||
2.52.0
|
||||
|
||||
135
0019-Issue-7096-2nd-During-replication-online-total-init-.patch
Normal file
135
0019-Issue-7096-2nd-During-replication-online-total-init-.patch
Normal file
@ -0,0 +1,135 @@
|
||||
From 40484cb0b5034bc3c1e23b2ae1f2d39eedce07e9 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 27 Jan 2026 14:26:29 +0100
|
||||
Subject: [PATCH] Issue 7096 - (2nd) During replication online total init the
|
||||
function idl_id_is_in_idlist is not scaling with large database (#7205)
|
||||
|
||||
Bug Description:
|
||||
The fix for #7096 optimized the BDB backend's `idl_new_range_fetch()`
|
||||
function to use ID ranges instead of checking the full ID list during
|
||||
online total initialization. However, the LMDB backend's
|
||||
`idl_lmdb_range_fetch()` function and its callback
|
||||
`idl_range_add_id_cb()` were not updated and still use the non-scaling
|
||||
`idl_id_is_in_idlist()` function.
|
||||
|
||||
Fix Description:
|
||||
Apply the same optimization to the LMDB backend.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7096
|
||||
|
||||
Reviewed by: @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 39 ++++++++++----------------
|
||||
1 file changed, 15 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
index 2d978353f..613d53815 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
@@ -66,6 +66,7 @@ typedef struct {
|
||||
size_t leftoverlen;
|
||||
size_t leftovercnt;
|
||||
IDList *idl;
|
||||
+ IdRange_t *idrange_list;
|
||||
int flag_err;
|
||||
ID lastid;
|
||||
ID suffix;
|
||||
@@ -700,9 +701,9 @@ error:
|
||||
}
|
||||
}
|
||||
}
|
||||
- slapi_ch_free((void **)&leftover);
|
||||
- idrange_free(&idrange_list);
|
||||
}
|
||||
+ slapi_ch_free((void **)&leftover);
|
||||
+ idrange_free(&idrange_list);
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
idl ? idl->b_nids : 0, *flag_err);
|
||||
@@ -716,7 +717,6 @@ static int
|
||||
idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
{
|
||||
idl_range_ctx_t *rctx = ctx;
|
||||
- int idl_rc = 0;
|
||||
ID id = 0;
|
||||
|
||||
if (key->data == NULL) {
|
||||
@@ -779,10 +779,12 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
* found entry is the one from the suffix
|
||||
*/
|
||||
rctx->suffix = keyval;
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
- } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist(rctx->idl, keyval)) {
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
+ idrange_add_id(&rctx->idrange_list, id);
|
||||
+ } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist_ranges(rctx->idl, rctx->idrange_list, keyval)) {
|
||||
/* the parent is the suffix or already in idl. */
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
+ idrange_add_id(&rctx->idrange_list, id);
|
||||
} else {
|
||||
/* Otherwise, keep the {keyval,id} in leftover array */
|
||||
if (!rctx->leftover) {
|
||||
@@ -797,14 +799,7 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
rctx->leftovercnt++;
|
||||
}
|
||||
} else {
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
- }
|
||||
- if (idl_rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
|
||||
- "Unable to extend id list (err=%d)\n", idl_rc);
|
||||
- idl_free(&rctx->idl);
|
||||
- rctx->flag_err = LDAP_UNWILLING_TO_PERFORM;
|
||||
- return DBI_RC_NOTFOUND;
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
}
|
||||
#if defined(DB_ALLIDS_ON_READ)
|
||||
/* enforce the allids read limit */
|
||||
@@ -841,7 +836,6 @@ idl_lmdb_range_fetch(
|
||||
{
|
||||
int ret = 0;
|
||||
int ret2 = 0;
|
||||
- int idl_rc = 0;
|
||||
dbi_cursor_t cursor = {0};
|
||||
back_txn s_txn;
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
@@ -891,6 +885,7 @@ idl_lmdb_range_fetch(
|
||||
idl_range_ctx.lastid = 0;
|
||||
idl_range_ctx.count = 0;
|
||||
idl_range_ctx.index_id = index_id;
|
||||
+ idl_range_ctx.idrange_list = NULL;
|
||||
if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
struct _back_info_index_key bck_info;
|
||||
/* We are doing a bulk import
|
||||
@@ -966,22 +961,18 @@ error:
|
||||
while(remaining > 0) {
|
||||
for (size_t i = 0; i < idl_range_ctx.leftovercnt; i++) {
|
||||
if (idl_range_ctx.leftover[i].key > 0 &&
|
||||
- idl_id_is_in_idlist(idl_range_ctx.idl, idl_range_ctx.leftover[i].key) != 0) {
|
||||
+ idl_id_is_in_idlist_ranges(idl_range_ctx.idl, idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].key) != 0) {
|
||||
/* if the leftover key has its parent in the idl */
|
||||
- idl_rc = idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
|
||||
- if (idl_rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
|
||||
- "Unable to extend id list (err=%d)\n", idl_rc);
|
||||
- idl_free(&idl_range_ctx.idl);
|
||||
- break;
|
||||
- }
|
||||
+ idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
|
||||
+ idrange_add_id(&idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].id);
|
||||
idl_range_ctx.leftover[i].key = 0;
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
- slapi_ch_free((void **)&idl_range_ctx.leftover);
|
||||
}
|
||||
+ slapi_ch_free((void **)&idl_range_ctx.leftover);
|
||||
+ idrange_free(&idl_range_ctx.idrange_list);
|
||||
*flag_err = idl_range_ctx.flag_err;
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_lmdb_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
--
|
||||
2.52.0
|
||||
|
||||
149
0020-Issue-7206-Should-log-whether-TLS-key-is-PQC-or-not-.patch
Normal file
149
0020-Issue-7206-Should-log-whether-TLS-key-is-PQC-or-not-.patch
Normal file
@ -0,0 +1,149 @@
|
||||
From 8c6d1cfca22f87b20b79a419ceabdb182846ff4a Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 27 Jan 2026 15:39:39 +0100
|
||||
Subject: [PATCH] Issue 7206 - Should log whether TLS key is PQC or not (#7207)
|
||||
|
||||
Append [PQC] to the cipher name when logging SSL/TLS if the Key Exchange is one of the KEM group
|
||||
If connection debug level is enabled, logs in error log the keaType and keaGroup (as integer)
|
||||
|
||||
Issue: #7206
|
||||
|
||||
Reviewed by: @tbordaz, @droideck (Thanks!)
|
||||
|
||||
* Issue 7206 - Should log whether TLS key is PQC or not
|
||||
|
||||
* Fix Sourcery A/I comments
|
||||
|
||||
* Check PQC in test case
|
||||
|
||||
* Update ldap/servers/slapd/auth.c
|
||||
|
||||
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
|
||||
---
|
||||
dirsrvtests/tests/suites/tls/mldsa_test.py | 7 ++++
|
||||
ldap/servers/slapd/auth.c | 45 +++++++++++++++++++++-
|
||||
ldap/servers/slapd/ssl.c | 2 +-
|
||||
3 files changed, 52 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/tls/mldsa_test.py b/dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
index 2c815088b..d6cbaf662 100644
|
||||
--- a/dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
+++ b/dirsrvtests/tests/suites/tls/mldsa_test.py
|
||||
@@ -274,6 +274,7 @@ def test_mldsa(topo):
|
||||
"""
|
||||
|
||||
inst = topo.standalone
|
||||
+ inst.config.set('nsslapd-errorlog-level', str(16384 + 8))
|
||||
inst.enable_tls()
|
||||
|
||||
cm = CertmapLegacy(inst)
|
||||
@@ -293,6 +294,9 @@ def test_mldsa(topo):
|
||||
'loginShell': '/bin/false',
|
||||
'description': cert_dn })
|
||||
|
||||
+ inst.config.set("nsslapd-accesslog-logbuffering", "off")
|
||||
+ inst.config.set("nsslapd-errorlog-logbuffering", "off")
|
||||
+
|
||||
tmpdir_kwargs = {}
|
||||
if sys.version_info >= (3, 12):
|
||||
tmpdir_kwargs['delete'] = not DEBUGGING
|
||||
@@ -313,6 +317,9 @@ def test_mldsa(topo):
|
||||
res.check_returncode()
|
||||
# If ldapsearch is successful then defaultnamingcontext should be in res.stdout
|
||||
assert "defaultnamingcontext" in res.stdout
|
||||
+ assert inst.ds_access_log.match('.*RESULT.*dn="uid=test_user,ou=people,dc=example,dc=com".*')
|
||||
+ assert inst.ds_access_log.match('.*TLS.*[PQC].*')
|
||||
+ assert inst.ds_error_log.match('.*check_pqc.*')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
diff --git a/ldap/servers/slapd/auth.c b/ldap/servers/slapd/auth.c
|
||||
index 48e4b7129..9e83408d4 100644
|
||||
--- a/ldap/servers/slapd/auth.c
|
||||
+++ b/ldap/servers/slapd/auth.c
|
||||
@@ -395,6 +395,43 @@ handle_bad_certificate(void *clientData, PRFileDesc *prfd)
|
||||
}
|
||||
|
||||
|
||||
+/*
|
||||
+ * Determine if the connection key exchange is Post Quantum Cryptography aware.
|
||||
+ * This function may need to evolve with NSS as more PQC methods get supported.
|
||||
+ */
|
||||
+static bool
|
||||
+check_pqc(uint64_t connid, SSLChannelInfo *sci)
|
||||
+{
|
||||
+ /*
|
||||
+ * FYI: To interpret the values:
|
||||
+ * KeaType and KeaGroup values are defined in
|
||||
+ * https://github.com/nss-dev/nss/blob/master/lib/ssl/sslt.h
|
||||
+ * Respectively in SSLKEAType and SSLNamedGroup enums
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_CONNS, "check_pqc", "conn=%" PRIu64 " TLS keaType=%d keaGroup=%d\n",
|
||||
+ connid, sci->keaType, sci->keaGroup);
|
||||
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
+ /* NSS supports PQC (because of the ifdef). Now lets check if the connection uses it */
|
||||
+ /* Check that PQC KeaType is hybrid */
|
||||
+ switch (sci->keaType) {
|
||||
+ case ssl_kea_ecdh_hybrid:
|
||||
+ case ssl_kea_ecdh_hybrid_psk:
|
||||
+ break;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+ /* Check that PQC keaGroup is KEM */
|
||||
+ switch (sci->keaGroup) {
|
||||
+ case ssl_grp_kem_secp256r1mlkem768:
|
||||
+ case ssl_grp_kem_secp384r1mlkem1024:
|
||||
+ case ssl_grp_kem_mlkem768x25519:
|
||||
+ case ssl_grp_kem_xyber768d00:
|
||||
+ return true;
|
||||
+ }
|
||||
+#endif
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Get an identity from the client's certificate (if any was sent).
|
||||
*
|
||||
@@ -417,6 +454,7 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData)
|
||||
SSLCipherSuiteInfo cipherInfo;
|
||||
char *subject = NULL;
|
||||
char sslversion[64];
|
||||
+ bool pqc = false;
|
||||
int err = 0;
|
||||
|
||||
if ((slapd_ssl_getChannelInfo(prfd, &channelInfo, sizeof(channelInfo))) != SECSuccess) {
|
||||
@@ -454,7 +492,12 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData)
|
||||
}
|
||||
|
||||
keySize = cipherInfo.effectiveKeyBits;
|
||||
- cipher = slapi_ch_strdup(cipherInfo.symCipherName);
|
||||
+ pqc = check_pqc(conn->c_connid, &channelInfo);
|
||||
+ if (pqc) {
|
||||
+ cipher = slapi_ch_smprintf("%s[PQC]", cipherInfo.symCipherName);
|
||||
+ } else {
|
||||
+ cipher = slapi_ch_strdup(cipherInfo.symCipherName);
|
||||
+ }
|
||||
|
||||
/* If inside an Start TLS operation, perform the privacy level discovery
|
||||
* and if the security degree achieved after the handshake is not reckoned
|
||||
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
|
||||
index 053db5424..7d5db2cdd 100644
|
||||
--- a/ldap/servers/slapd/ssl.c
|
||||
+++ b/ldap/servers/slapd/ssl.c
|
||||
@@ -748,7 +748,7 @@ SSLPLCY_Install(void)
|
||||
if (!slapd_pk11_isFIPS()) {
|
||||
/* Set explicitly PQC algorithm policy if it is not set by default */
|
||||
for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
|
||||
- int oflags = 0;
|
||||
+ PRUint32 oflags = 0;
|
||||
(void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
|
||||
if ((oflags & flags) != flags) {
|
||||
s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
From 27d3ea211847fa7ae674c5e4dcf485706e4ac591 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 30 Jan 2026 12:00:13 +0100
|
||||
Subject: [PATCH] Issue 7027 - (2nd) 389-ds-base OpenScanHub Leaks Detected
|
||||
(#7211)
|
||||
|
||||
Fix Description:
|
||||
Update coverity annotations.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7027
|
||||
|
||||
Reviewed by: @aadhikar (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/log.c | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index ea744ac1e..80c07382a 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -206,8 +206,8 @@ compress_log_file(char *log_name, int32_t mode)
|
||||
|
||||
if ((source = fopen(log_name, "r")) == NULL) {
|
||||
/* Failed to open log file */
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -217,17 +217,17 @@ compress_log_file(char *log_name, int32_t mode)
|
||||
if (bytes_written == 0)
|
||||
{
|
||||
fclose(source);
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return -1;
|
||||
}
|
||||
bytes_read = fread(buf, 1, LOG_CHUNK, source);
|
||||
}
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
fclose(source);
|
||||
PR_Delete(log_name); /* remove the old uncompressed log */
|
||||
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
195
0022-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
Normal file
195
0022-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
Normal file
@ -0,0 +1,195 @@
|
||||
From 5ebce22d4214bec5ed94ad84c4448164be99389a Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 2 Feb 2026 15:39:18 +0100
|
||||
Subject: [PATCH] Issue 7213 - MDB_BAD_VALSIZE error while handling VLV (#7214)
|
||||
|
||||
* Issue 7213 - MDB_BAD_VALSIZE error while handling VLV
|
||||
Avoid failing lmdb operation when handling VLV index by truncating the key so that key+data is small enough.
|
||||
|
||||
Issue: #7213
|
||||
|
||||
Reviewed by: @mreynolds389 , @vashirov (Thanks!)
|
||||
|
||||
Assisted by: Claude A/I
|
||||
---
|
||||
.../tests/suites/vlv/regression_test.py | 110 ++++++++++++++++++
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 5 +
|
||||
ldap/servers/slapd/back-ldbm/vlv.c | 7 +-
|
||||
3 files changed, 121 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index f7847ac74..7cdf16a84 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1175,6 +1175,116 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
|
||||
|
||||
|
||||
+def test_vlv_long_attribute_value(topology_st, request):
|
||||
+ """
|
||||
+ Test VLV with an entry containing a very long attribute value (2K).
|
||||
+
|
||||
+ :id: 99126fa4-003e-11f1-b7d6-c85309d5c3e3
|
||||
+ :setup: Standalone instance.
|
||||
+ :steps:
|
||||
+ 1. Cleanup leftover from previous tests
|
||||
+ 2. Create VLV search and index on cn attribute
|
||||
+ 3. Reindex VLV
|
||||
+ 4. Add an entry with a cn attribute having 2K character value
|
||||
+ 5. Verify the entry was added successfully
|
||||
+ 6. Perform a VLV search to ensure it still works
|
||||
+ 7. Add another entry with a cn attribute having 2K character value
|
||||
+ 8. Verify the entry was added successfully
|
||||
+ 9. Perform a VLV search to ensure it still works
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ 3. Should Success.
|
||||
+ 4. Should Success.
|
||||
+ 5. Should Success.
|
||||
+ 6. Should Success.
|
||||
+ 7. Should Success.
|
||||
+ 8. Should Success.
|
||||
+ 9. Should Success.
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ reindex_task = Tasks(inst)
|
||||
+
|
||||
+ users_to_delete = []
|
||||
+
|
||||
+ def fin():
|
||||
+ cleanup(inst)
|
||||
+ # Clean the added users
|
||||
+ for user in users_to_delete:
|
||||
+ user.delete()
|
||||
+
|
||||
+ if not DEBUGGING:
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Clean previous tests leftover
|
||||
+ fin()
|
||||
+
|
||||
+ # Create VLV search and index
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ # Add a few regular users first
|
||||
+ add_users(inst, 10)
|
||||
+
|
||||
+ # Create a very long cn value (2K characters)
|
||||
+ long_cn_value = 'a' * 2048 + '1'
|
||||
+
|
||||
+ # Add an entry with the long cn attribute
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user_properties = {
|
||||
+ 'uid': 'longcnuser1',
|
||||
+ 'cn': long_cn_value,
|
||||
+ 'sn': 'user1',
|
||||
+ 'uidNumber': '99999',
|
||||
+ 'gidNumber': '99999',
|
||||
+ 'homeDirectory': '/home/longcnuser1'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ users_to_delete.append(user);
|
||||
+
|
||||
+ # Verify the entry was created and has the long cn value
|
||||
+ entry = user.get_attr_vals_utf8('cn')
|
||||
+ assert entry[0] == long_cn_value
|
||||
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
|
||||
+
|
||||
+ # Perform VLV search to ensure VLV still works with long attribute values
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
|
||||
+ assert count > 0
|
||||
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
|
||||
+
|
||||
+ # Add another entry with the long cn attribute
|
||||
+ long_cn_value = 'a' * 2048 + '2'
|
||||
+
|
||||
+ user_properties = {
|
||||
+ 'uid': 'longcnuser2',
|
||||
+ 'cn': long_cn_value,
|
||||
+ 'sn': 'user2',
|
||||
+ 'uidNumber': '99998',
|
||||
+ 'gidNumber': '99998',
|
||||
+ 'homeDirectory': '/home/longcnuser2'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ users_to_delete.append(user);
|
||||
+
|
||||
+ # Verify the entry was created and has the long cn value
|
||||
+ entry = user.get_attr_vals_utf8('cn')
|
||||
+ assert entry[0] == long_cn_value
|
||||
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
|
||||
+
|
||||
+ # Perform VLV search to ensure VLV still works with long attribute values
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
|
||||
+ assert count > 1
|
||||
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
|
||||
+
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
index d320ecbeb..cd797621d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
@@ -2134,10 +2134,15 @@ void *dbmdb_recno_cache_build(void *arg)
|
||||
recno = 1;
|
||||
}
|
||||
while (rc == 0) {
|
||||
+ struct ldbminfo *li = (struct ldbminfo *)rcctx->cursor->be->be_database->plg_private;
|
||||
slapi_log_err(SLAPI_LOG_DEBUG, "dbmdb_recno_cache_build", "recno=%d\n", recno);
|
||||
if (recno % RECNO_CACHE_INTERVAL == 1) {
|
||||
/* Prepare the cache data */
|
||||
len = sizeof(*rce) + data.mv_size + key.mv_size;
|
||||
+ if (len > li->li_max_key_len) {
|
||||
+ key.mv_size = li->li_max_key_len - data.mv_size - sizeof(*rce);
|
||||
+ len = li->li_max_key_len;
|
||||
+ }
|
||||
rce = (dbmdb_recno_cache_elmt_t*)slapi_ch_malloc(len);
|
||||
rce->len = len;
|
||||
rce->recno = recno;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
index 8f9263f25..f2f882b5a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
@@ -866,6 +866,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
struct vlv_key *key = NULL;
|
||||
dbi_val_t data = {0};
|
||||
dblayer_private *priv = NULL;
|
||||
+ size_t key_size_limit = li->li_max_key_len - sizeof(entry->ep_id);
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_BACKEND, &be);
|
||||
priv = (dblayer_private *)li->li_dblayer_private;
|
||||
@@ -886,6 +887,10 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
return rc;
|
||||
}
|
||||
|
||||
+ /* Truncate the key if it is too long */
|
||||
+ if (key->key.size > key_size_limit) {
|
||||
+ key->key.size = key_size_limit;
|
||||
+ }
|
||||
if (NULL != txn) {
|
||||
db_txn = txn->back_txn_txn;
|
||||
} else {
|
||||
@@ -930,7 +935,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
if (txn && txn->back_special_handling_fn) {
|
||||
rc = txn->back_special_handling_fn(be, BTXNACT_VLV_DEL, db, &key->key, &data, txn);
|
||||
} else {
|
||||
- rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, NULL);
|
||||
+ rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, &data);
|
||||
}
|
||||
if (rc == 0) {
|
||||
if (txn && txn->back_special_handling_fn) {
|
||||
--
|
||||
2.52.0
|
||||
|
||||
285
0023-Issue-6753-Port-ticket-47781-test-7210.patch
Normal file
285
0023-Issue-6753-Port-ticket-47781-test-7210.patch
Normal file
@ -0,0 +1,285 @@
|
||||
From 56f1881b5fb0198af4810eeca9e98736c297a2a5 Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <mirielka@users.noreply.github.com>
|
||||
Date: Tue, 3 Feb 2026 10:28:02 +0100
|
||||
Subject: [PATCH] Issue 6753 - Port ticket 47781 test (#7210)
|
||||
|
||||
Description:
|
||||
Port ticket 47781 test into dirsrvtests/tests/suites/replication/replication_deadlock_test.py
|
||||
|
||||
Relates: #6753
|
||||
Author: Lenka Doudova
|
||||
Assisted by: Cursor
|
||||
Reviewer: Mark Reynolds
|
||||
---
|
||||
.../replication/replication_deadlock_test.py | 129 +++++++++++++++++-
|
||||
dirsrvtests/tests/tickets/ticket47781_test.py | 104 --------------
|
||||
2 files changed, 128 insertions(+), 105 deletions(-)
|
||||
delete mode 100644 dirsrvtests/tests/tickets/ticket47781_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/replication_deadlock_test.py b/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
|
||||
index 8ddbb43bd..9c122add3 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
|
||||
@@ -15,14 +15,20 @@ with replication data when replication agreements are present.
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
+import ldap
|
||||
|
||||
+from lib389._mapped_object import DSLdapObjects
|
||||
from lib389.topologies import topology_m2 as topo
|
||||
+from lib389.topologies import topology_st
|
||||
from lib389.replica import Replicas, ReplicationManager
|
||||
from lib389.agreement import Agreements
|
||||
from lib389.tasks import ImportTask, ExportTask
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.tombstone import Tombstones
|
||||
-from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.backend import Backends
|
||||
+from lib389._constants import (DEFAULT_SUFFIX, DEFAULT_BENAME, defaultProperties,
|
||||
+ REPLICATION_BIND_DN, REPLICATION_BIND_PW,
|
||||
+ REPLICATION_BIND_METHOD, REPLICATION_TRANSPORT)
|
||||
|
||||
pytestmark = pytest.mark.tier2
|
||||
|
||||
@@ -200,6 +206,127 @@ def test_replication_deadlock_tombstone_search(topo):
|
||||
os.remove(export_ldif)
|
||||
|
||||
|
||||
+def test_replication_deadlock_tombstone_invalid_agreement(topology_st):
|
||||
+ """Test deadlock scenario after importing LDIF with replication data and invalid agreement
|
||||
+
|
||||
+ This test verifies that a deadlock does not occur while searching for tombstones after
|
||||
+ setting up an invalid replication agreement (pointing to a non-existent server)
|
||||
+
|
||||
+ :id: a1b2c3d4-e5f6-4781-9abc-def012345678
|
||||
+ :setup: Standalone instance with replication enabled
|
||||
+ :steps:
|
||||
+ 1. Create a supplier with an invalid replication agreement (port 5555, non-existent server)
|
||||
+ 2. Add two test user entries
|
||||
+ 3. Export LDIF with replication data
|
||||
+ 4. Restart the server
|
||||
+ 5. Import the LDIF back
|
||||
+ 6. Search for tombstone entries with timeout (should not hang/deadlock)
|
||||
+ 7. Cleanup: restore original timeout settings, delete invalid agreement, test entries, and export file
|
||||
+ :expectedresults:
|
||||
+ 1. Invalid replication agreement is created successfully
|
||||
+ 2. Test entries are created successfully
|
||||
+ 3. LDIF export completes successfully
|
||||
+ 4. Server restarts successfully
|
||||
+ 5. LDIF import completes successfully
|
||||
+ 6. Tombstone search completes without deadlock (returns at least one entry; no hang/timeout)
|
||||
+ 7. Cleanup completes successfully
|
||||
+ """
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ export_ldif = os.path.join(standalone.get_ldif_dir(), 'export.ldif')
|
||||
+
|
||||
+ # Step 1: Create supplier with invalid replication agreement
|
||||
+ log.info('Creating supplier with invalid replication agreement (port 5555 - non-existent server)')
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+ repl.create_first_supplier(standalone)
|
||||
+
|
||||
+ replicas = Replicas(standalone)
|
||||
+ replica = replicas.get(DEFAULT_SUFFIX)
|
||||
+ agreements = Agreements(standalone, basedn=replica.dn)
|
||||
+
|
||||
+ # Create agreement with invalid port (non-existent server)
|
||||
+ invalid_agreement = agreements.create(properties={
|
||||
+ 'cn': r'meTo_$host:$port',
|
||||
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX,
|
||||
+ 'nsDS5ReplicaHost': standalone.host,
|
||||
+ 'nsDS5ReplicaPort': '5555', # Invalid port - server does not exist
|
||||
+ 'nsDS5ReplicaBindDN': defaultProperties[REPLICATION_BIND_DN],
|
||||
+ 'nsDS5ReplicaBindMethod': defaultProperties[REPLICATION_BIND_METHOD],
|
||||
+ 'nsDS5ReplicaTransportInfo': defaultProperties[REPLICATION_TRANSPORT],
|
||||
+ 'nsDS5ReplicaCredentials': defaultProperties[REPLICATION_BIND_PW]
|
||||
+ })
|
||||
+
|
||||
+ # Step 2: Add two test entries
|
||||
+ log.info('Adding two test entries')
|
||||
+ users = UserAccounts(standalone, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ entry1 = users.create(properties={
|
||||
+ 'uid': 'entry1',
|
||||
+ 'cn': 'entry1',
|
||||
+ 'sn': 'user',
|
||||
+ 'userpassword': 'password',
|
||||
+ 'uidNumber': '1001',
|
||||
+ 'gidNumber': '2001',
|
||||
+ 'homeDirectory': '/home/entry1'
|
||||
+ })
|
||||
+
|
||||
+ entry2 = users.create(properties={
|
||||
+ 'uid': 'entry2',
|
||||
+ 'cn': 'entry2',
|
||||
+ 'sn': 'user',
|
||||
+ 'userpassword': 'password',
|
||||
+ 'uidNumber': '1002',
|
||||
+ 'gidNumber': '2002',
|
||||
+ 'homeDirectory': '/home/entry2'
|
||||
+ })
|
||||
+
|
||||
+ # Step 3: Export LDIF with replication data
|
||||
+ log.info('Exporting LDIF with replication data')
|
||||
+ backends = Backends(standalone)
|
||||
+ export_task = backends.export_ldif(be_names=DEFAULT_BENAME, ldif=export_ldif, replication=True)
|
||||
+ export_task.wait()
|
||||
+
|
||||
+ # Step 4: Restart the server
|
||||
+ log.info('Restarting server')
|
||||
+ standalone.restart()
|
||||
+
|
||||
+ # Step 5: Import the LDIF
|
||||
+ log.info('Importing replication LDIF file')
|
||||
+ import_task = ImportTask(standalone)
|
||||
+ import_task.import_suffix_from_ldif(ldiffile=export_ldif, suffix=DEFAULT_SUFFIX)
|
||||
+ import_task.wait()
|
||||
+
|
||||
+ # Step 6: Search for tombstones with timeout - should not hang/deadlock
|
||||
+ log.info('Searching for tombstone entries (should find entries and not hang)')
|
||||
+ # Save original timeout settings so we can restore them in cleanup
|
||||
+ orig_network_timeout = standalone.get_option(ldap.OPT_NETWORK_TIMEOUT)
|
||||
+ orig_timeout = standalone.get_option(ldap.OPT_TIMEOUT)
|
||||
+ # Set explicit timeouts to detect deadlocks
|
||||
+ standalone.set_option(ldap.OPT_NETWORK_TIMEOUT, 5)
|
||||
+ standalone.set_option(ldap.OPT_TIMEOUT, 5)
|
||||
+
|
||||
+ # Verify at least one tombstone exists and the search does not hang/timeout
|
||||
+ try:
|
||||
+ results = DSLdapObjects(standalone, DEFAULT_SUFFIX).filter('(objectclass=nsTombstone)')
|
||||
+ log.info(f'Found {len(results)} tombstone entries')
|
||||
+ assert len(results) > 0, "Tombstone search should return at least one entry"
|
||||
+ except Exception as e:
|
||||
+ log.error(f"Tombstone search failed with error: {e}")
|
||||
+ pytest.fail(f"Tombstone search failed: {e}")
|
||||
+ finally:
|
||||
+ # Restore original timeout settings
|
||||
+ standalone.set_option(ldap.OPT_NETWORK_TIMEOUT, orig_network_timeout)
|
||||
+ standalone.set_option(ldap.OPT_TIMEOUT, orig_timeout)
|
||||
+ # Cleanup test entries and export file
|
||||
+ log.info('Cleaning up test entries')
|
||||
+ invalid_agreement.delete()
|
||||
+ entry1.delete()
|
||||
+ entry2.delete()
|
||||
+ if os.path.exists(export_ldif):
|
||||
+ os.remove(export_ldif)
|
||||
+
|
||||
+ log.info('Test completed successfully - no deadlock detected')
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main(["-s", CURRENT_FILE])
|
||||
diff --git a/dirsrvtests/tests/tickets/ticket47781_test.py b/dirsrvtests/tests/tickets/ticket47781_test.py
|
||||
deleted file mode 100644
|
||||
index ffb9a5e1a..000000000
|
||||
--- a/dirsrvtests/tests/tickets/ticket47781_test.py
|
||||
+++ /dev/null
|
||||
@@ -1,104 +0,0 @@
|
||||
-# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2016 Red Hat, Inc.
|
||||
-# All rights reserved.
|
||||
-#
|
||||
-# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
-# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
-import logging
|
||||
-
|
||||
-import pytest
|
||||
-from lib389.tasks import *
|
||||
-from lib389.topologies import topology_st
|
||||
-from lib389.replica import ReplicationManager
|
||||
-
|
||||
-from lib389._constants import (defaultProperties, DEFAULT_SUFFIX, ReplicaRole,
|
||||
- REPLICAID_SUPPLIER_1, REPLICATION_BIND_DN, REPLICATION_BIND_PW,
|
||||
- REPLICATION_BIND_METHOD, REPLICATION_TRANSPORT, RA_NAME,
|
||||
- RA_BINDDN, RA_BINDPW, RA_METHOD, RA_TRANSPORT_PROT)
|
||||
-
|
||||
-pytestmark = pytest.mark.tier2
|
||||
-
|
||||
-log = logging.getLogger(__name__)
|
||||
-
|
||||
-
|
||||
-def test_ticket47781(topology_st):
|
||||
- """
|
||||
- Testing for a deadlock after doing an online import of an LDIF with
|
||||
- replication data. The replication agreement should be invalid.
|
||||
- """
|
||||
-
|
||||
- log.info('Testing Ticket 47781 - Testing for deadlock after importing LDIF with replication data')
|
||||
-
|
||||
- supplier = topology_st.standalone
|
||||
- repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
- repl.create_first_supplier(supplier)
|
||||
-
|
||||
- properties = {RA_NAME: r'meTo_$host:$port',
|
||||
- RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
|
||||
- RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
|
||||
- RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
|
||||
- RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
|
||||
- # The agreement should point to a server that does NOT exist (invalid port)
|
||||
- repl_agreement = supplier.agreement.create(suffix=DEFAULT_SUFFIX,
|
||||
- host=supplier.host,
|
||||
- port=5555,
|
||||
- properties=properties)
|
||||
-
|
||||
- #
|
||||
- # add two entries
|
||||
- #
|
||||
- log.info('Adding two entries...')
|
||||
-
|
||||
- supplier.add_s(Entry(('cn=entry1,dc=example,dc=com', {
|
||||
- 'objectclass': 'top person'.split(),
|
||||
- 'sn': 'user',
|
||||
- 'cn': 'entry1'})))
|
||||
-
|
||||
- supplier.add_s(Entry(('cn=entry2,dc=example,dc=com', {
|
||||
- 'objectclass': 'top person'.split(),
|
||||
- 'sn': 'user',
|
||||
- 'cn': 'entry2'})))
|
||||
-
|
||||
- #
|
||||
- # export the replication ldif
|
||||
- #
|
||||
- log.info('Exporting replication ldif...')
|
||||
- args = {EXPORT_REPL_INFO: True}
|
||||
- exportTask = Tasks(supplier)
|
||||
- exportTask.exportLDIF(DEFAULT_SUFFIX, None, "/tmp/export.ldif", args)
|
||||
-
|
||||
- #
|
||||
- # Restart the server
|
||||
- #
|
||||
- log.info('Restarting server...')
|
||||
- supplier.stop()
|
||||
- supplier.start()
|
||||
-
|
||||
- #
|
||||
- # Import the ldif
|
||||
- #
|
||||
- log.info('Import replication LDIF file...')
|
||||
- importTask = Tasks(supplier)
|
||||
- args = {TASK_WAIT: True}
|
||||
- importTask.importLDIF(DEFAULT_SUFFIX, None, "/tmp/export.ldif", args)
|
||||
- os.remove("/tmp/export.ldif")
|
||||
-
|
||||
- #
|
||||
- # Search for tombstones - we should not hang/timeout
|
||||
- #
|
||||
- log.info('Search for tombstone entries(should find one and not hang)...')
|
||||
- supplier.set_option(ldap.OPT_NETWORK_TIMEOUT, 5)
|
||||
- supplier.set_option(ldap.OPT_TIMEOUT, 5)
|
||||
- entries = supplier.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, 'objectclass=nsTombstone')
|
||||
- if not entries:
|
||||
- log.fatal('Search failed to find any entries.')
|
||||
- assert PR_False
|
||||
-
|
||||
-
|
||||
-if __name__ == '__main__':
|
||||
- # Run isolated
|
||||
- # -s for DEBUG mode
|
||||
- CURRENT_FILE = os.path.realpath(__file__)
|
||||
- pytest.main("-s %s" % CURRENT_FILE)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1645
0024-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
Normal file
1645
0024-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
Normal file
File diff suppressed because it is too large
Load Diff
265
0025-Issue-6753-Port-ticket-48896-test.patch
Normal file
265
0025-Issue-6753-Port-ticket-48896-test.patch
Normal file
@ -0,0 +1,265 @@
|
||||
From 603f4deb1e87c819c1830f58c7be4281d981fbbd Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <lryznaro@redhat.com>
|
||||
Date: Mon, 2 Feb 2026 16:46:52 +0100
|
||||
Subject: [PATCH] Issue 6753 - Port ticket 48896 test
|
||||
|
||||
Description:
|
||||
Port ticket 48896 test into dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
|
||||
|
||||
Relates: #6753
|
||||
Author: Lenka Doudova
|
||||
Assisted by: Cursor
|
||||
Reviewer: Mark Reynolds
|
||||
---
|
||||
.../suites/password/pwdPolicy_token_test.py | 68 ++++++---
|
||||
dirsrvtests/tests/tickets/ticket48896_test.py | 139 ------------------
|
||||
2 files changed, 44 insertions(+), 163 deletions(-)
|
||||
delete mode 100644 dirsrvtests/tests/tickets/ticket48896_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
|
||||
index ae4eb300f..a3caaa230 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
|
||||
@@ -15,6 +15,7 @@ from lib389._constants import *
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389.topologies import topology_st as topo
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -25,14 +26,13 @@ else:
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
-USER_DN = 'uid=Test_user1,ou=People,dc=example,dc=com'
|
||||
USER_ACI = '(targetattr="userpassword")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
|
||||
-TOKEN = 'test_user1'
|
||||
+TOKEN = 'test_user123'
|
||||
|
||||
user_properties = {
|
||||
- 'uid': 'Test_user1',
|
||||
- 'cn': 'test_user1',
|
||||
- 'sn': 'test_user1',
|
||||
+ 'uid': 'Test_user123',
|
||||
+ 'cn': 'test_user123',
|
||||
+ 'sn': 'test_user123',
|
||||
'uidNumber': '1001',
|
||||
'gidNumber': '2001',
|
||||
'userpassword': PASSWORD,
|
||||
@@ -59,28 +59,48 @@ def test_token_lengths(topo):
|
||||
:id: dae9d916-2a03-4707-b454-9e901d295b13
|
||||
:setup: Standalone instance
|
||||
:steps:
|
||||
- 1. Test token length rejects password of the same length as rdn value
|
||||
+ 1. Create user, setup global password policy
|
||||
+ 2. Bind as user, change password to 'Abcd012+'
|
||||
+ 3. Bind as user with 'Abcd012+', attempt changes to 'user', 'us123', 'Tuse!1234', 'Tuse!0987', 'Tabc!1234'
|
||||
+ 4. For each passwordMinTokenLength 4, 6, 10: change settings, rebind as user, attempt password with token of that length from TOKEN
|
||||
+ 5. Cleanup - delete user
|
||||
:expectedresults:
|
||||
- 1. Passwords are rejected
|
||||
+ 1. User created, password policy enabled and set
|
||||
+ 2. Success
|
||||
+ 3. All attempts fail with CONSTRAINT_VIOLATION
|
||||
+ 4. All attempts fail with CONSTRAINT_VIOLATION
|
||||
+ 5. User successfully deleted
|
||||
"""
|
||||
user = pwd_setup(topo)
|
||||
- for length in ['4', '6', '10']:
|
||||
- topo.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
- topo.standalone.config.set('passwordMinTokenLength', length)
|
||||
- topo.standalone.simple_bind_s(USER_DN, PASSWORD)
|
||||
- time.sleep(1)
|
||||
-
|
||||
- try:
|
||||
- passwd = TOKEN[:int(length)]
|
||||
- log.info("Testing password len {} token ({})".format(length, passwd))
|
||||
- user.replace('userpassword', passwd)
|
||||
- log.fatal('Password incorrectly allowed!')
|
||||
- assert False
|
||||
- except ldap.CONSTRAINT_VIOLATION as e:
|
||||
- log.info('Password correctly rejected: ' + str(e))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Unexpected failure ' + str(e))
|
||||
- assert False
|
||||
+ dm = DirectoryManager(topo.standalone)
|
||||
+
|
||||
+ try:
|
||||
+ # Verify that the user can change their password
|
||||
+ user.rebind(PASSWORD)
|
||||
+ user.replace('userpassword', 'Abcd012+')
|
||||
+
|
||||
+ # Verify that the default password policy is enforced
|
||||
+ user.rebind('Abcd012+')
|
||||
+ for new_password in ['user', 'us123', 'Tuse!1234', 'Tuse!0987', 'Tabc!1234']:
|
||||
+ log.info(f"Testing password {new_password}")
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user.replace('userpassword', new_password)
|
||||
+
|
||||
+ # Verify that the password policy is enforced for different token lengths
|
||||
+ for length in ['4', '6', '10']:
|
||||
+ dm.rebind(PASSWORD)
|
||||
+ topo.standalone.config.set('passwordMinTokenLength', length)
|
||||
+ user.rebind('Abcd012+')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ passwd = TOKEN[:int(length)]
|
||||
+ log.info("Testing password len {} token ({})".format(length, passwd))
|
||||
+ user.replace('userpassword', passwd)
|
||||
+
|
||||
+ finally:
|
||||
+ # Cleanup
|
||||
+ user.delete()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
diff --git a/dirsrvtests/tests/tickets/ticket48896_test.py b/dirsrvtests/tests/tickets/ticket48896_test.py
|
||||
deleted file mode 100644
|
||||
index a1897589e..000000000
|
||||
--- a/dirsrvtests/tests/tickets/ticket48896_test.py
|
||||
+++ /dev/null
|
||||
@@ -1,139 +0,0 @@
|
||||
-# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2016 Red Hat, Inc.
|
||||
-# All rights reserved.
|
||||
-#
|
||||
-# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
-# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
-import pytest
|
||||
-from lib389.tasks import *
|
||||
-from lib389.utils import *
|
||||
-from lib389.topologies import topology_st
|
||||
-
|
||||
-from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
|
||||
-
|
||||
-# Skip on older versions
|
||||
-pytestmark = [pytest.mark.tier2,
|
||||
- pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")]
|
||||
-
|
||||
-logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
-log = logging.getLogger(__name__)
|
||||
-
|
||||
-CONFIG_DN = 'cn=config'
|
||||
-UID = 'buser123'
|
||||
-TESTDN = 'uid=%s,' % UID + DEFAULT_SUFFIX
|
||||
-
|
||||
-
|
||||
-def check_attr_val(topology_st, dn, attr, expected):
|
||||
- try:
|
||||
- centry = topology_st.standalone.search_s(dn, ldap.SCOPE_BASE, 'cn=*')
|
||||
- if centry:
|
||||
- val = centry[0].getValue(attr)
|
||||
- if val == expected:
|
||||
- log.info('Default value of %s is %s' % (attr, expected))
|
||||
- else:
|
||||
- log.info('Default value of %s is not %s, but %s' % (attr, expected, val))
|
||||
- assert False
|
||||
- else:
|
||||
- log.fatal('Failed to get %s' % dn)
|
||||
- assert False
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to search ' + dn + ': ' + e.message['desc'])
|
||||
- assert False
|
||||
-
|
||||
-
|
||||
-def replace_pw(server, curpw, newpw, expstr, rc):
|
||||
- log.info('Binding as {%s, %s}' % (TESTDN, curpw))
|
||||
- server.simple_bind_s(TESTDN, curpw)
|
||||
-
|
||||
- hit = 0
|
||||
- log.info('Replacing password: %s -> %s, which should %s' % (curpw, newpw, expstr))
|
||||
- try:
|
||||
- server.modify_s(TESTDN, [(ldap.MOD_REPLACE, 'userPassword', ensure_bytes(newpw))])
|
||||
- except Exception as e:
|
||||
- log.info("Exception (expected): %s" % type(e).__name__)
|
||||
- hit = 1
|
||||
- assert isinstance(e, rc)
|
||||
-
|
||||
- if (0 != rc) and (0 == hit):
|
||||
- log.info('Expected to fail with %s, but passed' % rc.__name__)
|
||||
- assert False
|
||||
-
|
||||
- log.info('PASSED')
|
||||
-
|
||||
-
|
||||
-def test_ticket48896(topology_st):
|
||||
- """
|
||||
- """
|
||||
- log.info('Testing Ticket 48896 - Default Setting for passwordMinTokenLength does not work')
|
||||
-
|
||||
- log.info("Setting global password policy with password syntax.")
|
||||
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
- topology_st.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'passwordCheckSyntax', b'on'),
|
||||
- (ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', b'on')])
|
||||
-
|
||||
- config = topology_st.standalone.search_s(CONFIG_DN, ldap.SCOPE_BASE, 'cn=*')
|
||||
- mintokenlen = config[0].getValue('passwordMinTokenLength')
|
||||
- history = config[0].getValue('passwordInHistory')
|
||||
-
|
||||
- log.info('Default passwordMinTokenLength == %s' % mintokenlen)
|
||||
- log.info('Default passwordInHistory == %s' % history)
|
||||
-
|
||||
- log.info('Adding a user.')
|
||||
- curpw = 'password'
|
||||
- topology_st.standalone.add_s(Entry((TESTDN,
|
||||
- {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
|
||||
- 'cn': 'test user',
|
||||
- 'sn': 'user',
|
||||
- 'userPassword': curpw})))
|
||||
-
|
||||
- newpw = 'Abcd012+'
|
||||
- exp = 'be ok'
|
||||
- rc = 0
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = 'user'
|
||||
- exp = 'fail'
|
||||
- rc = ldap.CONSTRAINT_VIOLATION
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = UID
|
||||
- exp = 'fail'
|
||||
- rc = ldap.CONSTRAINT_VIOLATION
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = 'Tuse!1234'
|
||||
- exp = 'fail'
|
||||
- rc = ldap.CONSTRAINT_VIOLATION
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = 'Tuse!0987'
|
||||
- exp = 'fail'
|
||||
- rc = ldap.CONSTRAINT_VIOLATION
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = 'Tabc!1234'
|
||||
- exp = 'fail'
|
||||
- rc = ldap.CONSTRAINT_VIOLATION
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- curpw = 'Abcd012+'
|
||||
- newpw = 'Direc+ory389'
|
||||
- exp = 'be ok'
|
||||
- rc = 0
|
||||
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
|
||||
-
|
||||
- log.info('SUCCESS')
|
||||
-
|
||||
-
|
||||
-if __name__ == '__main__':
|
||||
- # Run isolated
|
||||
- # -s for DEBUG mode
|
||||
- CURRENT_FILE = os.path.realpath(__file__)
|
||||
- pytest.main("-s %s" % CURRENT_FILE)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
30
0026-Issue-6810-Fix-PAM-PTA-test-7219.patch
Normal file
30
0026-Issue-6810-Fix-PAM-PTA-test-7219.patch
Normal file
@ -0,0 +1,30 @@
|
||||
From ff44acffcc67c985148c4df280685a674fec010a Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 15:19:58 +0530
|
||||
Subject: [PATCH] Issue 6810 - Fix PAM PTA test (#7219)
|
||||
|
||||
Description: Fix the PAM PTA test by add missing yield in fixture.
|
||||
|
||||
Relates: #6810
|
||||
|
||||
Reviewed by: @jchapma
|
||||
---
|
||||
dirsrvtests/tests/suites/plugins/pam_pta_test.py | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/pam_pta_test.py b/dirsrvtests/tests/suites/plugins/pam_pta_test.py
|
||||
index 484c1bc80..e55a7f7ee 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/pam_pta_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/pam_pta_test.py
|
||||
@@ -104,6 +104,8 @@ def pam_service_ldapserver(migrated_child_config):
|
||||
f.write(line + "\n")
|
||||
os.chmod(pam_file, 0o644)
|
||||
|
||||
+ yield
|
||||
+
|
||||
except Exception as e:
|
||||
if os.path.exists(backup_file):
|
||||
# Restore backup on error
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
From 2a0ed9c267fc56a14e84ad53ffaeb0b822594367 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 15:41:09 +0530
|
||||
Subject: [PATCH] Issue 7076 - Fix revert_cache() never called in modrdn
|
||||
(#7220)
|
||||
|
||||
Description: The postentry check in PR #7077 was broken - postentry is always NULL
|
||||
at that point, fixed by removing the check.
|
||||
|
||||
Relates: #7076
|
||||
|
||||
Reviewed by: @vashirov, @mreynolds389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 12 +++++++-----
|
||||
1 file changed, 7 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
index 759edb80d..e859789b3 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
@@ -102,6 +102,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
Connection *pb_conn = NULL;
|
||||
int32_t parent_op = 0;
|
||||
int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */
|
||||
+ int32_t cache_mod_phase = 0; /* set when we reach the cache modification phase */
|
||||
struct timespec parent_time;
|
||||
Slapi_Mods *smods_add_rdn = NULL;
|
||||
|
||||
@@ -1181,6 +1182,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
+ /* We're now past the BETXN PRE phase and entering the cache modification phase */
|
||||
+ cache_mod_phase = 1;
|
||||
postentry = slapi_entry_dup(ec->ep_entry);
|
||||
|
||||
if (parententry != NULL) {
|
||||
@@ -1363,12 +1366,11 @@ error_return:
|
||||
}
|
||||
}
|
||||
|
||||
- /* Revert the caches if this is the parent operation and cache modifications were made.
|
||||
- * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed,
|
||||
- * so we should only revert if we got past that point (i.e., BETXN POST plugin failures).
|
||||
- * For BETXN PRE failures, no cache modifications were made to parent/newparent entries.
|
||||
+ /* Revert the caches if this is the parent operation AND we reached the
|
||||
+ * cache modification phase. If BETXN PRE fails, cache_mod_phase is 0
|
||||
+ * and we don't need to revert since no cache modifications were made.
|
||||
*/
|
||||
- if (parent_op && betxn_callback_fails && postentry) {
|
||||
+ if (parent_op && betxn_callback_fails && cache_mod_phase) {
|
||||
revert_cache(inst, &parent_time);
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1492
0028-Issue-6951-Dynamic-Certificate-refresh-phase-4-Updat.patch
Normal file
1492
0028-Issue-6951-Dynamic-Certificate-refresh-phase-4-Updat.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,93 @@
|
||||
From d5a83e8f2ccd0c9d11792f026947c4785996b4a6 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 15:33:08 +0000
|
||||
Subject: [PATCH] Issue 7224 - CI Test - Simplify
|
||||
test_reserve_descriptor_validation (#7225)
|
||||
|
||||
Description:
|
||||
Previously, the test_reserve_descriptor_validation CItest calculated
|
||||
the expected number of file descriptors based on backends, indexes,
|
||||
SSL/FIPS mode, and compared it to the value returned by the server.
|
||||
This approach is fragile, especially in FIPS mode.
|
||||
|
||||
Fix:
|
||||
The test has been updated to simply verify that the server corrects
|
||||
the configured nsslapd-reservedescriptors value if it is set too low,
|
||||
instead of calculating the expected total.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7224
|
||||
|
||||
Reviewed by: @bsimonova (Thank you)
|
||||
---
|
||||
.../suites/resource_limits/fdlimits_test.py | 36 +++++++------------
|
||||
1 file changed, 13 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
index c843a4b24..a49e378c3 100644
|
||||
--- a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
+++ b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
@@ -27,7 +27,7 @@ RESRV_FD_ATTR = "nsslapd-reservedescriptors"
|
||||
GLOBAL_LIMIT = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
SYSTEMD_LIMIT = ensure_str(check_output("systemctl show -p LimitNOFILE dirsrv@standalone1".split(" ")).strip()).split('=')[1]
|
||||
CUSTOM_VAL = str(int(SYSTEMD_LIMIT) - 10)
|
||||
-RESRV_DESC_VAL = str(10)
|
||||
+RESRV_DESC_VAL_LOW = 10
|
||||
TOO_HIGH_VAL = str(GLOBAL_LIMIT * 2)
|
||||
TOO_HIGH_VAL2 = str(int(SYSTEMD_LIMIT) * 2)
|
||||
TOO_LOW_VAL = "0"
|
||||
@@ -86,40 +86,30 @@ def test_reserve_descriptor_validation(topology_st):
|
||||
:id: 9bacdbcc-7754-4955-8a56-1d8c82bce274
|
||||
:setup: Standalone Instance
|
||||
:steps:
|
||||
- 1. Set attr nsslapd-reservedescriptors to a low value of RESRV_DESC_VAL (10)
|
||||
+ 1. Set attr nsslapd-reservedescriptors to a low value (10)
|
||||
2. Verify low value has been set
|
||||
3. Restart instance (On restart the reservedescriptor attr will be validated)
|
||||
- 4. Check updated value for nsslapd-reservedescriptors attr
|
||||
+ 4. Verify corrected value for nsslapd-reservedescriptors > low value
|
||||
:expectedresults:
|
||||
1. Success
|
||||
- 2. A value of RESRV_DESC_VAL (10) is returned
|
||||
+ 2. A value of RESRV_DESC_VAL_LOW (10) is returned
|
||||
3. Success
|
||||
- 4. A value of STANDALONE_INST_RESRV_DESCS (55) is returned
|
||||
+ 4. Corrected value for nsslapd-reservedescriptors > low value
|
||||
"""
|
||||
|
||||
- # Set nsslapd-reservedescriptors to a low value (RESRV_DESC_VAL:10)
|
||||
- topology_st.standalone.config.set(RESRV_FD_ATTR, RESRV_DESC_VAL)
|
||||
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
|
||||
- assert resrv_fd == RESRV_DESC_VAL
|
||||
+ # Set nsslapd-reservedescriptors to a low value (10)
|
||||
+ topology_st.standalone.config.set(RESRV_FD_ATTR, str(RESRV_DESC_VAL_LOW))
|
||||
+ resrv_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
|
||||
+ assert resrv_fd == RESRV_DESC_VAL_LOW
|
||||
|
||||
# An instance restart triggers a validation of the configured nsslapd-reservedescriptors attribute
|
||||
topology_st.standalone.restart()
|
||||
|
||||
- """
|
||||
- A standalone instance contains a single backend with default indexes
|
||||
- so we only check these. TODO add tests for repl, chaining, PTA, SSL
|
||||
- """
|
||||
- STANDALONE_INST_RESRV_DESCS = 25 if is_fips() else 20 # Reserve descriptor constant (higher in FIPS mode)
|
||||
- backends = Backends(topology_st.standalone)
|
||||
- STANDALONE_INST_RESRV_DESCS += (len(backends.list()) * 4) # 4 = Backend descriptor constant
|
||||
- for be in backends.list() :
|
||||
- STANDALONE_INST_RESRV_DESCS += len(be.get_indexes().list())
|
||||
-
|
||||
- # Varify reservedescriptors has been updated
|
||||
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
|
||||
- assert resrv_fd == str(STANDALONE_INST_RESRV_DESCS)
|
||||
+ # Get the corrected value
|
||||
+ corrected_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
|
||||
+ assert corrected_fd > RESRV_DESC_VAL_LOW
|
||||
|
||||
- log.info("test_reserve_descriptor_validation PASSED")
|
||||
+ log.info(f"test_reserve_descriptor_validation PASSED (corrected from {RESRV_DESC_VAL_LOW} to {corrected_fd})")
|
||||
|
||||
@pytest.mark.skipif(ds_is_older("1.4.1.2"), reason="Not implemented")
|
||||
def test_reserve_descriptors_high(topology_st):
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
From 9bfbec8c4aad1c698fd80b3086e11f06fd9df26d Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 9 Feb 2026 13:15:44 +0100
|
||||
Subject: [PATCH] Issue 7178 - Bundled jemalloc fails to build with GCC 15
|
||||
(#7216)
|
||||
|
||||
Description:
|
||||
Update spec file to fix build failures on Fedora Rawhide
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7178
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thanks!)
|
||||
---
|
||||
rpm.mk | 1 +
|
||||
rpm/389-ds-base.spec.in | 10 +++++-----
|
||||
rpm/jemalloc-5.3.0_throw_bad_alloc.patch | 12 ++++++++++++
|
||||
3 files changed, 18 insertions(+), 5 deletions(-)
|
||||
create mode 100644 rpm/jemalloc-5.3.0_throw_bad_alloc.patch
|
||||
|
||||
diff --git a/rpm.mk b/rpm.mk
|
||||
index f91011814..372cee5a6 100644
|
||||
--- a/rpm.mk
|
||||
+++ b/rpm.mk
|
||||
@@ -143,6 +143,7 @@ srpmdistdir:
|
||||
rpmbuildprep:
|
||||
cp dist/sources/$(TARBALL) $(RPMBUILD)/SOURCES/
|
||||
cp rpm/$(PACKAGE)-* $(RPMBUILD)/SOURCES/
|
||||
+ cp rpm/jemalloc-5.3.0_throw_bad_alloc.patch $(RPMBUILD)/SOURCES/
|
||||
if [ $(BUNDLE_JEMALLOC) -eq 1 ]; then \
|
||||
cp dist/sources/$(JEMALLOC_TARBALL) $(RPMBUILD)/SOURCES/ ; \
|
||||
fi
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index 51bfd7e77..dc8c75dac 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -152,8 +152,8 @@ BuildRequires: python%{python3_pkgversion}-devel
|
||||
# For cockpit
|
||||
%if %{with cockpit}
|
||||
BuildRequires: rsync
|
||||
-BuildRequires: npm
|
||||
-BuildRequires: nodejs
|
||||
+BuildRequires: /usr/bin/npm
|
||||
+BuildRequires: /usr/bin/node
|
||||
%endif
|
||||
|
||||
# END BUILD REQUIRES
|
||||
@@ -188,9 +188,7 @@ Requires: %{name}-robdb-libs = %{version}-%{release}
|
||||
Requires: libdb
|
||||
%endif
|
||||
%endif
|
||||
-Requires: lmdb
|
||||
-# This picks up libperl.so as a Requires, so we add this versioned one
|
||||
-Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
|
||||
+Requires: lmdb-libs
|
||||
# Needed by logconv.pl
|
||||
%if %{without libbdb_ro}
|
||||
%if %{without bundle_libdb}
|
||||
@@ -217,6 +215,7 @@ Source0: %{name}-%{version}.tar.bz2
|
||||
Source2: %{name}-devel.README
|
||||
%if %{with bundle_jemalloc}
|
||||
Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2
|
||||
+Source5: jemalloc-5.3.0_throw_bad_alloc.patch
|
||||
%endif
|
||||
%if %{with bundle_libdb}
|
||||
Source4: https://fedorapeople.org/groups/389ds/libdb-5.3.28-59.tar.bz2
|
||||
@@ -434,6 +433,7 @@ COCKPIT_FLAGS="--disable-cockpit"
|
||||
|
||||
# Build jemalloc
|
||||
pushd ../%{jemalloc_name}-%{jemalloc_ver}
|
||||
+patch -p1 -F3 < %{SOURCE5}
|
||||
%configure \
|
||||
--libdir=%{_libdir}/%{pkgname}/lib \
|
||||
--bindir=%{_libdir}/%{pkgname}/bin \
|
||||
diff --git a/rpm/jemalloc-5.3.0_throw_bad_alloc.patch b/rpm/jemalloc-5.3.0_throw_bad_alloc.patch
|
||||
new file mode 100644
|
||||
index 000000000..685e6968c
|
||||
--- /dev/null
|
||||
+++ b/rpm/jemalloc-5.3.0_throw_bad_alloc.patch
|
||||
@@ -0,0 +1,12 @@
|
||||
+diff --git a/src/jemalloc_cpp.cpp b/src/jemalloc_cpp.cpp
|
||||
+index fffd6aee..5a682991 100644
|
||||
+--- a/src/jemalloc_cpp.cpp
|
||||
++++ b/src/jemalloc_cpp.cpp
|
||||
+@@ -93,7 +93,7 @@ handleOOM(std::size_t size, bool nothrow) {
|
||||
+ }
|
||||
+
|
||||
+ if (ptr == nullptr && !nothrow)
|
||||
+- std::__throw_bad_alloc();
|
||||
++ throw std::bad_alloc();
|
||||
+ return ptr;
|
||||
+ }
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
From fb4254a97fe0da25064d2f6296705c9e1810ffda Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 9 Feb 2026 13:18:09 +0100
|
||||
Subject: [PATCH] Issue 7121 - (2nd) LeakSanitizer: various leaks during
|
||||
replication (#7212)
|
||||
|
||||
Bug Description:
|
||||
With the previous fix 75e0e487545893a7b0d83f94f9264c10f8bb0353 applied,
|
||||
server can crash in ber_bvcpy.
|
||||
|
||||
```
|
||||
Program terminated with signal SIGSEGV, Segmentation fault.
|
||||
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
|
||||
47 len = bvs->bv_len;
|
||||
[Current thread is 1 (Thread 0x7f1db47fe640 (LWP 36576))]
|
||||
(gdb) bt
|
||||
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
|
||||
#1 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:40
|
||||
#2 slapi_value_set_berval (bval=0x7f1d00000000, value=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:322
|
||||
#3 slapi_value_set_berval (value=value@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000) at ldap/servers/slapd/value.c:317
|
||||
#4 0x00007f1e48b7d787 in value_init (v=v@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0)
|
||||
at ldap/servers/slapd/value.c:179
|
||||
#5 0x00007f1e48b7d884 in value_new (bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0) at ldap/servers/slapd/value.c:158
|
||||
#6 0x00007f1e48b7ddb7 in slapi_value_dup (v=0x7f1d00000000) at ldap/servers/slapd/value.c:147
|
||||
#7 0x00007f1e48b7e262 in valueset_set_valueset (vs2=0x7f1d502b5218, vs1=0x7f1da2c5b358) at ldap/servers/slapd/valueset.c:1244
|
||||
#8 valueset_set_valueset (vs1=0x7f1da2c5b358, vs2=0x7f1d502b5218) at ldap/servers/slapd/valueset.c:1220
|
||||
#9 0x00007f1e48add4af in slapi_attr_dup (attr=0x7f1d502b51e0) at ldap/servers/slapd/attr.c:396
|
||||
#10 0x00007f1e48af0f60 in slapi_entry_dup (e=0x7f1da2c19000) at ldap/servers/slapd/entry.c:2036
|
||||
#11 0x00007f1e442c734e in ldbm_back_modify (pb=0x7f1da2c00000) at ldap/servers/slapd/back-ldbm/ldbm_modify.c:741
|
||||
#12 0x00007f1e48b30076 in op_shared_modify (pb=pb@entry=0x7f1da2c00000, pw_change=pw_change@entry=0, old_pw=0x0)
|
||||
at ldap/servers/slapd/modify.c:1079
|
||||
#13 0x00007f1e48b30ced in do_modify (pb=pb@entry=0x7f1da2c00000) at ldap/servers/slapd/modify.c:377
|
||||
#14 0x000055e990e2fd1c in connection_dispatch_operation (pb=0x7f1da2c00000, op=<optimized out>, conn=<optimized out>)
|
||||
at ldap/servers/slapd/connection.c:672
|
||||
#15 connection_threadmain (arg=<optimized out>) at ldap/servers/slapd/connection.c:1955
|
||||
#16 0x00007f1e48839bd4 in _pt_root (arg=0x7f1e439d9500) at pthreads/../../../../nspr/pr/src/pthreads/ptthread.c:191
|
||||
#17 0x00007f1e4868a19a in start_thread (arg=<optimized out>) at pthread_create.c:443
|
||||
#18 0x00007f1e4870f100 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
|
||||
```
|
||||
|
||||
The fix changed from always setting `v_csnset = NULL` to only freeing it
|
||||
inside the if-block.
|
||||
|
||||
Fix Description:
|
||||
Keep `csnset_free()` outside the if-block to handle all values, not just
|
||||
those matching the condtion.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/7121
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/entrywsi.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/entrywsi.c b/ldap/servers/slapd/entrywsi.c
|
||||
index e1bdc1bab..0d044092d 100644
|
||||
--- a/ldap/servers/slapd/entrywsi.c
|
||||
+++ b/ldap/servers/slapd/entrywsi.c
|
||||
@@ -1185,8 +1185,8 @@ resolve_attribute_state_deleted_to_present(Slapi_Entry *e, Slapi_Attr *a, Slapi_
|
||||
if ((csn_compare(vucsn, deletedcsn) >= 0) ||
|
||||
value_distinguished_at_csn(e, a, valuestoupdate[i], deletedcsn)) {
|
||||
entry_deleted_value_to_present_value(a, valuestoupdate[i]);
|
||||
- csnset_free(&valuestoupdate[i]->v_csnset);
|
||||
}
|
||||
+ csnset_free(&valuestoupdate[i]->v_csnset);
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
778
0032-Issue-7223-Revert-index-scan-limits-for-system-index.patch
Normal file
778
0032-Issue-7223-Revert-index-scan-limits-for-system-index.patch
Normal file
@ -0,0 +1,778 @@
|
||||
From 3938942a5418add83616b1413f3070d394c30a7f Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes
|
||||
|
||||
This reverts changes introduced by the following commits:
|
||||
c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for scan limits
|
||||
8b6b3a9f9 Issue 6966 - On large DB, unlimited IDL scan limit reduce the SRCH performance
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../tests/suites/config/config_test.py | 27 +---
|
||||
.../healthcheck/health_system_indexes_test.py | 136 +-----------------
|
||||
.../paged_results/paged_results_test.py | 25 +---
|
||||
ldap/servers/slapd/back-ldbm/back-ldbm.h | 1 -
|
||||
ldap/servers/slapd/back-ldbm/index.c | 2 -
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 104 +++-----------
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.c | 30 ----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 -
|
||||
.../slapd/back-ldbm/ldbm_index_config.c | 8 --
|
||||
src/lib389/lib389/backend.py | 50 ++-----
|
||||
src/lib389/lib389/cli_conf/backend.py | 20 ---
|
||||
11 files changed, 40 insertions(+), 364 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
|
||||
index cbb8875fa..2c7d949d0 100644
|
||||
--- a/dirsrvtests/tests/suites/config/config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/config_test.py
|
||||
@@ -706,19 +706,17 @@ def test_ndn_cache_size_enforcement(topo, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-def test_require_index(topo, request):
|
||||
+def test_require_index(topo):
|
||||
"""Validate that unindexed searches are rejected
|
||||
|
||||
:id: fb6e31f2-acc2-4e75-a195-5c356faeb803
|
||||
:setup: Standalone instance
|
||||
:steps:
|
||||
1. Set "nsslapd-require-index" to "on"
|
||||
- 2. ancestorid/idlscanlimit to 100
|
||||
- 3. Test an unindexed search is rejected
|
||||
+ 2. Test an unindexed search is rejected
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. Success
|
||||
"""
|
||||
|
||||
# Set the config
|
||||
@@ -729,10 +727,6 @@ def test_require_index(topo, request):
|
||||
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
- topo.standalone.restart()
|
||||
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(101):
|
||||
@@ -743,15 +737,10 @@ def test_require_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
raw_objects.filter("(description=test*)")
|
||||
|
||||
- def fin():
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
-
|
||||
|
||||
|
||||
@pytest.mark.skipif(ds_is_older('1.4.2'), reason="The config setting only exists in 1.4.2 and higher")
|
||||
-def test_require_internal_index(topo, request):
|
||||
+def test_require_internal_index(topo):
|
||||
"""Ensure internal operations require indexed attributes
|
||||
|
||||
:id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
|
||||
@@ -782,10 +771,6 @@ def test_require_internal_index(topo, request):
|
||||
# Create a bunch of users
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
- topo.standalone.restart()
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(102, 202):
|
||||
users.create_test_user(uid=i)
|
||||
@@ -810,12 +795,6 @@ def test_require_internal_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
user.delete()
|
||||
|
||||
- def fin():
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
-
|
||||
-
|
||||
|
||||
def get_pstack(pid):
|
||||
"""Get a pstack of the pid."""
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 486fad44b..140845a33 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -172,9 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the parentId index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
- standalone.restart()
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -263,8 +261,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
|
||||
|
||||
log.info("Re-add the entryusn index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
+ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -408,132 +405,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
-def test_missing_scanlimit(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
|
||||
-
|
||||
- :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. Create DS instance
|
||||
- 2. Remove nsIndexIDListScanLimit from parentId index
|
||||
- 3. Use healthcheck without --json option
|
||||
- 4. Use healthcheck with --json option
|
||||
- 5. Verify the remediation command has properly quoted scanlimit
|
||||
- 6. Re-add the scanlimit
|
||||
- 7. Use healthcheck without --json option
|
||||
- 8. Use healthcheck with --json option
|
||||
- :expectedresults:
|
||||
- 1. Success
|
||||
- 2. Success
|
||||
- 3. healthcheck reports DSBLE0007 code and related details
|
||||
- 4. healthcheck reports DSBLE0007 code and related details
|
||||
- 5. The scanlimit value is quoted in the remediation command
|
||||
- 6. Success
|
||||
- 7. healthcheck reports no issues found
|
||||
- 8. healthcheck reports no issues found
|
||||
- """
|
||||
-
|
||||
- RET_CODE = "DSBLE0007"
|
||||
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
-
|
||||
- standalone = topology_st.standalone
|
||||
-
|
||||
- log.info("Remove nsIndexIDListScanLimit from parentId index")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
-
|
||||
- # Verify the remediation command has properly quoted scanlimit
|
||||
- args = FakeArgs()
|
||||
- args.instance = standalone.serverid
|
||||
- args.verbose = standalone.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.exclude_check = []
|
||||
- args.check = ["backends"]
|
||||
- args.dry_run = False
|
||||
- args.json = False
|
||||
- health_check_run(standalone, topology_st.logcap.log, args)
|
||||
- # Check that the scanlimit is quoted in the output
|
||||
- assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
- log.info("Verified scanlimit is properly quoted in remediation command")
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
-
|
||||
- log.info("Re-add the nsIndexIDListScanLimit")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
-
|
||||
-def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
|
||||
-
|
||||
- :id: af8214ad-5e4c-422a-8f74-3e99227551df
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. Create DS instance
|
||||
- 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
|
||||
- 3. Use healthcheck and verify a single combined command is generated
|
||||
- 4. Re-add the matching rule and scanlimit
|
||||
- 5. Use healthcheck without --json option
|
||||
- 6. Use healthcheck with --json option
|
||||
- :expectedresults:
|
||||
- 1. Success
|
||||
- 2. Success
|
||||
- 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
|
||||
- 4. Success
|
||||
- 5. healthcheck reports no issues found
|
||||
- 6. healthcheck reports no issues found
|
||||
- """
|
||||
-
|
||||
- RET_CODE = "DSBLE0007"
|
||||
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
-
|
||||
- standalone = topology_st.standalone
|
||||
-
|
||||
- log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- # Run healthcheck and verify combined command
|
||||
- args = FakeArgs()
|
||||
- args.instance = standalone.serverid
|
||||
- args.verbose = standalone.verbose
|
||||
- args.list_errors = False
|
||||
- args.list_checks = False
|
||||
- args.exclude_check = []
|
||||
- args.check = ["backends"]
|
||||
- args.dry_run = False
|
||||
- args.json = False
|
||||
- health_check_run(standalone, topology_st.logcap.log, args)
|
||||
-
|
||||
- # Verify DSBLE0007 is reported
|
||||
- assert topology_st.logcap.contains(RET_CODE)
|
||||
- log.info("healthcheck returned code: %s" % RET_CODE)
|
||||
-
|
||||
- # Verify a single combined command is generated with both --add-mr and --add-scanlimit
|
||||
- assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
|
||||
- log.info("Verified combined command with both --add-mr and --add-scanlimit")
|
||||
-
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
|
||||
- parentid_index = Index(standalone, PARENTID_DN)
|
||||
- parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
-
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
-
|
||||
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
@@ -574,8 +445,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the missing system indexes")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
|
||||
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
|
||||
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
backend.add_index("nsuniqueid", ["eq"])
|
||||
standalone.restart()
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
index 61d6702da..1bb94b53a 100644
|
||||
--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
@@ -306,19 +306,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
|
||||
del_users(users_list)
|
||||
|
||||
|
||||
-@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [
|
||||
+@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err", [
|
||||
(50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100',
|
||||
- ldap.UNWILLING_TO_PERFORM, True),
|
||||
+ ldap.UNWILLING_TO_PERFORM),
|
||||
(5, 15, DN_CONFIG, 'nsslapd-timelimit', '20',
|
||||
- ldap.UNAVAILABLE_CRITICAL_EXTENSION, False),
|
||||
+ ldap.UNAVAILABLE_CRITICAL_EXTENSION),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20',
|
||||
- ldap.SIZELIMIT_EXCEEDED, False),
|
||||
+ ldap.SIZELIMIT_EXCEEDED),
|
||||
(21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5',
|
||||
- ldap.SIZELIMIT_EXCEEDED, False),
|
||||
+ ldap.SIZELIMIT_EXCEEDED),
|
||||
(5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20',
|
||||
- ldap.ADMINLIMIT_EXCEEDED, False)])
|
||||
+ ldap.ADMINLIMIT_EXCEEDED)])
|
||||
def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
- suffix, attr_name, attr_value, expected_err, restart):
|
||||
+ suffix, attr_name, attr_value, expected_err):
|
||||
"""Verify that search with a simple paged results control
|
||||
throws expected exceptoins when corresponding limits are
|
||||
exceeded.
|
||||
@@ -341,15 +341,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
|
||||
users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX)
|
||||
attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value)
|
||||
- ancestorid_index = None
|
||||
- if attr_name == 'nsslapd-idlistscanlimit':
|
||||
- backend = Backends(topology_st.standalone).get_backend(DEFAULT_SUFFIX)
|
||||
- ancestorid_index = backend.get_index('ancestorid')
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
|
||||
-
|
||||
- if (restart):
|
||||
- log.info('Instance restarted')
|
||||
- topology_st.standalone.restart()
|
||||
conf_param_dict = {attr_name: attr_value}
|
||||
search_flt = r'(uid=test*)'
|
||||
searchreq_attrlist = ['dn', 'sn']
|
||||
@@ -402,8 +393,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
- if ancestorid_index:
|
||||
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
|
||||
del_users(users_list)
|
||||
change_conf_attr(topology_st, suffix, attr_name, attr_value_bck)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
index b187c26bc..e23e7ff43 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -583,7 +583,6 @@ struct ldbminfo
|
||||
int li_mode;
|
||||
int li_lookthroughlimit;
|
||||
int li_allidsthreshold;
|
||||
- int li_system_allidsthreshold;
|
||||
char *li_directory;
|
||||
int li_reslimit_lookthrough_handle;
|
||||
uint64_t li_dbcachesize;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index 0ab82948c..a5004be19 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -997,8 +997,6 @@ index_read_ext_allids(
|
||||
}
|
||||
if (pb) {
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
|
||||
- } else if (strcasecmp(type, LDBM_ANCESTORID_STR) == 0) {
|
||||
- is_and = 1;
|
||||
}
|
||||
ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0;
|
||||
/* the caller can pass in a value of 0 - just ignore those - but if the index
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 2a6e8cbb8..2b71cd4f7 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/* Forward declarations */
|
||||
static void ldbm_instance_destructor(void **arg);
|
||||
-Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit);
|
||||
+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr);
|
||||
|
||||
|
||||
/* Creates and initializes a new ldbm_instance structure.
|
||||
@@ -126,7 +126,7 @@ done:
|
||||
* Take a bunch of strings, and create a index config entry
|
||||
*/
|
||||
Slapi_Entry *
|
||||
-ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit)
|
||||
+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr)
|
||||
{
|
||||
Slapi_Entry *e = slapi_entry_alloc();
|
||||
struct berval *vals[2];
|
||||
@@ -167,11 +167,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
|
||||
slapi_entry_add_values(e, "nsMatchingRule", vals);
|
||||
}
|
||||
|
||||
- if (scanlimit) {
|
||||
- val.bv_val = scanlimit;
|
||||
- val.bv_len = strlen(scanlimit);
|
||||
- slapi_entry_add_values(e, "nsIndexIDListScanLimit", vals);
|
||||
- }
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -184,60 +179,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
{
|
||||
Slapi_Entry *e;
|
||||
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
/* write the dse file only on the final index */
|
||||
int flags = LDBM_INSTANCE_CONFIG_DONT_WRITE;
|
||||
- char *ancestorid_indexes_limit = NULL;
|
||||
- char *parentid_indexes_limit = NULL;
|
||||
- struct attrinfo *ai = NULL;
|
||||
- int index_already_configured = 0;
|
||||
- struct index_idlistsizeinfo *iter;
|
||||
- int cookie;
|
||||
- int limit;
|
||||
-
|
||||
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
- if (ai && ai->ai_idlistinfo) {
|
||||
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
|
||||
- if (iter) {
|
||||
- limit = iter->ai_idlistsizelimit;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from attribute index\n",
|
||||
- limit);
|
||||
- } else {
|
||||
- limit = li->li_system_allidsthreshold;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from default (fail to read limit)\n",
|
||||
- limit);
|
||||
- }
|
||||
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
|
||||
- } else {
|
||||
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set ancestorid limit to %d from default (no attribute or limit)\n",
|
||||
- li->li_system_allidsthreshold);
|
||||
- }
|
||||
-
|
||||
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
- if (ai && ai->ai_idlistinfo) {
|
||||
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
|
||||
- if (iter) {
|
||||
- limit = iter->ai_idlistsizelimit;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from attribute index\n",
|
||||
- limit);
|
||||
- } else {
|
||||
- limit = li->li_system_allidsthreshold;
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from default (fail to read limit)\n",
|
||||
- limit);
|
||||
- }
|
||||
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
|
||||
- } else {
|
||||
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
|
||||
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
|
||||
- "set parentid limit to %d from default (no attribute or limit)\n",
|
||||
- li->li_system_allidsthreshold);
|
||||
- }
|
||||
|
||||
/*
|
||||
* Always index (entrydn or entryrdn), parentid, objectclass,
|
||||
@@ -245,48 +188,42 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* since they are used by some searches, replication and the
|
||||
* ACL routines.
|
||||
*/
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
- /* Check if the attrinfo is actually for parentid, not a fallback to .default */
|
||||
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
|
||||
- if (!index_already_configured) {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
- }
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
+ slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* For MMR, we need this attribute (to replace use of dncomp in delete). */
|
||||
- e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* write the dse file only on the final index */
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
/* ldbm_instance_config_add_index_entry(inst, 2, argv); */
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -294,18 +231,9 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
|
||||
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
|
||||
- if (!index_already_configured) {
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
- }
|
||||
-
|
||||
- slapi_ch_free_string(&ancestorid_indexes_limit);
|
||||
- slapi_ch_free_string(&parentid_indexes_limit);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
+ slapi_entry_free(e);
|
||||
|
||||
return 0;
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
index c24e3d766..6a2ce4c27 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
@@ -385,35 +385,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
|
||||
return retval;
|
||||
}
|
||||
|
||||
-static void *
|
||||
-ldbm_config_system_allidsthreshold_get(void *arg)
|
||||
-{
|
||||
- struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
-
|
||||
- return (void *)((uintptr_t)(li->li_system_allidsthreshold));
|
||||
-}
|
||||
-
|
||||
-static int
|
||||
-ldbm_config_system_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
|
||||
-{
|
||||
- struct ldbminfo *li = (struct ldbminfo *)arg;
|
||||
- int retval = LDAP_SUCCESS;
|
||||
- int val = (int)((uintptr_t)value);
|
||||
-
|
||||
- /* Do whatever we can to make sure the data is ok. */
|
||||
-
|
||||
- /* Catch attempts to configure a stupidly low ancestorid allidsthreshold */
|
||||
- if ((val > -1) && (val < 5000)) {
|
||||
- val = 5000;
|
||||
- }
|
||||
-
|
||||
- if (apply) {
|
||||
- li->li_system_allidsthreshold = val;
|
||||
- }
|
||||
-
|
||||
- return retval;
|
||||
-}
|
||||
-
|
||||
static void *
|
||||
ldbm_config_pagedallidsthreshold_get(void *arg)
|
||||
{
|
||||
@@ -1094,7 +1065,6 @@ static config_info ldbm_config[] = {
|
||||
{CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
- {CONFIG_SYSTEMIDLISTSCANLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_system_allidsthreshold_get, &ldbm_config_system_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_DIRECTORY, CONFIG_TYPE_STRING, "", &ldbm_config_directory_get, &ldbm_config_directory_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE | CONFIG_FLAG_SKIP_DEFAULT_SETTING},
|
||||
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0},
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
index 29a3426ab..e69bfeedf 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
@@ -60,7 +60,6 @@ struct config_info
|
||||
#define CONFIG_RANGELOOKTHROUGHLIMIT "nsslapd-rangelookthroughlimit"
|
||||
#define CONFIG_PAGEDLOOKTHROUGHLIMIT "nsslapd-pagedlookthroughlimit"
|
||||
#define CONFIG_IDLISTSCANLIMIT "nsslapd-idlistscanlimit"
|
||||
-#define CONFIG_SYSTEMIDLISTSCANLIMIT "nsslapd-systemidlistscanlimit"
|
||||
#define CONFIG_PAGEDIDLISTSCANLIMIT "nsslapd-pagedidlistscanlimit"
|
||||
#define CONFIG_DIRECTORY "nsslapd-directory"
|
||||
#define CONFIG_MODE "nsslapd-mode"
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
index bae2a64b9..38e7368e1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
|
||||
@@ -384,14 +384,6 @@ ldbm_instance_config_add_index_entry(
|
||||
}
|
||||
}
|
||||
|
||||
- /* get nsIndexIDListScanLimit and its values, and add them */
|
||||
- if (0 == slapi_entry_attr_find(e, "nsIndexIDListScanLimit", &attr)) {
|
||||
- for (j = slapi_attr_first_value(attr, &sval); j != -1; j = slapi_attr_next_value(attr, j, &sval)) {
|
||||
- attrValue = slapi_value_get_berval(sval);
|
||||
- eBuf = PR_sprintf_append(eBuf, "nsIndexIDListScanLimit: %s\n", attrValue->bv_val);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
ldbm_config_add_dse_entry(li, eBuf, flags);
|
||||
if (eBuf) {
|
||||
PR_smprintf_free(eBuf);
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 274d45abe..f3dbe7c92 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -645,10 +645,11 @@ class Backend(DSLdapObject):
|
||||
indexes = self.get_indexes()
|
||||
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
+ # Note: entryrdn and ancestorid are internal system indexes that are not
|
||||
+ # exposed in cn=config - they are managed internally by the server.
|
||||
+ # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
|
||||
expected_system_indexes = {
|
||||
- 'entryrdn': {'types': ['subtree'], 'matching_rule': None},
|
||||
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
- 'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
|
||||
+ 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
@@ -705,17 +706,14 @@ class Backend(DSLdapObject):
|
||||
# Generate remediation command
|
||||
index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']])
|
||||
cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
|
||||
- if expected_config.get('matching_rule'):
|
||||
+ if expected_config['matching_rule']:
|
||||
cmd += f" --matching-rule {expected_config['matching_rule']}"
|
||||
- if expected_config.get('scanlimit'):
|
||||
- cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
else:
|
||||
# Index exists, check configuration
|
||||
actual_types = index.get_attr_vals_utf8('nsIndexType') or []
|
||||
actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or []
|
||||
- actual_scanlimit = index.get_attr_vals_utf8('nsIndexIDListScanLimit') or []
|
||||
|
||||
# Normalize to lowercase for comparison
|
||||
actual_types = [t.lower() for t in actual_types]
|
||||
@@ -730,31 +728,16 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
- # Check matching rules and scanlimit together to generate a single combined command
|
||||
+ # Check matching rules
|
||||
expected_mr = expected_config.get('matching_rule')
|
||||
- expected_scanlimit = expected_config.get('scanlimit')
|
||||
-
|
||||
- missing_mr = False
|
||||
if expected_mr:
|
||||
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
|
||||
if expected_mr.lower() not in actual_mrs_lower:
|
||||
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
|
||||
- missing_mr = True
|
||||
-
|
||||
- missing_scanlimit = False
|
||||
- if expected_scanlimit and (len(actual_scanlimit) == 0):
|
||||
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
|
||||
- missing_scanlimit = True
|
||||
-
|
||||
- # Generate a single combined command for all missing items
|
||||
- if missing_mr or missing_scanlimit:
|
||||
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
|
||||
- if missing_mr:
|
||||
- cmd += f" --add-mr {expected_mr}"
|
||||
- if missing_scanlimit:
|
||||
- cmd += f" --add-scanlimit \"{expected_scanlimit}\""
|
||||
- remediation_commands.append(cmd)
|
||||
- reindex_attrs.add(attr_name)
|
||||
+ # Add the missing matching rule
|
||||
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
|
||||
+ remediation_commands.append(cmd)
|
||||
+ reindex_attrs.add(attr_name)
|
||||
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
@@ -993,13 +976,12 @@ class Backend(DSLdapObject):
|
||||
return
|
||||
raise ValueError("Can not delete index because it does not exist")
|
||||
|
||||
- def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=None, reindex=False):
|
||||
+ def add_index(self, attr_name, types, matching_rules=None, reindex=False):
|
||||
""" Add an index.
|
||||
|
||||
:param attr_name - name of the attribute to index
|
||||
:param types - a List of index types(eq, pres, sub, approx)
|
||||
:param matching_rules - a List of matching rules for the index
|
||||
- :param idlistscanlimit - a List of fine grain definitions for scanning limit
|
||||
:param reindex - If set to True then index the attribute after creating it.
|
||||
"""
|
||||
|
||||
@@ -1029,15 +1011,6 @@ class Backend(DSLdapObject):
|
||||
# Only add if there are actually rules present in the list.
|
||||
if len(mrs) > 0:
|
||||
props['nsMatchingRule'] = mrs
|
||||
-
|
||||
- if idlistscanlimit is not None:
|
||||
- scanlimits = []
|
||||
- for scanlimit in idlistscanlimit:
|
||||
- scanlimits.append(scanlimit)
|
||||
- # Only add if there are actually limits in the list.
|
||||
- if len(scanlimits) > 0:
|
||||
- props['nsIndexIDListScanLimit'] = scanlimits
|
||||
-
|
||||
new_index.create(properties=props, basedn="cn=index," + self._dn)
|
||||
|
||||
if reindex:
|
||||
@@ -1349,7 +1322,6 @@ class DatabaseConfig(DSLdapObject):
|
||||
'nsslapd-lookthroughlimit',
|
||||
'nsslapd-mode',
|
||||
'nsslapd-idlistscanlimit',
|
||||
- 'nsslapd-systemidlistscanlimit',
|
||||
'nsslapd-directory',
|
||||
'nsslapd-import-cachesize',
|
||||
'nsslapd-idl-switch',
|
||||
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
|
||||
index 9772e39d4..68efa795c 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -39,7 +39,6 @@ arg_to_attr = {
|
||||
'mode': 'nsslapd-mode',
|
||||
'state': 'nsslapd-state',
|
||||
'idlistscanlimit': 'nsslapd-idlistscanlimit',
|
||||
- 'systemidlistscanlimit': 'nsslapd-systemidlistscanlimit',
|
||||
'directory': 'nsslapd-directory',
|
||||
'dbcachesize': 'nsslapd-dbcachesize',
|
||||
'logdirectory': 'nsslapd-db-logdirectory',
|
||||
@@ -626,21 +625,6 @@ def backend_set_index(inst, basedn, log, args):
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
raise ValueError('Can not delete matching rule type because it does not exist')
|
||||
|
||||
- if args.replace_scanlimit is not None:
|
||||
- for replace_scanlimit in args.replace_scanlimit:
|
||||
- index.replace('nsIndexIDListScanLimit', replace_scanlimit)
|
||||
-
|
||||
- if args.add_scanlimit is not None:
|
||||
- for add_scanlimit in args.add_scanlimit:
|
||||
- index.add('nsIndexIDListScanLimit', add_scanlimit)
|
||||
-
|
||||
- if args.del_scanlimit is not None:
|
||||
- for del_scanlimit in args.del_scanlimit:
|
||||
- try:
|
||||
- index.remove('nsIndexIDListScanLimit', del_scanlimit)
|
||||
- except ldap.NO_SUCH_ATTRIBUTE:
|
||||
- raise ValueError('Can not delete a fine grain limit definition because it does not exist')
|
||||
-
|
||||
if args.reindex:
|
||||
be.reindex(attrs=[args.attr])
|
||||
log.info("Index successfully updated")
|
||||
@@ -963,9 +947,6 @@ def create_parser(subparsers):
|
||||
edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)')
|
||||
edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index')
|
||||
edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index')
|
||||
- edit_index_parser.add_argument('--add-scanlimit', action='append', help='Adds a fine grain limit definiton to the index')
|
||||
- edit_index_parser.add_argument('--replace-scanlimit', action='append', help='Replaces a fine grain limit definiton to the index')
|
||||
- edit_index_parser.add_argument('--del-scanlimit', action='append', help='Removes a fine grain limit definiton to the index')
|
||||
edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index')
|
||||
edit_index_parser.add_argument('be_name', help='The backend name or suffix')
|
||||
|
||||
@@ -1092,7 +1073,6 @@ def create_parser(subparsers):
|
||||
'will check when examining candidate entries in response to a search request')
|
||||
set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files')
|
||||
set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation')
|
||||
- set_db_config_parser.add_argument('--systemidlistscanlimit', help='Specifies the number of entry IDs that are fetch from ancestorid/parentid indexes')
|
||||
set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance')
|
||||
set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size in bytes')
|
||||
set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs')
|
||||
--
|
||||
2.52.0
|
||||
|
||||
212
0033-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
Normal file
212
0033-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
Normal file
@ -0,0 +1,212 @@
|
||||
From 4c44e4c522afc0f5401754f29a47645889e21aca Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove
|
||||
nsIndexIDListScanLimit from parentid
|
||||
|
||||
Description:
|
||||
Add `upgrade_remove_index_scanlimit()` function that removes the
|
||||
nsIndexIDListScanLimit attribute from parentid index configuration
|
||||
if present.
|
||||
|
||||
This attribute was incorrectly added by a previous version and can
|
||||
cause issues with index configuration. The upgrade function runs
|
||||
automatically on server startup and removes the attribute if found.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 52 +++++++++
|
||||
ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++
|
||||
2 files changed, 157 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 140845a33..aea88e0e2 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -453,6 +453,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
+ """Check if upgrade function removes nsIndexIDListScanLimit from parentid index
|
||||
+
|
||||
+ :id: 2808886e-c1c1-441d-b3a3-299c4ef1ab4a
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Stop the server
|
||||
+ 3. Use DSEldif to add nsIndexIDListScanLimit to parentid index
|
||||
+ 4. Start the server (triggers upgrade)
|
||||
+ 5. Verify nsIndexIDListScanLimit is removed from parentid index
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. nsIndexIDListScanLimit is no longer present
|
||||
+ """
|
||||
+ from lib389.dseldif import DSEldif
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
|
||||
+
|
||||
+ log.info("Stop the server")
|
||||
+ standalone.stop()
|
||||
+
|
||||
+ log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+ dse_ldif.add(PARENTID_DN, "nsIndexIDListScanLimit", SCANLIMIT_VALUE)
|
||||
+
|
||||
+ # Verify it was added
|
||||
+ scanlimit = dse_ldif.get(PARENTID_DN, "nsIndexIDListScanLimit")
|
||||
+ assert scanlimit is not None, "Failed to add nsIndexIDListScanLimit"
|
||||
+ log.info(f"Added nsIndexIDListScanLimit: {scanlimit}")
|
||||
+
|
||||
+ log.info("Start the server (triggers upgrade)")
|
||||
+ standalone.start()
|
||||
+
|
||||
+ log.info("Verify nsIndexIDListScanLimit was removed by upgrade")
|
||||
+ # Check via LDAP - the upgrade should have removed it
|
||||
+ parentid_index = Index(standalone, PARENTID_DN)
|
||||
+ scanlimit_after = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
|
||||
+ log.info(f"nsIndexIDListScanLimit after upgrade: {scanlimit_after}")
|
||||
+
|
||||
+ # The upgrade function should have removed nsIndexIDListScanLimit
|
||||
+ assert not scanlimit_after, \
|
||||
+ f"nsIndexIDListScanLimit should have been removed but found: {scanlimit_after}"
|
||||
+
|
||||
+ log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index b02e37ed6..dcd16940b 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -330,6 +330,107 @@ upgrade_remove_subtree_rename(void)
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Remove nsIndexIDListScanLimit from parentid index configuration.
|
||||
+ *
|
||||
+ * This attribute was incorrectly added by a previous version and can
|
||||
+ * cause issues with index configuration. Remove it if present.
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_remove_index_scanlimit(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ const char *attrs_to_check[] = {"parentid", NULL};
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_dn || !be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
+ const char *attr_name = attrs_to_check[attr_idx];
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
+ attr_name, be_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* Check if nsIndexIDListScanLimit is present */
|
||||
+ if (slapi_entry_attr_get_ref(idx_entries[0], "nsIndexIDListScanLimit") != NULL) {
|
||||
+ /* Remove nsIndexIDListScanLimit */
|
||||
+ Slapi_PBlock *mod_pb = slapi_pblock_new();
|
||||
+ Slapi_Mods smods;
|
||||
+ int rc;
|
||||
+
|
||||
+ slapi_mods_init(&smods, 1);
|
||||
+ slapi_mods_add(&smods, LDAP_MOD_DELETE, "nsIndexIDListScanLimit", 0, NULL);
|
||||
+
|
||||
+ slapi_modify_internal_set_pb(
|
||||
+ mod_pb, idx_dn,
|
||||
+ slapi_mods_get_ldapmods_byref(&smods),
|
||||
+ NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_modify_internal_pb(mod_pb);
|
||||
+ slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_index_scanlimit",
|
||||
+ "Removed 'nsIndexIDListScanLimit' from index '%s' in backend '%s'\n",
|
||||
+ attr_name, be_name);
|
||||
+ } else if (rc != LDAP_NO_SUCH_ATTRIBUTE) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_index_scanlimit",
|
||||
+ "Failed to remove 'nsIndexIDListScanLimit' from index '%s' in backend '%s': error %d\n",
|
||||
+ attr_name, be_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_mods_done(&smods);
|
||||
+ slapi_pblock_destroy(mod_pb);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
* matching rule.
|
||||
@@ -649,6 +750,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
+ if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
313
0034-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
Normal file
313
0034-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
Normal file
@ -0,0 +1,313 @@
|
||||
From 41670301ccad5558296a3380a4974f7c0d4baede Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index
|
||||
config entry
|
||||
|
||||
Description:
|
||||
Add `upgrade_remove_ancestorid_index_config()` function that removes:
|
||||
* ancestorid from `cn=default indexes`
|
||||
* ancestorid index config entries from each backend's `cn=index`
|
||||
|
||||
Also remove ancestorid index configuration from template-dse.ldif.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 85 +++++++++++
|
||||
ldap/ldif/template-dse.ldif.in | 8 --
|
||||
ldap/servers/slapd/upgrade.c | 133 +++++++++++++++++-
|
||||
3 files changed, 214 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index aea88e0e2..eb727b902 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -504,6 +504,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
|
||||
log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
|
||||
|
||||
+ # Verify idempotency - restart again and ensure no errors
|
||||
+ log.info("Restart server again to verify idempotency (no errors on second run)")
|
||||
+ standalone.restart()
|
||||
+ # Verify the attribute is still absent
|
||||
+ scanlimit_after_second = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
|
||||
+ assert not scanlimit_after_second, \
|
||||
+ f"nsIndexIDListScanLimit should still be absent after second restart but found: {scanlimit_after_second}"
|
||||
+ log.info("Idempotency verified - no issues on second restart")
|
||||
+
|
||||
+
|
||||
+def test_upgrade_removes_ancestorid_index_config(topology_st):
|
||||
+ """Check if upgrade function removes ancestorid index config entry
|
||||
+
|
||||
+ :id: 3f3d6e9b-75ac-4f0d-b2ce-7204e6eacd0a
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Stop the server
|
||||
+ 3. Use DSEldif to add an ancestorid index config entry
|
||||
+ 4. Start the server (triggers upgrade)
|
||||
+ 5. Verify ancestorid index config entry is removed
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. ancestorid index config entry is no longer present
|
||||
+ """
|
||||
+ from lib389.dseldif import DSEldif
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ ANCESTORID_DN = "cn=ancestorid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
+
|
||||
+ log.info("Stop the server")
|
||||
+ standalone.stop()
|
||||
+
|
||||
+ log.info("Add ancestorid index config entry using DSEldif")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+
|
||||
+ # Create a fake ancestorid index entry
|
||||
+ ancestorid_entry = [
|
||||
+ "dn: {}\n".format(ANCESTORID_DN),
|
||||
+ "objectClass: top\n",
|
||||
+ "objectClass: nsIndex\n",
|
||||
+ "cn: ancestorid\n",
|
||||
+ "nsSystemIndex: true\n",
|
||||
+ "nsIndexType: eq\n",
|
||||
+ "nsMatchingRule: integerOrderingMatch\n",
|
||||
+ "\n"
|
||||
+ ]
|
||||
+ dse_ldif.add_entry(ancestorid_entry)
|
||||
+
|
||||
+ # Verify it was added by re-reading dse.ldif
|
||||
+ dse_ldif2 = DSEldif(standalone)
|
||||
+ cn_value = dse_ldif2.get(ANCESTORID_DN, "cn")
|
||||
+ assert cn_value is not None, "Failed to add ancestorid index config entry"
|
||||
+ log.info(f"Added ancestorid index entry with cn: {cn_value}")
|
||||
+
|
||||
+ log.info("Start the server (triggers upgrade)")
|
||||
+ standalone.start()
|
||||
+
|
||||
+ log.info("Verify ancestorid index config entry was removed by upgrade")
|
||||
+ # Check via LDAP - the upgrade should have removed the entry
|
||||
+ try:
|
||||
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
|
||||
+ # If we can get the entry, it wasn't removed - this is a failure
|
||||
+ cn_after = ancestorid_index.get_attr_vals_utf8("cn")
|
||||
+ assert False, f"ancestorid index config entry should have been removed but still exists: {cn_after}"
|
||||
+ except Exception as e:
|
||||
+ # Entry should not exist - this is expected
|
||||
+ log.info(f"ancestorid index config entry correctly removed (got exception: {e})")
|
||||
+
|
||||
+ log.info("Upgrade successfully removed ancestorid index config entry")
|
||||
+
|
||||
+ # Verify idempotency - restart again and ensure no errors
|
||||
+ log.info("Restart server again to verify idempotency (no errors on second run)")
|
||||
+ standalone.restart()
|
||||
+ # Verify the entry is still absent
|
||||
+ try:
|
||||
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
|
||||
+ cn_after_second = ancestorid_index.get_attr_vals_utf8("cn")
|
||||
+ assert False, f"ancestorid index config entry should still be absent after second restart but found: {cn_after_second}"
|
||||
+ except Exception as e:
|
||||
+ log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
|
||||
+
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
|
||||
index bb8c71cd9..b6ab6f6c6 100644
|
||||
--- a/ldap/ldif/template-dse.ldif.in
|
||||
+++ b/ldap/ldif/template-dse.ldif.in
|
||||
@@ -998,14 +998,6 @@ cn: aci
|
||||
nssystemindex: true
|
||||
nsindextype: pres
|
||||
|
||||
-dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
-objectclass: top
|
||||
-objectclass: nsIndex
|
||||
-cn: ancestorid
|
||||
-nssystemindex: true
|
||||
-nsindextype: eq
|
||||
-nsmatchingrule: integerOrderingMatch
|
||||
-
|
||||
dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
objectclass: top
|
||||
objectclass: nsIndex
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index dcd16940b..6b1b012da 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -431,6 +431,126 @@ upgrade_remove_index_scanlimit(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Remove ancestorid index configuration entry if present.
|
||||
+ *
|
||||
+ * The ancestorid index is special - it has no corresponding attribute type
|
||||
+ * and should not have a DSE config entry. If an entry exists, remove it.
|
||||
+ *
|
||||
+ * This function removes:
|
||||
+ * 1. The ancestorid entry from cn=default indexes (to prevent re-creation on startup)
|
||||
+ * 2. The ancestorid entry from each backend's cn=index (if it exists)
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_remove_ancestorid_index_config(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+ int rc;
|
||||
+
|
||||
+ /*
|
||||
+ * First, remove ancestorid from cn=default indexes to prevent
|
||||
+ * ldbm_instance_create_default_user_indexes() from re-creating it.
|
||||
+ */
|
||||
+ {
|
||||
+ Slapi_PBlock *def_pb = slapi_pblock_new();
|
||||
+ char *def_idx_dn = slapi_create_dn_string(
|
||||
+ "cn=ancestorid,cn=default indexes,cn=config,%s", be_base_dn);
|
||||
+
|
||||
+ if (def_idx_dn) {
|
||||
+ slapi_delete_internal_set_pb(
|
||||
+ def_pb, def_idx_dn, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_delete_internal_pb(def_pb);
|
||||
+ slapi_pblock_get(def_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Removed 'ancestorid' from default indexes.\n");
|
||||
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Failed to remove 'ancestorid' from default indexes: error %d\n", rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&def_idx_dn);
|
||||
+ }
|
||||
+ slapi_pblock_destroy(def_pb);
|
||||
+ }
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_dn || !be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=ancestorid,cn=index,%s",
|
||||
+ be_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* ancestorid index entry exists - delete it */
|
||||
+ Slapi_PBlock *del_pb = slapi_pblock_new();
|
||||
+
|
||||
+ slapi_delete_internal_set_pb(
|
||||
+ del_pb, idx_dn, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_delete_internal_pb(del_pb);
|
||||
+ slapi_pblock_get(del_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS) {
|
||||
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Removed 'ancestorid' index config entry in backend '%s'.\n",
|
||||
+ be_name);
|
||||
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
|
||||
+ "Failed to remove 'ancestorid' index config entry in backend '%s': error %d\n",
|
||||
+ be_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_destroy(del_pb);
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
* matching rule.
|
||||
@@ -445,7 +565,7 @@ upgrade_check_id_index_matching_rule(void)
|
||||
Slapi_Entry **backends = NULL;
|
||||
const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
- const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
|
||||
+ const char *attrs_to_check[] = {"parentid", NULL};
|
||||
upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
|
||||
/* Search for all backend instances */
|
||||
@@ -459,8 +579,9 @@ upgrade_check_id_index_matching_rule(void)
|
||||
|
||||
if (backends) {
|
||||
for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
- if (!be_name) {
|
||||
+ if (!be_dn || !be_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -469,8 +590,8 @@ upgrade_check_id_index_matching_rule(void)
|
||||
const char *attr_name = attrs_to_check[attr_idx];
|
||||
struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
Slapi_Entry **idx_entries = NULL;
|
||||
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
|
||||
- attr_name, be_name, be_base_dn);
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
+ attr_name, be_dn);
|
||||
char *idx_filter = "(objectclass=nsIndex)";
|
||||
PRBool has_matching_rule = PR_FALSE;
|
||||
|
||||
@@ -754,6 +875,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
+ if (upgrade_remove_ancestorid_index_config() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
300
0035-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
Normal file
300
0035-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
Normal file
@ -0,0 +1,300 @@
|
||||
From a260b50aa0c8e6c5b8b3fd0b164e9bbc4a15983f Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during
|
||||
backend startup
|
||||
|
||||
Description:
|
||||
Add `ldbm_instance_check_index_config()` function that checks on-disk
|
||||
index data and logs a message in case of a mismatch with DSE config entry.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 262 ++++++++++++++++++++++++
|
||||
1 file changed, 262 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 2b71cd4f7..17bfc09a0 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -239,6 +239,266 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
}
|
||||
|
||||
|
||||
+/*
|
||||
+ * Check if an index has integerOrderingMatch configured in DSE.
|
||||
+ *
|
||||
+ * This function performs an internal LDAP search to check if the index
|
||||
+ * configuration entry has nsMatchingRule: integerOrderingMatch.
|
||||
+ *
|
||||
+ * Parameters:
|
||||
+ * inst_name - backend instance name (e.g., "userRoot")
|
||||
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
|
||||
+ *
|
||||
+ * Returns:
|
||||
+ * PR_TRUE if integerOrderingMatch is configured
|
||||
+ * PR_FALSE if not configured or index entry doesn't exist
|
||||
+ */
|
||||
+static PRBool
|
||||
+ldbm_instance_index_has_int_order_in_dse(const char *inst_name, const char *index_name)
|
||||
+{
|
||||
+ Slapi_PBlock *pb = NULL;
|
||||
+ Slapi_Entry **entries = NULL;
|
||||
+ char *idx_dn = NULL;
|
||||
+ PRBool has_int_order = PR_FALSE;
|
||||
+
|
||||
+ idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,cn=ldbm database,cn=plugins,cn=config",
|
||||
+ index_name, inst_name);
|
||||
+ if (idx_dn == NULL) {
|
||||
+ return PR_FALSE;
|
||||
+ }
|
||||
+
|
||||
+ pb = slapi_pblock_new();
|
||||
+ slapi_search_internal_set_pb(pb, idx_dn, LDAP_SCOPE_BASE,
|
||||
+ "(objectclass=nsIndex)", NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
|
||||
+
|
||||
+ if (entries && entries[0]) {
|
||||
+ Slapi_Attr *mr_attr = NULL;
|
||||
+ if (slapi_entry_attr_find(entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ int idx;
|
||||
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
+ idx != -1;
|
||||
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
+ const struct berval *bval = slapi_value_get_berval(sval);
|
||||
+ if (bval && bval->bv_val &&
|
||||
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
+ has_int_order = PR_TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return has_int_order;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check a system index for ordering mismatch between config and on-disk data.
|
||||
+ *
|
||||
+ * This function compares what's configured in DSE (nsMatchingRule) with
|
||||
+ * what's actually on disk. A mismatch can occur in two scenarios:
|
||||
+ * 1. Ordering rule is configured but disk has lexicographic order
|
||||
+ * (rule was added after index was created)
|
||||
+ * 2. No ordering rule configured but disk has integer order
|
||||
+ * (rule was removed after index was created with it)
|
||||
+ *
|
||||
+ * This function reads the first keys from the specified index and checks
|
||||
+ * if they are stored in lexicographic order (string: "1" < "10" < "2") or
|
||||
+ * integer order (numeric: "1" < "2" < "10").
|
||||
+ *
|
||||
+ * Parameters:
|
||||
+ * be - backend
|
||||
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
|
||||
+ *
|
||||
+ */
|
||||
+static void
|
||||
+ldbm_instance_check_index_config(backend *be, const char *index_name)
|
||||
+{
|
||||
+ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
+ struct attrinfo *ai = NULL;
|
||||
+ dbi_db_t *db = NULL;
|
||||
+ dbi_cursor_t dbc = {0};
|
||||
+ dbi_val_t key = {0};
|
||||
+ dbi_val_t data = {0};
|
||||
+ int ret = 0;
|
||||
+ PRBool config_has_int_order = PR_FALSE;
|
||||
+ PRBool disk_has_int_order = PR_TRUE; /* Assume integer order until proven otherwise */
|
||||
+ ID prev_id = 0;
|
||||
+ int key_count = 0;
|
||||
+ PRBool first_key = PR_TRUE;
|
||||
+ PRBool found_ordering_evidence = PR_FALSE;
|
||||
+
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': checking %s index ordering...\n",
|
||||
+ inst->inst_name, index_name);
|
||||
+
|
||||
+ /* Check if integerOrderingMatch is configured in DSE */
|
||||
+ config_has_int_order = ldbm_instance_index_has_int_order_in_dse(inst->inst_name, index_name);
|
||||
+
|
||||
+ /* Get attrinfo for the index */
|
||||
+ ainfo_get(be, (char *)index_name, &ai);
|
||||
+ if (ai == NULL || strcmp(ai->ai_type, index_name) != 0) {
|
||||
+ /* No index config found */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': no %s attrinfo found, skipping check\n",
|
||||
+ inst->inst_name, index_name);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* Open the index file */
|
||||
+ ret = dblayer_get_index_file(be, ai, &db, 0);
|
||||
+ if (ret != 0 || db == NULL) {
|
||||
+ /* Index file doesn't exist or can't be opened - this is fine for new instances */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': could not open %s index file (ret=%d), skipping order check\n",
|
||||
+ inst->inst_name, index_name, ret);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* Create a cursor to read keys */
|
||||
+ ret = dblayer_new_cursor(be, db, NULL, &dbc);
|
||||
+ if (ret != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': could not create cursor on %s index (ret=%d)\n",
|
||||
+ inst->inst_name, index_name, ret);
|
||||
+ dblayer_release_index_file(be, ai, db);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ dblayer_value_init(be, &key);
|
||||
+ dblayer_value_init(be, &data);
|
||||
+
|
||||
+ /*
|
||||
+ * Read up to 100 unique keys and check their ordering.
|
||||
+ * With lexicographic ordering: "1" < "10" < "100" < "2" < "20" < "3"
|
||||
+ * With integer ordering: "1" < "2" < "3" < "10" < "20" < "100"
|
||||
+ *
|
||||
+ * If we find a case where prev_id > current_id (numerically), but the
|
||||
+ * keys are still in order (lexicographically), then the index uses
|
||||
+ * lexicographic ordering.
|
||||
+ */
|
||||
+ while (key_count < 100) {
|
||||
+ ID current_id;
|
||||
+
|
||||
+ ret = dblayer_cursor_op(&dbc, first_key ? DBI_OP_MOVE_TO_FIRST : DBI_OP_NEXT_KEY, &key, &data);
|
||||
+ first_key = PR_FALSE; /* Always advance cursor on next iteration */
|
||||
+ if (ret != 0) {
|
||||
+ break; /* No more keys or error */
|
||||
+ }
|
||||
+
|
||||
+ /* Skip non-equality keys */
|
||||
+ if (key.size < 2 || *(char *)key.data != EQ_PREFIX) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Parse the ID from the key (format: "=<id>") */
|
||||
+ current_id = (ID)strtoul((char *)key.data + 1, NULL, 10);
|
||||
+ if (current_id == 0) {
|
||||
+ continue; /* Invalid ID, skip */
|
||||
+ }
|
||||
+
|
||||
+ key_count++;
|
||||
+
|
||||
+ if (prev_id != 0) {
|
||||
+ /*
|
||||
+ * Check ordering: if prev_id > current_id numerically,
|
||||
+ * but we got this key after prev in DB order, then
|
||||
+ * the index is using lexicographic ordering.
|
||||
+ *
|
||||
+ * Example: if we see "10" followed by "2", that's lexicographic
|
||||
+ * because "10" < "2" as strings, but 10 > 2 as integers.
|
||||
+ */
|
||||
+ if (prev_id > current_id) {
|
||||
+ /* Found evidence of lexicographic ordering */
|
||||
+ disk_has_int_order = PR_FALSE;
|
||||
+ found_ordering_evidence = PR_TRUE;
|
||||
+ break;
|
||||
+ } else if (prev_id < current_id) {
|
||||
+ /*
|
||||
+ * This is consistent with integer ordering, but we need
|
||||
+ * to find a case that proves lexicographic ordering.
|
||||
+ * For example, seeing "1" followed by "2" is ambiguous,
|
||||
+ * but seeing "1" followed by "10" (not "2") proves lexicographic.
|
||||
+ *
|
||||
+ * A definitive test: if we see an ID followed by a smaller
|
||||
+ * ID, that's lexicographic. If all IDs are strictly increasing,
|
||||
+ * it could be either (or the index only has sequential IDs).
|
||||
+ */
|
||||
+ found_ordering_evidence = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
+ prev_id = current_id;
|
||||
+ }
|
||||
+
|
||||
+ /* Close the cursor and free values */
|
||||
+ dblayer_cursor_op(&dbc, DBI_OP_CLOSE, NULL, NULL);
|
||||
+ dblayer_value_free(be, &key);
|
||||
+ dblayer_value_free(be, &data);
|
||||
+
|
||||
+ /* Release the index file */
|
||||
+ dblayer_release_index_file(be, ai, db);
|
||||
+
|
||||
+ /*
|
||||
+ * Report findings and check for config/disk mismatch.
|
||||
+ * Log an error if there's a discrepancy between what's configured
|
||||
+ * in DSE and what's actually on disk.
|
||||
+ */
|
||||
+ if (!found_ordering_evidence) {
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': %s index ordering check - "
|
||||
+ "could not determine on-disk ordering (index may be empty or have sequential IDs only). "
|
||||
+ "Config has integerOrderingMatch: %s\n",
|
||||
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
|
||||
+ } else if (config_has_int_order && !disk_has_int_order) {
|
||||
+ /* Config expects integer ordering, but disk has lexicographic - MISMATCH */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': MISMATCH - %s index has integerOrderingMatch configured, "
|
||||
+ "but on-disk data uses lexicographic ordering. "
|
||||
+ "This will cause searches to return incorrect or incomplete results. "
|
||||
+ "Please reindex the %s attribute: "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s\n",
|
||||
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
|
||||
+ } else if (!config_has_int_order && disk_has_int_order) {
|
||||
+ /* Config expects lexicographic ordering, but disk has integer - MISMATCH */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': MISMATCH - %s index does not have integerOrderingMatch configured, "
|
||||
+ "but on-disk data uses integer ordering. "
|
||||
+ "This will cause searches to return incorrect or incomplete results. "
|
||||
+ "Please reindex the %s attribute: "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s\n",
|
||||
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
|
||||
+ } else {
|
||||
+ /* Config and disk ordering match - no action needed */
|
||||
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': %s index ordering check passed - "
|
||||
+ "config has integerOrderingMatch: %s, on-disk data matches.\n",
|
||||
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check system indexes for ordering mismatches.
|
||||
+ * If a mismatch is detected, log an error advising the administrator
|
||||
+ * to reindex the affected attribute.
|
||||
+ *
|
||||
+ * Note: We only check parentid here. The ancestorid index is a special
|
||||
+ * system index that has no DSE config entry - its ordering is hardcoded
|
||||
+ * in ldbm_instance_init_config_entry() and cannot be changed by users.
|
||||
+ */
|
||||
+static void
|
||||
+ldbm_instance_check_indexes(backend *be)
|
||||
+{
|
||||
+ /* Check parentid index */
|
||||
+ ldbm_instance_check_index_config(be, LDBM_PARENTID_STR);
|
||||
+}
|
||||
+
|
||||
/* Starts a backend instance */
|
||||
int
|
||||
ldbm_instance_start(backend *be)
|
||||
@@ -308,6 +568,8 @@ ldbm_instance_startall(struct ldbminfo *li)
|
||||
ldbm_instance_register_modify_callback(inst);
|
||||
vlv_init(inst);
|
||||
slapi_mtn_be_started(inst->inst_be);
|
||||
+ /* Check index configuration for potential issues */
|
||||
+ ldbm_instance_check_indexes(inst->inst_be);
|
||||
}
|
||||
if (slapi_exist_referral(inst->inst_be)) {
|
||||
slapi_be_set_flag(inst->inst_be, SLAPI_BE_FLAG_CONTAINS_REFERRAL);
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1233
0036-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
Normal file
1233
0036-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
Normal file
File diff suppressed because it is too large
Load Diff
62
0037-Issue-7230-Regression-in-healtcheck-NssCheck-7235.patch
Normal file
62
0037-Issue-7230-Regression-in-healtcheck-NssCheck-7235.patch
Normal file
@ -0,0 +1,62 @@
|
||||
From 3ebb30a65e2b40610301e5feedda0408ac9f3631 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Tue, 10 Feb 2026 10:35:48 +0000
|
||||
Subject: [PATCH] Issue 7230 - Regression in healtcheck NssCheck (#7235)
|
||||
|
||||
Description:
|
||||
Dynamic Certificate lib389 updadates modified get_cert_details() to
|
||||
return a dict instead of tuple format. _lint_certificate_expiration() and
|
||||
tls.list_cas() still assumes tuple style access.
|
||||
|
||||
Fix:
|
||||
Update method to use dict key.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7230
|
||||
|
||||
Co-authored-by: @flo-renaud
|
||||
|
||||
Reviewed by: @droideck (Thank you)
|
||||
---
|
||||
src/lib389/lib389/cli_ctl/tls.py | 4 ++--
|
||||
src/lib389/lib389/nss_ssl.py | 4 ++--
|
||||
2 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_ctl/tls.py b/src/lib389/lib389/cli_ctl/tls.py
|
||||
index 8834f5758..91f1f3a59 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/tls.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/tls.py
|
||||
@@ -25,9 +25,9 @@ def list_client_cas(inst, log, args):
|
||||
|
||||
def list_cas(inst, log, args):
|
||||
tls = NssSsl(dirsrv=inst)
|
||||
- # This turns an array of [('CA', 'C,,')]
|
||||
+ # Returns a list of cert dicts, eg {'cn': 'nickname', etc}
|
||||
for c in tls.list_ca_certs():
|
||||
- log.info(c[0])
|
||||
+ log.info(c['cn'])
|
||||
|
||||
|
||||
def show_cert(inst, log, args):
|
||||
diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py
|
||||
index 764434166..fae65d19c 100644
|
||||
--- a/src/lib389/lib389/nss_ssl.py
|
||||
+++ b/src/lib389/lib389/nss_ssl.py
|
||||
@@ -91,13 +91,13 @@ class NssSsl(DSLint):
|
||||
if diff_date < timedelta(days=0):
|
||||
# Expired
|
||||
report = copy.deepcopy(DSCERTLE0002)
|
||||
- report['detail'] = report['detail'].replace('CERT', cert[0])
|
||||
+ report['detail'] = report['detail'].replace('CERT', cert['cn'])
|
||||
report['check'] = f'tls:certificate_expiration'
|
||||
yield report
|
||||
elif diff_date < timedelta(days=30):
|
||||
# Expiring within 30 days
|
||||
report = copy.deepcopy(DSCERTLE0001)
|
||||
- report['detail'] = report['detail'].replace('CERT', cert[0])
|
||||
+ report['detail'] = report['detail'].replace('CERT', cert['cn'])
|
||||
report['check'] = f'tls:certificate_expiration'
|
||||
yield report
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1225
0038-Issue-3555-UI-Fix-audit-issue-with-npm-isaacs-brace-.patch
Normal file
1225
0038-Issue-3555-UI-Fix-audit-issue-with-npm-isaacs-brace-.patch
Normal file
File diff suppressed because it is too large
Load Diff
97
0039-Issue-7221-CI-tests-fix-some-flaky-tests.patch
Normal file
97
0039-Issue-7221-CI-tests-fix-some-flaky-tests.patch
Normal file
@ -0,0 +1,97 @@
|
||||
From ae4a39474df08d56064453f0fb6c2272e6c3dc8b Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Fri, 6 Feb 2026 15:13:30 -0500
|
||||
Subject: [PATCH] Issue 7221 - CI tests - fix some flaky tests
|
||||
|
||||
Description:
|
||||
|
||||
Try to harden some of the flaky tests with sleeps and more relaxed contraints
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7221
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/acl/acivattr_test.py | 7 +++++--
|
||||
dirsrvtests/tests/suites/import/regression_test.py | 2 +-
|
||||
.../suites/replication/wait_for_async_feature_test.py | 2 +-
|
||||
dirsrvtests/tests/suites/retrocl/basic_test.py | 2 +-
|
||||
4 files changed, 8 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/acl/acivattr_test.py b/dirsrvtests/tests/suites/acl/acivattr_test.py
|
||||
index d55eea023..efe2d5ef1 100644
|
||||
--- a/dirsrvtests/tests/suites/acl/acivattr_test.py
|
||||
+++ b/dirsrvtests/tests/suites/acl/acivattr_test.py
|
||||
@@ -1,12 +1,12 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2019 Red Hat, Inc.
|
||||
+# Copyright (C) 2026 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
-import pytest, os, ldap
|
||||
+import pytest, os, ldap, time
|
||||
from lib389._constants import DEFAULT_SUFFIX, PW_DM
|
||||
from lib389.idm.user import UserAccount
|
||||
from lib389.idm.organization import Organization
|
||||
@@ -190,6 +190,8 @@ def test_positive(topo, _add_user, aci_of_user, user, entry, aci):
|
||||
"""
|
||||
# set aci
|
||||
Domain(topo.standalone, DNBASE).set("aci", aci)
|
||||
+ time.sleep(.5)
|
||||
+
|
||||
# create connection
|
||||
conn = UserAccount(topo.standalone, user).bind(PW_DM)
|
||||
# according to the aci , user will be able to change description
|
||||
@@ -242,6 +244,7 @@ def test_negative(topo, _add_user, aci_of_user, user, entry, aci):
|
||||
"""
|
||||
# set aci
|
||||
Domain(topo.standalone, DNBASE).set("aci", aci)
|
||||
+ time.sleep(.5)
|
||||
# create connection
|
||||
conn = UserAccount(topo.standalone, user).bind(PW_DM)
|
||||
# according to the aci , user will not be able to change description
|
||||
diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
index 0e3ba1930..25a7f359d 100644
|
||||
--- a/dirsrvtests/tests/suites/import/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
@@ -687,7 +687,7 @@ def test_ldif2db_after_backend_create(topo, verify):
|
||||
import_time_2 = create_backend_and_import(instance, ldif_file_2, 'o=test_2', 'test_2')
|
||||
|
||||
log.info('Import times should be approximately the same')
|
||||
- assert abs(import_time_1 - import_time_2) < 15
|
||||
+ assert abs(import_time_1 - import_time_2) < 20
|
||||
|
||||
|
||||
def test_ldif_missing_suffix_entry(topo, request, verify):
|
||||
diff --git a/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py b/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
|
||||
index c5ab585e4..84ce1ca2b 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
|
||||
@@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
|
||||
installation1_prefix = None
|
||||
|
||||
# Expected minimum and maximum number of async result in usual cases
|
||||
-USUAL_MIN_AP = 3
|
||||
+USUAL_MIN_AP = 2
|
||||
USUAL_MAX_AP = 11
|
||||
|
||||
@pytest.fixture(params=[(None, (USUAL_MIN_AP, USUAL_MAX_AP)),
|
||||
diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py
|
||||
index b53a60851..2fce72049 100644
|
||||
--- a/dirsrvtests/tests/suites/retrocl/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/retrocl/basic_test.py
|
||||
@@ -492,7 +492,7 @@ def test_retrocl_trimming_entries(topology_st):
|
||||
if inst.searchErrorsLog("trim_changelog: removed "):
|
||||
log.info(f'Trimming detected after {attempt * 6} seconds')
|
||||
break
|
||||
-
|
||||
+
|
||||
log.info('Verify trimming occurred by checking error log')
|
||||
assert inst.searchErrorsLog("trim_changelog: removed ")
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
From 58f5d129496cc8b4271daf5d0cd3ab31e8b926a8 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 12:47:23 +0530
|
||||
Subject: [PATCH] Issue 7233 - test_produce_division_by_zero fails with
|
||||
IsADirectoryError in conftest.py (#7234)
|
||||
|
||||
Description: glob('/*/*') matches directories causing open() to fail.
|
||||
|
||||
Fixes: #7233
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
---
|
||||
dirsrvtests/conftest.py | 2 ++
|
||||
.../suites/disk_monitoring/disk_monitoring_divide_test.py | 7 ++++---
|
||||
2 files changed, 6 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/conftest.py b/dirsrvtests/conftest.py
|
||||
index 0db6045f4..19b34c4a4 100644
|
||||
--- a/dirsrvtests/conftest.py
|
||||
+++ b/dirsrvtests/conftest.py
|
||||
@@ -126,6 +126,8 @@ def pytest_runtest_makereport(item, call):
|
||||
text = asan_report.read()
|
||||
extra.append(pytest_html.extras.text(text, name=os.path.basename(f)))
|
||||
for f in glob.glob(f'{p.log_dir.split("/slapd",1)[0]}/*/*'):
|
||||
+ if not os.path.isfile(f):
|
||||
+ continue
|
||||
if f.endswith('gz'):
|
||||
with gzip.open(f, 'rb') as dirsrv_log:
|
||||
text = dirsrv_log.read()
|
||||
diff --git a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
|
||||
index 9d952f93a..3e52e7c6b 100644
|
||||
--- a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
|
||||
+++ b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
|
||||
@@ -31,15 +31,16 @@ def create_dummy_mount(topology_st, request):
|
||||
log.info('Create dummy mount')
|
||||
for cmd in cmds:
|
||||
log.info('Command used : %s' % cmd)
|
||||
- subprocess.Popen(cmd, shell=True)
|
||||
+ subprocess.run(cmd, shell=True)
|
||||
|
||||
def fin():
|
||||
cmds = ['umount /var/log/dirsrv/slapd-{}/tmp'.format(topology_st.standalone.serverid),
|
||||
+ 'rmdir /var/log/dirsrv/slapd-{}/tmp'.format(topology_st.standalone.serverid),
|
||||
'setenforce 1']
|
||||
|
||||
for cmd in cmds:
|
||||
- log.info('Command used : %s' % cmds)
|
||||
- subprocess.Popen(cmd, shell=True)
|
||||
+ log.info('Command used : %s' % cmd)
|
||||
+ subprocess.run(cmd, shell=True)
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
226
0041-Issue-7241-Drop-dateutil-7242.patch
Normal file
226
0041-Issue-7241-Drop-dateutil-7242.patch
Normal file
@ -0,0 +1,226 @@
|
||||
From bbda49b86f3841ac5100894da426edc541b6226c Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 09:15:18 +0100
|
||||
Subject: [PATCH] Issue 7241 - Drop dateutil (#7242)
|
||||
|
||||
Bug Description:
|
||||
python-dateutil is unmaintained upstream and is marked for deprecation.
|
||||
|
||||
Fix Description:
|
||||
* Replace `dateutil.tz.tzoffset` with `datetime.timezone(datetime.timedelta())`.
|
||||
* Replace `dateutil.parser.parse` with standard `datetime` calls.
|
||||
* Import `datetime` as `dt` to avoid confusion between module and class.
|
||||
* Fix month lookup bug ('Oct': 9 / 'Sep': 10).
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7241
|
||||
|
||||
Reviewed by: jchapma, droideck (Thanks!)
|
||||
---
|
||||
.../suites/password/pwdPolicy_warning_test.py | 5 +-
|
||||
src/lib389/lib389/dirsrv_log.py | 48 ++++++++++++-------
|
||||
src/lib389/lib389/tests/dirsrv_log_test.py | 13 +++--
|
||||
src/lib389/pyproject.toml | 2 -
|
||||
src/lib389/requirements.txt | 1 -
|
||||
5 files changed, 38 insertions(+), 31 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
||||
index 154ec01f1..2341da6eb 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
||||
@@ -15,9 +15,8 @@ from lib389.topologies import topology_st
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389._constants import (DEFAULT_SUFFIX, DN_CONFIG, PASSWORD, DN_DM)
|
||||
-from dateutil.parser import parse as dt_parse
|
||||
from lib389.config import Config
|
||||
-import datetime
|
||||
+import datetime as dt
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -351,7 +350,7 @@ def test_with_different_password_states(topology_st, global_policy, add_user):
|
||||
old_ts = user.get_attr_val_utf8('passwordExpirationTime')
|
||||
log.info("Old passwordExpirationTime: {}".format(old_ts))
|
||||
|
||||
- new_ts = (dt_parse(old_ts) - datetime.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
|
||||
+ new_ts = (dt.datetime.strptime(old_ts, '%Y%m%d%H%M%SZ') - dt.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
|
||||
log.info("New passwordExpirationTime: {}".format(new_ts))
|
||||
user.replace('passwordExpirationTime', new_ts)
|
||||
|
||||
diff --git a/src/lib389/lib389/dirsrv_log.py b/src/lib389/lib389/dirsrv_log.py
|
||||
index e40105ad3..3d923a8bf 100644
|
||||
--- a/src/lib389/lib389/dirsrv_log.py
|
||||
+++ b/src/lib389/lib389/dirsrv_log.py
|
||||
@@ -10,11 +10,11 @@
|
||||
"""
|
||||
|
||||
import copy
|
||||
+import datetime as dt
|
||||
import json
|
||||
import glob
|
||||
import re
|
||||
import gzip
|
||||
-from dateutil.parser import parse as dt_parse
|
||||
from lib389.utils import ensure_bytes
|
||||
from lib389._mapped_object_lint import DSLint
|
||||
from lib389.lint import (
|
||||
@@ -34,8 +34,8 @@ MONTH_LOOKUP = {
|
||||
'Jun': 6,
|
||||
'Jul': 7,
|
||||
'Aug': 8,
|
||||
- 'Oct': 9,
|
||||
- 'Sep': 10,
|
||||
+ 'Sep': 9,
|
||||
+ 'Oct': 10,
|
||||
'Nov': 11,
|
||||
'Dec': 12,
|
||||
}
|
||||
@@ -50,9 +50,9 @@ class DirsrvLog(DSLint):
|
||||
"""
|
||||
self.dirsrv = dirsrv
|
||||
self.log = self.dirsrv.log
|
||||
- self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
|
||||
+ self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
|
||||
# JSON timestamp uses strftime %FT%T --> 2025-02-12T17:00:47.663123181 -0500
|
||||
- self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
|
||||
+ self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
|
||||
self.prog_datetime = re.compile(r'^(?P<timestamp>\[.*\])')
|
||||
self.jsonFormat = False
|
||||
|
||||
@@ -157,20 +157,32 @@ class DirsrvLog(DSLint):
|
||||
else:
|
||||
timedata = self.prog_timestamp.match(ts).groupdict()
|
||||
|
||||
- # Now, have to convert month to an int.
|
||||
- dt_str = '{YEAR}-{MONTH}-{DAY} {HOUR}-{MINUTE}-{SECOND} {TZ}'.format(
|
||||
- YEAR=timedata['year'],
|
||||
- MONTH=timedata['month'],
|
||||
- DAY=timedata['day'],
|
||||
- HOUR=timedata['hour'],
|
||||
- MINUTE=timedata['minute'],
|
||||
- SECOND=timedata['second'],
|
||||
- TZ=timedata['tz'],
|
||||
- )
|
||||
- dt = dt_parse(dt_str)
|
||||
+ # Convert month to an int.
|
||||
+ month = timedata['month']
|
||||
+ if not month.isdigit():
|
||||
+ month = MONTH_LOOKUP[month]
|
||||
+ else:
|
||||
+ month = int(month)
|
||||
+
|
||||
+ # Parse timezone offset string (e.g. "+1000" or "-0500") into a timezone
|
||||
+ tz_str = timedata['tz']
|
||||
+ tz_sign = 1 if tz_str[0] == '+' else -1
|
||||
+ tz_hours = int(tz_str[1:3])
|
||||
+ tz_minutes = int(tz_str[3:5])
|
||||
+ tz = dt.timezone(dt.timedelta(hours=tz_sign * tz_hours, minutes=tz_sign * tz_minutes))
|
||||
+
|
||||
+ parsed_dt = dt.datetime(
|
||||
+ int(timedata['year']),
|
||||
+ month,
|
||||
+ int(timedata['day']),
|
||||
+ int(timedata['hour']),
|
||||
+ int(timedata['minute']),
|
||||
+ int(timedata['second']),
|
||||
+ tzinfo=tz
|
||||
+ )
|
||||
if timedata['nanosecond']:
|
||||
- dt = dt.replace(microsecond=int(int(timedata['nanosecond']) / 1000))
|
||||
- return dt
|
||||
+ parsed_dt = parsed_dt.replace(microsecond=int(timedata['nanosecond']) // 1000)
|
||||
+ return parsed_dt
|
||||
|
||||
def get_time_in_secs(self, log_line):
|
||||
"""Take the timestamp (not the date) from a DS access log and convert
|
||||
diff --git a/src/lib389/lib389/tests/dirsrv_log_test.py b/src/lib389/lib389/tests/dirsrv_log_test.py
|
||||
index 920e67a01..d0259ced9 100644
|
||||
--- a/src/lib389/lib389/tests/dirsrv_log_test.py
|
||||
+++ b/src/lib389/lib389/tests/dirsrv_log_test.py
|
||||
@@ -12,8 +12,7 @@ from lib389 import DirSrv, Entry
|
||||
import pytest
|
||||
import time
|
||||
import shutil
|
||||
-import datetime
|
||||
-from dateutil.tz import tzoffset
|
||||
+import datetime as dt
|
||||
|
||||
INSTANCE_PORT = 54321
|
||||
INSTANCE_SERVERID = 'standalone'
|
||||
@@ -74,7 +73,7 @@ def test_access_log(topology):
|
||||
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.726093186 +1000] conn=1 fd=64 slot=64 connection from ::1 to ::1') ==
|
||||
{
|
||||
'slot': '64', 'remote': '::1', 'action': 'CONNECT', 'timestamp': '[27/Apr/2016:12:49:49.726093186 +1000]', 'fd': '64', 'conn': '1', 'local': '::1',
|
||||
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 726093, tzinfo=tzoffset(None, 36000))
|
||||
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 726093, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
||||
}
|
||||
)
|
||||
assert(
|
||||
@@ -82,21 +81,21 @@ def test_access_log(topology):
|
||||
{
|
||||
'rem': 'base="cn=config" scope=0 filter="(objectClass=*)" attrs="nsslapd-instancedir nsslapd-errorlog nsslapd-accesslog nsslapd-auditlog nsslapd-certdir nsslapd-schemadir nsslapd-bakdir nsslapd-ldifdir"', # noqa
|
||||
'action': 'SRCH', 'timestamp': '[27/Apr/2016:12:49:49.727235997 +1000]', 'conn': '1', 'op': '2',
|
||||
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 727235, tzinfo=tzoffset(None, 36000))
|
||||
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 727235, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
||||
}
|
||||
)
|
||||
assert(
|
||||
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 +1000] conn=1 op=4 fd=64 closed - U1') ==
|
||||
{
|
||||
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 +1000]', 'conn': '1', 'op': '4',
|
||||
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, 36000))
|
||||
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
||||
}
|
||||
)
|
||||
assert(
|
||||
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 -1000] conn=1 op=4 fd=64 closed - U1') ==
|
||||
{
|
||||
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 -1000]', 'conn': '1', 'op': '4',
|
||||
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, -36000))
|
||||
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=-36000)))
|
||||
}
|
||||
)
|
||||
|
||||
@@ -113,7 +112,7 @@ def test_error_log(topology):
|
||||
topology.standalone.ds_error_log.parse_line('[27/Apr/2016:13:46:35.775670167 +1000] slapd started. Listening on All Interfaces port 54321 for LDAP requests') == # noqa
|
||||
{
|
||||
'timestamp': '[27/Apr/2016:13:46:35.775670167 +1000]', 'message': 'slapd started. Listening on All Interfaces port 54321 for LDAP requests',
|
||||
- 'datetime': datetime.datetime(2016, 4, 27, 13, 0, 0, 775670, tzinfo=tzoffset(None, 36000))
|
||||
+ 'datetime': dt.datetime(2016, 4, 27, 13, 46, 35, 775670, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
||||
}
|
||||
)
|
||||
|
||||
diff --git a/src/lib389/pyproject.toml b/src/lib389/pyproject.toml
|
||||
index 63c7c9710..e067d1590 100644
|
||||
--- a/src/lib389/pyproject.toml
|
||||
+++ b/src/lib389/pyproject.toml
|
||||
@@ -5,7 +5,6 @@ requires = [
|
||||
"argparse-manpage[setuptools]",
|
||||
"pyasn1",
|
||||
"pyasn1-modules",
|
||||
- "python-dateutil",
|
||||
"argcomplete",
|
||||
"python-ldap",
|
||||
"distro",
|
||||
@@ -43,7 +42,6 @@ classifiers = [
|
||||
dependencies = [
|
||||
"pyasn1",
|
||||
"pyasn1-modules",
|
||||
- "python-dateutil",
|
||||
"argcomplete",
|
||||
"python-ldap",
|
||||
"distro",
|
||||
diff --git a/src/lib389/requirements.txt b/src/lib389/requirements.txt
|
||||
index 5e1b3dad7..94b10e3c2 100644
|
||||
--- a/src/lib389/requirements.txt
|
||||
+++ b/src/lib389/requirements.txt
|
||||
@@ -1,6 +1,5 @@
|
||||
pyasn1
|
||||
pyasn1-modules
|
||||
-python-dateutil
|
||||
argcomplete
|
||||
argparse-manpage
|
||||
python-ldap
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
From d19c50372d5c5d901f05ce6e7dd03313f41fc197 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 10:42:09 +0000
|
||||
Subject: [PATCH] Issue 7231 - Sync repl tests fail in FIPS mode due to non
|
||||
FIPS compliant crypto (#7232)
|
||||
|
||||
Description:
|
||||
Several sync_repl tests fail when running on a FIPS enabled system. The failures
|
||||
are caused by the sync repl client (Sync_persist), using TLS options and ciphers
|
||||
that are not FIPS compatible.
|
||||
|
||||
Fix:
|
||||
Update the sync repl client to use FIPS approved TLS version.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7231
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thank you)
|
||||
---
|
||||
.../tests/suites/syncrepl_plugin/basic_test.py | 14 +++++++++++---
|
||||
1 file changed, 11 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
index 85b4ac078..d0e7e8a32 100644
|
||||
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
@@ -21,7 +21,7 @@ from lib389.idm.group import Groups
|
||||
from lib389.topologies import topology_st as topology
|
||||
from lib389.topologies import topology_m2 as topo_m2
|
||||
from lib389.paths import Paths
|
||||
-from lib389.utils import ds_is_older
|
||||
+from lib389.utils import ds_is_older, is_fips
|
||||
from lib389.plugins import RetroChangelogPlugin, ContentSyncPlugin, AutoMembershipPlugin, MemberOfPlugin, MemberOfSharedConfig, AutoMembershipDefinitions, MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate
|
||||
from lib389._constants import *
|
||||
|
||||
@@ -214,10 +214,13 @@ class Sync_persist(threading.Thread, ReconnectLDAPObject, SyncreplConsumer):
|
||||
|
||||
def run(self):
|
||||
"""Start a sync repl client"""
|
||||
- ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt"))
|
||||
ldap_connection = TestSyncer(self.inst.toLDAPURL())
|
||||
ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt"))
|
||||
+ if is_fips():
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, ldap.OPT_X_TLS_PROTOCOL_TLS1_2)
|
||||
ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
|
||||
+
|
||||
ldap_connection.simple_bind_s('cn=directory manager', 'password')
|
||||
ldap_search = ldap_connection.syncrepl_search(
|
||||
"dc=example,dc=com",
|
||||
@@ -257,6 +260,7 @@ def test_sync_repl_mep(topology, request):
|
||||
5. Success
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
|
||||
# Enable/configure retroCL
|
||||
plugin = RetroChangelogPlugin(inst)
|
||||
@@ -342,6 +346,7 @@ def test_sync_repl_cookie(topology, init_sync_repl_plugins, request):
|
||||
5.: succeeds
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
|
||||
# create a sync repl client and wait 5 seconds to be sure it is running
|
||||
sync_repl = Sync_persist(inst)
|
||||
@@ -408,6 +413,8 @@ def test_sync_repl_cookie_add_del(topology, init_sync_repl_plugins, request):
|
||||
6.: succeeds
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
+
|
||||
# create a sync repl client and wait 5 seconds to be sure it is running
|
||||
sync_repl = Sync_persist(inst)
|
||||
sync_repl.start()
|
||||
@@ -551,6 +558,7 @@ def test_sync_repl_cenotaph(topo_m2, request):
|
||||
5. Should succeeds
|
||||
"""
|
||||
m1 = topo_m2.ms["supplier1"]
|
||||
+ m1.enable_tls()
|
||||
# Enable/configure retroCL
|
||||
plugin = RetroChangelogPlugin(m1)
|
||||
plugin.disable()
|
||||
@@ -609,7 +617,7 @@ def test_sync_repl_dynamic_plugin(topology, request):
|
||||
3. Should succeeds
|
||||
4. Should succeeds
|
||||
"""
|
||||
-
|
||||
+ topology.standalone.enable_tls()
|
||||
# Reset the instance in a default config
|
||||
# Disable content sync plugin
|
||||
topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
From 1df3852cf0e073cfe006d661aecdd909862fc79a Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 09:58:54 -0500
|
||||
Subject: [PATCH] Issue 7248 - CLI - attribute uniqueness - fix usage for
|
||||
exclude subtree option
|
||||
|
||||
Description:
|
||||
|
||||
Fix typo in usage message for the exclude subtree option
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7248
|
||||
|
||||
Reviewed by: progier (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_conf/plugins/attruniq.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
index bc925eb1c..26ca5d819 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
@@ -127,7 +127,7 @@ def _add_parser_args(parser):
|
||||
help='Sets the DN under which the plug-in checks for uniqueness of '
|
||||
'the attributes value. This attribute is multi-valued (uniqueness-subtrees)')
|
||||
parser.add_argument('--exclude-subtree', nargs='+',
|
||||
- help='Sets subtrees that should not excludedfrom attribute uniqueness. '
|
||||
+ help='Sets subtrees that should be excluded from attribute uniqueness checks. '
|
||||
'This attribute is multi-valued (uniqueness-exclude-subtrees)')
|
||||
parser.add_argument('--across-all-subtrees', choices=['on', 'off'], type=str.lower,
|
||||
help='If enabled (on), the plug-in checks that the attribute is unique across all subtrees '
|
||||
--
|
||||
2.52.0
|
||||
|
||||
201
0044-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch
Normal file
201
0044-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch
Normal file
@ -0,0 +1,201 @@
|
||||
From 63bf648f699b8fcd8f319254b0348d969ceea7a0 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 11:13:45 -0500
|
||||
Subject: [PATCH] Issue - CLI - dsctl db2index needs some hardening with MBD
|
||||
|
||||
Description:
|
||||
|
||||
The usage for dsctl db2index was confusing. The way the attr options and
|
||||
backend name were displayed it looks like the backend name could come after
|
||||
the attributes, but instead the backend name was treated as an attribute.
|
||||
|
||||
Instead make the backend name required, and change the attribute naming to
|
||||
require individual options instead of a list of values.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7250
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../tests/suites/import/import_test.py | 2 +-
|
||||
src/lib389/lib389/__init__.py | 40 ++++++-------------
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 37 +++++++----------
|
||||
3 files changed, 27 insertions(+), 52 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
|
||||
index c7275e4cb..ad6d05a1e 100644
|
||||
--- a/dirsrvtests/tests/suites/import/import_test.py
|
||||
+++ b/dirsrvtests/tests/suites/import/import_test.py
|
||||
@@ -514,7 +514,7 @@ def test_entry_with_escaped_characters_fails_to_import_and_index(topo, _import_c
|
||||
count += 1
|
||||
# Now re-index the database
|
||||
topo.standalone.stop()
|
||||
- topo.standalone.db2index()
|
||||
+ topo.standalone.db2index(bename="userroot")
|
||||
topo.standalone.start()
|
||||
# Should not return error.
|
||||
assert not topo.standalone.searchErrorsLog('error')
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index d57a91929..01b3ea23c 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -2953,7 +2953,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
|
||||
return True
|
||||
|
||||
- def db2index(self, bename=None, suffixes=None, attrs=None, vlvTag=None):
|
||||
+ def db2index(self, bename, suffixes=None, attrs=None, vlvTag=None):
|
||||
"""
|
||||
@param bename - The backend name to reindex
|
||||
@param suffixes - List/tuple of suffixes to reindex, currently unused
|
||||
@@ -2966,34 +2966,18 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
if self.status():
|
||||
self.log.error("db2index: Can not operate while directory server is running")
|
||||
return False
|
||||
- cmd = [prog, ]
|
||||
- # No backend specified, do an upgrade on all backends
|
||||
- # Backend and no attrs specified, reindex with all backend indexes
|
||||
- # Backend and attr/s specified, reindex backend with attr/s
|
||||
- if bename:
|
||||
- cmd.append('db2index')
|
||||
- cmd.append('-n')
|
||||
- cmd.append(bename)
|
||||
- if attrs:
|
||||
- for attr in attrs:
|
||||
- cmd.append('-t')
|
||||
- cmd.append(attr)
|
||||
- else:
|
||||
- dse_ldif = DSEldif(self)
|
||||
- indexes = dse_ldif.get_indexes(bename)
|
||||
- if indexes:
|
||||
- for idx in indexes:
|
||||
- cmd.append('-t')
|
||||
- cmd.append(idx)
|
||||
+ cmd = [prog, 'db2index', '-n', bename, '-D', self.get_config_dir()]
|
||||
+ if attrs:
|
||||
+ for attr in attrs:
|
||||
+ cmd.append('-t')
|
||||
+ cmd.append(attr)
|
||||
else:
|
||||
- cmd.append('upgradedb')
|
||||
- cmd.append('-a')
|
||||
- now = datetime.now().isoformat()
|
||||
- cmd.append(os.path.join(self.get_bak_dir(), 'reindex_%s' % now))
|
||||
- cmd.append('-f')
|
||||
-
|
||||
- cmd.append('-D')
|
||||
- cmd.append(self.get_config_dir())
|
||||
+ dse_ldif = DSEldif(self)
|
||||
+ indexes = dse_ldif.get_indexes(bename)
|
||||
+ if indexes:
|
||||
+ for idx in indexes:
|
||||
+ cmd.append('-t')
|
||||
+ cmd.append(idx)
|
||||
|
||||
try:
|
||||
result = subprocess.check_output(cmd, encoding='utf-8')
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
index 16da966d1..cd96cdaf7 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
@@ -26,32 +26,18 @@ class IndexOrdering(Enum):
|
||||
|
||||
|
||||
def dbtasks_db2index(inst, log, args):
|
||||
- rtn = False
|
||||
- if not args.backend:
|
||||
- if not inst.db2index():
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
- elif args.backend and not args.attr:
|
||||
- if not inst.db2index(bename=args.backend):
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
+ inst.log = log
|
||||
+ if not inst.db2index(bename=args.backend, attrs=args.attr):
|
||||
+ log.fatal("db2index failed")
|
||||
+ return False
|
||||
else:
|
||||
- if not inst.db2index(bename=args.backend, attrs=args.attr):
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
- if rtn:
|
||||
log.info("db2index successful")
|
||||
- return rtn
|
||||
- else:
|
||||
- log.fatal("db2index failed")
|
||||
- return rtn
|
||||
+ return True
|
||||
|
||||
|
||||
def dbtasks_db2bak(inst, log, args):
|
||||
# Needs an output name?
|
||||
+ inst.log = log
|
||||
if not inst.db2bak(args.archive):
|
||||
log.fatal("db2bak failed")
|
||||
return False
|
||||
@@ -61,6 +47,7 @@ def dbtasks_db2bak(inst, log, args):
|
||||
|
||||
def dbtasks_bak2db(inst, log, args):
|
||||
# Needs the archive to restore.
|
||||
+ inst.log = log
|
||||
if not inst.bak2db(args.archive):
|
||||
log.fatal("bak2db failed")
|
||||
return False
|
||||
@@ -70,6 +57,7 @@ def dbtasks_bak2db(inst, log, args):
|
||||
|
||||
def dbtasks_db2ldif(inst, log, args):
|
||||
# If export filename is provided, check if file path exists
|
||||
+ inst.log = log
|
||||
if args.ldif:
|
||||
path = Path(args.ldif)
|
||||
parent = path.parent.absolute()
|
||||
@@ -88,6 +76,7 @@ def dbtasks_db2ldif(inst, log, args):
|
||||
|
||||
def dbtasks_ldif2db(inst, log, args):
|
||||
# Check if ldif file exists
|
||||
+ inst.log = log
|
||||
if not os.path.exists(args.ldif):
|
||||
raise ValueError("The LDIF file does not exist: " + args.ldif)
|
||||
|
||||
@@ -103,6 +92,7 @@ def dbtasks_ldif2db(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_backups(inst, log, args):
|
||||
+ inst.log = log
|
||||
if args.delete:
|
||||
# Delete backup
|
||||
inst.del_backup(args.delete[0])
|
||||
@@ -117,6 +107,7 @@ def dbtasks_backups(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_ldifs(inst, log, args):
|
||||
+ inst.log = log
|
||||
if args.delete:
|
||||
# Delete LDIF file
|
||||
inst.del_ldif(args.delete[0])
|
||||
@@ -131,6 +122,7 @@ def dbtasks_ldifs(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_verify(inst, log, args):
|
||||
+ inst.log = log
|
||||
if not inst.dbverify(bename=args.backend):
|
||||
log.fatal("dbverify failed")
|
||||
return False
|
||||
@@ -521,9 +513,8 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
def create_parser(subcommands):
|
||||
db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
|
||||
- # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.")
|
||||
- db2index_parser.add_argument('backend', nargs="?", help="The backend to reindex. IE userRoot", default=False)
|
||||
- db2index_parser.add_argument('--attr', nargs="*", help="The attribute's to reindex. IE --attr aci cn givenname", default=False)
|
||||
+ db2index_parser.add_argument('backend', help="The backend to reindex. IE userRoot")
|
||||
+ db2index_parser.add_argument('--attr', action='append', help="An attribute to reindex. IE: --attr member --attr cn ...")
|
||||
db2index_parser.set_defaults(func=dbtasks_db2index)
|
||||
|
||||
db2bak_parser = subcommands.add_parser('db2bak', help="Initialise a BDB backup of the database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
From d52901f69e9b7952b33b219ec197308a1a20bda9 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 15:13:05 +0100
|
||||
Subject: [PATCH] Issue 7252 - PQC - Need to iterate on SECOidTag instead of
|
||||
using OID (#7254)
|
||||
|
||||
* Issue 7252 - PQC - Need to iterate on SECOidTag instead of using OID
|
||||
|
||||
Need to dynamically iterate on SECOidTag instead of using SEC_OID_ML_DSA_* OIDs to avoid issue with upcoming nss versions and fix a RHEL build break with nss 3.112
|
||||
|
||||
Issue: #7252
|
||||
|
||||
Reviewed by: @mreynolds389, @droideck, @vashirov
|
||||
|
||||
|
||||
* Update ldap/servers/slapd/ssl.c
|
||||
|
||||
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/ssl.c | 24 +++++++++---------------
|
||||
1 file changed, 9 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
|
||||
index 7d5db2cdd..d05c64fb1 100644
|
||||
--- a/ldap/servers/slapd/ssl.c
|
||||
+++ b/ldap/servers/slapd/ssl.c
|
||||
@@ -732,31 +732,25 @@ SSLPLCY_Install(void)
|
||||
{
|
||||
|
||||
SECStatus s = 0;
|
||||
-#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
int flags = NSS_USE_ALG_IN_SIGNATURE | NSS_USE_ALG_IN_SSL;
|
||||
- static const SECOidTag oids[] = {
|
||||
- SEC_OID_ML_DSA_44,
|
||||
- SEC_OID_ML_DSA_65,
|
||||
- SEC_OID_ML_DSA_87,
|
||||
- };
|
||||
-#endif
|
||||
+ SECOidData *oid = NULL;
|
||||
|
||||
s = NSS_SetDomesticPolicy();
|
||||
|
||||
-#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
|
||||
/* Should rely on the crypto module policy in FIPS mode */
|
||||
if (!slapd_pk11_isFIPS()) {
|
||||
/* Set explicitly PQC algorithm policy if it is not set by default */
|
||||
- for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
|
||||
- PRUint32 oflags = 0;
|
||||
- (void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
|
||||
- if ((oflags & flags) != flags) {
|
||||
- s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
|
||||
+ for (SECOidTag tag = 1; s == SECSuccess && (oid = SECOID_FindOIDByTag(tag)) != NULL; tag++) {
|
||||
+ if (oid->mechanism != CKM_INVALID_MECHANISM &&
|
||||
+ PL_strncasecmp(oid->desc, "ML-DSA-", 7) == 0) {
|
||||
+ PRUint32 oflags = 0;
|
||||
+ (void) NSS_GetAlgorithmPolicy(tag, &oflags);
|
||||
+ if ((oflags & flags) != flags) {
|
||||
+ s = NSS_SetAlgorithmPolicy(tag, flags, 0);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
-#endif
|
||||
-
|
||||
return s ? PR_FAILURE : PR_SUCCESS;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
3031
0046-Issue-6951-Dynamic-Certificas-Refresh-CI-tests-7238.patch
Normal file
3031
0046-Issue-6951-Dynamic-Certificas-Refresh-CI-tests-7238.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
||||
From 48ad61231203d9ccb96d0fe542aae93dbb74a9bf Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 15:38:52 +0100
|
||||
Subject: [PATCH] Issue 7184 - (2nd) argparse.HelpFormatter
|
||||
_format_actions_usage() is deprecated (#7257)
|
||||
|
||||
Description:
|
||||
`_format_actions_usage()` was also removed in Python 3.14.3.
|
||||
Replace version check with `isinstance()` to handle the return type of
|
||||
`_get_actions_usage_parts()` more robustly across Python versions.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7184
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7253
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_base/__init__.py | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
|
||||
index f1055aadc..3af8a46e6 100644
|
||||
--- a/src/lib389/lib389/cli_base/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_base/__init__.py
|
||||
@@ -420,11 +420,11 @@ class CustomHelpFormatter(argparse.HelpFormatter):
|
||||
else:
|
||||
# Use _get_actions_usage_parts() for Python 3.13 and later
|
||||
action_parts = self._get_actions_usage_parts(parent_arguments, [])
|
||||
- if sys.version_info >= (3, 15):
|
||||
- # Python 3.15 returns a tuple (list of actions, count of actions)
|
||||
+ if isinstance(action_parts, tuple):
|
||||
+ # Python 3.14.3+ and 3.15+ return a tuple (list of actions, count of actions)
|
||||
formatted_options = ' '.join(action_parts[0])
|
||||
else:
|
||||
- # Python 3.13 and 3.14 return a list of actions
|
||||
+ # Earlier versions return a list of actions
|
||||
formatted_options = ' '.join(action_parts)
|
||||
|
||||
# If formatted_options already in usage - remove them
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
From c7ef5b3073bbd94a5d2b544556368c830c165e0d Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 16:27:25 +0100
|
||||
Subject: [PATCH] Issue 7213 - (2nd) MDB_BAD_VALSIZE error while handling VLV
|
||||
(#7258)
|
||||
|
||||
Decription:
|
||||
Disable test_vlv_long_attribute_value on BDB as it hangs sometimes in
|
||||
CI, blocking other pipelines.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7213
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 7cdf16a84..89a747199 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1175,6 +1175,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
|
||||
|
||||
|
||||
+@pytest.mark.skipif(get_default_db_lib() == "bdb", reason="Hangs on BDB")
|
||||
def test_vlv_long_attribute_value(topology_st, request):
|
||||
"""
|
||||
Test VLV with an entry containing a very long attribute value (2K).
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
From 7e575cc8cc6f1bf558f50ca0fc55145e469d60d2 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 16:58:24 +0100
|
||||
Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid (#7256)
|
||||
|
||||
Description:
|
||||
`ldbm_instance_create_default_indexes()` configured ancestorid with
|
||||
integerOrderingMatch in the in-memory attrinfo, but ancestorid on disk
|
||||
might be using lexicographic ordering (data before the upgrade or after
|
||||
ldif2db import).
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 17bfc09a0..1569eb7ff 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -231,7 +231,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
38
0050-Issue-3134-Fix-build-break-7260.patch
Normal file
38
0050-Issue-3134-Fix-build-break-7260.patch
Normal file
@ -0,0 +1,38 @@
|
||||
From 245bc3b53f385e12e4dc9d2cb765a55e10e0fdc5 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 17:51:12 +0100
|
||||
Subject: [PATCH] Issue 3134 - Fix build break (#7260)
|
||||
|
||||
Fix build break of PR #7238 related to import rpm
|
||||
|
||||
Issue: #3134
|
||||
|
||||
Reviewed by: @vashirov (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/utils.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
|
||||
index 07cb34d93..4df36cf1f 100644
|
||||
--- a/src/lib389/lib389/utils.py
|
||||
+++ b/src/lib389/lib389/utils.py
|
||||
@@ -28,7 +28,6 @@ from datetime import (datetime, timedelta)
|
||||
import sys
|
||||
import filecmp
|
||||
import pwd
|
||||
-import rpm
|
||||
import shlex
|
||||
import operator
|
||||
import subprocess
|
||||
@@ -2135,6 +2134,8 @@ def get_timeout_scale():
|
||||
|
||||
def rpm_is_older(pkg, version):
|
||||
"""Check if an RPM package version is older than specified version"""
|
||||
+ # rpm module is not installed in build environment so let import it only when used.
|
||||
+ import rpm
|
||||
ts = rpm.TransactionSet()
|
||||
mi = ts.dbMatch('name', pkg)
|
||||
for h in mi:
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
From ac3d9253e0a7a4b5f0108506bcf25255b302fd16 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 11 Feb 2026 15:51:47 -0500
|
||||
Subject: [PATCH] Issue 7066/7052 - allow password history to be set to zero
|
||||
and remove history
|
||||
|
||||
Description:
|
||||
|
||||
For local password policies the server was incorrectly rejecting updates that
|
||||
set the value to zero. When password history is set to zero the old passwords
|
||||
in the entry history are not cleaned as expected.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7052
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7066
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../tests/suites/password/pwp_history_test.py | 7 ++++---
|
||||
ldap/servers/slapd/modify.c | 2 +-
|
||||
ldap/servers/slapd/pw.c | 13 ++++++++++++-
|
||||
3 files changed, 17 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
index cf68d743c..78b448a87 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
@@ -189,9 +189,9 @@ def test_history_is_not_overwritten(topology_st, user):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('policy',
|
||||
- [(pytest.param('global', marks=pytest.mark.xfail(reason="DS7052"))),
|
||||
- (pytest.param('subtree', marks=pytest.mark.xfail(reason="DS7066, DS7052"))),
|
||||
- (pytest.param('user', marks=pytest.mark.xfail(reason="DS7066, DS7052")))])
|
||||
+ [(pytest.param('global')),
|
||||
+ (pytest.param('subtree')),
|
||||
+ (pytest.param('user'))])
|
||||
def test_basic(topology_st, user, policy):
|
||||
"""Test basic password policy history feature functionality with dynamic count reduction
|
||||
|
||||
@@ -282,6 +282,7 @@ def test_basic(topology_st, user, policy):
|
||||
# Password history [password3, password4], current password is "password1"
|
||||
|
||||
# Reset password by Directory Manager(admin reset)
|
||||
+ dm = DirectoryManager(topology_st.standalone)
|
||||
dm.rebind()
|
||||
time.sleep(.5)
|
||||
change_password(user, 'password-reset', success=True)
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 9e5bce80b..0ecce9bc8 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -87,7 +87,7 @@ static struct attr_value_check
|
||||
{CONFIG_PW_WARNING_ATTRIBUTE, check_pw_duration_value, 0, -1},
|
||||
{CONFIG_PW_MINLENGTH_ATTRIBUTE, attr_check_minmax, 2, 512},
|
||||
{CONFIG_PW_MAXFAILURE_ATTRIBUTE, attr_check_minmax, 1, 32767},
|
||||
- {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 1, 24},
|
||||
+ {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 0, 24},
|
||||
{CONFIG_PW_LOCKDURATION_ATTRIBUTE, check_pw_duration_value, -1, -1},
|
||||
{CONFIG_PW_RESETFAILURECOUNT_ATTRIBUTE, check_pw_resetfailurecount_value, -1, -1},
|
||||
{CONFIG_PW_GRACELIMIT_ATTRIBUTE, attr_check_minmax, 0, -1},
|
||||
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
|
||||
index 055ec0d74..c53ecf23d 100644
|
||||
--- a/ldap/servers/slapd/pw.c
|
||||
+++ b/ldap/servers/slapd/pw.c
|
||||
@@ -1535,7 +1535,18 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw)
|
||||
pwpolicy = new_passwdPolicy(pb, dn);
|
||||
|
||||
if (pwpolicy->pw_inhistory == 0){
|
||||
- /* We are only enforcing the current password, just return */
|
||||
+ /* We are only enforcing the current password, just return but first
|
||||
+ * cleanup any old passwords in the history */
|
||||
+ attribute.mod_type = "passwordHistory";
|
||||
+ attribute.mod_op = LDAP_MOD_REPLACE;
|
||||
+ attribute.mod_values = NULL;
|
||||
+ list_of_mods[0] = &attribute;
|
||||
+ list_of_mods[1] = NULL;
|
||||
+ mod_pb = slapi_pblock_new();
|
||||
+ slapi_modify_internal_set_pb_ext(mod_pb, sdn, list_of_mods, NULL, NULL, pw_get_componentID(), 0);
|
||||
+ slapi_modify_internal_pb(mod_pb);
|
||||
+ slapi_pblock_destroy(mod_pb);
|
||||
+
|
||||
return res;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1379
0052-Issue-7243-UI-add-support-for-hot-certificates.patch
Normal file
1379
0052-Issue-7243-UI-add-support-for-hot-certificates.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@
|
||||
From b216b86c5607dc0421eb609c46f3004844fb37c0 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Tue, 17 Feb 2026 17:40:44 +0530
|
||||
Subject: [PATCH] Issue 6758 - Fix Enable Replication dropdown not opening
|
||||
(#7262)
|
||||
|
||||
Description: Removed hardcoded isOpen={false} and empty onToggle handler that
|
||||
prevented dropdown from opening. Let component manage its own state.
|
||||
|
||||
Relates: #6758
|
||||
|
||||
Reviewed by: @vashirov
|
||||
---
|
||||
src/cockpit/389-console/src/lib/replication/replModals.jsx | 2 --
|
||||
1 file changed, 2 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx
|
||||
index ba4859617..83a8b75a5 100644
|
||||
--- a/src/cockpit/389-console/src/lib/replication/replModals.jsx
|
||||
+++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx
|
||||
@@ -1757,8 +1757,6 @@ export class EnableReplModal extends React.Component {
|
||||
handleChange(syntheticEvent);
|
||||
}}
|
||||
options={[_("Supplier"), _("Hub"), _("Consumer")]}
|
||||
- isOpen={false}
|
||||
- onToggle={() => {}}
|
||||
placeholder={_("Select role...")}
|
||||
ariaLabel="Replication role selection"
|
||||
isMulti={false}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
538
0054-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
Normal file
538
0054-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
Normal file
@ -0,0 +1,538 @@
|
||||
From 6ce19a9a3e36213a5604144aa5eb3cba666e5ed4 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Feb 2026 09:26:57 +0100
|
||||
Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
|
||||
parentid (#7264)
|
||||
|
||||
Description:
|
||||
integerOrderingMatch was introduced as a requirement for parentid and
|
||||
ancestorid indexes for performance reasons. But after #7096 the order
|
||||
for parentid doesn't make a lot of difference.
|
||||
|
||||
Fix Description:
|
||||
* Remove integerOrderingMatch requirement for parentid.
|
||||
* Read only first 100 keys from dbscan in index ordering check
|
||||
* Do not run dsctl index-check during RPM upgrade
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/pull/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 83 ++++----------
|
||||
ldap/servers/slapd/upgrade.c | 105 ------------------
|
||||
rpm/389-ds-base.spec.in | 3 -
|
||||
src/lib389/lib389/backend.py | 5 +-
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++---------
|
||||
5 files changed, 73 insertions(+), 222 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index dd42cd197..8dc82c779 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -179,7 +179,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
|
||||
def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
|
||||
+ """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch.
|
||||
+ Both lexicographic and integer orderings are valid for parentid.
|
||||
|
||||
:id: 7ffa71db-8995-430a-bed8-59bce944221c
|
||||
:setup: Standalone instance
|
||||
@@ -189,19 +190,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
3. Use healthcheck without --json option
|
||||
4. Use healthcheck with --json option
|
||||
5. Re-add the matching rule
|
||||
- 6. Use healthcheck without --json option
|
||||
- 7. Use healthcheck with --json option
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. healthcheck reports DSBLE0007 code and related details
|
||||
- 4. healthcheck reports DSBLE0007 code and related details
|
||||
+ 3. healthcheck reports no issues found
|
||||
+ 4. healthcheck reports no issues found
|
||||
5. Success
|
||||
- 6. healthcheck reports no issues found
|
||||
- 7. healthcheck reports no issues found
|
||||
"""
|
||||
|
||||
- RET_CODE = "DSBLE0007"
|
||||
PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
|
||||
standalone = topology_st.standalone
|
||||
@@ -210,17 +206,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
log.info("Re-add the integerOrderingMatch matching rule")
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
standalone.restart()
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
-
|
||||
|
||||
def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
|
||||
@@ -910,7 +903,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
|
||||
|
||||
|
||||
def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
- """Check if dsctl index-check --fix adds missing integerOrderingMatch
|
||||
+ """Check that removing integerOrderingMatch from parentid config is not
|
||||
+ flagged as an issue when disk ordering cannot be determined.
|
||||
+ Both lexicographic and integer orderings are valid for parentid.
|
||||
|
||||
:id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
|
||||
:setup: Standalone instance
|
||||
@@ -918,18 +913,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
3. Remove integerOrderingMatch from parentid index using DSEldif
|
||||
- 4. Run dsctl index-check (should detect issue)
|
||||
- 5. Run dsctl index-check --fix
|
||||
- 6. Verify integerOrderingMatch was added back
|
||||
- 7. Start the server
|
||||
+ 4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown)
|
||||
+ 5. Start the server
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
3. Success
|
||||
- 4. index-check returns False and detects missing matching rule
|
||||
- 5. index-check returns True after fix
|
||||
- 6. integerOrderingMatch is present
|
||||
- 7. Success
|
||||
+ 4. index-check returns True (no issues, disk ordering unknown)
|
||||
+ 5. Success
|
||||
"""
|
||||
from lib389.cli_ctl.dbtasks import dbtasks_index_check
|
||||
from lib389.dseldif import DSEldif
|
||||
@@ -963,34 +954,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
f"integerOrderingMatch should be removed, but found: {mr}"
|
||||
log.info("integerOrderingMatch removed from parentid index")
|
||||
|
||||
- log.info("Run index-check without --fix (should detect issue)")
|
||||
+ log.info("Run index-check (should NOT detect issue - disk ordering unknown)")
|
||||
args = FakeArgs()
|
||||
args.backend = "userRoot"
|
||||
args.fix = False
|
||||
|
||||
result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
|
||||
- assert result is False, "index-check should detect missing matching rule"
|
||||
- assert topology_st.logcap.contains("missing integerOrderingMatch")
|
||||
+ assert result is True, \
|
||||
+ "index-check should not flag missing integerOrderingMatch when disk ordering is unknown"
|
||||
+ assert topology_st.logcap.contains("could not determine disk ordering")
|
||||
topology_st.logcap.flush()
|
||||
|
||||
- log.info("Run index-check with --fix")
|
||||
- args.fix = True
|
||||
- result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
|
||||
- assert result is True, "index-check --fix should succeed"
|
||||
- assert topology_st.logcap.contains("integerOrderingMatch")
|
||||
- topology_st.logcap.flush()
|
||||
-
|
||||
- log.info("Verify integerOrderingMatch was added back")
|
||||
- dse_ldif = DSEldif(standalone) # Reload to get fresh data
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- assert matching_rules is not None, "nsMatchingRule should be present"
|
||||
- found_int_order = False
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- found_int_order = True
|
||||
- break
|
||||
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
|
||||
- log.info("integerOrderingMatch successfully added back")
|
||||
+ log.info("Restore integerOrderingMatch and start the server")
|
||||
+ dse_ldif = DSEldif(standalone)
|
||||
+ dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
log.info("Start the server")
|
||||
standalone.start()
|
||||
@@ -1080,7 +1057,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
:steps:
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
- 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
|
||||
+ 3. Add multiple issues: scanlimit and ancestorid config
|
||||
4. Run dsctl index-check (should detect all issues)
|
||||
5. Run dsctl index-check --fix
|
||||
6. Verify all issues were fixed
|
||||
@@ -1122,14 +1099,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
]
|
||||
dse_ldif.add_entry(ancestorid_entry)
|
||||
|
||||
- log.info("Add issue 3: Remove integerOrderingMatch from parentid")
|
||||
- dse_ldif = DSEldif(standalone) # Reload
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- if matching_rules:
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
|
||||
-
|
||||
log.info("Run index-check without --fix (should detect all issues)")
|
||||
args = FakeArgs()
|
||||
args.backend = "userRoot"
|
||||
@@ -1160,16 +1129,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
|
||||
assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
|
||||
|
||||
- # Check matching rule added back
|
||||
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
|
||||
- found_int_order = False
|
||||
- if matching_rules:
|
||||
- for mr in matching_rules:
|
||||
- if "integerorderingmatch" in mr.lower():
|
||||
- found_int_order = True
|
||||
- break
|
||||
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
|
||||
-
|
||||
log.info("All issues verified as fixed")
|
||||
|
||||
log.info("Run index-check again to confirm all clear")
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 6b1b012da..9557e9066 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -551,107 +551,6 @@ upgrade_remove_ancestorid_index_config(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
- * matching rule.
|
||||
- *
|
||||
- * This function logs a warning if we detect this condition, advising
|
||||
- * the administrator to reindex the affected attributes.
|
||||
- */
|
||||
-static upgrade_status
|
||||
-upgrade_check_id_index_matching_rule(void)
|
||||
-{
|
||||
- struct slapi_pblock *pb = slapi_pblock_new();
|
||||
- Slapi_Entry **backends = NULL;
|
||||
- const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
- const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
- const char *attrs_to_check[] = {"parentid", NULL};
|
||||
- upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
-
|
||||
- /* Search for all backend instances */
|
||||
- slapi_search_internal_set_pb(
|
||||
- pb, be_base_dn,
|
||||
- LDAP_SCOPE_ONELEVEL,
|
||||
- be_filter, NULL, 0, NULL, NULL,
|
||||
- plugin_get_default_component_id(), 0);
|
||||
- slapi_search_internal_pb(pb);
|
||||
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
-
|
||||
- if (backends) {
|
||||
- for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
- const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
|
||||
- const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
- if (!be_dn || !be_name) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- /* Check each attribute that should have integerOrderingMatch */
|
||||
- for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
- const char *attr_name = attrs_to_check[attr_idx];
|
||||
- struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
- Slapi_Entry **idx_entries = NULL;
|
||||
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
|
||||
- attr_name, be_dn);
|
||||
- char *idx_filter = "(objectclass=nsIndex)";
|
||||
- PRBool has_matching_rule = PR_FALSE;
|
||||
-
|
||||
- if (!idx_dn) {
|
||||
- slapi_pblock_destroy(idx_pb);
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- slapi_search_internal_set_pb(
|
||||
- idx_pb, idx_dn,
|
||||
- LDAP_SCOPE_BASE,
|
||||
- idx_filter, NULL, 0, NULL, NULL,
|
||||
- plugin_get_default_component_id(), 0);
|
||||
- slapi_search_internal_pb(idx_pb);
|
||||
- slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
-
|
||||
- if (idx_entries && idx_entries[0]) {
|
||||
- /* Index exists, check if it has integerOrderingMatch */
|
||||
- Slapi_Attr *mr_attr = NULL;
|
||||
- if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
- Slapi_Value *sval = NULL;
|
||||
- int idx;
|
||||
- for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
- idx != -1;
|
||||
- idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
- const struct berval *bval = slapi_value_get_berval(sval);
|
||||
- if (bval && bval->bv_val &&
|
||||
- strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
- has_matching_rule = PR_TRUE;
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if (!has_matching_rule) {
|
||||
- /* Index exists but doesn't have integerOrderingMatch, log a warning */
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
|
||||
- "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
|
||||
- "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
|
||||
- "To fix this, add the matching rule and reindex: "
|
||||
- "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
|
||||
- "dsconf <instance> backend index reindex --attr %s %s. "
|
||||
- "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
|
||||
- "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
|
||||
- attr_name, be_name, attr_name, be_name, attr_name, be_name);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- slapi_ch_free_string(&idx_dn);
|
||||
- slapi_free_search_results_internal(idx_pb);
|
||||
- slapi_pblock_destroy(idx_pb);
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- slapi_free_search_results_internal(pb);
|
||||
- slapi_pblock_destroy(pb);
|
||||
-
|
||||
- return uresult;
|
||||
-}
|
||||
|
||||
/*
|
||||
* Upgrade the base config of the PAM PTA plugin.
|
||||
@@ -879,10 +778,6 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
- if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
- return UPGRADE_FAILURE;
|
||||
- }
|
||||
-
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index 0e0e28285..370e3abd4 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -650,9 +650,6 @@ for dir in "$instbase"/slapd-* ; do
|
||||
else
|
||||
echo "instance $inst is not running" >> "$output" 2>&1 || :
|
||||
fi
|
||||
- # Run index-check on all instances (running or not)
|
||||
- # This fixes index ordering mismatches from older versions
|
||||
- dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
|
||||
ninst=$((ninst + 1))
|
||||
done
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index f3dbe7c92..6c8cbc018 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -647,9 +647,10 @@ class Backend(DSLdapObject):
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
# Note: entryrdn and ancestorid are internal system indexes that are not
|
||||
# exposed in cn=config - they are managed internally by the server.
|
||||
- # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
|
||||
+ # parentid works correctly with both lexicographic and integer ordering,
|
||||
+ # so integerOrderingMatch is not required.
|
||||
expected_system_indexes = {
|
||||
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
|
||||
+ 'parentid': {'types': ['eq'], 'matching_rule': None},
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
index cd96cdaf7..b02de203f 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
@@ -10,6 +10,7 @@
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
+import signal
|
||||
import subprocess
|
||||
from enum import Enum
|
||||
from lib389._constants import TaskWarning
|
||||
@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
|
||||
if not index_file:
|
||||
return IndexOrdering.UNKNOWN
|
||||
|
||||
+ # Only read the first 100 lines from dbscan to avoid scanning the
|
||||
+ # entire index (which can take hours on large databases).
|
||||
try:
|
||||
- result = subprocess.run(
|
||||
+ proc = subprocess.Popen(
|
||||
[dbscan_path, "-f", index_file],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
- timeout=60,
|
||||
)
|
||||
|
||||
- if result.returncode != 0:
|
||||
- log.warning(" dbscan returned non-zero exit code for %s", index_file)
|
||||
- return IndexOrdering.UNKNOWN
|
||||
-
|
||||
- # Parse keys from dbscan output
|
||||
keys = []
|
||||
- for line in result.stdout.split("\n"):
|
||||
+ line_count = 0
|
||||
+ assert proc.stdout is not None
|
||||
+ for line in proc.stdout:
|
||||
+ line_count += 1
|
||||
+ if line_count > 100:
|
||||
+ break
|
||||
line = line.strip()
|
||||
if line.startswith("="):
|
||||
match = re.match(r"^=(\d+)", line)
|
||||
if match:
|
||||
keys.append(int(match.group(1)))
|
||||
|
||||
+ proc.terminate()
|
||||
+ try:
|
||||
+ proc.wait(timeout=5)
|
||||
+ except subprocess.TimeoutExpired:
|
||||
+ proc.kill()
|
||||
+ proc.wait()
|
||||
+
|
||||
+ if proc.returncode not in (0, -signal.SIGTERM):
|
||||
+ log.warning(" dbscan returned non-zero exit code for %s", index_file)
|
||||
+ return IndexOrdering.UNKNOWN
|
||||
+
|
||||
if len(keys) < 2:
|
||||
return IndexOrdering.UNKNOWN
|
||||
|
||||
# Check if keys are in integer order by looking for decreasing numeric values
|
||||
# (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
|
||||
prev_id = keys[0]
|
||||
- for i in range(1, min(len(keys), 100)):
|
||||
- current_id = keys[i]
|
||||
+ for current_id in keys[1:]:
|
||||
if prev_id > current_id:
|
||||
return IndexOrdering.LEXICOGRAPHIC
|
||||
prev_id = current_id
|
||||
|
||||
return IndexOrdering.INTEGER
|
||||
|
||||
- except subprocess.TimeoutExpired:
|
||||
- log.warning(" dbscan timed out for %s", index_file)
|
||||
- return IndexOrdering.UNKNOWN
|
||||
except OSError as e:
|
||||
log.warning(" Error running dbscan: %s", e)
|
||||
return IndexOrdering.UNKNOWN
|
||||
@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
# Track all issues found
|
||||
all_ok = True
|
||||
- mismatches = [] # (backend, index_name) tuples needing reindex
|
||||
- missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch
|
||||
+ config_fixes = [] # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr"
|
||||
scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit
|
||||
ancestorid_configs_to_remove = [] # backend names with ancestorid config entries
|
||||
remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes
|
||||
@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
if disk_ordering == IndexOrdering.UNKNOWN:
|
||||
log.info(" %s - could not determine disk ordering, skipping", index_name)
|
||||
- # For parentid, still check if matching rule is missing
|
||||
- if index_name == "parentid":
|
||||
- config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
|
||||
- if not config_has_int_order:
|
||||
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
|
||||
- missing_matching_rules.append((backend, index_name))
|
||||
- all_ok = False
|
||||
continue
|
||||
|
||||
config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
|
||||
@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.info(" %s - config: %s, disk: %s",
|
||||
index_name, config_desc, disk_ordering.value)
|
||||
|
||||
- # For parentid, the desired state is always integer ordering
|
||||
+ # Both orderings are valid for parentid, but config must match disk.
|
||||
if index_name == "parentid":
|
||||
- if not config_has_int_order:
|
||||
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
|
||||
- if (backend, index_name) not in missing_matching_rules:
|
||||
- missing_matching_rules.append((backend, index_name))
|
||||
+ if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC:
|
||||
+ log.warning(" %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name)
|
||||
+ config_fixes.append((backend, index_name, "remove_mr"))
|
||||
all_ok = False
|
||||
-
|
||||
- if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
|
||||
- log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name)
|
||||
- if (backend, index_name) not in mismatches:
|
||||
- mismatches.append((backend, index_name))
|
||||
+ elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER:
|
||||
+ log.warning(" %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name)
|
||||
+ config_fixes.append((backend, index_name, "add_mr"))
|
||||
all_ok = False
|
||||
|
||||
# Handle issues
|
||||
@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e)
|
||||
return False
|
||||
|
||||
- # Add missing matching rules to dse.ldif
|
||||
- for backend, index_name in missing_matching_rules:
|
||||
+ # Fix config-vs-disk ordering mismatches by adjusting config to match disk
|
||||
+ for backend, index_name, action in config_fixes:
|
||||
index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
|
||||
index_name, backend
|
||||
)
|
||||
- log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
|
||||
- try:
|
||||
- dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
- log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
|
||||
- except Exception as e:
|
||||
- log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
|
||||
- return False
|
||||
-
|
||||
- # Reindex indexes with disk ordering issues
|
||||
- for backend, index_name in mismatches:
|
||||
- log.info(" Reindexing %s in backend %s...", index_name, backend)
|
||||
- if not inst.db2index(bename=backend, attrs=[index_name]):
|
||||
- log.error(" Failed to reindex %s", index_name)
|
||||
- return False
|
||||
- log.info(" Reindex of %s completed successfully", index_name)
|
||||
+ if action == "add_mr":
|
||||
+ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
|
||||
+ try:
|
||||
+ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
|
||||
+ except Exception as e:
|
||||
+ log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
|
||||
+ return False
|
||||
+ elif action == "remove_mr":
|
||||
+ log.info(" Removing integerOrderingMatch from %s in backend %s...", index_name, backend)
|
||||
+ try:
|
||||
+ dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch")
|
||||
+ log.info(" Removed integerOrderingMatch from %s", index_name)
|
||||
+ except Exception as e:
|
||||
+ log.error(" Failed to remove integerOrderingMatch from %s: %s", index_name, e)
|
||||
+ return False
|
||||
|
||||
log.info("All issues fixed")
|
||||
return True
|
||||
@@ -563,5 +562,5 @@ def create_parser(subcommands):
|
||||
index_check_parser.add_argument('backend', nargs='?', default=None,
|
||||
help="Backend to check. If not specified, all backends are checked.")
|
||||
index_check_parser.add_argument('--fix', action='store_true', default=False,
|
||||
- help="Fix mismatches by reindexing affected indexes")
|
||||
+ help="Fix mismatches by adjusting config to match on-disk data")
|
||||
index_check_parser.set_defaults(func=dbtasks_index_check)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
227
0055-Issue-7236-Fix-GSSAPI-tests-7237.patch
Normal file
227
0055-Issue-7236-Fix-GSSAPI-tests-7237.patch
Normal file
@ -0,0 +1,227 @@
|
||||
From 26feecae026581e39a43f001faff59e81a92c03d Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <mirielka@users.noreply.github.com>
|
||||
Date: Wed, 18 Feb 2026 14:33:49 +0100
|
||||
Subject: [PATCH] Issue 7236 - Fix GSSAPI tests (#7237)
|
||||
|
||||
* Issue 7236 - Fix GSSAPI tests
|
||||
|
||||
Description:
|
||||
Fix for failing GSSAPI tests
|
||||
Add GSSAPI_ACK variable to pytest workflow for proper execution in
|
||||
Github CI
|
||||
|
||||
Relates: #7236
|
||||
Author: Lenka Doudova
|
||||
Reviewer: Barbora Simonova, Viktor Ashirov
|
||||
---
|
||||
.github/workflows/lmdbpytest.yml | 2 +-
|
||||
.github/workflows/pytest.yml | 2 +-
|
||||
.../tests/suites/gssapi/simple_gssapi_test.py | 2 +
|
||||
.../suites/gssapi_repl/gssapi_repl_test.py | 43 +++++---------
|
||||
src/lib389/lib389/topologies.py | 57 +++++++++++++++++++
|
||||
5 files changed, 76 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/.github/workflows/lmdbpytest.yml b/.github/workflows/lmdbpytest.yml
|
||||
index 2d0a122bf..376090bf6 100644
|
||||
--- a/.github/workflows/lmdbpytest.yml
|
||||
+++ b/.github/workflows/lmdbpytest.yml
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
sudo docker exec $CID sh -c "systemctl enable --now cockpit.socket"
|
||||
sudo docker exec $CID sh -c "mkdir -p /workspace/assets/cores && chmod 777 /workspace{,/assets{,/cores}}"
|
||||
sudo docker exec $CID sh -c "echo '/workspace/assets/cores/core.%e.%P' > /proc/sys/kernel/core_pattern"
|
||||
- sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=mdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
|
||||
+ sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=mdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" -e GSSAPI_ACK=1 $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
|
||||
|
||||
- name: Make the results file readable by all
|
||||
if: always()
|
||||
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
|
||||
index 8a543be85..a51553656 100644
|
||||
--- a/.github/workflows/pytest.yml
|
||||
+++ b/.github/workflows/pytest.yml
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
echo "Tests skipped because read-only Berkeley Database is installed." > pytest.html
|
||||
echo "<?xml version="1.0" encoding="utf-8"?>'Tests skipped because read-only Berkeley Database is installed.'" > pytest.xml
|
||||
else
|
||||
- sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=bdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
|
||||
+ sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=bdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" -e GSSAPI_ACK=1 $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
|
||||
fi
|
||||
|
||||
- name: Make the results file readable by all
|
||||
diff --git a/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
|
||||
index be6f68a9a..e48de3491 100644
|
||||
--- a/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
|
||||
+++ b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
|
||||
@@ -34,6 +34,8 @@ def testuser(topology_st_gssapi):
|
||||
})
|
||||
# Give them a krb princ
|
||||
user.create_keytab()
|
||||
+ # Make krb5 config readable by everyone for the tests to work
|
||||
+ os.chmod(user._instance.realm.krb5confrealm, 0o644)
|
||||
return user
|
||||
|
||||
@gssapi_ack
|
||||
diff --git a/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py b/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
|
||||
index 402684aab..fa7fc9c24 100644
|
||||
--- a/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
|
||||
+++ b/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
|
||||
@@ -10,7 +10,7 @@ import pytest
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
from lib389.agreement import *
|
||||
-from lib389.topologies import topology_m2
|
||||
+from lib389.topologies import topology_m2_gssapi, gssapi_ack
|
||||
|
||||
pytestmark = pytest.mark.tier2
|
||||
|
||||
@@ -69,25 +69,8 @@ def _allow_machine_account(inst, name):
|
||||
(ldap.MOD_REPLACE, 'nsDS5ReplicaBindDN', f"uid={name},ou=Machines,{DEFAULT_SUFFIX}".encode('utf-8'))
|
||||
])
|
||||
|
||||
-def _verify_etc_hosts():
|
||||
- #Check if /etc/hosts is compatible with the test
|
||||
- NEEDED_HOSTS = ( ('ldapkdc.example.com', '127.0.0.1'),
|
||||
- ('ldapkdc1.example.com', '127.0.1.1'),
|
||||
- ('ldapkdc2.example.com', '127.0.2.1'))
|
||||
- found_hosts = {}
|
||||
- with open('/etc/hosts','r') as f:
|
||||
- for l in f:
|
||||
- s = l.split()
|
||||
- if len(s) < 2:
|
||||
- continue
|
||||
- for nh in NEEDED_HOSTS:
|
||||
- if (s[0] == nh[1] and s[1] == nh[0]):
|
||||
- found_hosts[s[1]] = True
|
||||
- return len(found_hosts) == len(NEEDED_HOSTS)
|
||||
-
|
||||
-@pytest.mark.skipif(not _verify_etc_hosts(), reason="/etc/hosts does not contains the needed hosts.")
|
||||
-@pytest.mark.skipif(True, reason="Test disabled because it requires specific kerberos requirement (server principal, keytab, etc ...")
|
||||
-def test_gssapi_repl(topology_m2):
|
||||
+@gssapi_ack
|
||||
+def test_gssapi_repl(topology_m2_gssapi):
|
||||
"""Test gssapi authenticated replication agreement of two suppliers using KDC
|
||||
|
||||
:id: 552850aa-afc3-473e-9c39-aae802b46f11
|
||||
@@ -112,8 +95,8 @@ def test_gssapi_repl(topology_m2):
|
||||
6. Test User should be created on M1 and M2 both
|
||||
7. Test User should be created on M1 and M2 both
|
||||
"""
|
||||
- supplier1 = topology_m2.ms["supplier1"]
|
||||
- supplier2 = topology_m2.ms["supplier2"]
|
||||
+ supplier1 = topology_m2_gssapi.ms["supplier1"]
|
||||
+ supplier2 = topology_m2_gssapi.ms["supplier2"]
|
||||
|
||||
# Create the locations on each supplier for the other to bind to.
|
||||
_create_machine_ou(supplier1)
|
||||
@@ -134,10 +117,9 @@ def test_gssapi_repl(topology_m2):
|
||||
# Creating agreement from supplier 1 to supplier 2
|
||||
|
||||
# Set the replica bind method to sasl gssapi
|
||||
- properties = {RA_NAME: r'meTo_$host:$port',
|
||||
+ properties = {RA_NAME: 'meTo_' + supplier2.host + ':' + str(supplier2.port),
|
||||
RA_METHOD: 'SASL/GSSAPI',
|
||||
RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
|
||||
- supplier1.agreement.delete(suffix=SUFFIX, consumer_host=supplier2.host, consumer_port=supplier2.port)
|
||||
m1_m2_agmt = supplier1.agreement.create(suffix=SUFFIX, host=supplier2.host, port=supplier2.port, properties=properties)
|
||||
if not m1_m2_agmt:
|
||||
log.fatal("Fail to create a supplier -> supplier replica agreement")
|
||||
@@ -147,10 +129,9 @@ def test_gssapi_repl(topology_m2):
|
||||
# Creating agreement from supplier 2 to supplier 1
|
||||
|
||||
# Set the replica bind method to sasl gssapi
|
||||
- properties = {RA_NAME: r'meTo_$host:$port',
|
||||
+ properties = {RA_NAME: 'meTo_' + supplier1.host + ':' + str(supplier1.port),
|
||||
RA_METHOD: 'SASL/GSSAPI',
|
||||
RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
|
||||
- supplier2.agreement.delete(suffix=SUFFIX, consumer_host=supplier1.host, consumer_port=supplier1.port)
|
||||
m2_m1_agmt = supplier2.agreement.create(suffix=SUFFIX, host=supplier1.host, port=supplier1.port, properties=properties)
|
||||
if not m2_m1_agmt:
|
||||
log.fatal("Fail to create a supplier -> supplier replica agreement")
|
||||
@@ -169,9 +150,15 @@ def test_gssapi_repl(topology_m2):
|
||||
|
||||
# Check replication is working...
|
||||
if supplier1.testReplication(DEFAULT_SUFFIX, supplier2):
|
||||
- log.info('Replication is working.')
|
||||
+ log.info('Replication is working: supplier1 -> supplier2')
|
||||
else:
|
||||
- log.fatal('Replication is not working.')
|
||||
+ log.fatal('Replication is not working: supplier1 -> supplier2')
|
||||
+ assert False
|
||||
+
|
||||
+ if supplier2.testReplication(DEFAULT_SUFFIX, supplier1):
|
||||
+ log.info('Replication is working: supplier2 -> supplier1')
|
||||
+ else:
|
||||
+ log.fatal('Replication is not working: supplier2 -> supplier1')
|
||||
assert False
|
||||
|
||||
# Add a user to supplier 1
|
||||
diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py
|
||||
index 33341f669..84e620cb3 100644
|
||||
--- a/src/lib389/lib389/topologies.py
|
||||
+++ b/src/lib389/lib389/topologies.py
|
||||
@@ -499,6 +499,63 @@ def topology_m2(request):
|
||||
topology.logcap = LogCapture()
|
||||
return topology
|
||||
|
||||
+@pytest.fixture(scope="module")
|
||||
+def topology_m2_gssapi(request):
|
||||
+ """Create Replication Deployment with two suppliers with GSSAPI enabled.
|
||||
+
|
||||
+ Similar to topology_st_gssapi but for two suppliers. Configures Kerberos
|
||||
+ realm, principals and keytabs for ldap/ldapkdc1.<domain> and ldap/ldapkdc2.<domain>,
|
||||
+ SASL mappings, and disables SSL port on both instances so GSSAPI can be used.
|
||||
+ """
|
||||
+ hostname = socket.gethostname().split('.', 1)
|
||||
+ assert len(hostname) == 2
|
||||
+ domain = hostname[1]
|
||||
+ REALM = domain.upper()
|
||||
+ host_supplier_1 = 'ldapkdc1.' + domain
|
||||
+ host_supplier_2 = 'ldapkdc2.' + domain
|
||||
+
|
||||
+ topology = create_topology({ReplicaRole.SUPPLIER: 2}, request=request,
|
||||
+ cleanup_cb=lambda x: krb.destroy_realm())
|
||||
+
|
||||
+ supplier1 = topology.ms["supplier1"]
|
||||
+ supplier2 = topology.ms["supplier2"]
|
||||
+ supplier1.host = host_supplier_1
|
||||
+ supplier2.host = host_supplier_2
|
||||
+
|
||||
+ krb = MitKrb5(realm=REALM, debug=DEBUGGING)
|
||||
+ if krb.check_realm():
|
||||
+ krb.destroy_realm()
|
||||
+ krb.create_realm()
|
||||
+
|
||||
+ krb.create_principal(principal=f'ldap/{host_supplier_1}')
|
||||
+ krb.create_principal(principal=f'ldap/{host_supplier_2}')
|
||||
+ krb.create_keytab(principal=f'ldap/{host_supplier_1}', keytab='/etc/krb5.keytab')
|
||||
+ krb.create_keytab(principal=f'ldap/{host_supplier_2}', keytab='/etc/krb5.keytab')
|
||||
+
|
||||
+ os.chown('/etc/krb5.keytab', supplier1.get_user_uid(), supplier1.get_group_gid())
|
||||
+
|
||||
+ for inst, host in [(supplier1, host_supplier_1), (supplier2, host_supplier_2)]:
|
||||
+ saslmappings = SaslMappings(inst)
|
||||
+ for m in saslmappings.list():
|
||||
+ m.delete()
|
||||
+ saslmappings.create(properties={
|
||||
+ 'cn': 'suffix map',
|
||||
+ 'nsSaslMapRegexString': '\\(.*\\)',
|
||||
+ 'nsSaslMapBaseDNTemplate': inst.creation_suffix,
|
||||
+ 'nsSaslMapFilterTemplate': '(uid=\\1)'
|
||||
+ })
|
||||
+ inst.realm = krb
|
||||
+ inst.config.set('nsslapd-localhost', host)
|
||||
+ inst.sslport = None
|
||||
+
|
||||
+ supplier1.restart()
|
||||
+ supplier2.restart()
|
||||
+ supplier1.clearTmpDir(__file__)
|
||||
+ supplier2.clearTmpDir(__file__)
|
||||
+
|
||||
+ topology.logcap = LogCapture()
|
||||
+ return topology
|
||||
+
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def topology_m3(request):
|
||||
--
|
||||
2.52.0
|
||||
|
||||
260
0056-Issue-6753-Port-ticket-49039-test.patch
Normal file
260
0056-Issue-6753-Port-ticket-49039-test.patch
Normal file
@ -0,0 +1,260 @@
|
||||
From a4ae29afc6547e8231b933cfa1b95d7f7b37a25c Mon Sep 17 00:00:00 2001
|
||||
From: Lenka Doudova <lryznaro@redhat.com>
|
||||
Date: Tue, 10 Feb 2026 05:45:32 +0100
|
||||
Subject: [PATCH] Issue 6753 - Port ticket 49039 test
|
||||
|
||||
Description:
|
||||
Port ticket 49039 test into
|
||||
dirsrvtests/tests/suites/password/pwp_test.py
|
||||
|
||||
Relates: #6753
|
||||
Author: Lenka Doudova
|
||||
Assisted by: Cursor
|
||||
Reviewer: Barbora Simonova, Viktor Ashirov
|
||||
---
|
||||
dirsrvtests/tests/suites/password/pwp_test.py | 83 +++++++++++-
|
||||
dirsrvtests/tests/tickets/ticket49039_test.py | 127 ------------------
|
||||
2 files changed, 82 insertions(+), 128 deletions(-)
|
||||
delete mode 100644 dirsrvtests/tests/tickets/ticket49039_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_test.py b/dirsrvtests/tests/suites/password/pwp_test.py
|
||||
index 663d9bea9..6dae08cb2 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwp_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_test.py
|
||||
@@ -9,11 +9,14 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
+import subprocess
|
||||
import pytest
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
-from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
+from lib389._constants import DEFAULT_SUFFIX, PASSWORD
|
||||
from lib389.config import Config
|
||||
+from lib389.pwpolicy import PwPolicyManager
|
||||
from lib389.idm.group import Group
|
||||
from lib389.utils import ds_is_older
|
||||
import ldap
|
||||
@@ -512,6 +515,84 @@ def test_passwordlockout(topo, _fix_password):
|
||||
_change_password_with_own(topo, user.dn, 'dby3rs2', 'secreter')
|
||||
|
||||
|
||||
+def test_password_must_change_ignores_min_age(topo):
|
||||
+ """Test that passwordMinAge does not block password update when the password was reset.
|
||||
+
|
||||
+ :id: a1b2c3d4-e5f6-4903-9abc-def012345678
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Enable TLS (for ldappasswd StartTLS)
|
||||
+ 2. Set global policy via PwPolicyManager: passwordMustChange, passwordExp,
|
||||
+ passwordMaxAge, passwordMinAge (high), passwordChange
|
||||
+ 3. Bind as Directory Manager
|
||||
+ 4. Create user
|
||||
+ 5. Reset user password as Directory Manager
|
||||
+ 6. User binds and changes own password (must succeed; min age must not block)
|
||||
+ 7. Rebind as Directory Manager, reset user password again
|
||||
+ 8. Run ldappasswd as user (StartTLS) to change password to password2
|
||||
+ 9. Bind as user with password2 to verify
|
||||
+ 10. Cleanup: delete user
|
||||
+ :expectedresults:
|
||||
+ 1. TLS enabled
|
||||
+ 2. Policy set successfully
|
||||
+ 3. Bind succeeds
|
||||
+ 4. User created
|
||||
+ 5. Reset succeeds
|
||||
+ 6. User password change succeeds (min age does not block after reset)
|
||||
+ 7. Reset succeeds
|
||||
+ 8. ldappasswd succeeds
|
||||
+ 9. Bind succeeds
|
||||
+ 10. User deleted
|
||||
+ """
|
||||
+
|
||||
+ topo.standalone.enable_tls()
|
||||
+
|
||||
+ policy = PwPolicyManager(topo.standalone)
|
||||
+ policy.set_global_policy(properties={'nsslapd-pwpolicy-local': 'on',
|
||||
+ 'passwordMustChange': 'on',
|
||||
+ 'passwordExp': 'on',
|
||||
+ 'passwordMaxAge': '86400000',
|
||||
+ 'passwordMinAge': '8640000',
|
||||
+ 'passwordChange': 'on'})
|
||||
+ dm = DirectoryManager(topo.standalone)
|
||||
+ dm.bind()
|
||||
+
|
||||
+ user = _create_user(topo, 'user', 'Test User', '1002', PASSWORD)
|
||||
+ try:
|
||||
+ # Reset password as Directory Manager
|
||||
+ user.replace('userpassword', PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ # Reset password as user (must succeed; min age must not block after reset)
|
||||
+ user.rebind(PASSWORD)
|
||||
+ user.replace('userpassword', PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ # Reset again as Directory Manager
|
||||
+ dm.rebind(PASSWORD)
|
||||
+ user.replace('userpassword', PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ # Change password through ldappasswd as user to ensure functionality
|
||||
+ env = os.environ.copy()
|
||||
+ env['LDAPTLS_CACERTDIR'] = topo.standalone.get_cert_dir()
|
||||
+ cmd = [
|
||||
+ 'ldappasswd',
|
||||
+ '-ZZ','-H', f"ldap://{topo.standalone.host}:{topo.standalone.port}",
|
||||
+ '-D', user.dn, '-w', PASSWORD,
|
||||
+ '-a', PASSWORD, '-s', 'password2',
|
||||
+ user.dn,
|
||||
+ ]
|
||||
+ result = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
||||
+ assert result.returncode == 0, f'ldappasswd failed: {result.stderr}'
|
||||
+
|
||||
+ # Bind as user with new password
|
||||
+ user.bind('password2')
|
||||
+ finally:
|
||||
+ dm.rebind(PASSWORD)
|
||||
+ user.delete()
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/dirsrvtests/tests/tickets/ticket49039_test.py b/dirsrvtests/tests/tickets/ticket49039_test.py
|
||||
deleted file mode 100644
|
||||
index 0313f69a3..000000000
|
||||
--- a/dirsrvtests/tests/tickets/ticket49039_test.py
|
||||
+++ /dev/null
|
||||
@@ -1,127 +0,0 @@
|
||||
-# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2022 Red Hat, Inc.
|
||||
-# All rights reserved.
|
||||
-#
|
||||
-# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
-# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
-import time
|
||||
-import ldap
|
||||
-import logging
|
||||
-import pytest
|
||||
-import os
|
||||
-from lib389 import Entry
|
||||
-from lib389._constants import *
|
||||
-from lib389.properties import *
|
||||
-from lib389.tasks import *
|
||||
-from lib389.utils import *
|
||||
-from lib389.topologies import topology_st as topo
|
||||
-from lib389.pwpolicy import PwPolicyManager
|
||||
-
|
||||
-
|
||||
-pytestmark = pytest.mark.tier2
|
||||
-
|
||||
-DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
-if DEBUGGING:
|
||||
- logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
-else:
|
||||
- logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
-log = logging.getLogger(__name__)
|
||||
-
|
||||
-USER_DN = 'uid=user,dc=example,dc=com'
|
||||
-
|
||||
-
|
||||
-def test_ticket49039(topo):
|
||||
- """Test "password must change" verses "password min age". Min age should not
|
||||
- block password update if the password was reset.
|
||||
- """
|
||||
-
|
||||
- # Setup SSL (for ldappasswd test)
|
||||
- topo.standalone.enable_tls()
|
||||
-
|
||||
- # Configure password policy
|
||||
- try:
|
||||
- policy = PwPolicyManager(topo.standalone)
|
||||
- policy.set_global_policy(properties={'nsslapd-pwpolicy-local': 'on',
|
||||
- 'passwordMustChange': 'on',
|
||||
- 'passwordExp': 'on',
|
||||
- 'passwordMaxAge': '86400000',
|
||||
- 'passwordMinAge': '8640000',
|
||||
- 'passwordChange': 'on'})
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to set password policy: ' + str(e))
|
||||
-
|
||||
- # Add user, bind, and set password
|
||||
- try:
|
||||
- topo.standalone.add_s(Entry((USER_DN, {
|
||||
- 'objectclass': 'top extensibleObject'.split(),
|
||||
- 'uid': 'user1',
|
||||
- 'userpassword': PASSWORD
|
||||
- })))
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to add user: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- # Reset password as RootDN
|
||||
- try:
|
||||
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- time.sleep(1)
|
||||
-
|
||||
- # Reset password as user
|
||||
- try:
|
||||
- topo.standalone.simple_bind_s(USER_DN, PASSWORD)
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- try:
|
||||
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to change password: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- ###################################
|
||||
- # Make sure ldappasswd also works
|
||||
- ###################################
|
||||
-
|
||||
- # Reset password as RootDN
|
||||
- try:
|
||||
- topo.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to bind as rootdn: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- try:
|
||||
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- time.sleep(1)
|
||||
-
|
||||
- # Run ldappasswd as the User.
|
||||
- os.environ["LDAPTLS_CACERTDIR"] = topo.standalone.get_cert_dir()
|
||||
- cmd = ('ldappasswd' + ' -h ' + topo.standalone.host + ' -Z -p 38901 -D ' + USER_DN +
|
||||
- ' -w password -a password -s password2 ' + USER_DN)
|
||||
- os.system(cmd)
|
||||
- time.sleep(1)
|
||||
-
|
||||
- try:
|
||||
- topo.standalone.simple_bind_s(USER_DN, "password2")
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
|
||||
- assert False
|
||||
-
|
||||
- log.info('Test Passed')
|
||||
-
|
||||
-
|
||||
-if __name__ == '__main__':
|
||||
- # Run isolated
|
||||
- # -s for DEBUG mode
|
||||
- CURRENT_FILE = os.path.realpath(__file__)
|
||||
- pytest.main("-s %s" % CURRENT_FILE)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -286,6 +286,57 @@ Patch: 0002-Issue-Revise-paged-result-search-locking.patch
|
||||
Patch: 0003-Issue-7108-Fix-shutdown-crash-in-entry-cache-destruc.patch
|
||||
Patch: 0004-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
|
||||
Patch: 0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
|
||||
Patch: 0006-Issue-6753-Port-ticket-548-test-7101.patch
|
||||
Patch: 0007-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch
|
||||
Patch: 0008-Issue-7169-Fix-automember_plugin-CI-test-failures-71.patch
|
||||
Patch: 0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch
|
||||
Patch: 0010-Issue-7196-DynamicCertificates-returns-empty-DER-719.patch
|
||||
Patch: 0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
|
||||
Patch: 0012-Issue-7170-Support-of-PQC-keys-7188.patch
|
||||
Patch: 0013-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch
|
||||
Patch: 0014-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch
|
||||
Patch: 0015-Issue-7014-memberOf-ignored-deferred-updates-with-LM.patch
|
||||
Patch: 0016-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch
|
||||
Patch: 0017-Issue-6947-Revise-time-skew-check-in-healthcheck-too.patch
|
||||
Patch: 0018-Issue-7201-Syscall-overhead-in-LMDB-import-writer-th.patch
|
||||
Patch: 0019-Issue-7096-2nd-During-replication-online-total-init-.patch
|
||||
Patch: 0020-Issue-7206-Should-log-whether-TLS-key-is-PQC-or-not-.patch
|
||||
Patch: 0021-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch
|
||||
Patch: 0022-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
|
||||
Patch: 0023-Issue-6753-Port-ticket-47781-test-7210.patch
|
||||
Patch: 0024-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch
|
||||
Patch: 0025-Issue-6753-Port-ticket-48896-test.patch
|
||||
Patch: 0026-Issue-6810-Fix-PAM-PTA-test-7219.patch
|
||||
Patch: 0027-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch
|
||||
Patch: 0028-Issue-6951-Dynamic-Certificate-refresh-phase-4-Updat.patch
|
||||
Patch: 0029-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch
|
||||
Patch: 0030-Issue-7178-Bundled-jemalloc-fails-to-build-with-GCC-.patch
|
||||
Patch: 0031-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch
|
||||
Patch: 0032-Issue-7223-Revert-index-scan-limits-for-system-index.patch
|
||||
Patch: 0033-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
|
||||
Patch: 0034-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
|
||||
Patch: 0035-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
|
||||
Patch: 0036-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
|
||||
Patch: 0037-Issue-7230-Regression-in-healtcheck-NssCheck-7235.patch
|
||||
Patch: 0038-Issue-3555-UI-Fix-audit-issue-with-npm-isaacs-brace-.patch
|
||||
Patch: 0039-Issue-7221-CI-tests-fix-some-flaky-tests.patch
|
||||
Patch: 0040-Issue-7233-test_produce_division_by_zero-fails-with-.patch
|
||||
Patch: 0041-Issue-7241-Drop-dateutil-7242.patch
|
||||
Patch: 0042-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch
|
||||
Patch: 0043-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch
|
||||
Patch: 0044-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch
|
||||
Patch: 0045-Issue-7252-PQC-Need-to-iterate-on-SECOidTag-instead-.patch
|
||||
Patch: 0046-Issue-6951-Dynamic-Certificas-Refresh-CI-tests-7238.patch
|
||||
Patch: 0047-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch
|
||||
Patch: 0048-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch
|
||||
Patch: 0049-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch
|
||||
Patch: 0050-Issue-3134-Fix-build-break-7260.patch
|
||||
Patch: 0051-Issue-7066-7052-allow-password-history-to-be-set-to-.patch
|
||||
Patch: 0052-Issue-7243-UI-add-support-for-hot-certificates.patch
|
||||
Patch: 0053-Issue-6758-Fix-Enable-Replication-dropdown-not-openi.patch
|
||||
Patch: 0054-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
|
||||
Patch: 0055-Issue-7236-Fix-GSSAPI-tests-7237.patch
|
||||
Patch: 0056-Issue-6753-Port-ticket-49039-test.patch
|
||||
|
||||
%description
|
||||
389 Directory Server is an LDAPv3 compliant server. The base package includes
|
||||
|
||||
Loading…
Reference in New Issue
Block a user