# --- 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 os import time import ldap import logging import pytest from subprocess import Popen from lib389 import DirSrv, Entry from lib389._constants import * from lib389.properties import * from lib389.tasks import * from lib389.utils import * logging.getLogger(__name__).setLevel(logging.DEBUG) log = logging.getLogger(__name__) installation1_prefix = None CONFIG_DN = 'cn=config' BOU = 'BOU' BINDOU = 'ou=%s,%s' % (BOU, DEFAULT_SUFFIX) BUID = 'buser123' TUID = 'tuser0' BINDDN = 'uid=%s,%s' % (BUID, BINDOU) BINDPW = BUID TESTDN = 'uid=%s,ou=people,%s' % (TUID, DEFAULT_SUFFIX) TESTPW = TUID BOGUSDN = 'uid=bogus,%s' % DEFAULT_SUFFIX BOGUSDN2 = 'uid=bogus,ou=people,%s' % DEFAULT_SUFFIX BOGUSSUFFIX = 'uid=bogus,ou=people,dc=bogus' GROUPOU = 'ou=groups,%s' % DEFAULT_SUFFIX BOGUSOU = 'ou=OU,%s' % DEFAULT_SUFFIX logging.getLogger(__name__).setLevel(logging.DEBUG) log = logging.getLogger(__name__) installation1_prefix = None class TopologyStandalone(object): def __init__(self, standalone): standalone.open() self.standalone = standalone @pytest.fixture(scope="module") def topology(request): global installation1_prefix if installation1_prefix: args_instance[SER_DEPLOYED_DIR] = installation1_prefix # Creating standalone instance ... standalone = DirSrv(verbose=False) args_instance[SER_HOST] = HOST_STANDALONE args_instance[SER_PORT] = PORT_STANDALONE args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX args_standalone = args_instance.copy() standalone.allocate(args_standalone) instance_standalone = standalone.exists() if instance_standalone: standalone.delete() standalone.create() standalone.open() # Delete each instance in the end def fin(): standalone.delete() request.addfinalizer(fin) # Clear out the tmp dir standalone.clearTmpDir(__file__) return TopologyStandalone(standalone) def pattern_accesslog(file, log_pattern): for i in range(5): try: pattern_accesslog.last_pos += 1 except AttributeError: pattern_accesslog.last_pos = 0 found = None file.seek(pattern_accesslog.last_pos) # Use a while true iteration because 'for line in file: hit a # python bug that break file.tell() while True: line = file.readline() found = log_pattern.search(line) if ((line == '') or (found)): break pattern_accesslog.last_pos = file.tell() if found: return line else: time.sleep(1) return None def check_op_result(server, op, dn, superior, exists, rc): targetdn = dn if op == 'search': if exists: opstr = 'Searching existing entry' else: opstr = 'Searching non-existing entry' elif op == 'add': if exists: opstr = 'Adding existing entry' else: opstr = 'Adding non-existing entry' elif op == 'modify': if exists: opstr = 'Modifying existing entry' else: opstr = 'Modifying non-existing entry' elif op == 'modrdn': if superior != None: targetdn = superior if exists: opstr = 'Moving to existing superior' else: opstr = 'Moving to non-existing superior' else: if exists: opstr = 'Renaming existing entry' else: opstr = 'Renaming non-existing entry' elif op == 'delete': if exists: opstr = 'Deleting existing entry' else: opstr = 'Deleting non-existing entry' if ldap.SUCCESS == rc: expstr = 'be ok' else: expstr = 'fail with %s' % rc.__name__ log.info('%s %s, which should %s.' % (opstr, targetdn, expstr)) time.sleep(1) hit = 0 try: if op == 'search': centry = server.search_s(dn, ldap.SCOPE_BASE, 'objectclass=*') elif op == 'add': server.add_s(Entry((dn, {'objectclass': 'top extensibleObject'.split(), 'cn': 'test entry'}))) elif op == 'modify': server.modify_s(dn, [(ldap.MOD_REPLACE, 'description', 'test')]) elif op == 'modrdn': if superior != None: server.rename_s(dn, 'uid=new', newsuperior=superior, delold=1) else: server.rename_s(dn, 'uid=new', delold=1) elif op == 'delete': server.delete_s(dn) else: log.fatal('Unknown operation %s' % op) assert False except ldap.LDAPError as e: hit = 1 log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, rc) if e.message.has_key('matched'): log.info('Matched is returned: ' + e.message['matched']) if rc != ldap.NO_SUCH_OBJECT: assert False if ldap.SUCCESS == rc: if op == 'search': log.info('Search should return none') assert len(centry) == 0 else: if 0 == hit: log.info('Expected to fail with %s, but passed' % rc.__name__) assert False log.info('PASSED\n') def test_ticket1347760(topology): """ Prevent revealing the entry info to whom has no access rights. """ log.info('Testing Bug 1347760 - Information disclosure via repeated use of LDAP ADD operation, etc.') log.info('Disabling accesslog logbuffering') topology.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-accesslog-logbuffering', 'off')]) log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD)) topology.standalone.simple_bind_s(DN_DM, PASSWORD) log.info('Adding ou=%s a bind user belongs to.' % BOU) topology.standalone.add_s(Entry((BINDOU, { 'objectclass': 'top organizationalunit'.split(), 'ou': BOU}))) log.info('Adding a bind user.') topology.standalone.add_s(Entry((BINDDN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(), 'cn': 'bind user', 'sn': 'user', 'userPassword': BINDPW}))) log.info('Adding a test user.') topology.standalone.add_s(Entry((TESTDN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(), 'cn': 'test user', 'sn': 'user', 'userPassword': TESTPW}))) log.info('Deleting aci in %s.' % DEFAULT_SUFFIX) topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_DELETE, 'aci', None)]) log.info('Bind case 1. the bind user has no rights to read the entry itself, bind should be successful.') log.info('Bind as {%s,%s} who has no access rights.' % (BINDDN, BINDPW)) try: topology.standalone.simple_bind_s(BINDDN, BINDPW) except ldap.LDAPError as e: log.info('Desc ' + e.message['desc']) assert False file_path = os.path.join(topology.standalone.prefix, 'var/log/dirsrv/slapd-%s/access' % topology.standalone.serverid) file_obj = open(file_path, "r") log.info('Access log path: %s' % file_path) log.info('Bind case 2-1. the bind user does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__) log.info('Bind as {%s,%s} who does not exist.' % (BOGUSDN, 'bogus')) try: topology.standalone.simple_bind_s(BOGUSDN, 'bogus') except ldap.LDAPError as e: log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, ldap.INVALID_CREDENTIALS) regex = re.compile('No such entry') cause = pattern_accesslog(file_obj, regex) if cause == None: log.fatal('Cause not found - %s' % cause) assert False else: log.info('Cause found - %s' % cause) time.sleep(1) log.info('Bind case 2-2. the bind user\'s suffix does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__) log.info('Bind as {%s,%s} who does not exist.' % (BOGUSSUFFIX, 'bogus')) try: topology.standalone.simple_bind_s(BOGUSSUFFIX, 'bogus') except ldap.LDAPError as e: log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, ldap.INVALID_CREDENTIALS) regex = re.compile('No such suffix') cause = pattern_accesslog(file_obj, regex) if cause == None: log.fatal('Cause not found - %s' % cause) assert False else: log.info('Cause found - %s' % cause) time.sleep(1) log.info('Bind case 2-3. the bind user\'s password is wrong, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__) log.info('Bind as {%s,%s} who does not exist.' % (BINDDN, 'bogus')) try: topology.standalone.simple_bind_s(BINDDN, 'bogus') except ldap.LDAPError as e: log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, ldap.INVALID_CREDENTIALS) regex = re.compile('Invalid credentials') cause = pattern_accesslog(file_obj, regex) if cause == None: log.fatal('Cause not found - %s' % cause) assert False else: log.info('Cause found - %s' % cause) time.sleep(1) log.info('Adding aci for %s to %s.' % (BINDDN, BINDOU)) acival = '(targetattr="*")(version 3.0; acl "%s"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN) log.info('aci: %s' % acival) log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD)) topology.standalone.simple_bind_s(DN_DM, PASSWORD) topology.standalone.modify_s(BINDOU, [(ldap.MOD_ADD, 'aci', acival)]) time.sleep(1) log.info('Bind case 3. the bind user has the right to read the entry itself, bind should be successful.') log.info('Bind as {%s,%s} which should be ok.\n' % (BINDDN, BINDPW)) topology.standalone.simple_bind_s(BINDDN, BINDPW) log.info('The following operations are against the subtree the bind user %s has no rights.' % BINDDN) # Search exists = True rc = ldap.SUCCESS log.info('Search case 1. the bind user has no rights to read the search entry, it should return no search results with %s' % rc) check_op_result(topology.standalone, 'search', TESTDN, None, exists, rc) exists = False rc = ldap.SUCCESS log.info('Search case 2-1. the search entry does not exist, the search should return no search results with %s' % rc.__name__) check_op_result(topology.standalone, 'search', BOGUSDN, None, exists, rc) exists = False rc = ldap.SUCCESS log.info('Search case 2-2. the search entry does not exist, the search should return no search results with %s' % rc.__name__) check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc) # Add exists = True rc = ldap.INSUFFICIENT_ACCESS log.info('Add case 1. the bind user has no rights AND the adding entry exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Add case 2-1. the bind user has no rights AND the adding entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'add', BOGUSDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Add case 2-2. the bind user has no rights AND the adding entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'add', BOGUSDN2, None, exists, rc) # Modify exists = True rc = ldap.INSUFFICIENT_ACCESS log.info('Modify case 1. the bind user has no rights AND the modifying entry exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modify', TESTDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modify case 2-1. the bind user has no rights AND the modifying entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modify case 2-2. the bind user has no rights AND the modifying entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modify', BOGUSDN2, None, exists, rc) # Modrdn exists = True rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 1. the bind user has no rights AND the renaming entry exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', TESTDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 2-1. the bind user has no rights AND the renaming entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 2-2. the bind user has no rights AND the renaming entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', BOGUSDN2, None, exists, rc) exists = True rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 3. the bind user has no rights AND the node moving an entry to exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', TESTDN, GROUPOU, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 4-1. the bind user has no rights AND the node moving an entry to does not, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Modrdn case 4-2. the bind user has no rights AND the node moving an entry to does not, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc) # Delete exists = True rc = ldap.INSUFFICIENT_ACCESS log.info('Delete case 1. the bind user has no rights AND the deleting entry exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'delete', TESTDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Delete case 2-1. the bind user has no rights AND the deleting entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc) exists = False rc = ldap.INSUFFICIENT_ACCESS log.info('Delete case 2-2. the bind user has no rights AND the deleting entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'delete', BOGUSDN2, None, exists, rc) log.info('EXTRA: Check no regressions') log.info('Adding aci for %s to %s.' % (BINDDN, DEFAULT_SUFFIX)) acival = '(targetattr="*")(version 3.0; acl "%s-all"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN) log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD)) topology.standalone.simple_bind_s(DN_DM, PASSWORD) topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', acival)]) time.sleep(1) log.info('Bind as {%s,%s}.' % (BINDDN, BINDPW)) try: topology.standalone.simple_bind_s(BINDDN, BINDPW) except ldap.LDAPError as e: log.info('Desc ' + e.message['desc']) assert False time.sleep(1) exists = False rc = ldap.NO_SUCH_OBJECT log.info('Search case. the search entry does not exist, the search should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc) file_obj.close() exists = True rc = ldap.ALREADY_EXISTS log.info('Add case. the adding entry already exists, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc) exists = False rc = ldap.NO_SUCH_OBJECT log.info('Modify case. the modifying entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc) exists = False rc = ldap.NO_SUCH_OBJECT log.info('Modrdn case 1. the renaming entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc) exists = False rc = ldap.NO_SUCH_OBJECT log.info('Modrdn case 2. the node moving an entry to does not, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc) exists = False rc = ldap.NO_SUCH_OBJECT log.info('Delete case. the deleting entry does not exist, it should fail with %s' % rc.__name__) check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc) log.info('Inactivate %s' % BINDDN) nsinactivate = '%s/sbin/ns-inactivate.pl' % topology.standalone.prefix p = Popen([nsinactivate, '-Z', 'standalone', '-D', DN_DM, '-w', PASSWORD, '-I', BINDDN]) assert(p.wait() == 0) log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, BUID, ldap.UNWILLING_TO_PERFORM.__name__)) try: topology.standalone.simple_bind_s(BINDDN, BUID) except ldap.LDAPError as e: log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, ldap.UNWILLING_TO_PERFORM) log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, 'bogus', ldap.INVALID_CREDENTIALS.__name__)) try: topology.standalone.simple_bind_s(BINDDN, 'bogus') except ldap.LDAPError as e: log.info("Exception (expected): %s" % type(e).__name__) log.info('Desc ' + e.message['desc']) assert isinstance(e, ldap.INVALID_CREDENTIALS) log.info('SUCCESS') if __name__ == '__main__': # Run isolated # -s for DEBUG mode CURRENT_FILE = os.path.realpath(__file__) pytest.main("-s %s" % CURRENT_FILE)