test_auth.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Mike Place <mp@saltstack.com>
  4. """
  5. # Import pytohn libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import time
  8. import pytest
  9. # Import Salt libraries
  10. import salt.master
  11. import salt.utils.platform
  12. from salt import auth
  13. from salt.exceptions import SaltDeserializationError
  14. from tests.support.case import ModuleCase
  15. from tests.support.mock import MagicMock, call, patch
  16. # Import Salt Testing libs
  17. from tests.support.unit import TestCase, skipIf
  18. class LoadAuthTestCase(TestCase):
  19. def setUp(self): # pylint: disable=W0221
  20. patches = (
  21. ("salt.payload.Serial", None),
  22. (
  23. "salt.loader.auth",
  24. dict(
  25. return_value={
  26. "pam.auth": "fake_func_str",
  27. "pam.groups": "fake_groups_function_str",
  28. }
  29. ),
  30. ),
  31. (
  32. "salt.loader.eauth_tokens",
  33. dict(
  34. return_value={
  35. "localfs.mk_token": "fake_func_mktok",
  36. "localfs.get_token": "fake_func_gettok",
  37. "localfs.rm_roken": "fake_func_rmtok",
  38. }
  39. ),
  40. ),
  41. )
  42. for mod, mock in patches:
  43. if mock:
  44. patcher = patch(mod, **mock)
  45. else:
  46. patcher = patch(mod)
  47. patcher.start()
  48. self.addCleanup(patcher.stop)
  49. self.lauth = auth.LoadAuth({}) # Load with empty opts
  50. def test_get_tok_with_broken_file_will_remove_bad_token(self):
  51. fake_get_token = MagicMock(side_effect=SaltDeserializationError("hi"))
  52. patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
  53. patch_get_token = patch.dict(
  54. self.lauth.tokens, {"testfs.get_token": fake_get_token},
  55. )
  56. mock_rm_token = MagicMock()
  57. patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
  58. with patch_opts, patch_get_token, patch_rm_token:
  59. expected_token = "fnord"
  60. self.lauth.get_tok(expected_token)
  61. mock_rm_token.assert_called_with(expected_token)
  62. def test_get_tok_with_no_expiration_should_remove_bad_token(self):
  63. fake_get_token = MagicMock(return_value={"no_expire_here": "Nope"})
  64. patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
  65. patch_get_token = patch.dict(
  66. self.lauth.tokens, {"testfs.get_token": fake_get_token},
  67. )
  68. mock_rm_token = MagicMock()
  69. patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
  70. with patch_opts, patch_get_token, patch_rm_token:
  71. expected_token = "fnord"
  72. self.lauth.get_tok(expected_token)
  73. mock_rm_token.assert_called_with(expected_token)
  74. def test_get_tok_with_expire_before_current_time_should_remove_token(self):
  75. fake_get_token = MagicMock(return_value={"expire": time.time() - 1})
  76. patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
  77. patch_get_token = patch.dict(
  78. self.lauth.tokens, {"testfs.get_token": fake_get_token},
  79. )
  80. mock_rm_token = MagicMock()
  81. patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
  82. with patch_opts, patch_get_token, patch_rm_token:
  83. expected_token = "fnord"
  84. self.lauth.get_tok(expected_token)
  85. mock_rm_token.assert_called_with(expected_token)
  86. def test_get_tok_with_valid_expiration_should_return_token(self):
  87. expected_token = {"expire": time.time() + 1}
  88. fake_get_token = MagicMock(return_value=expected_token)
  89. patch_opts = patch.dict(self.lauth.opts, {"eauth_tokens": "testfs"})
  90. patch_get_token = patch.dict(
  91. self.lauth.tokens, {"testfs.get_token": fake_get_token},
  92. )
  93. mock_rm_token = MagicMock()
  94. patch_rm_token = patch.object(self.lauth, "rm_token", mock_rm_token)
  95. with patch_opts, patch_get_token, patch_rm_token:
  96. token_name = "fnord"
  97. actual_token = self.lauth.get_tok(token_name)
  98. mock_rm_token.assert_not_called()
  99. assert expected_token is actual_token, "Token was not returned"
  100. def test_load_name(self):
  101. valid_eauth_load = {
  102. "username": "test_user",
  103. "show_timeout": False,
  104. "test_password": "",
  105. "eauth": "pam",
  106. }
  107. # Test a case where the loader auth doesn't have the auth type
  108. without_auth_type = dict(valid_eauth_load)
  109. without_auth_type.pop("eauth")
  110. ret = self.lauth.load_name(without_auth_type)
  111. self.assertEqual(
  112. ret, "", "Did not bail when the auth loader didn't have the auth type."
  113. )
  114. # Test a case with valid params
  115. with patch(
  116. "salt.utils.args.arg_lookup",
  117. MagicMock(return_value={"args": ["username", "password"]}),
  118. ) as format_call_mock:
  119. expected_ret = call("fake_func_str")
  120. ret = self.lauth.load_name(valid_eauth_load)
  121. format_call_mock.assert_has_calls((expected_ret,), any_order=True)
  122. self.assertEqual(ret, "test_user")
  123. def test_get_groups(self):
  124. valid_eauth_load = {
  125. "username": "test_user",
  126. "show_timeout": False,
  127. "test_password": "",
  128. "eauth": "pam",
  129. }
  130. with patch("salt.utils.args.format_call") as format_call_mock:
  131. expected_ret = call(
  132. "fake_groups_function_str",
  133. {
  134. "username": "test_user",
  135. "test_password": "",
  136. "show_timeout": False,
  137. "eauth": "pam",
  138. },
  139. expected_extra_kws=auth.AUTH_INTERNAL_KEYWORDS,
  140. )
  141. self.lauth.get_groups(valid_eauth_load)
  142. format_call_mock.assert_has_calls((expected_ret,), any_order=True)
  143. class MasterACLTestCase(ModuleCase):
  144. """
  145. A class to check various aspects of the publisher ACL system
  146. """
  147. def setUp(self):
  148. self.fire_event_mock = MagicMock(return_value="dummy_tag")
  149. self.addCleanup(delattr, self, "fire_event_mock")
  150. opts = self.get_temp_config("master")
  151. patches = (
  152. ("zmq.Context", MagicMock()),
  153. ("salt.payload.Serial.dumps", MagicMock()),
  154. ("salt.master.tagify", MagicMock()),
  155. ("salt.utils.event.SaltEvent.fire_event", self.fire_event_mock),
  156. ("salt.auth.LoadAuth.time_auth", MagicMock(return_value=True)),
  157. ("salt.minion.MasterMinion", MagicMock()),
  158. ("salt.utils.verify.check_path_traversal", MagicMock()),
  159. ("salt.client.get_local_client", MagicMock(return_value=opts["conf_file"])),
  160. )
  161. for mod, mock in patches:
  162. patcher = patch(mod, mock)
  163. patcher.start()
  164. self.addCleanup(patcher.stop)
  165. opts["publisher_acl"] = {}
  166. opts["publisher_acl_blacklist"] = {}
  167. opts["master_job_cache"] = ""
  168. opts["sign_pub_messages"] = False
  169. opts["con_cache"] = ""
  170. opts["external_auth"] = {}
  171. opts["external_auth"]["pam"] = {
  172. "test_user": [
  173. {"*": ["test.ping"]},
  174. {"minion_glob*": ["foo.bar"]},
  175. {"minion_func_test": ["func_test.*"]},
  176. ],
  177. "test_group%": [{"*": ["test.echo"]}],
  178. "test_user_mminion": [{"target_minion": ["test.ping"]}],
  179. "*": [{"my_minion": ["my_mod.my_func"]}],
  180. "test_user_func": [
  181. {
  182. "*": [
  183. {"test.echo": {"args": ["MSG:.*"]}},
  184. {
  185. "test.echo": {
  186. "kwargs": {
  187. "text": "KWMSG:.*",
  188. "anything": ".*",
  189. "none": None,
  190. }
  191. }
  192. },
  193. {
  194. "my_mod.*": {
  195. "args": ["a.*", "b.*"],
  196. "kwargs": {"kwa": "kwa.*", "kwb": "kwb"},
  197. }
  198. },
  199. ]
  200. },
  201. {
  202. "minion1": [
  203. {"test.echo": {"args": ["TEST", None, "TEST.*"]}},
  204. {"test.empty": {}},
  205. ]
  206. },
  207. ],
  208. }
  209. self.clear = salt.master.ClearFuncs(opts, MagicMock())
  210. self.addCleanup(delattr, self, "clear")
  211. # overwrite the _send_pub method so we don't have to serialize MagicMock
  212. self.clear._send_pub = lambda payload: True
  213. # make sure to return a JID, instead of a mock
  214. self.clear.mminion.returners = {".prep_jid": lambda x: 1}
  215. self.valid_clear_load = {
  216. "tgt_type": "glob",
  217. "jid": "",
  218. "cmd": "publish",
  219. "tgt": "test_minion",
  220. "kwargs": {
  221. "username": "test_user",
  222. "password": "test_password",
  223. "show_timeout": False,
  224. "eauth": "pam",
  225. "show_jid": False,
  226. },
  227. "ret": "",
  228. "user": "test_user",
  229. "key": "",
  230. "arg": "",
  231. "fun": "test.ping",
  232. }
  233. self.addCleanup(delattr, self, "valid_clear_load")
  234. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  235. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  236. def test_master_publish_name(self):
  237. """
  238. Test to ensure a simple name can auth against a given function.
  239. This tests to ensure test_user can access test.ping but *not* sys.doc
  240. """
  241. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  242. with patch(
  243. "salt.utils.minions.CkMinions.check_minions",
  244. MagicMock(return_value=_check_minions_return),
  245. ):
  246. # Can we access test.ping?
  247. self.clear.publish(self.valid_clear_load)
  248. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.ping")
  249. # Are we denied access to sys.doc?
  250. sys_doc_load = self.valid_clear_load
  251. sys_doc_load["fun"] = "sys.doc"
  252. self.clear.publish(sys_doc_load)
  253. self.assertNotEqual(
  254. self.fire_event_mock.call_args[0][0]["fun"], "sys.doc"
  255. ) # If sys.doc were to fire, this would match
  256. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  257. def test_master_publish_group(self):
  258. """
  259. Tests to ensure test_group can access test.echo but *not* sys.doc
  260. """
  261. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  262. with patch(
  263. "salt.utils.minions.CkMinions.check_minions",
  264. MagicMock(return_value=_check_minions_return),
  265. ):
  266. self.valid_clear_load["kwargs"]["user"] = "new_user"
  267. self.valid_clear_load["fun"] = "test.echo"
  268. self.valid_clear_load["arg"] = "hello"
  269. with patch(
  270. "salt.auth.LoadAuth.get_groups",
  271. return_value=["test_group", "second_test_group"],
  272. ):
  273. self.clear.publish(self.valid_clear_load)
  274. # Did we fire test.echo?
  275. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
  276. # Request sys.doc
  277. self.valid_clear_load["fun"] = "sys.doc"
  278. # Did we fire it?
  279. self.assertNotEqual(self.fire_event_mock.call_args[0][0]["fun"], "sys.doc")
  280. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  281. def test_master_publish_some_minions(self):
  282. """
  283. Tests to ensure we can only target minions for which we
  284. have permission with publisher acl.
  285. Note that in order for these sorts of tests to run correctly that
  286. you should NOT patch check_minions!
  287. """
  288. self.valid_clear_load["kwargs"]["username"] = "test_user_mminion"
  289. self.valid_clear_load["user"] = "test_user_mminion"
  290. self.clear.publish(self.valid_clear_load)
  291. self.assertEqual(self.fire_event_mock.mock_calls, [])
  292. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  293. def test_master_not_user_glob_all(self):
  294. """
  295. Test to ensure that we DO NOT access to a given
  296. function to all users with publisher acl. ex:
  297. '*':
  298. my_minion:
  299. - my_func
  300. Yes, this seems like a bit of a no-op test but it's
  301. here to document that this functionality
  302. is NOT supported currently.
  303. WARNING: Do not patch this wit
  304. """
  305. self.valid_clear_load["kwargs"]["username"] = "NOT_A_VALID_USERNAME"
  306. self.valid_clear_load["user"] = "NOT_A_VALID_USERNAME"
  307. self.valid_clear_load["fun"] = "test.ping"
  308. self.clear.publish(self.valid_clear_load)
  309. self.assertEqual(self.fire_event_mock.mock_calls, [])
  310. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  311. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  312. def test_master_minion_glob(self):
  313. """
  314. Test to ensure we can allow access to a given
  315. function for a user to a subset of minions
  316. selected by a glob. ex:
  317. test_user:
  318. 'minion_glob*':
  319. - glob_mod.glob_func
  320. This test is a bit tricky, because ultimately the real functionality
  321. lies in what's returned from check_minions, but this checks a limited
  322. amount of logic on the way there as well. Note the inline patch.
  323. """
  324. requested_function = "foo.bar"
  325. requested_tgt = "minion_glob1"
  326. self.valid_clear_load["tgt"] = requested_tgt
  327. self.valid_clear_load["fun"] = requested_function
  328. _check_minions_return = {"minions": ["minion_glob1"], "missing": []}
  329. with patch(
  330. "salt.utils.minions.CkMinions.check_minions",
  331. MagicMock(return_value=_check_minions_return),
  332. ): # Assume that there is a listening minion match
  333. self.clear.publish(self.valid_clear_load)
  334. self.assertTrue(
  335. self.fire_event_mock.called,
  336. "Did not fire {0} for minion tgt {1}".format(
  337. requested_function, requested_tgt
  338. ),
  339. )
  340. self.assertEqual(
  341. self.fire_event_mock.call_args[0][0]["fun"],
  342. requested_function,
  343. "Did not fire {0} for minion glob".format(requested_function),
  344. )
  345. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  346. def test_master_function_glob(self):
  347. """
  348. Test to ensure that we can allow access to a given
  349. set of functions in an execution module as selected
  350. by a glob. ex:
  351. my_user:
  352. my_minion:
  353. 'test.*'
  354. """
  355. # Unimplemented
  356. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  357. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  358. def test_args_empty_spec(self):
  359. """
  360. Test simple arg restriction allowed.
  361. 'test_user_func':
  362. minion1:
  363. - test.empty:
  364. """
  365. _check_minions_return = {"minions": ["minion1"], "missing": []}
  366. with patch(
  367. "salt.utils.minions.CkMinions.check_minions",
  368. MagicMock(return_value=_check_minions_return),
  369. ):
  370. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  371. self.valid_clear_load.update(
  372. {
  373. "user": "test_user_func",
  374. "tgt": "minion1",
  375. "fun": "test.empty",
  376. "arg": ["TEST"],
  377. }
  378. )
  379. self.clear.publish(self.valid_clear_load)
  380. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.empty")
  381. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  382. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  383. def test_args_simple_match(self):
  384. """
  385. Test simple arg restriction allowed.
  386. 'test_user_func':
  387. minion1:
  388. - test.echo:
  389. args:
  390. - 'TEST'
  391. - 'TEST.*'
  392. """
  393. _check_minions_return = {"minions": ["minion1"], "missing": []}
  394. with patch(
  395. "salt.utils.minions.CkMinions.check_minions",
  396. MagicMock(return_value=_check_minions_return),
  397. ):
  398. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  399. self.valid_clear_load.update(
  400. {
  401. "user": "test_user_func",
  402. "tgt": "minion1",
  403. "fun": "test.echo",
  404. "arg": ["TEST", "any", "TEST ABC"],
  405. }
  406. )
  407. self.clear.publish(self.valid_clear_load)
  408. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
  409. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  410. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  411. def test_args_more_args(self):
  412. """
  413. Test simple arg restriction allowed to pass unlisted args.
  414. 'test_user_func':
  415. minion1:
  416. - test.echo:
  417. args:
  418. - 'TEST'
  419. - 'TEST.*'
  420. """
  421. _check_minions_return = {"minions": ["minion1"], "missing": []}
  422. with patch(
  423. "salt.utils.minions.CkMinions.check_minions",
  424. MagicMock(return_value=_check_minions_return),
  425. ):
  426. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  427. self.valid_clear_load.update(
  428. {
  429. "user": "test_user_func",
  430. "tgt": "minion1",
  431. "fun": "test.echo",
  432. "arg": [
  433. "TEST",
  434. "any",
  435. "TEST ABC",
  436. "arg 3",
  437. {"kwarg1": "val1", "__kwarg__": True},
  438. ],
  439. }
  440. )
  441. self.clear.publish(self.valid_clear_load)
  442. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
  443. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  444. def test_args_simple_forbidden(self):
  445. """
  446. Test simple arg restriction forbidden.
  447. 'test_user_func':
  448. minion1:
  449. - test.echo:
  450. args:
  451. - 'TEST'
  452. - 'TEST.*'
  453. """
  454. _check_minions_return = {"minions": ["minion1"], "missing": []}
  455. with patch(
  456. "salt.utils.minions.CkMinions.check_minions",
  457. MagicMock(return_value=_check_minions_return),
  458. ):
  459. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  460. # Wrong last arg
  461. self.valid_clear_load.update(
  462. {
  463. "user": "test_user_func",
  464. "tgt": "minion1",
  465. "fun": "test.echo",
  466. "arg": ["TEST", "any", "TESLA"],
  467. }
  468. )
  469. self.clear.publish(self.valid_clear_load)
  470. self.assertEqual(self.fire_event_mock.mock_calls, [])
  471. # Wrong first arg
  472. self.valid_clear_load["arg"] = ["TES", "any", "TEST1234"]
  473. self.clear.publish(self.valid_clear_load)
  474. self.assertEqual(self.fire_event_mock.mock_calls, [])
  475. # Missing the last arg
  476. self.valid_clear_load["arg"] = ["TEST", "any"]
  477. self.clear.publish(self.valid_clear_load)
  478. self.assertEqual(self.fire_event_mock.mock_calls, [])
  479. # No args
  480. self.valid_clear_load["arg"] = []
  481. self.clear.publish(self.valid_clear_load)
  482. self.assertEqual(self.fire_event_mock.mock_calls, [])
  483. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  484. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  485. def test_args_kwargs_match(self):
  486. """
  487. Test simple kwargs restriction allowed.
  488. 'test_user_func':
  489. '*':
  490. - test.echo:
  491. kwargs:
  492. text: 'KWMSG:.*'
  493. """
  494. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  495. with patch(
  496. "salt.utils.minions.CkMinions.check_minions",
  497. MagicMock(return_value=_check_minions_return),
  498. ):
  499. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  500. self.valid_clear_load.update(
  501. {
  502. "user": "test_user_func",
  503. "tgt": "*",
  504. "fun": "test.echo",
  505. "arg": [
  506. {
  507. "text": "KWMSG: a message",
  508. "anything": "hello all",
  509. "none": "hello none",
  510. "__kwarg__": True,
  511. }
  512. ],
  513. }
  514. )
  515. self.clear.publish(self.valid_clear_load)
  516. self.assertEqual(self.fire_event_mock.call_args[0][0]["fun"], "test.echo")
  517. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  518. def test_args_kwargs_mismatch(self):
  519. """
  520. Test simple kwargs restriction allowed.
  521. 'test_user_func':
  522. '*':
  523. - test.echo:
  524. kwargs:
  525. text: 'KWMSG:.*'
  526. """
  527. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  528. with patch(
  529. "salt.utils.minions.CkMinions.check_minions",
  530. MagicMock(return_value=_check_minions_return),
  531. ):
  532. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  533. self.valid_clear_load.update(
  534. {"user": "test_user_func", "tgt": "*", "fun": "test.echo"}
  535. )
  536. # Wrong kwarg value
  537. self.valid_clear_load["arg"] = [
  538. {
  539. "text": "KWMSG a message",
  540. "anything": "hello all",
  541. "none": "hello none",
  542. "__kwarg__": True,
  543. }
  544. ]
  545. self.clear.publish(self.valid_clear_load)
  546. self.assertEqual(self.fire_event_mock.mock_calls, [])
  547. # Missing kwarg value
  548. self.valid_clear_load["arg"] = [
  549. {"anything": "hello all", "none": "hello none", "__kwarg__": True}
  550. ]
  551. self.clear.publish(self.valid_clear_load)
  552. self.assertEqual(self.fire_event_mock.mock_calls, [])
  553. self.valid_clear_load["arg"] = [{"__kwarg__": True}]
  554. self.clear.publish(self.valid_clear_load)
  555. self.assertEqual(self.fire_event_mock.mock_calls, [])
  556. self.valid_clear_load["arg"] = [{}]
  557. self.clear.publish(self.valid_clear_load)
  558. self.assertEqual(self.fire_event_mock.mock_calls, [])
  559. self.valid_clear_load["arg"] = []
  560. self.clear.publish(self.valid_clear_load)
  561. self.assertEqual(self.fire_event_mock.mock_calls, [])
  562. # Missing kwarg allowing any value
  563. self.valid_clear_load["arg"] = [
  564. {"text": "KWMSG: a message", "none": "hello none", "__kwarg__": True}
  565. ]
  566. self.clear.publish(self.valid_clear_load)
  567. self.assertEqual(self.fire_event_mock.mock_calls, [])
  568. self.valid_clear_load["arg"] = [
  569. {"text": "KWMSG: a message", "anything": "hello all", "__kwarg__": True}
  570. ]
  571. self.clear.publish(self.valid_clear_load)
  572. self.assertEqual(self.fire_event_mock.mock_calls, [])
  573. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  574. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  575. def test_args_mixed_match(self):
  576. """
  577. Test mixed args and kwargs restriction allowed.
  578. 'test_user_func':
  579. '*':
  580. - 'my_mod.*':
  581. args:
  582. - 'a.*'
  583. - 'b.*'
  584. kwargs:
  585. 'kwa': 'kwa.*'
  586. 'kwb': 'kwb'
  587. """
  588. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  589. with patch(
  590. "salt.utils.minions.CkMinions.check_minions",
  591. MagicMock(return_value=_check_minions_return),
  592. ):
  593. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  594. self.valid_clear_load.update(
  595. {
  596. "user": "test_user_func",
  597. "tgt": "*",
  598. "fun": "my_mod.some_func",
  599. "arg": [
  600. "alpha",
  601. "beta",
  602. "gamma",
  603. {
  604. "kwa": "kwarg #1",
  605. "kwb": "kwb",
  606. "one_more": "just one more",
  607. "__kwarg__": True,
  608. },
  609. ],
  610. }
  611. )
  612. self.clear.publish(self.valid_clear_load)
  613. self.assertEqual(
  614. self.fire_event_mock.call_args[0][0]["fun"], "my_mod.some_func"
  615. )
  616. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  617. def test_args_mixed_mismatch(self):
  618. """
  619. Test mixed args and kwargs restriction forbidden.
  620. 'test_user_func':
  621. '*':
  622. - 'my_mod.*':
  623. args:
  624. - 'a.*'
  625. - 'b.*'
  626. kwargs:
  627. 'kwa': 'kwa.*'
  628. 'kwb': 'kwb'
  629. """
  630. _check_minions_return = {"minions": ["some_minions"], "missing": []}
  631. with patch(
  632. "salt.utils.minions.CkMinions.check_minions",
  633. MagicMock(return_value=_check_minions_return),
  634. ):
  635. self.valid_clear_load["kwargs"].update({"username": "test_user_func"})
  636. self.valid_clear_load.update(
  637. {"user": "test_user_func", "tgt": "*", "fun": "my_mod.some_func"}
  638. )
  639. # Wrong arg value
  640. self.valid_clear_load["arg"] = [
  641. "alpha",
  642. "gamma",
  643. {
  644. "kwa": "kwarg #1",
  645. "kwb": "kwb",
  646. "one_more": "just one more",
  647. "__kwarg__": True,
  648. },
  649. ]
  650. self.clear.publish(self.valid_clear_load)
  651. self.assertEqual(self.fire_event_mock.mock_calls, [])
  652. # Wrong kwarg value
  653. self.valid_clear_load["arg"] = [
  654. "alpha",
  655. "beta",
  656. "gamma",
  657. {
  658. "kwa": "kkk",
  659. "kwb": "kwb",
  660. "one_more": "just one more",
  661. "__kwarg__": True,
  662. },
  663. ]
  664. self.clear.publish(self.valid_clear_load)
  665. self.assertEqual(self.fire_event_mock.mock_calls, [])
  666. # Missing arg
  667. self.valid_clear_load["arg"] = [
  668. "alpha",
  669. {
  670. "kwa": "kwarg #1",
  671. "kwb": "kwb",
  672. "one_more": "just one more",
  673. "__kwarg__": True,
  674. },
  675. ]
  676. self.clear.publish(self.valid_clear_load)
  677. self.assertEqual(self.fire_event_mock.mock_calls, [])
  678. # Missing kwarg
  679. self.valid_clear_load["arg"] = [
  680. "alpha",
  681. "beta",
  682. "gamma",
  683. {"kwa": "kwarg #1", "one_more": "just one more", "__kwarg__": True},
  684. ]
  685. self.clear.publish(self.valid_clear_load)
  686. self.assertEqual(self.fire_event_mock.mock_calls, [])
  687. class AuthACLTestCase(ModuleCase):
  688. """
  689. A class to check various aspects of the publisher ACL system
  690. """
  691. def setUp(self):
  692. self.auth_check_mock = MagicMock(return_value=True)
  693. opts = self.get_temp_config("master")
  694. patches = (
  695. ("salt.minion.MasterMinion", MagicMock()),
  696. ("salt.utils.verify.check_path_traversal", MagicMock()),
  697. ("salt.utils.minions.CkMinions.auth_check", self.auth_check_mock),
  698. ("salt.auth.LoadAuth.time_auth", MagicMock(return_value=True)),
  699. ("salt.client.get_local_client", MagicMock(return_value=opts["conf_file"])),
  700. )
  701. for mod, mock in patches:
  702. patcher = patch(mod, mock)
  703. patcher.start()
  704. self.addCleanup(patcher.stop)
  705. self.addCleanup(delattr, self, "auth_check_mock")
  706. opts["publisher_acl"] = {}
  707. opts["publisher_acl_blacklist"] = {}
  708. opts["master_job_cache"] = ""
  709. opts["sign_pub_messages"] = False
  710. opts["con_cache"] = ""
  711. opts["external_auth"] = {}
  712. opts["external_auth"]["pam"] = {"test_user": [{"alpha_minion": ["test.ping"]}]}
  713. self.clear = salt.master.ClearFuncs(opts, MagicMock())
  714. self.addCleanup(delattr, self, "clear")
  715. # overwrite the _send_pub method so we don't have to serialize MagicMock
  716. self.clear._send_pub = lambda payload: True
  717. # make sure to return a JID, instead of a mock
  718. self.clear.mminion.returners = {".prep_jid": lambda x: 1}
  719. self.valid_clear_load = {
  720. "tgt_type": "glob",
  721. "jid": "",
  722. "cmd": "publish",
  723. "tgt": "test_minion",
  724. "kwargs": {
  725. "username": "test_user",
  726. "password": "test_password",
  727. "show_timeout": False,
  728. "eauth": "pam",
  729. "show_jid": False,
  730. },
  731. "ret": "",
  732. "user": "test_user",
  733. "key": "",
  734. "arg": "",
  735. "fun": "test.ping",
  736. }
  737. self.addCleanup(delattr, self, "valid_clear_load")
  738. @skipIf(salt.utils.platform.is_windows(), "PAM eauth not available on Windows")
  739. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  740. def test_acl_simple_allow(self):
  741. self.clear.publish(self.valid_clear_load)
  742. self.assertEqual(
  743. self.auth_check_mock.call_args[0][0], [{"alpha_minion": ["test.ping"]}]
  744. )
  745. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  746. def test_acl_simple_deny(self):
  747. with patch(
  748. "salt.auth.LoadAuth.get_auth_list",
  749. MagicMock(return_value=[{"beta_minion": ["test.ping"]}]),
  750. ):
  751. self.clear.publish(self.valid_clear_load)
  752. self.assertEqual(
  753. self.auth_check_mock.call_args[0][0], [{"beta_minion": ["test.ping"]}]
  754. )