ticket1347760_test.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. try:
  73. pattern_accesslog.last_pos += 1
  74. except AttributeError:
  75. pattern_accesslog.last_pos = 0
  76. found = None
  77. file.seek(pattern_accesslog.last_pos)
  78. # Use a while true iteration because 'for line in file: hit a
  79. # python bug that break file.tell()
  80. while True:
  81. line = file.readline()
  82. found = log_pattern.search(line)
  83. if ((line == '') or (found)):
  84. break
  85. pattern_accesslog.last_pos = file.tell()
  86. if found:
  87. return line
  88. else:
  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. hit = 0
  130. try:
  131. if op == 'search':
  132. centry = server.search_s(dn, ldap.SCOPE_BASE, 'objectclass=*')
  133. elif op == 'add':
  134. server.add_s(Entry((dn, {'objectclass': 'top extensibleObject'.split(),
  135. 'cn': 'test entry'})))
  136. elif op == 'modify':
  137. server.modify_s(dn, [(ldap.MOD_REPLACE, 'description', 'test')])
  138. elif op == 'modrdn':
  139. if superior != None:
  140. server.rename_s(dn, 'uid=new', newsuperior=superior, delold=1)
  141. else:
  142. server.rename_s(dn, 'uid=new', delold=1)
  143. elif op == 'delete':
  144. server.delete_s(dn)
  145. else:
  146. log.fatal('Unknown operation %s' % op)
  147. assert False
  148. except ldap.LDAPError as e:
  149. hit = 1
  150. log.info("Exception (expected): %s" % type(e).__name__)
  151. log.info('Desc ' + e.message['desc'])
  152. assert isinstance(e, rc)
  153. if e.message.has_key('matched'):
  154. log.info('Matched is returned: ' + e.message['matched'])
  155. if rc != ldap.NO_SUCH_OBJECT:
  156. assert False
  157. if ldap.SUCCESS == rc:
  158. if op == 'search':
  159. log.info('Search should return none')
  160. assert len(centry) == 0
  161. else:
  162. if 0 == hit:
  163. log.info('Expected to fail with %s, but passed' % rc.__name__)
  164. assert False
  165. log.info('PASSED\n')
  166. def test_ticket1347760(topology):
  167. """
  168. Prevent revealing the entry info to whom has no access rights.
  169. """
  170. log.info('Testing Bug 1347760 - Information disclosure via repeated use of LDAP ADD operation, etc.')
  171. log.info('Disabling accesslog logbuffering')
  172. topology.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-accesslog-logbuffering', 'off')])
  173. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  174. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  175. log.info('Adding ou=%s a bind user belongs to.' % BOU)
  176. topology.standalone.add_s(Entry((BINDOU, {
  177. 'objectclass': 'top organizationalunit'.split(),
  178. 'ou': BOU})))
  179. log.info('Adding a bind user.')
  180. topology.standalone.add_s(Entry((BINDDN,
  181. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  182. 'cn': 'bind user',
  183. 'sn': 'user',
  184. 'userPassword': BINDPW})))
  185. log.info('Adding a test user.')
  186. topology.standalone.add_s(Entry((TESTDN,
  187. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  188. 'cn': 'test user',
  189. 'sn': 'user',
  190. 'userPassword': TESTPW})))
  191. log.info('Deleting aci in %s.' % DEFAULT_SUFFIX)
  192. topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_DELETE, 'aci', None)])
  193. log.info('Bind case 1. the bind user has no rights to read the entry itself, bind should be successful.')
  194. log.info('Bind as {%s,%s} who has no access rights.' % (BINDDN, BINDPW))
  195. try:
  196. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  197. except ldap.LDAPError as e:
  198. log.info('Desc ' + e.message['desc'])
  199. assert False
  200. file_path = os.path.join(topology.standalone.prefix, 'var/log/dirsrv/slapd-%s/access' % topology.standalone.serverid)
  201. file_obj = open(file_path, "r")
  202. log.info('Access log path: %s' % file_path)
  203. log.info('Bind case 2-1. the bind user does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  204. log.info('Bind as {%s,%s} who does not exist.' % (BOGUSDN, 'bogus'))
  205. try:
  206. topology.standalone.simple_bind_s(BOGUSDN, 'bogus')
  207. except ldap.LDAPError as e:
  208. log.info("Exception (expected): %s" % type(e).__name__)
  209. log.info('Desc ' + e.message['desc'])
  210. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  211. regex = re.compile('No such entry')
  212. cause = pattern_accesslog(file_obj, regex)
  213. if cause == None:
  214. log.fatal('Cause not found - %s' % cause)
  215. assert False
  216. else:
  217. log.info('Cause found - %s' % cause)
  218. log.info('Bind case 2-2. the bind user\'s suffix does not exist, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  219. log.info('Bind as {%s,%s} who does not exist.' % (BOGUSSUFFIX, 'bogus'))
  220. try:
  221. topology.standalone.simple_bind_s(BOGUSSUFFIX, 'bogus')
  222. except ldap.LDAPError as e:
  223. log.info("Exception (expected): %s" % type(e).__name__)
  224. log.info('Desc ' + e.message['desc'])
  225. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  226. regex = re.compile('No such suffix')
  227. cause = pattern_accesslog(file_obj, regex)
  228. if cause == None:
  229. log.fatal('Cause not found - %s' % cause)
  230. assert False
  231. else:
  232. log.info('Cause found - %s' % cause)
  233. log.info('Bind case 2-3. the bind user\'s password is wrong, bind should fail with error %s' % ldap.INVALID_CREDENTIALS.__name__)
  234. log.info('Bind as {%s,%s} who does not exist.' % (BINDDN, 'bogus'))
  235. try:
  236. topology.standalone.simple_bind_s(BINDDN, 'bogus')
  237. except ldap.LDAPError as e:
  238. log.info("Exception (expected): %s" % type(e).__name__)
  239. log.info('Desc ' + e.message['desc'])
  240. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  241. regex = re.compile('Invalid credentials')
  242. cause = pattern_accesslog(file_obj, regex)
  243. if cause == None:
  244. log.fatal('Cause not found - %s' % cause)
  245. assert False
  246. else:
  247. log.info('Cause found - %s' % cause)
  248. log.info('Adding aci for %s to %s.' % (BINDDN, BINDOU))
  249. acival = '(targetattr="*")(version 3.0; acl "%s"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  250. log.info('aci: %s' % acival)
  251. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  252. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  253. topology.standalone.modify_s(BINDOU, [(ldap.MOD_ADD, 'aci', acival)])
  254. log.info('Bind case 3. the bind user has the right to read the entry itself, bind should be successful.')
  255. log.info('Bind as {%s,%s} which should be ok.\n' % (BINDDN, BINDPW))
  256. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  257. log.info('The following operations are against the subtree the bind user %s has no rights.' % BINDDN)
  258. # Search
  259. exists = True
  260. rc = ldap.SUCCESS
  261. 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)
  262. check_op_result(topology.standalone, 'search', TESTDN, None, exists, rc)
  263. exists = False
  264. rc = ldap.SUCCESS
  265. log.info('Search case 2-1. the search entry does not exist, the search should return no search results with %s' % rc.__name__)
  266. check_op_result(topology.standalone, 'search', BOGUSDN, None, exists, rc)
  267. exists = False
  268. rc = ldap.SUCCESS
  269. log.info('Search case 2-2. the search entry does not exist, the search should return no search results with %s' % rc.__name__)
  270. check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc)
  271. # Add
  272. exists = True
  273. rc = ldap.INSUFFICIENT_ACCESS
  274. log.info('Add case 1. the bind user has no rights AND the adding entry exists, it should fail with %s' % rc.__name__)
  275. check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc)
  276. exists = False
  277. rc = ldap.INSUFFICIENT_ACCESS
  278. 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__)
  279. check_op_result(topology.standalone, 'add', BOGUSDN, None, exists, rc)
  280. exists = False
  281. rc = ldap.INSUFFICIENT_ACCESS
  282. 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__)
  283. check_op_result(topology.standalone, 'add', BOGUSDN2, None, exists, rc)
  284. # Modify
  285. exists = True
  286. rc = ldap.INSUFFICIENT_ACCESS
  287. log.info('Modify case 1. the bind user has no rights AND the modifying entry exists, it should fail with %s' % rc.__name__)
  288. check_op_result(topology.standalone, 'modify', TESTDN, None, exists, rc)
  289. exists = False
  290. rc = ldap.INSUFFICIENT_ACCESS
  291. 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__)
  292. check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc)
  293. exists = False
  294. rc = ldap.INSUFFICIENT_ACCESS
  295. 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__)
  296. check_op_result(topology.standalone, 'modify', BOGUSDN2, None, exists, rc)
  297. # Modrdn
  298. exists = True
  299. rc = ldap.INSUFFICIENT_ACCESS
  300. log.info('Modrdn case 1. the bind user has no rights AND the renaming entry exists, it should fail with %s' % rc.__name__)
  301. check_op_result(topology.standalone, 'modrdn', TESTDN, None, exists, rc)
  302. exists = False
  303. rc = ldap.INSUFFICIENT_ACCESS
  304. 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__)
  305. check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  306. exists = False
  307. rc = ldap.INSUFFICIENT_ACCESS
  308. 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__)
  309. check_op_result(topology.standalone, 'modrdn', BOGUSDN2, None, exists, rc)
  310. exists = True
  311. rc = ldap.INSUFFICIENT_ACCESS
  312. 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__)
  313. check_op_result(topology.standalone, 'modrdn', TESTDN, GROUPOU, exists, rc)
  314. exists = False
  315. rc = ldap.INSUFFICIENT_ACCESS
  316. 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__)
  317. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  318. exists = False
  319. rc = ldap.INSUFFICIENT_ACCESS
  320. 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__)
  321. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  322. # Delete
  323. exists = True
  324. rc = ldap.INSUFFICIENT_ACCESS
  325. log.info('Delete case 1. the bind user has no rights AND the deleting entry exists, it should fail with %s' % rc.__name__)
  326. check_op_result(topology.standalone, 'delete', TESTDN, None, exists, rc)
  327. exists = False
  328. rc = ldap.INSUFFICIENT_ACCESS
  329. 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__)
  330. check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc)
  331. exists = False
  332. rc = ldap.INSUFFICIENT_ACCESS
  333. 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__)
  334. check_op_result(topology.standalone, 'delete', BOGUSDN2, None, exists, rc)
  335. log.info('EXTRA: Check no regressions')
  336. log.info('Adding aci for %s to %s.' % (BINDDN, DEFAULT_SUFFIX))
  337. acival = '(targetattr="*")(version 3.0; acl "%s-all"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  338. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  339. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  340. topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', acival)])
  341. log.info('Bind as {%s,%s}.' % (BINDDN, BINDPW))
  342. try:
  343. topology.standalone.simple_bind_s(BINDDN, BINDPW)
  344. except ldap.LDAPError as e:
  345. log.info('Desc ' + e.message['desc'])
  346. assert False
  347. exists = False
  348. rc = ldap.NO_SUCH_OBJECT
  349. log.info('Search case. the search entry does not exist, the search should fail with %s' % rc.__name__)
  350. check_op_result(topology.standalone, 'search', BOGUSDN2, None, exists, rc)
  351. file_obj.close()
  352. exists = True
  353. rc = ldap.ALREADY_EXISTS
  354. log.info('Add case. the adding entry already exists, it should fail with %s' % rc.__name__)
  355. check_op_result(topology.standalone, 'add', TESTDN, None, exists, rc)
  356. exists = False
  357. rc = ldap.NO_SUCH_OBJECT
  358. log.info('Modify case. the modifying entry does not exist, it should fail with %s' % rc.__name__)
  359. check_op_result(topology.standalone, 'modify', BOGUSDN, None, exists, rc)
  360. exists = False
  361. rc = ldap.NO_SUCH_OBJECT
  362. log.info('Modrdn case 1. the renaming entry does not exist, it should fail with %s' % rc.__name__)
  363. check_op_result(topology.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  364. exists = False
  365. rc = ldap.NO_SUCH_OBJECT
  366. log.info('Modrdn case 2. the node moving an entry to does not, it should fail with %s' % rc.__name__)
  367. check_op_result(topology.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  368. exists = False
  369. rc = ldap.NO_SUCH_OBJECT
  370. log.info('Delete case. the deleting entry does not exist, it should fail with %s' % rc.__name__)
  371. check_op_result(topology.standalone, 'delete', BOGUSDN, None, exists, rc)
  372. log.info('Inactivate %s' % BINDDN)
  373. nsinactivate = '%s/sbin/ns-inactivate.pl' % topology.standalone.prefix
  374. p = Popen([nsinactivate, '-Z', 'standalone', '-D', DN_DM, '-w', PASSWORD, '-I', BINDDN])
  375. assert(p.wait() == 0)
  376. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, BUID, ldap.UNWILLING_TO_PERFORM.__name__))
  377. try:
  378. topology.standalone.simple_bind_s(BINDDN, BUID)
  379. except ldap.LDAPError as e:
  380. log.info("Exception (expected): %s" % type(e).__name__)
  381. log.info('Desc ' + e.message['desc'])
  382. assert isinstance(e, ldap.UNWILLING_TO_PERFORM)
  383. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, 'bogus', ldap.INVALID_CREDENTIALS.__name__))
  384. try:
  385. topology.standalone.simple_bind_s(BINDDN, 'bogus')
  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.INVALID_CREDENTIALS)
  390. log.info('SUCCESS')
  391. if __name__ == '__main__':
  392. # Run isolated
  393. # -s for DEBUG mode
  394. CURRENT_FILE = os.path.realpath(__file__)
  395. pytest.main("-s %s" % CURRENT_FILE)