|
|
93ba16 |
From bf067513a88156f5b3882dd4098a61110ced4caf Mon Sep 17 00:00:00 2001
|
|
|
93ba16 |
From: tbordaz <tbordaz@redhat.com>
|
|
|
93ba16 |
Date: Fri, 2 Oct 2020 12:03:12 +0200
|
|
|
93ba16 |
Subject: [PATCH 4/5] Issue 4297- On ADD replication URP issue internal
|
|
|
93ba16 |
searches with filter containing unescaped chars (#4355)
|
|
|
93ba16 |
|
|
|
93ba16 |
Bug description:
|
|
|
93ba16 |
In MMR a consumer receiving a ADD has to do some checking based on basedn.
|
|
|
93ba16 |
It checks if the entry was a tombstone or if the conflicting parent entry was a tombstone.
|
|
|
93ba16 |
|
|
|
93ba16 |
To do this checking, URP does internal searches using basedn.
|
|
|
93ba16 |
A '*' (ASTERISK) is valid in a RDN and in a DN. But using a DN in an assertionvalue of a filter, the ASTERISK needs to be escaped else the server will interprete the filtertype to be a substring. (see
|
|
|
93ba16 |
https://tools.ietf.org/html/rfc4515#section-3)
|
|
|
93ba16 |
|
|
|
93ba16 |
The problem is that if a added entry contains an ASTERISK in the DN, it will not be escaped in internal search and trigger substring search (likely unindexed).
|
|
|
93ba16 |
|
|
|
93ba16 |
Fix description:
|
|
|
93ba16 |
escape the DN before doing internal search in URP
|
|
|
93ba16 |
|
|
|
93ba16 |
Fixes: #4297
|
|
|
93ba16 |
|
|
|
93ba16 |
Reviewed by: Mark Reynolds, William Brown, Simon Pichugi (thanks !)
|
|
|
93ba16 |
|
|
|
93ba16 |
Platforms tested: F31
|
|
|
93ba16 |
---
|
|
|
93ba16 |
.../suites/replication/acceptance_test.py | 167 ++++++++++++++++++
|
|
|
93ba16 |
ldap/servers/plugins/replication/urp.c | 10 +-
|
|
|
93ba16 |
ldap/servers/slapd/filter.c | 21 +++
|
|
|
93ba16 |
ldap/servers/slapd/slapi-plugin.h | 1 +
|
|
|
93ba16 |
4 files changed, 196 insertions(+), 3 deletions(-)
|
|
|
93ba16 |
|
|
|
93ba16 |
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
|
|
93ba16 |
index 2897726f5..be432bea4 100644
|
|
|
93ba16 |
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
|
|
93ba16 |
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
|
|
93ba16 |
@@ -7,6 +7,8 @@
|
|
|
93ba16 |
# --- END COPYRIGHT BLOCK ---
|
|
|
93ba16 |
#
|
|
|
93ba16 |
import pytest
|
|
|
93ba16 |
+import logging
|
|
|
93ba16 |
+from lib389.replica import Replicas
|
|
|
93ba16 |
from lib389.tasks import *
|
|
|
93ba16 |
from lib389.utils import *
|
|
|
93ba16 |
from lib389.topologies import topology_m4 as topo_m4
|
|
|
93ba16 |
@@ -524,6 +526,171 @@ def test_invalid_agmt(topo_m4):
|
|
|
93ba16 |
assert False
|
|
|
93ba16 |
|
|
|
93ba16 |
|
|
|
93ba16 |
+def test_warining_for_invalid_replica(topo_m4):
|
|
|
93ba16 |
+ """Testing logs to indicate the inconsistency when configuration is performed.
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ :id: dd689d03-69b8-4bf9-a06e-2acd19d5e2c8
|
|
|
93ba16 |
+ :setup: MMR with four masters
|
|
|
93ba16 |
+ :steps:
|
|
|
93ba16 |
+ 1. Setup nsds5ReplicaBackoffMin to 20
|
|
|
93ba16 |
+ 2. Setup nsds5ReplicaBackoffMax to 10
|
|
|
93ba16 |
+ :expectedresults:
|
|
|
93ba16 |
+ 1. nsds5ReplicaBackoffMin should set to 20
|
|
|
93ba16 |
+ 2. An error should be generated and also logged in the error logs.
|
|
|
93ba16 |
+ """
|
|
|
93ba16 |
+ replicas = Replicas(topo_m4.ms["master1"])
|
|
|
93ba16 |
+ replica = replicas.list()[0]
|
|
|
93ba16 |
+ log.info('Set nsds5ReplicaBackoffMin to 20')
|
|
|
93ba16 |
+ replica.set('nsds5ReplicaBackoffMin', '20')
|
|
|
93ba16 |
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
|
|
93ba16 |
+ log.info('Set nsds5ReplicaBackoffMax to 10')
|
|
|
93ba16 |
+ replica.set('nsds5ReplicaBackoffMax', '10')
|
|
|
93ba16 |
+ log.info('Resetting configuration: nsds5ReplicaBackoffMin')
|
|
|
93ba16 |
+ replica.remove_all('nsds5ReplicaBackoffMin')
|
|
|
93ba16 |
+ log.info('Check the error log for the error')
|
|
|
93ba16 |
+ assert topo_m4.ms["master1"].ds_error_log.match('.*nsds5ReplicaBackoffMax.*10.*invalid.*')
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+@pytest.mark.skipif(ds_is_older('1.4.4'), reason="Not implemented")
|
|
|
93ba16 |
+def test_csngen_task(topo_m2):
|
|
|
93ba16 |
+ """Test csn generator test
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ :id: b976849f-dbed-447e-91a7-c877d5d71fd0
|
|
|
93ba16 |
+ :setup: MMR with 2 masters
|
|
|
93ba16 |
+ :steps:
|
|
|
93ba16 |
+ 1. Create a csngen_test task
|
|
|
93ba16 |
+ 2. Check that debug messages "_csngen_gen_tester_main" are in errors logs
|
|
|
93ba16 |
+ :expectedresults:
|
|
|
93ba16 |
+ 1. Should succeeds
|
|
|
93ba16 |
+ 2. Should succeeds
|
|
|
93ba16 |
+ """
|
|
|
93ba16 |
+ m1 = topo_m2.ms["master1"]
|
|
|
93ba16 |
+ csngen_task = csngenTestTask(m1)
|
|
|
93ba16 |
+ csngen_task.create(properties={
|
|
|
93ba16 |
+ 'ttl': '300'
|
|
|
93ba16 |
+ })
|
|
|
93ba16 |
+ time.sleep(10)
|
|
|
93ba16 |
+ log.info('Check the error log contains strings showing csn generator is tested')
|
|
|
93ba16 |
+ assert m1.searchErrorsLog("_csngen_gen_tester_main")
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+@pytest.mark.ds51082
|
|
|
93ba16 |
+def test_csnpurge_large_valueset(topo_m2):
|
|
|
93ba16 |
+ """Test csn generator test
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ :id: 63e2bdb2-0a8f-4660-9465-7b80a9f72a74
|
|
|
93ba16 |
+ :setup: MMR with 2 masters
|
|
|
93ba16 |
+ :steps:
|
|
|
93ba16 |
+ 1. Create a test_user
|
|
|
93ba16 |
+ 2. add a large set of values (more than 10)
|
|
|
93ba16 |
+ 3. delete all the values (more than 10)
|
|
|
93ba16 |
+ 4. configure the replica to purge those values (purgedelay=5s)
|
|
|
93ba16 |
+ 5. Waiting for 6 second
|
|
|
93ba16 |
+ 6. do a series of update
|
|
|
93ba16 |
+ :expectedresults:
|
|
|
93ba16 |
+ 1. Should succeeds
|
|
|
93ba16 |
+ 2. Should succeeds
|
|
|
93ba16 |
+ 3. Should succeeds
|
|
|
93ba16 |
+ 4. Should succeeds
|
|
|
93ba16 |
+ 5. Should succeeds
|
|
|
93ba16 |
+ 6. Should not crash
|
|
|
93ba16 |
+ """
|
|
|
93ba16 |
+ m1 = topo_m2.ms["master2"]
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ test_user = UserAccount(m1, TEST_ENTRY_DN)
|
|
|
93ba16 |
+ if test_user.exists():
|
|
|
93ba16 |
+ log.info('Deleting entry {}'.format(TEST_ENTRY_DN))
|
|
|
93ba16 |
+ test_user.delete()
|
|
|
93ba16 |
+ test_user.create(properties={
|
|
|
93ba16 |
+ 'uid': TEST_ENTRY_NAME,
|
|
|
93ba16 |
+ 'cn': TEST_ENTRY_NAME,
|
|
|
93ba16 |
+ 'sn': TEST_ENTRY_NAME,
|
|
|
93ba16 |
+ 'userPassword': TEST_ENTRY_NAME,
|
|
|
93ba16 |
+ 'uidNumber' : '1000',
|
|
|
93ba16 |
+ 'gidNumber' : '2000',
|
|
|
93ba16 |
+ 'homeDirectory' : '/home/mmrepl_test',
|
|
|
93ba16 |
+ })
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # create a large value set so that it is sorted
|
|
|
93ba16 |
+ for i in range(1,20):
|
|
|
93ba16 |
+ test_user.add('description', 'value {}'.format(str(i)))
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # delete all values of the valueset
|
|
|
93ba16 |
+ for i in range(1,20):
|
|
|
93ba16 |
+ test_user.remove('description', 'value {}'.format(str(i)))
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # set purging delay to 5 second and wait more that 5second
|
|
|
93ba16 |
+ replicas = Replicas(m1)
|
|
|
93ba16 |
+ replica = replicas.list()[0]
|
|
|
93ba16 |
+ log.info('nsds5ReplicaPurgeDelay to 5')
|
|
|
93ba16 |
+ replica.set('nsds5ReplicaPurgeDelay', '5')
|
|
|
93ba16 |
+ time.sleep(6)
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # add some new values to the valueset containing entries that should be purged
|
|
|
93ba16 |
+ for i in range(21,25):
|
|
|
93ba16 |
+ test_user.add('description', 'value {}'.format(str(i)))
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+@pytest.mark.ds51244
|
|
|
93ba16 |
+def test_urp_trigger_substring_search(topo_m2):
|
|
|
93ba16 |
+ """Test that a ADD of a entry with a '*' in its DN, triggers
|
|
|
93ba16 |
+ an internal search with a escaped DN
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ :id: 9869bb39-419f-42c3-a44b-c93eb0b77667
|
|
|
93ba16 |
+ :setup: MMR with 2 masters
|
|
|
93ba16 |
+ :steps:
|
|
|
93ba16 |
+ 1. enable internal operation loggging for plugins
|
|
|
93ba16 |
+ 2. Create on M1 a test_user with a '*' in its DN
|
|
|
93ba16 |
+ 3. Check the test_user is replicated
|
|
|
93ba16 |
+ 4. Check in access logs that the internal search does not contain '*'
|
|
|
93ba16 |
+ :expectedresults:
|
|
|
93ba16 |
+ 1. Should succeeds
|
|
|
93ba16 |
+ 2. Should succeeds
|
|
|
93ba16 |
+ 3. Should succeeds
|
|
|
93ba16 |
+ 4. Should succeeds
|
|
|
93ba16 |
+ """
|
|
|
93ba16 |
+ m1 = topo_m2.ms["master1"]
|
|
|
93ba16 |
+ m2 = topo_m2.ms["master2"]
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # Enable loggging of internal operation logging to capture URP intop
|
|
|
93ba16 |
+ log.info('Set nsslapd-plugin-logging to on')
|
|
|
93ba16 |
+ for inst in (m1, m2):
|
|
|
93ba16 |
+ inst.config.loglevel([AccessLog.DEFAULT, AccessLog.INTERNAL], service='access')
|
|
|
93ba16 |
+ inst.config.set('nsslapd-plugin-logging', 'on')
|
|
|
93ba16 |
+ inst.restart()
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # add a user with a DN containing '*'
|
|
|
93ba16 |
+ test_asterisk_uid = 'asterisk_*_in_value'
|
|
|
93ba16 |
+ test_asterisk_dn = 'uid={},{}'.format(test_asterisk_uid, DEFAULT_SUFFIX)
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ test_user = UserAccount(m1, test_asterisk_dn)
|
|
|
93ba16 |
+ if test_user.exists():
|
|
|
93ba16 |
+ log.info('Deleting entry {}'.format(test_asterisk_dn))
|
|
|
93ba16 |
+ test_user.delete()
|
|
|
93ba16 |
+ test_user.create(properties={
|
|
|
93ba16 |
+ 'uid': test_asterisk_uid,
|
|
|
93ba16 |
+ 'cn': test_asterisk_uid,
|
|
|
93ba16 |
+ 'sn': test_asterisk_uid,
|
|
|
93ba16 |
+ 'userPassword': test_asterisk_uid,
|
|
|
93ba16 |
+ 'uidNumber' : '1000',
|
|
|
93ba16 |
+ 'gidNumber' : '2000',
|
|
|
93ba16 |
+ 'homeDirectory' : '/home/asterisk',
|
|
|
93ba16 |
+ })
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # check that the ADD was replicated on M2
|
|
|
93ba16 |
+ test_user_m2 = UserAccount(m2, test_asterisk_dn)
|
|
|
93ba16 |
+ for i in range(1,5):
|
|
|
93ba16 |
+ if test_user_m2.exists():
|
|
|
93ba16 |
+ break
|
|
|
93ba16 |
+ else:
|
|
|
93ba16 |
+ log.info('Entry not yet replicated on M2, wait a bit')
|
|
|
93ba16 |
+ time.sleep(2)
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ # check that M2 access logs does not "(&(objectclass=nstombstone)(nscpentrydn=uid=asterisk_*_in_value,dc=example,dc=com))"
|
|
|
93ba16 |
+ log.info('Check that on M2, URP as not triggered such internal search')
|
|
|
93ba16 |
+ pattern = ".*\(Internal\).*SRCH.*\(&\(objectclass=nstombstone\)\(nscpentrydn=uid=asterisk_\*_in_value,dc=example,dc=com.*"
|
|
|
93ba16 |
+ found = m2.ds_access_log.match(pattern)
|
|
|
93ba16 |
+ log.info("found line: %s" % found)
|
|
|
93ba16 |
+ assert not found
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+
|
|
|
93ba16 |
if __name__ == '__main__':
|
|
|
93ba16 |
# Run isolated
|
|
|
93ba16 |
# -s for DEBUG mode
|
|
|
93ba16 |
diff --git a/ldap/servers/plugins/replication/urp.c b/ldap/servers/plugins/replication/urp.c
|
|
|
93ba16 |
index 37fe77379..2c2d83c6c 100644
|
|
|
93ba16 |
--- a/ldap/servers/plugins/replication/urp.c
|
|
|
93ba16 |
+++ b/ldap/servers/plugins/replication/urp.c
|
|
|
93ba16 |
@@ -1379,9 +1379,12 @@ urp_add_check_tombstone (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry,
|
|
|
93ba16 |
Slapi_Entry **entries = NULL;
|
|
|
93ba16 |
Slapi_PBlock *newpb;
|
|
|
93ba16 |
char *basedn = slapi_entry_get_ndn(entry);
|
|
|
93ba16 |
+ char *escaped_basedn;
|
|
|
93ba16 |
const Slapi_DN *suffix = slapi_get_suffix_by_dn(slapi_entry_get_sdn (entry));
|
|
|
93ba16 |
+ escaped_basedn = slapi_filter_escape_filter_value("nscpentrydn", basedn);
|
|
|
93ba16 |
|
|
|
93ba16 |
- char *filter = slapi_filter_sprintf("(&(objectclass=nstombstone)(nscpentrydn=%s))", basedn);
|
|
|
93ba16 |
+ char *filter = slapi_filter_sprintf("(&(objectclass=nstombstone)(nscpentrydn=%s))", escaped_basedn);
|
|
|
93ba16 |
+ slapi_ch_free((void **)&escaped_basedn);
|
|
|
93ba16 |
newpb = slapi_pblock_new();
|
|
|
93ba16 |
slapi_search_internal_set_pb(newpb,
|
|
|
93ba16 |
slapi_sdn_get_dn(suffix), /* Base DN */
|
|
|
93ba16 |
@@ -1574,11 +1577,12 @@ urp_find_tombstone_for_glue (Slapi_PBlock *pb, char *sessionid, const Slapi_Entr
|
|
|
93ba16 |
Slapi_PBlock *newpb;
|
|
|
93ba16 |
const char *basedn = slapi_sdn_get_dn(parentdn);
|
|
|
93ba16 |
char *conflict_csnstr = slapi_entry_attr_get_charptr(entry, "conflictcsn");
|
|
|
93ba16 |
+ char *escaped_basedn = slapi_filter_escape_filter_value("nscpentrydn", basedn);
|
|
|
93ba16 |
CSN *conflict_csn = csn_new_by_string(conflict_csnstr);
|
|
|
93ba16 |
slapi_ch_free_string(&conflict_csnstr);
|
|
|
93ba16 |
CSN *tombstone_csn = NULL;
|
|
|
93ba16 |
-
|
|
|
93ba16 |
- char *filter = slapi_filter_sprintf("(&(objectclass=nstombstone)(nscpentrydn=%s))", basedn);
|
|
|
93ba16 |
+ char *filter = slapi_filter_sprintf("(&(objectclass=nstombstone)(nscpentrydn=%s))", escaped_basedn);
|
|
|
93ba16 |
+ slapi_ch_free((void **)&escaped_basedn);
|
|
|
93ba16 |
newpb = slapi_pblock_new();
|
|
|
93ba16 |
char *parent_dn = slapi_dn_parent (basedn);
|
|
|
93ba16 |
slapi_search_internal_set_pb(newpb,
|
|
|
93ba16 |
diff --git a/ldap/servers/slapd/filter.c b/ldap/servers/slapd/filter.c
|
|
|
93ba16 |
index 393a4dcee..8e21b34c3 100644
|
|
|
93ba16 |
--- a/ldap/servers/slapd/filter.c
|
|
|
93ba16 |
+++ b/ldap/servers/slapd/filter.c
|
|
|
93ba16 |
@@ -127,6 +127,27 @@ filter_escape_filter_value(struct slapi_filter *f, const char *fmt, size_t len _
|
|
|
93ba16 |
return ptr;
|
|
|
93ba16 |
}
|
|
|
93ba16 |
|
|
|
93ba16 |
+/* Escaped an equality filter value (assertionValue) of a given attribute
|
|
|
93ba16 |
+ * Caller must free allocated escaped filter value
|
|
|
93ba16 |
+ */
|
|
|
93ba16 |
+char *
|
|
|
93ba16 |
+slapi_filter_escape_filter_value(char* filter_attr, char *filter_value)
|
|
|
93ba16 |
+{
|
|
|
93ba16 |
+ char *result;
|
|
|
93ba16 |
+ struct slapi_filter *f;
|
|
|
93ba16 |
+
|
|
|
93ba16 |
+ if ((filter_attr == NULL) || (filter_value == NULL)) {
|
|
|
93ba16 |
+ return NULL;
|
|
|
93ba16 |
+ }
|
|
|
93ba16 |
+ f = (struct slapi_filter *)slapi_ch_calloc(1, sizeof(struct slapi_filter));
|
|
|
93ba16 |
+ f->f_choice = LDAP_FILTER_EQUALITY;
|
|
|
93ba16 |
+ f->f_un.f_un_ava.ava_type = filter_attr;
|
|
|
93ba16 |
+ f->f_un.f_un_ava.ava_value.bv_len = strlen(filter_value);
|
|
|
93ba16 |
+ f->f_un.f_un_ava.ava_value.bv_val = filter_value;
|
|
|
93ba16 |
+ result = filter_escape_filter_value(f, FILTER_EQ_FMT, FILTER_EQ_LEN);
|
|
|
93ba16 |
+ slapi_ch_free((void**) &f);
|
|
|
93ba16 |
+ return result;
|
|
|
93ba16 |
+}
|
|
|
93ba16 |
|
|
|
93ba16 |
/*
|
|
|
93ba16 |
* get_filter_internal(): extract an LDAP filter from a BerElement and create
|
|
|
93ba16 |
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
|
|
93ba16 |
index ea9bcf87b..a8b563dd1 100644
|
|
|
93ba16 |
--- a/ldap/servers/slapd/slapi-plugin.h
|
|
|
93ba16 |
+++ b/ldap/servers/slapd/slapi-plugin.h
|
|
|
93ba16 |
@@ -5284,6 +5284,7 @@ int slapi_vattr_filter_test_ext(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Filter *
|
|
|
93ba16 |
int slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2);
|
|
|
93ba16 |
Slapi_Filter *slapi_filter_dup(Slapi_Filter *f);
|
|
|
93ba16 |
int slapi_filter_changetype(Slapi_Filter *f, const char *newtype);
|
|
|
93ba16 |
+char *slapi_filter_escape_filter_value(char* filter_attr, char *filter_value);
|
|
|
93ba16 |
|
|
|
93ba16 |
int slapi_attr_is_last_mod(char *attr);
|
|
|
93ba16 |
|
|
|
93ba16 |
--
|
|
|
93ba16 |
2.26.2
|
|
|
93ba16 |
|