andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
Blob Blame History Raw
From 67e19da62a9e8958458de54173dcd9bcaf53164d Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Thu, 30 Sep 2021 15:59:40 +0200
Subject: [PATCH 01/12] Issue 4678 - RFE automatique disable of virtual
 attribute checking (#4918)

Bug description:
	Virtual attributes are configured via Roles or COS definitions
        and registered during initialization of those plugins.
	Virtual attributes are processed during search evaluation of
	filter and returned attributes. This processing is expensive
	and prone to create contention between searches.
	Use of virtual attribute is not frequent. So many of the
	deployement process virtual attribute even if there is none.

Fix description:
	The fix configure the server to ignore virtual attribute by
        default (nsslapd-ignore-virtual-attrs: on).
        At startup, if a new virtual attribute is registered or
        it exists Roles/COS definitions, then the server is
	configured to process the virtual attributes
        (nsslapd-ignore-virtual-attrs: off)
        design: https://www.port389.org/docs/389ds/design/vattr-automatic-toggle.html

relates: https://github.com/389ds/389-ds-base/issues/4678

Reviewed by: William Brown, Simon Pichugin, Mark Reynolds (Thanks !!)

Platforms tested: F34
---
 .../tests/suites/config/config_test.py        |  40 +++-
 dirsrvtests/tests/suites/cos/cos_test.py      |  94 ++++++--
 dirsrvtests/tests/suites/roles/basic_test.py  | 200 +++++++++++++++++-
 ldap/servers/plugins/roles/roles_cache.c      |   9 +
 ldap/servers/slapd/libglobs.c                 |   2 +-
 ldap/servers/slapd/main.c                     |   2 +
 ldap/servers/slapd/proto-slap.h               |   1 +
 ldap/servers/slapd/vattr.c                    | 127 +++++++++++
 src/lib389/lib389/idm/role.py                 |   4 +
 9 files changed, 455 insertions(+), 24 deletions(-)

diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
index 2ecff8f98..19232c87d 100644
--- a/dirsrvtests/tests/suites/config/config_test.py
+++ b/dirsrvtests/tests/suites/config/config_test.py
@@ -351,7 +351,7 @@ def test_ignore_virtual_attrs(topo):
     :setup: Standalone instance
     :steps:
          1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
-         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
          3. Set the valid values i.e. on/ON and off/OFF for nsslapd-ignore-virtual-attrs
          4. Set invalid value for attribute nsslapd-ignore-virtual-attrs
          5. Set nsslapd-ignore-virtual-attrs=off
@@ -374,8 +374,8 @@ def test_ignore_virtual_attrs(topo):
     log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
     assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
 
-    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
-    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "off"
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
 
     log.info("Set the valid values i.e. on/ON and off/OFF for nsslapd-ignore-virtual-attrs")
     for attribute_value in ['on', 'off', 'ON', 'OFF']:
@@ -415,6 +415,40 @@ def test_ignore_virtual_attrs(topo):
     log.info("Test if virtual attribute i.e. postal code not shown while nsslapd-ignore-virtual-attrs: on")
     assert not test_user.present('postalcode', '117')
 
+def test_ignore_virtual_attrs_after_restart(topo):
+    """Test nsslapd-ignore-virtual-attrs configuration attribute
+       The attribute is ON by default. If it set to OFF, it keeps
+       its value on restart
+
+    :id: ac368649-4fda-473c-9ef8-e0c728b162af
+    :setup: Standalone instance
+    :steps:
+         1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
+         3. Set nsslapd-ignore-virtual-attrs=off
+         4. restart the instance
+         5. Check the attribute nsslapd-ignore-virtual-attrs is OFF
+    :expectedresults:
+         1. This should be successful
+         2. This should be successful
+         3. This should be successful
+         4. This should be successful
+         5. This should be successful
+    """
+
+    log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
+
+    log.info("Set nsslapd-ignore-virtual-attrs = off")
+    topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'off')
+
+    topo.standalone.restart()
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
 
 @pytest.mark.bz918694
 @pytest.mark.ds408
