ticket1347760_test.py 18 KB

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