repeated_ldap_add_test.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. # --- BEGIN COPYRIGHT BLOCK ---
  2. # Copyright (C) 2020 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. from subprocess import Popen
  10. import pytest
  11. from lib389.paths import Paths
  12. from lib389.tasks import *
  13. from lib389.utils import *
  14. from lib389.topologies import topology_st
  15. from lib389._constants import DN_DM, DEFAULT_SUFFIX, PASSWORD, SERVERID_STANDALONE
  16. pytestmark = pytest.mark.tier1
  17. logging.getLogger(__name__).setLevel(logging.DEBUG)
  18. log = logging.getLogger(__name__)
  19. CONFIG_DN = 'cn=config'
  20. BOU = 'BOU'
  21. BINDOU = 'ou=%s,%s' % (BOU, DEFAULT_SUFFIX)
  22. BUID = 'buser123'
  23. TUID = 'tuser0'
  24. BINDDN = 'uid=%s,%s' % (BUID, BINDOU)
  25. BINDPW = BUID
  26. TESTDN = 'uid=%s,ou=people,%s' % (TUID, DEFAULT_SUFFIX)
  27. TESTPW = TUID
  28. BOGUSDN = 'uid=bogus,%s' % DEFAULT_SUFFIX
  29. BOGUSDN2 = 'uid=bogus,ou=people,%s' % DEFAULT_SUFFIX
  30. BOGUSSUFFIX = 'uid=bogus,ou=people,dc=bogus'
  31. GROUPOU = 'ou=groups,%s' % DEFAULT_SUFFIX
  32. BOGUSOU = 'ou=OU,%s' % DEFAULT_SUFFIX
  33. def get_ldap_error_msg(e, type):
  34. return e.args[0][type]
  35. def pattern_accesslog(file, log_pattern):
  36. for i in range(5):
  37. try:
  38. pattern_accesslog.last_pos += 1
  39. except AttributeError:
  40. pattern_accesslog.last_pos = 0
  41. found = None
  42. file.seek(pattern_accesslog.last_pos)
  43. # Use a while true iteration because 'for line in file: hit a
  44. # python bug that break file.tell()
  45. while True:
  46. line = file.readline()
  47. found = log_pattern.search(line)
  48. if ((line == '') or (found)):
  49. break
  50. pattern_accesslog.last_pos = file.tell()
  51. if found:
  52. return line
  53. else:
  54. time.sleep(1)
  55. return None
  56. def check_op_result(server, op, dn, superior, exists, rc):
  57. targetdn = dn
  58. if op == 'search':
  59. if exists:
  60. opstr = 'Searching existing entry'
  61. else:
  62. opstr = 'Searching non-existing entry'
  63. elif op == 'add':
  64. if exists:
  65. opstr = 'Adding existing entry'
  66. else:
  67. opstr = 'Adding non-existing entry'
  68. elif op == 'modify':
  69. if exists:
  70. opstr = 'Modifying existing entry'
  71. else:
  72. opstr = 'Modifying non-existing entry'
  73. elif op == 'modrdn':
  74. if superior is not None:
  75. targetdn = superior
  76. if exists:
  77. opstr = 'Moving to existing superior'
  78. else:
  79. opstr = 'Moving to non-existing superior'
  80. else:
  81. if exists:
  82. opstr = 'Renaming existing entry'
  83. else:
  84. opstr = 'Renaming non-existing entry'
  85. elif op == 'delete':
  86. if exists:
  87. opstr = 'Deleting existing entry'
  88. else:
  89. opstr = 'Deleting non-existing entry'
  90. if ldap.SUCCESS == rc:
  91. expstr = 'be ok'
  92. else:
  93. expstr = 'fail with %s' % rc.__name__
  94. log.info('%s %s, which should %s.' % (opstr, targetdn, expstr))
  95. time.sleep(1)
  96. hit = 0
  97. try:
  98. if op == 'search':
  99. centry = server.search_s(dn, ldap.SCOPE_BASE, 'objectclass=*')
  100. elif op == 'add':
  101. server.add_s(Entry((dn, {'objectclass': 'top extensibleObject'.split(),
  102. 'cn': 'test entry'})))
  103. elif op == 'modify':
  104. server.modify_s(dn, [(ldap.MOD_REPLACE, 'description', b'test')])
  105. elif op == 'modrdn':
  106. if superior is not None:
  107. server.rename_s(dn, 'uid=new', newsuperior=superior, delold=1)
  108. else:
  109. server.rename_s(dn, 'uid=new', delold=1)
  110. elif op == 'delete':
  111. server.delete_s(dn)
  112. else:
  113. log.fatal('Unknown operation %s' % op)
  114. assert False
  115. except ldap.LDAPError as e:
  116. hit = 1
  117. log.info("Exception (expected): %s" % type(e).__name__)
  118. log.info('Desc {}'.format(get_ldap_error_msg(e,'desc')))
  119. assert isinstance(e, rc)
  120. if 'matched' in e.args:
  121. log.info('Matched is returned: {}'.format(get_ldap_error_msg(e, 'matched')))
  122. if rc != ldap.NO_SUCH_OBJECT:
  123. assert False
  124. if ldap.SUCCESS == rc:
  125. if op == 'search':
  126. log.info('Search should return none')
  127. assert len(centry) == 0
  128. else:
  129. if 0 == hit:
  130. log.info('Expected to fail with %s, but passed' % rc.__name__)
  131. assert False
  132. log.info('PASSED\n')
  133. @pytest.mark.bz1347760
  134. def test_repeated_ldap_add(topology_st):
  135. """Prevent revealing the entry info to whom has no access rights.
  136. :id: 76d278bd-3e51-4579-951a-753e6703b4df
  137. :setup: Standalone instance
  138. :steps:
  139. 1. Disable accesslog logbuffering
  140. 2. Bind as "cn=Directory Manager"
  141. 3. Add a organisational unit as BOU
  142. 4. Add a bind user as uid=buser123,ou=BOU,dc=example,dc=com
  143. 5. Add a test user as uid=tuser0,ou=People,dc=example,dc=com
  144. 6. Delete aci in dc=example,dc=com
  145. 7. Bind as Directory Manager, acquire an access log path and instance dir
  146. 8. Bind as uid=buser123,ou=BOU,dc=example,dc=com who has no right to read the entry
  147. 9. Bind as uid=bogus,ou=people,dc=bogus,bogus who does not exist
  148. 10. Bind as uid=buser123,ou=BOU,dc=example,dc=com,bogus with wrong password
  149. 11. Adding aci for uid=buser123,ou=BOU,dc=example,dc=com to ou=BOU,dc=example,dc=com.
  150. 12. Bind as uid=buser123,ou=BOU,dc=example,dc=com now who has right to read the entry
  151. :expectedresults:
  152. 1. Operation should be successful
  153. 2. Operation should be successful
  154. 3. Operation should be successful
  155. 4. Operation should be successful
  156. 5. Operation should be successful
  157. 6. Operation should be successful
  158. 7. Operation should be successful
  159. 8. Bind operation should be successful with no search result
  160. 9. Bind operation should Fail
  161. 10. Bind operation should Fail
  162. 11. Operation should be successful
  163. 12. Bind operation should be successful with search result
  164. """
  165. log.info('Testing Bug 1347760 - Information disclosure via repeated use of LDAP ADD operation, etc.')
  166. log.info('Disabling accesslog logbuffering')
  167. topology_st.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-accesslog-logbuffering', b'off')])
  168. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  169. topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
  170. log.info('Adding ou=%s a bind user belongs to.' % BOU)
  171. topology_st.standalone.add_s(Entry((BINDOU, {
  172. 'objectclass': 'top organizationalunit'.split(),
  173. 'ou': BOU})))
  174. log.info('Adding a bind user.')
  175. topology_st.standalone.add_s(Entry((BINDDN,
  176. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  177. 'cn': 'bind user',
  178. 'sn': 'user',
  179. 'userPassword': BINDPW})))
  180. log.info('Adding a test user.')
  181. topology_st.standalone.add_s(Entry((TESTDN,
  182. {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
  183. 'cn': 'test user',
  184. 'sn': 'user',
  185. 'userPassword': TESTPW})))
  186. log.info('Deleting aci in %s.' % DEFAULT_SUFFIX)
  187. topology_st.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_DELETE, 'aci', None)])
  188. log.info('While binding as DM, acquire an access log path and instance dir')
  189. ds_paths = Paths(serverid=topology_st.standalone.serverid,
  190. instance=topology_st.standalone)
  191. file_path = ds_paths.access_log
  192. inst_dir = ds_paths.inst_dir
  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_st.standalone.simple_bind_s(BINDDN, BINDPW)
  197. except ldap.LDAPError as e:
  198. log.info('Desc {}'.format(get_ldap_error_msg(e,'desc')))
  199. assert False
  200. file_obj = open(file_path, "r")
  201. log.info('Access log path: %s' % file_path)
  202. log.info(
  203. '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_st.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 {}'.format(get_ldap_error_msg(e,'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 is None:
  214. log.fatal('Cause not found - %s' % cause)
  215. assert False
  216. else:
  217. log.info('Cause found - %s' % cause)
  218. time.sleep(1)
  219. log.info(
  220. '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. with pytest.raises(ldap.INVALID_CREDENTIALS):
  223. topology_st.standalone.simple_bind_s(BOGUSSUFFIX, 'bogus')
  224. regex = re.compile('No suffix for bind')
  225. cause = pattern_accesslog(file_obj, regex)
  226. if cause is None:
  227. log.fatal('Cause not found - %s' % cause)
  228. assert False
  229. else:
  230. log.info('Cause found - %s' % cause)
  231. time.sleep(1)
  232. log.info(
  233. '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_st.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 {}'.format(get_ldap_error_msg(e,'desc')))
  240. assert isinstance(e, ldap.INVALID_CREDENTIALS)
  241. regex = re.compile('Invalid credentials')
  242. cause = pattern_accesslog(file_obj, regex)
  243. if cause is None:
  244. log.fatal('Cause not found - %s' % cause)
  245. assert False
  246. else:
  247. log.info('Cause found - %s' % cause)
  248. time.sleep(1)
  249. log.info('Adding aci for %s to %s.' % (BINDDN, BINDOU))
  250. acival = '(targetattr="*")(version 3.0; acl "%s"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  251. log.info('aci: %s' % acival)
  252. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  253. topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
  254. topology_st.standalone.modify_s(BINDOU, [(ldap.MOD_ADD, 'aci', ensure_bytes(acival))])
  255. time.sleep(1)
  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_st.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(
  264. 'Search case 1. the bind user has no rights to read the search entry, it should return no search results with %s' % rc)
  265. check_op_result(topology_st.standalone, 'search', TESTDN, None, exists, rc)
  266. exists = False
  267. rc = ldap.SUCCESS
  268. log.info(
  269. 'Search case 2-1. the search entry does not exist, the search should return no search results with %s' % rc.__name__)
  270. check_op_result(topology_st.standalone, 'search', BOGUSDN, None, exists, rc)
  271. exists = False
  272. rc = ldap.SUCCESS
  273. log.info(
  274. '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_st.standalone, 'search', BOGUSDN2, None, exists, rc)
  276. # Add
  277. exists = True
  278. rc = ldap.INSUFFICIENT_ACCESS
  279. log.info(
  280. 'Add case 1. the bind user has no rights AND the adding entry exists, it should fail with %s' % rc.__name__)
  281. check_op_result(topology_st.standalone, 'add', TESTDN, None, exists, rc)
  282. exists = False
  283. rc = ldap.INSUFFICIENT_ACCESS
  284. log.info(
  285. 'Add case 2-1. the bind user has no rights AND the adding entry does not exist, it should fail with %s' % rc.__name__)
  286. check_op_result(topology_st.standalone, 'add', BOGUSDN, None, exists, rc)
  287. exists = False
  288. rc = ldap.INSUFFICIENT_ACCESS
  289. log.info(
  290. 'Add case 2-2. the bind user has no rights AND the adding entry does not exist, it should fail with %s' % rc.__name__)
  291. check_op_result(topology_st.standalone, 'add', BOGUSDN2, None, exists, rc)
  292. # Modify
  293. exists = True
  294. rc = ldap.INSUFFICIENT_ACCESS
  295. log.info(
  296. 'Modify case 1. the bind user has no rights AND the modifying entry exists, it should fail with %s' % rc.__name__)
  297. check_op_result(topology_st.standalone, 'modify', TESTDN, None, exists, rc)
  298. exists = False
  299. rc = ldap.INSUFFICIENT_ACCESS
  300. log.info(
  301. 'Modify case 2-1. the bind user has no rights AND the modifying entry does not exist, it should fail with %s' % rc.__name__)
  302. check_op_result(topology_st.standalone, 'modify', BOGUSDN, None, exists, rc)
  303. exists = False
  304. rc = ldap.INSUFFICIENT_ACCESS
  305. log.info(
  306. 'Modify case 2-2. the bind user has no rights AND the modifying entry does not exist, it should fail with %s' % rc.__name__)
  307. check_op_result(topology_st.standalone, 'modify', BOGUSDN2, None, exists, rc)
  308. # Modrdn
  309. exists = True
  310. rc = ldap.INSUFFICIENT_ACCESS
  311. log.info(
  312. 'Modrdn case 1. the bind user has no rights AND the renaming entry exists, it should fail with %s' % rc.__name__)
  313. check_op_result(topology_st.standalone, 'modrdn', TESTDN, None, exists, rc)
  314. exists = False
  315. rc = ldap.INSUFFICIENT_ACCESS
  316. log.info(
  317. 'Modrdn case 2-1. the bind user has no rights AND the renaming entry does not exist, it should fail with %s' % rc.__name__)
  318. check_op_result(topology_st.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  319. exists = False
  320. rc = ldap.INSUFFICIENT_ACCESS
  321. log.info(
  322. 'Modrdn case 2-2. the bind user has no rights AND the renaming entry does not exist, it should fail with %s' % rc.__name__)
  323. check_op_result(topology_st.standalone, 'modrdn', BOGUSDN2, None, exists, rc)
  324. exists = True
  325. rc = ldap.INSUFFICIENT_ACCESS
  326. log.info(
  327. 'Modrdn case 3. the bind user has no rights AND the node moving an entry to exists, it should fail with %s' % rc.__name__)
  328. check_op_result(topology_st.standalone, 'modrdn', TESTDN, GROUPOU, exists, rc)
  329. exists = False
  330. rc = ldap.INSUFFICIENT_ACCESS
  331. log.info(
  332. '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__)
  333. check_op_result(topology_st.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  334. exists = False
  335. rc = ldap.INSUFFICIENT_ACCESS
  336. log.info(
  337. '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__)
  338. check_op_result(topology_st.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  339. # Delete
  340. exists = True
  341. rc = ldap.INSUFFICIENT_ACCESS
  342. log.info(
  343. 'Delete case 1. the bind user has no rights AND the deleting entry exists, it should fail with %s' % rc.__name__)
  344. check_op_result(topology_st.standalone, 'delete', TESTDN, None, exists, rc)
  345. exists = False
  346. rc = ldap.INSUFFICIENT_ACCESS
  347. log.info(
  348. 'Delete case 2-1. the bind user has no rights AND the deleting entry does not exist, it should fail with %s' % rc.__name__)
  349. check_op_result(topology_st.standalone, 'delete', BOGUSDN, None, exists, rc)
  350. exists = False
  351. rc = ldap.INSUFFICIENT_ACCESS
  352. log.info(
  353. 'Delete case 2-2. the bind user has no rights AND the deleting entry does not exist, it should fail with %s' % rc.__name__)
  354. check_op_result(topology_st.standalone, 'delete', BOGUSDN2, None, exists, rc)
  355. log.info('EXTRA: Check no regressions')
  356. log.info('Adding aci for %s to %s.' % (BINDDN, DEFAULT_SUFFIX))
  357. acival = '(targetattr="*")(version 3.0; acl "%s-all"; allow(all) userdn = "ldap:///%s";)' % (BUID, BINDDN)
  358. log.info('Bind as {%s,%s}' % (DN_DM, PASSWORD))
  359. topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
  360. topology_st.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', ensure_bytes(acival))])
  361. time.sleep(1)
  362. log.info('Bind as {%s,%s}.' % (BINDDN, BINDPW))
  363. try:
  364. topology_st.standalone.simple_bind_s(BINDDN, BINDPW)
  365. except ldap.LDAPError as e:
  366. log.info('Desc {}'.format(get_ldap_error_msg(e,'desc')))
  367. assert False
  368. time.sleep(1)
  369. exists = False
  370. rc = ldap.NO_SUCH_OBJECT
  371. log.info('Search case. the search entry does not exist, the search should fail with %s' % rc.__name__)
  372. check_op_result(topology_st.standalone, 'search', BOGUSDN2, None, exists, rc)
  373. file_obj.close()
  374. exists = True
  375. rc = ldap.ALREADY_EXISTS
  376. log.info('Add case. the adding entry already exists, it should fail with %s' % rc.__name__)
  377. check_op_result(topology_st.standalone, 'add', TESTDN, None, exists, rc)
  378. exists = False
  379. rc = ldap.NO_SUCH_OBJECT
  380. log.info('Modify case. the modifying entry does not exist, it should fail with %s' % rc.__name__)
  381. check_op_result(topology_st.standalone, 'modify', BOGUSDN, None, exists, rc)
  382. exists = False
  383. rc = ldap.NO_SUCH_OBJECT
  384. log.info('Modrdn case 1. the renaming entry does not exist, it should fail with %s' % rc.__name__)
  385. check_op_result(topology_st.standalone, 'modrdn', BOGUSDN, None, exists, rc)
  386. exists = False
  387. rc = ldap.NO_SUCH_OBJECT
  388. log.info('Modrdn case 2. the node moving an entry to does not, it should fail with %s' % rc.__name__)
  389. check_op_result(topology_st.standalone, 'modrdn', TESTDN, BOGUSOU, exists, rc)
  390. exists = False
  391. rc = ldap.NO_SUCH_OBJECT
  392. log.info('Delete case. the deleting entry does not exist, it should fail with %s' % rc.__name__)
  393. check_op_result(topology_st.standalone, 'delete', BOGUSDN, None, exists, rc)
  394. log.info('Inactivate %s' % BINDDN)
  395. if ds_paths.version < '1.3':
  396. nsinactivate = '%s/ns-inactivate.pl' % inst_dir
  397. cli_cmd = [nsinactivate, '-D', DN_DM, '-w', PASSWORD, '-I', BINDDN]
  398. else:
  399. dsidm = '%s/dsidm' % ds_paths.sbin_dir
  400. cli_cmd = [dsidm, SERVERID_STANDALONE, '-b', DEFAULT_SUFFIX, 'account', 'lock', BINDDN]
  401. log.info(cli_cmd)
  402. p = Popen(cli_cmd)
  403. assert (p.wait() == 0)
  404. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, BUID, ldap.UNWILLING_TO_PERFORM.__name__))
  405. try:
  406. topology_st.standalone.simple_bind_s(BINDDN, BUID)
  407. except ldap.LDAPError as e:
  408. log.info("Exception (expected): %s" % type(e).__name__)
  409. log.info('Desc {}'.format(get_ldap_error_msg(e,'desc')))
  410. assert isinstance(e, ldap.UNWILLING_TO_PERFORM)
  411. log.info('Bind as {%s,%s} which should fail with %s.' % (BINDDN, 'bogus', ldap.UNWILLING_TO_PERFORM.__name__))
  412. try:
  413. topology_st.standalone.simple_bind_s(BINDDN, 'bogus')
  414. except ldap.LDAPError as e:
  415. log.info("Exception (expected): %s" % type(e).__name__)
  416. log.info('Desc {}'.format(get_ldap_error_msg(e,'desc')))
  417. assert isinstance(e, ldap.UNWILLING_TO_PERFORM)
  418. log.info('SUCCESS')
  419. if __name__ == '__main__':
  420. # Run isolated
  421. # -s for DEBUG mode
  422. CURRENT_FILE = os.path.realpath(__file__)
  423. pytest.main("-s %s" % CURRENT_FILE)