diff --git a/dirsrvtests/tests/suites/cos/cos_test.py b/dirsrvtests/tests/suites/cos/cos_test.py
index d6a498c73..d1f99f96f 100644
--- a/dirsrvtests/tests/suites/cos/cos_test.py
+++ b/dirsrvtests/tests/suites/cos/cos_test.py
@@ -6,6 +6,8 @@
 # See LICENSE for details.
 # --- END COPYRIGHT BLOCK ---
 
+import logging
+import time
 import pytest, os, ldap
 from lib389.cos import  CosClassicDefinition, CosClassicDefinitions, CosTemplate
 from lib389._constants import DEFAULT_SUFFIX
@@ -14,26 +16,37 @@ from lib389.idm.role import FilteredRoles
 from lib389.idm.nscontainer import nsContainer
 from lib389.idm.user import UserAccount
 
+logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
 pytestmark = pytest.mark.tier1
+@pytest.fixture(scope="function")
+def reset_ignore_vattr(topo, request):
+    default_ignore_vattr_value = topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs')
+    def fin():
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', default_ignore_vattr_value)
 
-def test_positive(topo):
-    """
-        :id: a5a74235-597f-4fe8-8c38-826860927472
-        :setup: server
-        :steps:
-            1. Add filter role entry
-            2. Add ns container
-            3. Add cos template
-            4. Add CosClassic Definition
-            5. Cos entries should be added and searchable
-            6. employeeType attribute should be there in user entry as per the cos plugin property
-        :expectedresults:
-            1. Operation should success
-            2. Operation should success
-            3. Operation should success
-            4. Operation should success
-            5. Operation should success
-            6. Operation should success
+    request.addfinalizer(fin)
+
+def test_positive(topo, reset_ignore_vattr):
+    """CoS positive tests
+
+    :id: a5a74235-597f-4fe8-8c38-826860927472
+    :setup: server
+    :steps:
+        1. Add filter role entry
+        2. Add ns container
+        3. Add cos template
+        4. Add CosClassic Definition
+        5. Cos entries should be added and searchable
+        6. employeeType attribute should be there in user entry as per the cos plugin property
+    :expectedresults:
+        1. Operation should success
+        2. Operation should success
+        3. Operation should success
+        4. Operation should success
+        5. Operation should success
+        6. Operation should success
     """
     # Adding ns filter role
     roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX)
@@ -77,7 +90,52 @@ def test_positive(topo):
 
     #  CoS definition entry's cosSpecifier attribute specifies the employeeType attribute
     assert user.present('employeeType')
+    cosdef.delete()
+
+def test_vattr_on_cos_definition(topo, reset_ignore_vattr):
+    """Test nsslapd-ignore-virtual-attrs configuration attribute
+       The attribute is ON by default. If a cos definition is
+       added it is moved to OFF
+
+    :id: e7ef5254-386f-4362-bbb4-9409f3f51b08
+    :setup: Standalone instance
+    :steps:
+         1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
+         3. Create a cos definition for employeeType
+         4. Check the value of nsslapd-ignore-virtual-attrs should be OFF (with a delay for postop processing)
+         5. Check a message "slapi_vattrspi_regattr - Because employeeType,.." in error logs
+    :expectedresults:
+         1. This should be successful
+         2. This should be successful
+         3. This should be successful
+         4. This should be successful
+         5. This should be successful
+    """
+
+    log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
+
+    # creating CosClassicDefinition
+    log.info("Create a cos definition")
+    properties = {'cosTemplateDn': 'cn=cosClassicGenerateEmployeeTypeUsingnsroleTemplates,{}'.format(DEFAULT_SUFFIX),
+                  'cosAttribute': 'employeeType',
+                  'cosSpecifier': 'nsrole',
+                  'cn': 'cosClassicGenerateEmployeeTypeUsingnsrole'}
+    cosdef = CosClassicDefinition(topo.standalone,'cn=cosClassicGenerateEmployeeTypeUsingnsrole,{}'.format(DEFAULT_SUFFIX))\
+        .create(properties=properties)
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    time.sleep(2)
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
 
+    topo.standalone.stop()
+    assert topo.standalone.searchErrorsLog("slapi_vattrspi_regattr - Because employeeType is a new registered virtual attribute , nsslapd-ignore-virtual-attrs was set to \'off\'")
+    topo.standalone.start()
+    cosdef.delete()
 
 if __name__ == "__main__":
     CURRENT_FILE = os.path.realpath(__file__)
diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py
index 47a531794..bec3aedfc 100644
--- a/dirsrvtests/tests/suites/roles/basic_test.py
+++ b/dirsrvtests/tests/suites/roles/basic_test.py
@@ -11,6 +11,8 @@
 Importing necessary Modules.
 """
 
