ticket1347760_test.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. # --- BEGIN COPYRIGHT BLOCK ---
  2. # Copyright (C) 2016 Red Hat, Inc.
  3. # All rights reserved.
  4. #
  5. # License: GPL (version 3 or any later version).
  6. # See LICENSE for details.
  7. # --- END COPYRIGHT BLOCK ---
  8. #
  9. import os
  10. import sys
  11. import time
  12. import ldap
  13. import logging
  14. import pytest
  15. from subprocess import Popen
  16. from lib389 import DirSrv, Entry, tools, tasks
  17. from lib389.tools import DirSrvTools
  18. from lib389._constants import *
  19. from lib389.properties import *
  20. from lib389.tasks import *
  21. from lib389.utils import *
  22. logging.getLogger(__name__).setLevel(logging.DEBUG)
  23. log = logging.getLogger(__name__)
  24. installation1_prefix = None
  25. CONFIG_DN = 'cn=config'
  26. BOU = 'BOU'
  27. BINDOU = 'ou=%s,%s' % (BOU, DEFAULT_SUFFIX)
  28. BUID = 'buser123'
  29. TUID = 'tuser0'
  30. BINDDN = 'uid=%s,%s' % (BUID, BINDOU)
  31. BINDPW = BUID
  32. TESTDN = 'uid=%s,ou=people,%s' % (TUID, DEFAULT_SUFFIX)
  33. TESTPW = TUID
  34. BOGUSDN = 'uid=bogus,%s' % DEFAULT_SUFFIX
  35. BOGUSDN2 = 'uid=bogus,ou=people,%s' % DEFAULT_SUFFIX
  36. BOGUSSUFFIX = 'uid=bogus,ou=people,dc=bogus'
  37. GROUPOU = 'ou=groups,%s' % DEFAULT_SUFFIX
  38. BOGUSOU = 'ou=OU,%s' % DEFAULT_SUFFIX
  39. logging.getLogger(__name__).setLevel(logging.DEBUG)
  40. log = logging.getLogger(__name__)
  41. installation1_prefix = None
  42. class TopologyStandalone(object):
  43. def __init__(self, standalone):
  44. standalone.open()
  45. self.standalone = standalone
  46. @pytest.fixture(scope="module")
  47. def topology(request):
  48. global installation1_prefix
  49. if installation1_prefix:
  50. args_instance[SER_DEPLOYED_DIR] = installation1_prefix
  51. # Creating standalone instance ...
  52. standalone = DirSrv(verbose=False)
  53. args_instance[SER_HOST] = HOST_STANDALONE
  54. args_instance[SER_PORT] = PORT_STANDALONE
  55. args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
  56. args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
  57. args_standalone = args_instance.copy()
  58. standalone.allocate(args_standalone)
  59. instance_standalone = standalone.exists()
  60. if instance_standalone:
  61. standalone.delete()
  62. standalone.create()
  63. standalone.open()
  64. # Delete each instance in the end
  65. def fin():
  66. standalone.delete()
  67. request.addfinalizer(fin)
  68. # Clear out the tmp dir
  69. standalone.clearTmpDir(__file__)
  70. return TopologyStandalone(standalone)
  71. def pattern_accesslog(file, log_pattern):
  72. for i in range(5):
  73. try:
  74. pattern_accesslog.last_pos += 1
  75. except AttributeError:
  76. pattern_accesslog.last_pos = 0
  77. found = None
  78. file.seek(pattern_accesslog.last_pos)
  79. # Use a while true iteration because 'for line in file: hit a
  80. # python bug that break file.tell()
  81. while True:
  82. line = file.readline()
  83. found = log_pattern.search(line)
  84. if ((line == '') or (found)):
  85. break
  86. pattern_accesslog.last_pos = file.tell()
  87. if found:
  88. return line
  89. else:
  90. time.sleep(1)
  91. return None
  92. def check_op_result(server, op, dn, superior, exists, rc):
  93. targetdn = dn
  94. if op == 'search':
  95. if exists:
  96. opstr = 'Searching existing entry'
  97. else:
  98. opstr = 'Searching non-existing entry'
  99. elif op == 'add':
  100. if exists:
  101. opstr = 'Adding existing entry'
  102. else:
  103. opstr = 'Adding non-existing entry'
  104. elif op == 'modify':
  105. if exists:
  106. opstr = 'Modifying existing entry'
  107. else:
  108. opstr = 'Modifying non-existing entry'
  109. elif op == 'modrdn':
  110. if superior != None:
  111. targetdn = superior
  112. if exists:
  113. opstr = 'Moving to existing superior'
  114. else:
  115. opstr = 'Moving to non-existing superior'
  116. else:
  117. if exists:
  118. opstr = 'Renaming existing entry'
  119. else:
  120. opstr = 'Renaming non-existing entry'
  121. elif op == 'delete':
  122. if exists:
  123. opstr = 'Deleting existing entry'
  124. else:
  125. opstr = 'Deleting non-existing entry'
  126. if ldap.SUCCESS == rc:
  127. expstr = 'be ok'
  128. else:
  129. expstr = 'fail with %s' % rc.__name__
  130. log.info('%s %s, which should %s.' % (opstr, targetdn, expstr))
  131. hit = 0
  132. try:
  133. if op == 'search':
  134. centry = server.search_s(dn, ldap.SCOPE_BASE, 'objectclass=*')
  135. elif op == 'add':
  136. server.add_s(Entry((dn, {'objectclass': 'top extensibleObject'.split(),
  137. 'cn': 'test entry'})))
  138. elif op == 'modify':
  139. server.modify_s(dn, [(ldap.MOD_REPLACE, 'description', 'test')])
  140. elif op == 'modrdn':
  141. if superior != None:
  142. server.rename_s(dn, 'uid=new', newsuperior=superior, delold=1)
  143. else:
  144. server.rename_s(dn, 'uid=new', delold=1)
  145. elif op == 'delete':
  146. server.delete_s(dn)
  147. else:
  148. log.fatal('Unknown operation %s' % op)
  149. assert False
  150. except ldap.LDAPError as e:
  151. hit = 1
  152. log.info("Exception (expected): %s" % type(e).__name__)
  153. log.info('Desc ' + e.message['desc'])
  154. assert isinstance(e, rc)
  155. if e.message.has_key('matched'):
  156. log.info('Matched is returned: ' + e.message['matched'])
  157. if rc != ldap.NO_SUCH_OBJECT:
  158. assert False
  159. if ldap.SUCCESS == rc:
  160. if op == 'search':
  161. log.info('Search should return none')
  162. assert len(centry) == 0
  163. else:
  164. if 0 == hit:
  165. log.info('Expected to fail with %s, but passed' % rc.__name__)
  166. assert False
  167. log.info('PASSED\n')
  168. def test_ticket1347760(topology):
  169. """
  170. Prevent revealing the entry info to whom has no access rights.
  171. """
  172. log.info('Testing Bug 1347760 - Information disclosure via repeated use of LDAP ADD operation, etc.')
  173. log.info('Disabling accesslog logbuffering')
  174. topology.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-accesslog-logbuffering', 'off')])
  175. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  176. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  177. log.info('Adding ou=%s a bind user belongs to.' % BOU)
  178. topology.standalone.add_s(Entry((BINDOU, {
  179. 'objectclass': 'top organizationalunit'.split(),
  180. 'ou': BOU})))
  181. log.info('Adding a bind user.')
  182. topology.standalone.add_s(Entry((BINDDN,
  183. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  184. 'cn': 'bind user',
  185. 'sn': 'user',
  186. 'userPassword': BINDPW})))
  187. log.info('Adding a test user.')
  188. topology.standalone.add_s(Entry((TESTDN,
  189. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  190. 'cn': 'test user',
  191. 'sn': 'user',
  192. 'userPassword': TESTPW})))
  193. log.info('Deleting aci in %s.' % DEFAULT_SUFFIX)
  194. topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_DELETE, 'aci', None)])
  195. log.info('Bind case 1. the bind user has no rights to read the entry itself, bind should be successful.')
  196. log.info('Bind as {%s,%s} who has no access rights.' % (BINDDN, BINDPW))
  197. try:
  198. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  199. except ldap.LDAPError as e:
  200. log.info('Desc ' + e.message['desc'])
  201. assert False
  202. file_path = os.path.join(topology.standalone.prefix, 'var/log/dirsrv/slapd-%s/access' % topology.standalone.serverid)
  203. file_obj = open(file_path, "r")
  204. log.info('Access log path: %s' % file_path)
  205. log.info('Bind case 2-1. the bind user does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  206. log.info('Bind as {%s,%s} who does not exist.' % (BOGUSDN, 'bogus'))
  207. try:
  208. topology.standalone.simple_bind_s(BOGUSDN, 'bogus')
  209. except ldap.LDAPError as e:
  210. log.info("Exception (expected): %s" % type(e).__name__)
  211. log.info('Desc ' + e.message['desc'])
  212. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  213. regex = re.compile('No such entry')
  214. cause = pattern_accesslog(file_obj, regex)
  215. if cause == None:
  216. log.fatal('Cause not found - %s' % cause)
  217. assert False
  218. else:
  219. log.info('Cause found - %s' % cause)
  220. log.info('Bind case 2-2. the bind user\'s suffix does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  221. log.info('Bind as {%s,%s} who does not exist.' % (BOGUSSUFFIX, 'bogus'))
  222. try:
  223. topology.standalone.simple_bind_s(BOGUSSUFFIX, 'bogus')
  224. except ldap.LDAPError as e:
  225. log.info("Exception (expected): %s" % type(e).__name__)
  226. log.info('Desc ' + e.message['desc'])
  227. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  228. regex = re.compile('No such suffix')
  229. cause = pattern_accesslog(file_obj, regex)
  230. if cause == None:
  231. log.fatal('Cause not found - %s' % cause)
  232. assert False
  233. else:
  234. log.info('Cause found - %s' % cause)
  235. log.info('Bind case 2-3. the bind user\'s password is wrong, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  236. log.info('Bind as {%s,%s} who does not exist.' % (BINDDN, 'bogus'))
  237. try:
  238. topology.standalone.simple_bind_s(BINDDN, 'bogus')
  239. except ldap.LDAPError as e:
  240. log.info("Exception (expected): %s" % type(e).__name__)
  241. log.info('Desc ' + e.message['desc'])
  242. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  243. regex = re.compile('Invalid credentials')
  244. cause = pattern_accesslog(file_obj, regex)
  245. if cause == None:
  246. log.fatal('Cause not found - %s' % cause)
  247. assert False
  248. else:
  249. log.info('Cause found - %s' % cause)
  250. log.info('Adding aci for %s to %s.' % (BINDDN, BINDOU))
  251. acival = '(targetattr="*")(version 3.0; acl "%s"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  252. log.info('aci: %s' % acival)
  253. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  254. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  255. topology.standalone.modify_s(BINDOU, [(ldap.MOD_ADD, 'aci', acival)])
  256. log.info('Bind case 3. the bind user has the right to read the entry itself, bind should be successful.')
  257. log.info('Bind as {%s,%s} which should be ok.\n' % (BINDDN, BINDPW))
  258. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  259. log.info('The following operations are against the subtree the bind user %s has no rights.' % BINDDN)
  260. # Search
  261. exists = True
  262. rc = ldap.SUCCESS
  263. 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)
  264. check_op_result(topology.standalone, 'search', TESTDN, None, exists, rc)
  265. exists = False
  266. rc = ldap.SUCCESS
  267. log.info('Search case 2-1. the search entry does not exist, the search should return no search results with %s' % rc.__name__)
  268. check_op_result(topology.standalone, 'search', BOGUSDN, None, exists, rc)
  269. exists = False
  270. rc = ldap.SUCCESS
  271. log.info('Search case 2-2. the search entry does not exist, the search should return no search results with %s' % rc.__name__)
  272. check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc)
  273. # Add
  274. exists = True
  275. rc = ldap.INSUFFICIENT_ACCESS
  276. log.info('Add case 1. the bind user has no rights AND the adding entry exists, it should fail with %s' % rc.__name__)
  277. check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc)
  278. exists = False
  279. rc = ldap.INSUFFICIENT_ACCESS
  280. 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__)
  281. check_op_result(topology.standalone, 'add', BOGUSDN, None, exists, rc)
  282. exists = False
  283. rc = ldap.INSUFFICIENT_ACCESS
  284. 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__)
  285. check_op_result(topology.standalone, 'add', BOGUSDN2, None, exists, rc)
  286. # Modify
  287. exists = True
  288. rc = ldap.INSUFFICIENT_ACCESS
  289. log.info('Modify case 1. the bind user has no rights AND the modifying entry exists, it should fail with %s' % rc.__name__)
  290. check_op_result(topology.standalone, 'modify', TESTDN, None, exists, rc)
  291. exists = False
  292. rc = ldap.INSUFFICIENT_ACCESS
  293. 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__)
  294. check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc)
  295. exists = False
  296. rc = ldap.INSUFFICIENT_ACCESS
  297. 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__)
  298. check_op_result(topology.standalone, 'modify', BOGUSDN2, None, exists, rc)
  299. # Modrdn
  300. exists = True
  301. rc = ldap.INSUFFICIENT_ACCESS
  302. log.info('Modrdn case 1. the bind user has no rights AND the renaming entry exists, it should fail with %s' % rc.__name__)
  303. check_op_result(topology.standalone, 'modrdn', TESTDN, None, exists, rc)
  304. exists = False
  305. rc = ldap.INSUFFICIENT_ACCESS
  306. 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__)
  307. check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  308. exists = False
  309. rc = ldap.INSUFFICIENT_ACCESS
  310. 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__)
  311. check_op_result(topology.standalone, 'modrdn', BOGUSDN2, None, exists, rc)
  312. exists = True
  313. rc = ldap.INSUFFICIENT_ACCESS
  314. 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__)
  315. check_op_result(topology.standalone, 'modrdn', TESTDN, GROUPOU, exists, rc)
  316. exists = False
  317. rc = ldap.INSUFFICIENT_ACCESS
  318. 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__)
  319. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  320. exists = False
  321. rc = ldap.INSUFFICIENT_ACCESS
  322. 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__)
  323. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  324. # Delete
  325. exists = True
  326. rc = ldap.INSUFFICIENT_ACCESS
  327. log.info('Delete case 1. the bind user has no rights AND the deleting entry exists, it should fail with %s' % rc.__name__)
  328. check_op_result(topology.standalone, 'delete', TESTDN, None, exists, rc)
  329. exists = False
  330. rc = ldap.INSUFFICIENT_ACCESS
  331. 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__)
  332. check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc)
  333. exists = False
  334. rc = ldap.INSUFFICIENT_ACCESS
  335. 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__)
  336. check_op_result(topology.standalone, 'delete', BOGUSDN2, None, exists, rc)
  337. log.info('EXTRA: Check no regressions')
  338. log.info('Adding aci for %s to %s.' % (BINDDN, DEFAULT_SUFFIX))
  339. acival = '(targetattr="*")(version 3.0; acl "%s-all"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  340. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  341. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  342. topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', acival)])
  343. log.info('Bind as {%s,%s}.' % (BINDDN, BINDPW))
  344. try:
  345. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  346. except ldap.LDAPError as e:
  347. log.info('Desc ' + e.message['desc'])
  348. assert False
  349. exists = False
  350. rc = ldap.NO_SUCH_OBJECT
  351. log.info('Search case. the search entry does not exist, the search should fail with %s' % rc.__name__)
  352. check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc)
  353. file_obj.close()
  354. exists = True
  355. rc = ldap.ALREADY_EXISTS
  356. log.info('Add case. the adding entry already exists, it should fail with %s' % rc.__name__)
  357. check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc)
  358. exists = False
  359. rc = ldap.NO_SUCH_OBJECT
  360. log.info('Modify case. the modifying entry does not exist, it should fail with %s' % rc.__name__)
  361. check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc)
  362. exists = False
  363. rc = ldap.NO_SUCH_OBJECT
  364. log.info('Modrdn case 1. the renaming entry does not exist, it should fail with %s' % rc.__name__)
  365. check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  366. exists = False
  367. rc = ldap.NO_SUCH_OBJECT
  368. log.info('Modrdn case 2. the node moving an entry to does not, it should fail with %s' % rc.__name__)
  369. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  370. exists = False
  371. rc = ldap.NO_SUCH_OBJECT
  372. log.info('Delete case. the deleting entry does not exist, it should fail with %s' % rc.__name__)
  373. check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc)
  374. log.info('Inactivate %s' % BINDDN)
  375. nsinactivate = '%s/sbin/ns-inactivate.pl' % topology.standalone.prefix
  376. p = Popen([nsinactivate, '-Z', 'standalone', '-D', DN_DM, '-w', PASSWORD, '-I', BINDDN])
  377. assert(p.wait() == 0)
  378. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, BUID, ldap.UNWILLING_TO_PERFORM.__name__))
  379. try:
  380. topology.standalone.simple_bind_s(BINDDN, BUID)
  381. except ldap.LDAPError as e:
  382. log.info("Exception (expected): %s" % type(e).__name__)
  383. log.info('Desc ' + e.message['desc'])
  384. assert isinstance(e, ldap.UNWILLING_TO_PERFORM)
  385. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, 'bogus', ldap.INVALID_CREDENTIALS.__name__))
  386. try:
  387. topology.standalone.simple_bind_s(BINDDN, 'bogus')
  388. except ldap.LDAPError as e:
  389. log.info("Exception (expected): %s" % type(e).__name__)
  390. log.info('Desc ' + e.message['desc'])
  391. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  392. log.info('SUCCESS')
  393. if __name__ == '__main__':
  394. # Run isolated
  395. # -s for DEBUG mode
  396. CURRENT_FILE = os.path.realpath(__file__)
  397. pytest.main("-s %s" % CURRENT_FILE)