|
|
b663b9 |
From 5b7d67bdef7810c661ae4ba1fdfa620c86985661 Mon Sep 17 00:00:00 2001
|
|
|
b663b9 |
From: Mark Reynolds <mreynolds@redhat.com>
|
|
|
b663b9 |
Date: Fri, 27 Apr 2018 08:34:51 -0400
|
|
|
b663b9 |
Subject: [PATCH] Ticket 49652 - DENY aci's are not handled properly
|
|
|
b663b9 |
|
|
|
b663b9 |
Bug Description: There are really two issues here. One, when a resource
|
|
|
b663b9 |
is denied by a DENY aci the cached results for that resource
|
|
|
b663b9 |
are not proprely set, and on the same connection if the same
|
|
|
b663b9 |
operation repeated it will be allowed instead of denied because
|
|
|
b663b9 |
the cache result was not proprely updated.
|
|
|
b663b9 |
|
|
|
b663b9 |
Two, if there are no ALLOW aci's on a resource, then we don't
|
|
|
b663b9 |
check the deny rules, and resources that are restricted are
|
|
|
b663b9 |
returned to the client.
|
|
|
b663b9 |
|
|
|
b663b9 |
Fix Description: For issue one, when an entry is denied access reset all the
|
|
|
b663b9 |
attributes' cache results to DENIED as it's possible previously
|
|
|
b663b9 |
evaluated aci's granted access to some of these attributes which
|
|
|
b663b9 |
are still present in the acl result cache.
|
|
|
b663b9 |
|
|
|
b663b9 |
For issue two, if there are no ALLOW aci's on a resource but
|
|
|
b663b9 |
there are DENY aci's, then set the aclpb state flags to
|
|
|
b663b9 |
process DENY aci's
|
|
|
b663b9 |
|
|
|
b663b9 |
https://pagure.io/389-ds-base/issue/49652
|
|
|
b663b9 |
|
|
|
b663b9 |
Reviewed by: tbordaz & lkrispenz(Thanks!!)
|
|
|
b663b9 |
|
|
|
b663b9 |
(cherry picked from commit d77c7f0754f67022b42784c05be8a493a00f2ec5)
|
|
|
b663b9 |
---
|
|
|
b663b9 |
dirsrvtests/tests/suites/acl/acl_deny_test.py | 198 ++++++++++++++++++
|
|
|
b663b9 |
ldap/servers/plugins/acl/acl.c | 24 ++-
|
|
|
b663b9 |
2 files changed, 220 insertions(+), 2 deletions(-)
|
|
|
b663b9 |
create mode 100644 dirsrvtests/tests/suites/acl/acl_deny_test.py
|
|
|
b663b9 |
|
|
|
b663b9 |
diff --git a/dirsrvtests/tests/suites/acl/acl_deny_test.py b/dirsrvtests/tests/suites/acl/acl_deny_test.py
|
|
|
b663b9 |
new file mode 100644
|
|
|
b663b9 |
index 000000000..285664150
|
|
|
b663b9 |
--- /dev/null
|
|
|
b663b9 |
+++ b/dirsrvtests/tests/suites/acl/acl_deny_test.py
|
|
|
b663b9 |
@@ -0,0 +1,198 @@
|
|
|
b663b9 |
+import logging
|
|
|
b663b9 |
+import pytest
|
|
|
b663b9 |
+import os
|
|
|
b663b9 |
+import ldap
|
|
|
b663b9 |
+import time
|
|
|
b663b9 |
+from lib389._constants import *
|
|
|
b663b9 |
+from lib389.topologies import topology_st as topo
|
|
|
b663b9 |
+from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
|
|
|
b663b9 |
+from lib389.idm.domain import Domain
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
|
|
b663b9 |
+if DEBUGGING:
|
|
|
b663b9 |
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
|
|
b663b9 |
+else:
|
|
|
b663b9 |
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
|
|
b663b9 |
+log = logging.getLogger(__name__)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+BIND_DN2 = 'uid=tuser,ou=People,dc=example,dc=com'
|
|
|
b663b9 |
+BIND_RDN2 = 'tuser'
|
|
|
b663b9 |
+BIND_DN = 'uid=tuser1,ou=People,dc=example,dc=com'
|
|
|
b663b9 |
+BIND_RDN = 'tuser1'
|
|
|
b663b9 |
+SRCH_FILTER = "uid=tuser1"
|
|
|
b663b9 |
+SRCH_FILTER2 = "uid=tuser"
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+aci_list_A = ['(targetattr != "userPassword") (version 3.0; acl "Anonymous access"; allow (read, search, compare)userdn = "ldap:///anyone";)',
|
|
|
b663b9 |
+ '(targetattr = "*") (version 3.0;acl "allow tuser";allow (all)(userdn = "ldap:///uid=tuser5,ou=People,dc=example,dc=com");)',
|
|
|
b663b9 |
+ '(targetattr != "uid || mail") (version 3.0; acl "deny-attrs"; deny (all) (userdn = "ldap:///anyone");)',
|
|
|
b663b9 |
+ '(targetfilter = "(inetUserStatus=1)") ( version 3.0; acl "deny-specific-entry"; deny(all) (userdn = "ldap:///anyone");)']
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+aci_list_B = ['(targetattr != "userPassword") (version 3.0; acl "Anonymous access"; allow (read, search, compare)userdn = "ldap:///anyone";)',
|
|
|
b663b9 |
+ '(targetattr != "uid || mail") (version 3.0; acl "deny-attrs"; deny (all) (userdn = "ldap:///anyone");)',
|
|
|
b663b9 |
+ '(targetfilter = "(inetUserStatus=1)") ( version 3.0; acl "deny-specific-entry"; deny(all) (userdn = "ldap:///anyone");)']
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+@pytest.fixture(scope="module")
|
|
|
b663b9 |
+def aci_setup(topo):
|
|
|
b663b9 |
+ topo.standalone.log.info("Add {}".format(BIND_DN))
|
|
|
b663b9 |
+ user = UserAccount(topo.standalone, BIND_DN)
|
|
|
b663b9 |
+ user_props = TEST_USER_PROPERTIES.copy()
|
|
|
b663b9 |
+ user_props.update({'sn': BIND_RDN,
|
|
|
b663b9 |
+ 'cn': BIND_RDN,
|
|
|
b663b9 |
+ 'uid': BIND_RDN,
|
|
|
b663b9 |
+ 'inetUserStatus': '1',
|
|
|
b663b9 |
+ 'objectclass': 'extensibleObject',
|
|
|
b663b9 |
+ 'userpassword': PASSWORD})
|
|
|
b663b9 |
+ user.create(properties=user_props, basedn=SUFFIX)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ topo.standalone.log.info("Add {}".format(BIND_DN2))
|
|
|
b663b9 |
+ user2 = UserAccount(topo.standalone, BIND_DN2)
|
|
|
b663b9 |
+ user_props = TEST_USER_PROPERTIES.copy()
|
|
|
b663b9 |
+ user_props.update({'sn': BIND_RDN2,
|
|
|
b663b9 |
+ 'cn': BIND_RDN2,
|
|
|
b663b9 |
+ 'uid': BIND_RDN2,
|
|
|
b663b9 |
+ 'userpassword': PASSWORD})
|
|
|
b663b9 |
+ user2.create(properties=user_props, basedn=SUFFIX)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+def test_multi_deny_aci(topo, aci_setup):
|
|
|
b663b9 |
+ """Test that mutliple deny rules work, and that they the cache properly
|
|
|
b663b9 |
+ stores the result
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ :id: 294c366d-850e-459e-b5a0-3cc828ec3aca
|
|
|
b663b9 |
+ :setup: Standalone Instance
|
|
|
b663b9 |
+ :steps:
|
|
|
b663b9 |
+ 1. Add aci_list_A aci's and verify two searches on the same connection
|
|
|
b663b9 |
+ behave the same
|
|
|
b663b9 |
+ 2. Add aci_list_B aci's and verify search fails as expected
|
|
|
b663b9 |
+ :expectedresults:
|
|
|
b663b9 |
+ 1. Both searches do not return any entries
|
|
|
b663b9 |
+ 2. Seaches do not return any entries
|
|
|
b663b9 |
+ """
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ if DEBUGGING:
|
|
|
b663b9 |
+ # Maybe add aci logging?
|
|
|
b663b9 |
+ pass
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ suffix = Domain(topo.standalone, DEFAULT_SUFFIX)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ for run in range(2):
|
|
|
b663b9 |
+ topo.standalone.log.info("Pass " + str(run + 1))
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ # Test ACI List A
|
|
|
b663b9 |
+ topo.standalone.log.info("Testing two searches behave the same...")
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(DN_DM, PASSWORD)
|
|
|
b663b9 |
+ suffix.set('aci', aci_list_A, ldap.MOD_REPLACE)
|
|
|
b663b9 |
+ time.sleep(1)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(BIND_DN, PASSWORD)
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 1")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 2")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ # Bind a different user who has rights
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(BIND_DN2, PASSWORD)
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user (2)")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ if run > 0:
|
|
|
b663b9 |
+ # Second pass
|
|
|
b663b9 |
+ topo.standalone.restart()
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ # Reset ACI's and do the second test
|
|
|
b663b9 |
+ topo.standalone.log.info("Testing search does not return any entries...")
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(DN_DM, PASSWORD)
|
|
|
b663b9 |
+ suffix.set('aci', aci_list_B, ldap.MOD_REPLACE)
|
|
|
b663b9 |
+ time.sleep(1)
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(BIND_DN, PASSWORD)
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 1")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 2")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ if run > 0:
|
|
|
b663b9 |
+ # Second pass
|
|
|
b663b9 |
+ topo.standalone.restart()
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ # Bind as different user who has rights
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(BIND_DN2, PASSWORD)
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as good user (2)")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 1")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 2")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ # back to user 1
|
|
|
b663b9 |
+ topo.standalone.simple_bind_s(BIND_DN, PASSWORD)
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as user1")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER2)
|
|
|
b663b9 |
+ if entries is None or len(entries) == 0:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Failed to get entry as user1 (2)")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 1")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, SRCH_FILTER)
|
|
|
b663b9 |
+ if entries and entries[0]:
|
|
|
b663b9 |
+ topo.standalone.log.fatal("Incorrectly got an entry returned from search 2")
|
|
|
b663b9 |
+ assert False
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+ topo.standalone.log.info("Test PASSED")
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+
|
|
|
b663b9 |
+if __name__ == '__main__':
|
|
|
b663b9 |
+ # Run isolated
|
|
|
b663b9 |
+ # -s for DEBUG mode
|
|
|
b663b9 |
+ CURRENT_FILE = os.path.realpath(__file__)
|
|
|
b663b9 |
+ pytest.main(["-s", CURRENT_FILE])
|
|
|
b663b9 |
+
|
|
|
b663b9 |
diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c
|
|
|
b663b9 |
index bc154c78f..6d105f4fa 100644
|
|
|
b663b9 |
--- a/ldap/servers/plugins/acl/acl.c
|
|
|
b663b9 |
+++ b/ldap/servers/plugins/acl/acl.c
|
|
|
b663b9 |
@@ -1088,9 +1088,23 @@ acl_read_access_allowed_on_entry(
|
|
|
b663b9 |
** a DENY rule, then we don't have access to
|
|
|
b663b9 |
** the entry ( nice trick to get in )
|
|
|
b663b9 |
*/
|
|
|
b663b9 |
- if (aclpb->aclpb_state &
|
|
|
b663b9 |
- ACLPB_EXECUTING_DENY_HANDLES)
|
|
|
b663b9 |
+ if (aclpb->aclpb_state & ACLPB_EXECUTING_DENY_HANDLES) {
|
|
|
b663b9 |
+ aclEvalContext *c_ContextEval = &aclpb->aclpb_curr_entryEval_context;
|
|
|
b663b9 |
+ AclAttrEval *c_attrEval = NULL;
|
|
|
b663b9 |
+ /*
|
|
|
b663b9 |
+ * The entire entry is blocked, but previously evaluated allow aci's might
|
|
|
b663b9 |
+ * show some of the attributes as readable in the acl cache, so reset all
|
|
|
b663b9 |
+ * the cached attributes' status to FAIL.
|
|
|
b663b9 |
+ */
|
|
|
b663b9 |
+ for (size_t j = 0; j < c_ContextEval->acle_numof_attrs; j++) {
|
|
|
b663b9 |
+ c_attrEval = &c_ContextEval->acle_attrEval[j];
|
|
|
b663b9 |
+ c_attrEval->attrEval_r_status &= ~ACL_ATTREVAL_SUCCESS;
|
|
|
b663b9 |
+ c_attrEval->attrEval_r_status |= ACL_ATTREVAL_FAIL;
|
|
|
b663b9 |
+ c_attrEval->attrEval_s_status &= ~ACL_ATTREVAL_SUCCESS;
|
|
|
b663b9 |
+ c_attrEval->attrEval_s_status |= ACL_ATTREVAL_FAIL;
|
|
|
b663b9 |
+ }
|
|
|
b663b9 |
return LDAP_INSUFFICIENT_ACCESS;
|
|
|
b663b9 |
+ }
|
|
|
b663b9 |
|
|
|
b663b9 |
/* The other case is I don't have an
|
|
|
b663b9 |
** explicit allow rule -- which is fine.
|
|
|
b663b9 |
@@ -2908,6 +2922,12 @@ acl__TestRights(Acl_PBlock *aclpb, int access, const char **right, const char **
|
|
|
b663b9 |
result_reason->deciding_aci = NULL;
|
|
|
b663b9 |
result_reason->reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS;
|
|
|
b663b9 |
|
|
|
b663b9 |
+ /* If we have deny handles we should process them */
|
|
|
b663b9 |
+ if (aclpb->aclpb_num_deny_handles > 0) {
|
|
|
b663b9 |
+ aclpb->aclpb_state &= ~ACLPB_EXECUTING_ALLOW_HANDLES;
|
|
|
b663b9 |
+ aclpb->aclpb_state |= ACLPB_EXECUTING_DENY_HANDLES;
|
|
|
b663b9 |
+ }
|
|
|
b663b9 |
+
|
|
|
b663b9 |
TNF_PROBE_1_DEBUG(acl__TestRights_end, "ACL", "",
|
|
|
b663b9 |
tnf_string, no_allows, "");
|
|
|
b663b9 |
|
|
|
b663b9 |
--
|
|
|
b663b9 |
2.17.0
|
|
|
b663b9 |
|