+import logging
+import time
 import os
 import pytest
 
@@ -22,6 +24,9 @@ from lib389.topologies import topology_st as topo
 from lib389.idm.role import FilteredRoles, ManagedRoles, NestedRoles
 from lib389.idm.domain import Domain
 
+logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
 pytestmark = pytest.mark.tier1
 
 DNBASE = "o=acivattr,{}".format(DEFAULT_SUFFIX)
@@ -35,7 +40,7 @@ FILTERROLESALESROLE = "cn=FILTERROLESALESROLE,{}".format(DNBASE)
 FILTERROLEENGROLE = "cn=FILTERROLEENGROLE,{}".format(DNBASE)
 
 
-def test_filterrole(topo):
+def test_filterrole(topo, request):
     """Test Filter Role
 
     :id: 8ada4064-786b-11e8-8634-8c16451d917b
@@ -136,8 +141,20 @@ def test_filterrole(topo):
                   SALES_OU, DNBASE]:
         UserAccount(topo.standalone, dn_dn).delete()
 
+    def fin():
+        topo.standalone.restart()
+        try:
+            filtered_roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX)
+            for i in filtered_roles.list():
+                i.delete()
+        except:
+            pass
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
+    request.addfinalizer(fin)
+
 
-def test_managedrole(topo):
+def test_managedrole(topo, request):
     """Test Managed Role
 
     :id: d52a9c00-3bf6-11e9-9b7b-8c16451d917b
@@ -209,6 +226,16 @@ def test_managedrole(topo):
     for i in roles.list():
         i.delete()
 
+    def fin():
+        topo.standalone.restart()
+        try:
+            role = ManagedRoles(topo.standalone, DEFAULT_SUFFIX).get('ROLE1')
+            role.delete()
+        except:
+            pass
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
+    request.addfinalizer(fin)
 
 @pytest.fixture(scope="function")
 def _final(request, topo):
@@ -220,6 +247,7 @@ def _final(request, topo):
     def finofaci():
         """
         Removes and Restores ACIs and other users after the test.
+        And restore nsslapd-ignore-virtual-attrs to default
         """
         domain = Domain(topo.standalone, DEFAULT_SUFFIX)
         domain.remove_all('aci')
@@ -234,6 +262,8 @@ def _final(request, topo):
         for i in aci_list:
             domain.add("aci", i)
 
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
     request.addfinalizer(finofaci)
 
 
@@ -296,6 +326,172 @@ def test_nestedrole(topo, _final):
     conn = users.get('test_user_3').bind(PW_DM)
     assert UserAccounts(conn, DEFAULT_SUFFIX).list()
 
+def test_vattr_on_filtered_role(topo, request):
+    """Test nsslapd-ignore-virtual-attrs configuration attribute
+       The attribute is ON by default. If a filtered role is
+       added it is moved to OFF
+
+    :id: 88b3ad3c-f39a-4eb7-a8c9-07c685f11908
+    :setup: Standalone instance
+    :steps:
+         1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
+         3. Create a filtered role
+         4. Check the value of nsslapd-ignore-virtual-attrs should be OFF
+         5. Check a message "roles_cache_trigger_update_role - Because of virtual attribute.." in error logs
+    :expectedresults:
+         1. This should be successful
+         2. This should be successful
+         3. This should be successful
+         4. This should be successful
+         5. This should be successful
+    """
+
+    log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
+
+    log.info("Create a filtered role")
+    try:
+        Organization(topo.standalone).create(properties={"o": "acivattr"}, basedn=DEFAULT_SUFFIX)
+    except:
+        pass
+    roles = FilteredRoles(topo.standalone, DNBASE)
+    roles.create(properties={'cn': 'FILTERROLEENGROLE', 'nsRoleFilter': 'cn=eng*'})
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
+
+    topo.standalone.stop()
+    assert topo.standalone.searchErrorsLog("roles_cache_trigger_update_role - Because of virtual attribute definition \(role\), nsslapd-ignore-virtual-attrs was set to \'off\'")
+
+    def fin():
+        topo.standalone.restart()
+        try:
+            filtered_roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX)
+            for i in filtered_roles.list():
+                i.delete()
+        except:
+            pass
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
+    request.addfinalizer(fin)
+
+def test_vattr_on_filtered_role_restart(topo, request):
+    """Test nsslapd-ignore-virtual-attrs configuration attribute
+    If it exists a filtered role definition at restart then
+    nsslapd-ignore-virtual-attrs should be set to 'off'
+
+    :id: 972183f7-d18f-40e0-94ab-580e7b7d78d0
+    :setup: Standalone instance
+    :steps:
+         1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
+         3. Create a filtered role
+         4. Check the value of nsslapd-ignore-virtual-attrs should be OFF
+         5. restart the instance
+         6. Check the presence of virtual attribute is detected
+         7. Check the value of nsslapd-ignore-virtual-attrs should be OFF
+    :expectedresults:
+         1. This should be successful
+         2. This should be successful
+         3. This should be successful
+         4. This should be successful
+         5. This should be successful
+         6. This should be successful
+         7. This should be successful
+    """
+
+    log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
+
+    log.info("Create a filtered role")
+    try:
+        Organization(topo.standalone).create(properties={"o": "acivattr"}, basedn=DEFAULT_SUFFIX)
+    except:
+        pass
+    roles = FilteredRoles(topo.standalone, DNBASE)
+    roles.create(properties={'cn': 'FILTERROLEENGROLE', 'nsRoleFilter': 'cn=eng*'})
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
+
+    
+    log.info("Check the virtual attribute definition is found (after a required delay)")
+    topo.standalone.restart()
+    time.sleep(5)
+    assert topo.standalone.searchErrorsLog("Found a role/cos definition in")
+    assert topo.standalone.searchErrorsLog("roles_cache_trigger_update_role - Because of virtual attribute definition \(role\), nsslapd-ignore-virtual-attrs was set to \'off\'")
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
+
+    def fin():
+        topo.standalone.restart()
+        try:
+            filtered_roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX)
+            for i in filtered_roles.list():
+                i.delete()
+        except:
+            pass
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
+    request.addfinalizer(fin)
+
+
+def test_vattr_on_managed_role(topo, request):
+    """Test nsslapd-ignore-virtual-attrs configuration attribute
+       The attribute is ON by default. If a managed role is
+       added it is moved to OFF
+
+    :id: 664b722d-c1ea-41e4-8f6c-f9c87a212346
+    :setup: Standalone instance
+    :steps:
+         1. Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config
+         2. Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON
+         3. Create a managed role
+         4. Check the value of nsslapd-ignore-virtual-attrs should be OFF
+         5. Check a message "roles_cache_trigger_update_role - Because of virtual attribute.." in error logs
+    :expectedresults:
+         1. This should be successful
+         2. This should be successful
+         3. This should be successful
+         4. This should be successful
+         5. This should be successful
+    """
+
+    log.info("Check the attribute nsslapd-ignore-virtual-attrs is present in cn=config")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs')
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be ON")
+    assert topo.standalone.config.get_attr_val_utf8('nsslapd-ignore-virtual-attrs') == "on"
+
+    log.info("Create a managed role")
+    roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX)
+    role = roles.create(properties={"cn": 'ROLE1'})
+
+    log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
+    assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
+
+    topo.standalone.stop()
+    assert topo.standalone.searchErrorsLog("roles_cache_trigger_update_role - Because of virtual attribute definition \(role\), nsslapd-ignore-virtual-attrs was set to \'off\'")
+
+    def fin():
+        topo.standalone.restart()
+        try:
+            filtered_roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX)
+            for i in filtered_roles.list():
+                i.delete()
+        except:
+            pass
+        topo.standalone.config.set('nsslapd-ignore-virtual-attrs', 'on')
+
+    request.addfinalizer(fin)
 
 if __name__ == "__main__":
     CURRENT_FILE = os.path.realpath(__file__)
diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c
index 3d076a4cb..cd00e0aba 100644
--- a/ldap/servers/plugins/roles/roles_cache.c
+++ b/ldap/servers/plugins/roles/roles_cache.c
@@ -530,6 +530,15 @@ roles_cache_trigger_update_role(char *dn, Slapi_Entry *roles_entry, Slapi_DN *be
     }
 
     slapi_rwlock_unlock(global_lock);
+    {
+        /* A role definition has been updated, enable vattr handling */
+        char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE];
+        errorbuf[0] = '\0';
+        config_set_ignore_vattrs(CONFIG_IGNORE_VATTRS, "off", errorbuf, 1);
+        slapi_log_err(SLAPI_LOG_INFO,
+                      "roles_cache_trigger_update_role",
+                      "Because of virtual attribute definition (role), %s was set to 'off'\n", CONFIG_IGNORE_VATTRS);
+    }
 
     slapi_log_err(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_trigger_update_role: %p \n", roles_list);
 }
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 2ea4cd760..f6dacce30 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -1803,7 +1803,7 @@ FrontendConfig_init(void)
     init_ndn_cache_enabled = cfg->ndn_cache_enabled = LDAP_ON;
     cfg->ndn_cache_max_size = SLAPD_DEFAULT_NDN_SIZE;
     init_sasl_mapping_fallback = cfg->sasl_mapping_fallback = LDAP_OFF;
-    init_ignore_vattrs = cfg->ignore_vattrs = LDAP_OFF;
+    init_ignore_vattrs = cfg->ignore_vattrs = LDAP_ON;
     cfg->sasl_max_bufsize = SLAPD_DEFAULT_SASL_MAXBUFSIZE;
     cfg->unhashed_pw_switch = SLAPD_DEFAULT_UNHASHED_PW_SWITCH;
     init_return_orig_type = cfg->return_orig_type = LDAP_OFF;
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
index 4931a4ca4..61ed40b7d 100644
--- a/ldap/servers/slapd/main.c
+++ b/ldap/servers/slapd/main.c
@@ -1042,6 +1042,8 @@ main(int argc, char **argv)
         eq_start(); /* must be done after plugins started - DEPRECATED */
         eq_start_rel(); /* must be done after plugins started */
 
+        vattr_check(); /* Check if it exists virtual attribute definitions */
+
 #ifdef HPUX10
         /* HPUX linker voodoo */
         if (collation_init == NULL) {
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index c143f3772..442a621aa 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1462,6 +1462,7 @@ void subentry_create_filter(Slapi_Filter **filter);
  */
 void vattr_init(void);
 void vattr_cleanup(void);
+void vattr_check(void);
 
 /*
  * slapd_plhash.c - supplement to NSPR plhash
diff --git a/ldap/servers/slapd/vattr.c b/ldap/servers/slapd/vattr.c
index 09dab6ecf..24750a57c 100644
--- a/ldap/servers/slapd/vattr.c
+++ b/ldap/servers/slapd/vattr.c
@@ -64,6 +64,10 @@
 #define SOURCEFILE "vattr.c"
 static char *sourcefile = SOURCEFILE;
 
+/* stolen from roles_cache.h, must remain in sync */
+#define NSROLEATTR "nsRole"
+static Slapi_Eq_Context vattr_check_ctx = {0};
+
 /* Define only for module test code */
 /* #define VATTR_TEST_CODE */
 
@@ -130,6 +134,112 @@ vattr_cleanup()
 {
     /* We need to free and remove anything that was inserted first */
     vattr_map_destroy();
+    slapi_eq_cancel_rel(vattr_check_ctx);
+}
+
+static void
+vattr_check_thread(void *arg)
+{
+    Slapi_Backend *be = NULL;
+    char *cookie = NULL;
+    Slapi_DN *base_sdn = NULL;
+    Slapi_PBlock *search_pb = NULL;
+    Slapi_Entry **entries = NULL;
+    int32_t rc;
+    int32_t check_suffix; /* used to skip suffixes in ignored_backend */
+    PRBool exist_vattr_definition = PR_FALSE;
+    char *ignored_backend[5] = {"cn=config", "cn=schema", "cn=monitor", "cn=changelog", NULL}; /* suffixes to ignore */
+    char *suffix;
+    int ignore_vattrs;
+
+    ignore_vattrs = config_get_ignore_vattrs();
+
+    if (!ignore_vattrs) {
+        /* Nothing to do more, we are already evaluating virtual attribute */
+        return;
+    }
+
+    search_pb = slapi_pblock_new();
+    be = slapi_get_first_backend(&cookie);
+    while (be && !exist_vattr_definition && !slapi_is_shutting_down()) {
+        base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0);
+        suffix = (char *) slapi_sdn_get_dn(base_sdn);
+
+        if (suffix) {
+            /* First check that we need to check that suffix */
+            check_suffix = 1;
+            for (size_t i = 0; ignored_backend[i]; i++) {
+                if (strcasecmp(suffix, ignored_backend[i]) == 0) {
+                    check_suffix = 0;
+                    break;
+                }
+            }
+
+            /* search for a role or cos definition */
+            if (check_suffix) {
+                slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
+                        LDAP_SCOPE_SUBTREE, "(&(objectclass=ldapsubentry)(|(objectclass=nsRoleDefinition)(objectclass=cosSuperDefinition)))",
+                        NULL, 0, NULL, NULL, (void *) plugin_get_default_component_id(), 0);
+                slapi_search_internal_pb(search_pb);
+                slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+                if (rc == LDAP_SUCCESS) {
+                    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+                    if (entries && entries[0]) {
+                        /* it exists at least a cos or role definition */
+                        exist_vattr_definition = PR_TRUE;
+                        slapi_log_err(SLAPI_LOG_INFO,
+                                "vattr_check_thread",
+                                "Found a role/cos definition in %s\n", slapi_entry_get_dn(entries[0]));
+                    } else {
+                        slapi_log_err(SLAPI_LOG_INFO,
+                                "vattr_check_thread",
+                                "No role/cos definition in %s\n", slapi_sdn_get_dn(base_sdn));
+                    }
+                }
+                slapi_free_search_results_internal(search_pb);
+            } /* check_suffix */
+        } /* suffix */
+        be = (backend *) slapi_get_next_backend(cookie);
+    }
+    slapi_pblock_destroy(search_pb);
+    slapi_ch_free_string(&cookie);
+
+    /* Now if a virtual attribute is defined, then CONFIG_IGNORE_VATTRS -> off */
+    if (exist_vattr_definition) {
+        char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE];
+        errorbuf[0] = '\0';
+        config_set_ignore_vattrs(CONFIG_IGNORE_VATTRS, "off", errorbuf, 1);
+        slapi_log_err(SLAPI_LOG_INFO,
+                      "vattr_check_thread",
+                      "Because of virtual attribute definition, %s was set to 'off'\n", CONFIG_IGNORE_VATTRS);
+    }
+}
+static void
+vattr_check_schedule_once(time_t when __attribute__((unused)), void *arg)
+{
+    if (PR_CreateThread(PR_USER_THREAD,
+                        vattr_check_thread,
+                        (void *) arg,
+                        PR_PRIORITY_NORMAL,
+                        PR_GLOBAL_THREAD,
+                        PR_UNJOINABLE_THREAD,
+                        SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) {
+        slapi_log_err(SLAPI_LOG_ERR,
+                      "vattr_check_schedule_once",
+                      "Fails to check if %s needs to be toggled to FALSE\n", CONFIG_IGNORE_VATTRS);
+    }
+}
+#define VATTR_CHECK_DELAY 3
+void
+vattr_check()
+{
+    /* Schedule running a callback that will create a thread
+     * but make sure it is called a first thing when event loop is created */
+    time_t now;
+
+    now = slapi_current_rel_time_t();
+    vattr_check_ctx = slapi_eq_once_rel(vattr_check_schedule_once, NULL, now + VATTR_CHECK_DELAY);
 }
 
 /* The public interface functions start here */
@@ -1631,6 +1741,9 @@ slapi_vattrspi_regattr(vattr_sp_handle *h, char *type_name_to_register, char *DN
     char *type_to_add;
     int free_type_to_add = 0;
     Slapi_DN original_dn;
+    int ignore_vattrs;
+
+    ignore_vattrs = config_get_ignore_vattrs();
 
     slapi_sdn_init(&original_dn);
 
@@ -1676,6 +1789,20 @@ slapi_vattrspi_regattr(vattr_sp_handle *h, char *type_name_to_register, char *DN
     if (free_type_to_add) {
         slapi_ch_free((void **)&type_to_add);
     }
+    if (ignore_vattrs && strcasecmp(type_name_to_register, NSROLEATTR)) {
+        /* A new virtual attribute is registered.
+         * This new vattr being *different* than the default roles vattr 'nsRole'
+         * It is time to allow vattr lookup
+         */
+        char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE];
+        errorbuf[0] = '\0';
+        config_set_ignore_vattrs(CONFIG_IGNORE_VATTRS, "off", errorbuf, 1);
+        slapi_log_err(SLAPI_LOG_INFO,
+                      "slapi_vattrspi_regattr",
+                      "Because %s is a new registered virtual attribute , %s was set to 'off'\n",
+                      type_name_to_register,
+                      CONFIG_IGNORE_VATTRS);
+    }
 
     return ret;
 }
diff --git a/src/lib389/lib389/idm/role.py b/src/lib389/lib389/idm/role.py
index fe91aab6f..9a2bff3d6 100644
--- a/src/lib389/lib389/idm/role.py
+++ b/src/lib389/lib389/idm/role.py
@@ -252,6 +252,8 @@ class FilteredRole(Role):
         self._rdn_attribute = 'cn'
         self._create_objectclasses = ['nsComplexRoleDefinition', 'nsFilteredRoleDefinition']
 
+        self._protected = False
+
 
 
 class FilteredRoles(Roles):
@@ -285,6 +287,7 @@ class ManagedRole(Role):
         self._rdn_attribute = 'cn'
         self._create_objectclasses = ['nsSimpleRoleDefinition', 'nsManagedRoleDefinition']
 
+        self._protected = False
 
 class ManagedRoles(Roles):
     """DSLdapObjects that represents all Managed Roles entries
@@ -320,6 +323,7 @@ class NestedRole(Role):
         self._rdn_attribute = 'cn'
         self._create_objectclasses = ['nsComplexRoleDefinition', 'nsNestedRoleDefinition']
 
+        self._protected = False
 
 class NestedRoles(Roles):
     """DSLdapObjects that represents all NestedRoles entries in suffix.
-- 
2.31.1