test_masterapi.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import io
  4. import os
  5. import stat
  6. from functools import wraps
  7. import salt.config
  8. import salt.daemons.masterapi as masterapi
  9. import salt.utils.platform
  10. from tests.support.helpers import slowTest
  11. from tests.support.mock import MagicMock, patch
  12. from tests.support.runtests import RUNTIME_VARS
  13. from tests.support.unit import TestCase
  14. def gen_permissions(owner="", group="", others=""):
  15. """
  16. Helper method to generate file permission bits
  17. Usage: gen_permissions('rw', 'r', 'r')
  18. """
  19. ret = 0
  20. for c in owner:
  21. ret |= getattr(stat, "S_I{}USR".format(c.upper()), 0)
  22. for c in group:
  23. ret |= getattr(stat, "S_I{}GRP".format(c.upper()), 0)
  24. for c in others:
  25. ret |= getattr(stat, "S_I{}OTH".format(c.upper()), 0)
  26. return ret
  27. def patch_check_permissions(uid=1, groups=None, is_windows=False, permissive_pki=False):
  28. if not groups:
  29. groups = [uid]
  30. def decorator(func):
  31. @wraps(func)
  32. def wrapper(self):
  33. self.auto_key.opts["permissive_pki_access"] = permissive_pki
  34. if salt.utils.platform.is_windows():
  35. with patch(
  36. "salt.utils.platform.is_windows", MagicMock(return_value=True)
  37. ):
  38. func(self)
  39. else:
  40. with patch("os.stat", self.os_stat_mock), patch(
  41. "os.getuid", MagicMock(return_value=uid)
  42. ), patch(
  43. "salt.utils.user.get_gid_list", MagicMock(return_value=groups)
  44. ), patch(
  45. "salt.utils.platform.is_windows", MagicMock(return_value=is_windows)
  46. ):
  47. func(self)
  48. return wrapper
  49. return decorator
  50. class AutoKeyTest(TestCase):
  51. """
  52. Tests for the salt.daemons.masterapi.AutoKey class
  53. """
  54. def setUp(self):
  55. opts = salt.config.master_config(None)
  56. opts["user"] = "test_user"
  57. self.auto_key = masterapi.AutoKey(opts)
  58. self.stats = {}
  59. def os_stat_mock(self, filename):
  60. fmode = MagicMock()
  61. fstats = self.stats.get(filename, {})
  62. fmode.st_mode = fstats.get("mode", 0)
  63. fmode.st_gid = fstats.get("gid", 0)
  64. return fmode
  65. @patch_check_permissions(uid=0, is_windows=True)
  66. def test_check_permissions_windows(self):
  67. """
  68. Assert that all files are accepted on windows
  69. """
  70. self.stats["testfile"] = {
  71. "mode": gen_permissions("rwx", "rwx", "rwx"),
  72. "gid": 2,
  73. }
  74. self.assertTrue(self.auto_key.check_permissions("testfile"))
  75. @patch_check_permissions(permissive_pki=True)
  76. def test_check_permissions_others_can_write(self):
  77. """
  78. Assert that no file is accepted, when others can write to it
  79. """
  80. self.stats["testfile"] = {"mode": gen_permissions("", "", "w"), "gid": 1}
  81. if salt.utils.platform.is_windows():
  82. self.assertTrue(self.auto_key.check_permissions("testfile"))
  83. else:
  84. self.assertFalse(self.auto_key.check_permissions("testfile"))
  85. @patch_check_permissions()
  86. def test_check_permissions_group_can_write_not_permissive(self):
  87. """
  88. Assert that a file is accepted, when group can write to it and
  89. permissive_pki_access=False
  90. """
  91. self.stats["testfile"] = {"mode": gen_permissions("w", "w", ""), "gid": 1}
  92. if salt.utils.platform.is_windows():
  93. self.assertTrue(self.auto_key.check_permissions("testfile"))
  94. else:
  95. self.assertFalse(self.auto_key.check_permissions("testfile"))
  96. @patch_check_permissions(permissive_pki=True)
  97. def test_check_permissions_group_can_write_permissive(self):
  98. """
  99. Assert that a file is accepted, when group can write to it and
  100. permissive_pki_access=True
  101. """
  102. self.stats["testfile"] = {"mode": gen_permissions("w", "w", ""), "gid": 1}
  103. self.assertTrue(self.auto_key.check_permissions("testfile"))
  104. @patch_check_permissions(uid=0, permissive_pki=True)
  105. def test_check_permissions_group_can_write_permissive_root_in_group(self):
  106. """
  107. Assert that a file is accepted, when group can write to it,
  108. permissive_pki_access=False, salt is root and in the file owning group
  109. """
  110. self.stats["testfile"] = {"mode": gen_permissions("w", "w", ""), "gid": 0}
  111. self.assertTrue(self.auto_key.check_permissions("testfile"))
  112. @patch_check_permissions(uid=0, permissive_pki=True)
  113. def test_check_permissions_group_can_write_permissive_root_not_in_group(self):
  114. """
  115. Assert that no file is accepted, when group can write to it,
  116. permissive_pki_access=False, salt is root and **not** in the file owning
  117. group
  118. """
  119. self.stats["testfile"] = {"mode": gen_permissions("w", "w", ""), "gid": 1}
  120. if salt.utils.platform.is_windows():
  121. self.assertTrue(self.auto_key.check_permissions("testfile"))
  122. else:
  123. self.assertFalse(self.auto_key.check_permissions("testfile"))
  124. @patch_check_permissions()
  125. def test_check_permissions_only_owner_can_write(self):
  126. """
  127. Assert that a file is accepted, when only the owner can write to it
  128. """
  129. self.stats["testfile"] = {"mode": gen_permissions("w", "", ""), "gid": 1}
  130. self.assertTrue(self.auto_key.check_permissions("testfile"))
  131. @patch_check_permissions(uid=0)
  132. def test_check_permissions_only_owner_can_write_root(self):
  133. """
  134. Assert that a file is accepted, when only the owner can write to it and salt is root
  135. """
  136. self.stats["testfile"] = {"mode": gen_permissions("w", "", ""), "gid": 0}
  137. self.assertTrue(self.auto_key.check_permissions("testfile"))
  138. def _test_check_autosign_grains(
  139. self,
  140. test_func,
  141. file_content="test_value",
  142. file_name="test_grain",
  143. autosign_grains_dir="test_dir",
  144. permissions_ret=True,
  145. ):
  146. """
  147. Helper function for testing autosign_grains().
  148. Patches ``os.walk`` to return only ``file_name`` and ``salt.utils.files.fopen`` to open a
  149. mock file with ``file_content`` as content. Optionally sets ``opts`` values.
  150. Then executes test_func. The ``os.walk`` and ``salt.utils.files.fopen`` mock objects
  151. are passed to the function as arguments.
  152. """
  153. if autosign_grains_dir:
  154. self.auto_key.opts["autosign_grains_dir"] = autosign_grains_dir
  155. mock_file = io.StringIO(file_content)
  156. mock_dirs = [(None, None, [file_name])]
  157. with patch("os.walk", MagicMock(return_value=mock_dirs)) as mock_walk, patch(
  158. "salt.utils.files.fopen", MagicMock(return_value=mock_file)
  159. ) as mock_open, patch(
  160. "salt.daemons.masterapi.AutoKey.check_permissions",
  161. MagicMock(return_value=permissions_ret),
  162. ) as mock_permissions:
  163. test_func(mock_walk, mock_open, mock_permissions)
  164. def test_check_autosign_grains_no_grains(self):
  165. """
  166. Asserts that autosigning from grains fails when no grain values are passed.
  167. """
  168. def test_func(mock_walk, mock_open, mock_permissions):
  169. self.assertFalse(self.auto_key.check_autosign_grains(None))
  170. self.assertEqual(mock_walk.call_count, 0)
  171. self.assertEqual(mock_open.call_count, 0)
  172. self.assertEqual(mock_permissions.call_count, 0)
  173. self.assertFalse(self.auto_key.check_autosign_grains({}))
  174. self.assertEqual(mock_walk.call_count, 0)
  175. self.assertEqual(mock_open.call_count, 0)
  176. self.assertEqual(mock_permissions.call_count, 0)
  177. self._test_check_autosign_grains(test_func)
  178. def test_check_autosign_grains_no_autosign_grains_dir(self):
  179. """
  180. Asserts that autosigning from grains fails when the \'autosign_grains_dir\' config option
  181. is undefined.
  182. """
  183. def test_func(mock_walk, mock_open, mock_permissions):
  184. self.assertFalse(
  185. self.auto_key.check_autosign_grains({"test_grain": "test_value"})
  186. )
  187. self.assertEqual(mock_walk.call_count, 0)
  188. self.assertEqual(mock_open.call_count, 0)
  189. self.assertEqual(mock_permissions.call_count, 0)
  190. self._test_check_autosign_grains(test_func, autosign_grains_dir=None)
  191. def test_check_autosign_grains_accept(self):
  192. """
  193. Asserts that autosigning from grains passes when a matching grain value is in an
  194. autosign_grain file.
  195. """
  196. def test_func(*args):
  197. self.assertTrue(
  198. self.auto_key.check_autosign_grains({"test_grain": "test_value"})
  199. )
  200. file_content = "#test_ignore\ntest_value"
  201. self._test_check_autosign_grains(test_func, file_content=file_content)
  202. def test_check_autosign_grains_accept_not(self):
  203. """
  204. Asserts that autosigning from grains fails when the grain value is not in the
  205. autosign_grain files.
  206. """
  207. def test_func(*args):
  208. self.assertFalse(
  209. self.auto_key.check_autosign_grains({"test_grain": "test_invalid"})
  210. )
  211. file_content = "#test_invalid\ntest_value"
  212. self._test_check_autosign_grains(test_func, file_content=file_content)
  213. def test_check_autosign_grains_invalid_file_permissions(self):
  214. """
  215. Asserts that autosigning from grains fails when the grain file has the wrong permissions.
  216. """
  217. def test_func(*args):
  218. self.assertFalse(
  219. self.auto_key.check_autosign_grains({"test_grain": "test_value"})
  220. )
  221. file_content = "#test_ignore\ntest_value"
  222. self._test_check_autosign_grains(
  223. test_func, file_content=file_content, permissions_ret=False
  224. )
  225. class LocalFuncsTestCase(TestCase):
  226. """
  227. TestCase for salt.daemons.masterapi.LocalFuncs class
  228. """
  229. def setUp(self):
  230. opts = salt.config.master_config(None)
  231. self.local_funcs = masterapi.LocalFuncs(opts, "test-key")
  232. # runner tests
  233. @slowTest
  234. def test_runner_token_not_authenticated(self):
  235. """
  236. Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
  237. """
  238. mock_ret = {
  239. "error": {
  240. "name": "TokenAuthenticationError",
  241. "message": 'Authentication failure of type "token" occurred.',
  242. }
  243. }
  244. ret = self.local_funcs.runner({"token": "asdfasdfasdfasdf"})
  245. self.assertDictEqual(mock_ret, ret)
  246. @slowTest
  247. def test_runner_token_authorization_error(self):
  248. """
  249. Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
  250. not authorized.
  251. """
  252. token = "asdfasdfasdfasdf"
  253. load = {"token": token, "fun": "test.arg", "kwarg": {}}
  254. mock_token = {"token": token, "eauth": "foo", "name": "test"}
  255. mock_ret = {
  256. "error": {
  257. "name": "TokenAuthenticationError",
  258. "message": 'Authentication failure of type "token" occurred '
  259. "for user test.",
  260. }
  261. }
  262. with patch(
  263. "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
  264. ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
  265. ret = self.local_funcs.runner(load)
  266. self.assertDictEqual(mock_ret, ret)
  267. @slowTest
  268. def test_runner_token_salt_invocation_error(self):
  269. """
  270. Asserts that a SaltInvocationError is returned when the token authenticates, but the
  271. command is malformed.
  272. """
  273. token = "asdfasdfasdfasdf"
  274. load = {"token": token, "fun": "badtestarg", "kwarg": {}}
  275. mock_token = {"token": token, "eauth": "foo", "name": "test"}
  276. mock_ret = {
  277. "error": {
  278. "name": "SaltInvocationError",
  279. "message": "A command invocation error occurred: Check syntax.",
  280. }
  281. }
  282. with patch(
  283. "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
  284. ), patch(
  285. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
  286. ):
  287. ret = self.local_funcs.runner(load)
  288. self.assertDictEqual(mock_ret, ret)
  289. @slowTest
  290. def test_runner_eauth_not_authenticated(self):
  291. """
  292. Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
  293. """
  294. mock_ret = {
  295. "error": {
  296. "name": "EauthAuthenticationError",
  297. "message": 'Authentication failure of type "eauth" occurred for '
  298. "user UNKNOWN.",
  299. }
  300. }
  301. ret = self.local_funcs.runner({"eauth": "foo"})
  302. self.assertDictEqual(mock_ret, ret)
  303. @slowTest
  304. def test_runner_eauth_authorization_error(self):
  305. """
  306. Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
  307. not authorized.
  308. """
  309. load = {"eauth": "foo", "username": "test", "fun": "test.arg", "kwarg": {}}
  310. mock_ret = {
  311. "error": {
  312. "name": "EauthAuthenticationError",
  313. "message": 'Authentication failure of type "eauth" occurred for '
  314. "user test.",
  315. }
  316. }
  317. with patch(
  318. "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
  319. ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
  320. ret = self.local_funcs.runner(load)
  321. self.assertDictEqual(mock_ret, ret)
  322. @slowTest
  323. def test_runner_eauth_salt_invocation_error(self):
  324. """
  325. Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
  326. command is malformed.
  327. """
  328. load = {
  329. "eauth": "foo",
  330. "username": "test",
  331. "fun": "bad.test.arg.func",
  332. "kwarg": {},
  333. }
  334. mock_ret = {
  335. "error": {
  336. "name": "SaltInvocationError",
  337. "message": "A command invocation error occurred: Check syntax.",
  338. }
  339. }
  340. with patch(
  341. "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
  342. ), patch(
  343. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
  344. ):
  345. ret = self.local_funcs.runner(load)
  346. self.assertDictEqual(mock_ret, ret)
  347. # wheel tests
  348. @slowTest
  349. def test_wheel_token_not_authenticated(self):
  350. """
  351. Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
  352. """
  353. mock_ret = {
  354. "error": {
  355. "name": "TokenAuthenticationError",
  356. "message": 'Authentication failure of type "token" occurred.',
  357. }
  358. }
  359. ret = self.local_funcs.wheel({"token": "asdfasdfasdfasdf"})
  360. self.assertDictEqual(mock_ret, ret)
  361. @slowTest
  362. def test_wheel_token_authorization_error(self):
  363. """
  364. Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
  365. not authorized.
  366. """
  367. token = "asdfasdfasdfasdf"
  368. load = {"token": token, "fun": "test.arg", "kwarg": {}}
  369. mock_token = {"token": token, "eauth": "foo", "name": "test"}
  370. mock_ret = {
  371. "error": {
  372. "name": "TokenAuthenticationError",
  373. "message": 'Authentication failure of type "token" occurred '
  374. "for user test.",
  375. }
  376. }
  377. with patch(
  378. "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
  379. ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
  380. ret = self.local_funcs.wheel(load)
  381. self.assertDictEqual(mock_ret, ret)
  382. @slowTest
  383. def test_wheel_token_salt_invocation_error(self):
  384. """
  385. Asserts that a SaltInvocationError is returned when the token authenticates, but the
  386. command is malformed.
  387. """
  388. token = "asdfasdfasdfasdf"
  389. load = {"token": token, "fun": "badtestarg", "kwarg": {}}
  390. mock_token = {"token": token, "eauth": "foo", "name": "test"}
  391. mock_ret = {
  392. "error": {
  393. "name": "SaltInvocationError",
  394. "message": "A command invocation error occurred: Check syntax.",
  395. }
  396. }
  397. with patch(
  398. "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
  399. ), patch(
  400. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
  401. ):
  402. ret = self.local_funcs.wheel(load)
  403. self.assertDictEqual(mock_ret, ret)
  404. @slowTest
  405. def test_wheel_eauth_not_authenticated(self):
  406. """
  407. Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
  408. """
  409. mock_ret = {
  410. "error": {
  411. "name": "EauthAuthenticationError",
  412. "message": 'Authentication failure of type "eauth" occurred for '
  413. "user UNKNOWN.",
  414. }
  415. }
  416. ret = self.local_funcs.wheel({"eauth": "foo"})
  417. self.assertDictEqual(mock_ret, ret)
  418. @slowTest
  419. def test_wheel_eauth_authorization_error(self):
  420. """
  421. Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
  422. not authorized.
  423. """
  424. load = {"eauth": "foo", "username": "test", "fun": "test.arg", "kwarg": {}}
  425. mock_ret = {
  426. "error": {
  427. "name": "EauthAuthenticationError",
  428. "message": 'Authentication failure of type "eauth" occurred for '
  429. "user test.",
  430. }
  431. }
  432. with patch(
  433. "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
  434. ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])):
  435. ret = self.local_funcs.wheel(load)
  436. self.assertDictEqual(mock_ret, ret)
  437. @slowTest
  438. def test_wheel_eauth_salt_invocation_error(self):
  439. """
  440. Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
  441. command is malformed.
  442. """
  443. load = {
  444. "eauth": "foo",
  445. "username": "test",
  446. "fun": "bad.test.arg.func",
  447. "kwarg": {},
  448. }
  449. mock_ret = {
  450. "error": {
  451. "name": "SaltInvocationError",
  452. "message": "A command invocation error occurred: Check syntax.",
  453. }
  454. }
  455. with patch(
  456. "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
  457. ), patch(
  458. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"])
  459. ):
  460. ret = self.local_funcs.wheel(load)
  461. self.assertDictEqual(mock_ret, ret)
  462. @slowTest
  463. def test_wheel_user_not_authenticated(self):
  464. """
  465. Asserts that an UserAuthenticationError is returned when the user can't authenticate.
  466. """
  467. mock_ret = {
  468. "error": {
  469. "name": "UserAuthenticationError",
  470. "message": 'Authentication failure of type "user" occurred for '
  471. "user UNKNOWN.",
  472. }
  473. }
  474. ret = self.local_funcs.wheel({})
  475. self.assertDictEqual(mock_ret, ret)
  476. # publish tests
  477. @slowTest
  478. def test_publish_user_is_blacklisted(self):
  479. """
  480. Asserts that an AuthorizationError is returned when the user has been blacklisted.
  481. """
  482. mock_ret = {
  483. "error": {
  484. "name": "AuthorizationError",
  485. "message": "Authorization error occurred.",
  486. }
  487. }
  488. with patch(
  489. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=True)
  490. ):
  491. self.assertEqual(
  492. mock_ret, self.local_funcs.publish({"user": "foo", "fun": "test.arg"})
  493. )
  494. @slowTest
  495. def test_publish_cmd_blacklisted(self):
  496. """
  497. Asserts that an AuthorizationError is returned when the command has been blacklisted.
  498. """
  499. mock_ret = {
  500. "error": {
  501. "name": "AuthorizationError",
  502. "message": "Authorization error occurred.",
  503. }
  504. }
  505. with patch(
  506. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  507. ), patch(
  508. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=True)
  509. ):
  510. self.assertEqual(
  511. mock_ret, self.local_funcs.publish({"user": "foo", "fun": "test.arg"})
  512. )
  513. @slowTest
  514. def test_publish_token_not_authenticated(self):
  515. """
  516. Asserts that an AuthenticationError is returned when the token can't authenticate.
  517. """
  518. load = {
  519. "user": "foo",
  520. "fun": "test.arg",
  521. "tgt": "test_minion",
  522. "kwargs": {"token": "asdfasdfasdfasdf"},
  523. }
  524. mock_ret = {
  525. "error": {
  526. "name": "AuthenticationError",
  527. "message": "Authentication error occurred.",
  528. }
  529. }
  530. with patch(
  531. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  532. ), patch(
  533. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  534. ):
  535. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  536. @slowTest
  537. def test_publish_token_authorization_error(self):
  538. """
  539. Asserts that an AuthorizationError is returned when the token authenticates, but is not
  540. authorized.
  541. """
  542. token = "asdfasdfasdfasdf"
  543. load = {
  544. "user": "foo",
  545. "fun": "test.arg",
  546. "tgt": "test_minion",
  547. "arg": "bar",
  548. "kwargs": {"token": token},
  549. }
  550. mock_token = {"token": token, "eauth": "foo", "name": "test"}
  551. mock_ret = {
  552. "error": {
  553. "name": "AuthorizationError",
  554. "message": "Authorization error occurred.",
  555. }
  556. }
  557. with patch(
  558. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  559. ), patch(
  560. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  561. ), patch(
  562. "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token)
  563. ), patch(
  564. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])
  565. ):
  566. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  567. @slowTest
  568. def test_publish_eauth_not_authenticated(self):
  569. """
  570. Asserts that an AuthenticationError is returned when the user can't authenticate.
  571. """
  572. load = {
  573. "user": "test",
  574. "fun": "test.arg",
  575. "tgt": "test_minion",
  576. "kwargs": {"eauth": "foo"},
  577. }
  578. mock_ret = {
  579. "error": {
  580. "name": "AuthenticationError",
  581. "message": "Authentication error occurred.",
  582. }
  583. }
  584. with patch(
  585. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  586. ), patch(
  587. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  588. ):
  589. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  590. @slowTest
  591. def test_publish_eauth_authorization_error(self):
  592. """
  593. Asserts that an AuthorizationError is returned when the user authenticates, but is not
  594. authorized.
  595. """
  596. load = {
  597. "user": "test",
  598. "fun": "test.arg",
  599. "tgt": "test_minion",
  600. "kwargs": {"eauth": "foo"},
  601. "arg": "bar",
  602. }
  603. mock_ret = {
  604. "error": {
  605. "name": "AuthorizationError",
  606. "message": "Authorization error occurred.",
  607. }
  608. }
  609. with patch(
  610. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  611. ), patch(
  612. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  613. ), patch(
  614. "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True)
  615. ), patch(
  616. "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])
  617. ):
  618. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  619. @slowTest
  620. def test_publish_user_not_authenticated(self):
  621. """
  622. Asserts that an AuthenticationError is returned when the user can't authenticate.
  623. """
  624. load = {"user": "test", "fun": "test.arg", "tgt": "test_minion"}
  625. mock_ret = {
  626. "error": {
  627. "name": "AuthenticationError",
  628. "message": "Authentication error occurred.",
  629. }
  630. }
  631. with patch(
  632. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  633. ), patch(
  634. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  635. ):
  636. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  637. @slowTest
  638. def test_publish_user_authenticated_missing_auth_list(self):
  639. """
  640. Asserts that an AuthenticationError is returned when the user has an effective user id and is
  641. authenticated, but the auth_list is empty.
  642. """
  643. load = {
  644. "user": "test",
  645. "fun": "test.arg",
  646. "tgt": "test_minion",
  647. "kwargs": {"user": "test"},
  648. "arg": "foo",
  649. }
  650. mock_ret = {
  651. "error": {
  652. "name": "AuthenticationError",
  653. "message": "Authentication error occurred.",
  654. }
  655. }
  656. with patch(
  657. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  658. ), patch(
  659. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  660. ), patch(
  661. "salt.auth.LoadAuth.authenticate_key",
  662. MagicMock(return_value="fake-user-key"),
  663. ), patch(
  664. "salt.utils.master.get_values_of_matching_keys", MagicMock(return_value=[])
  665. ):
  666. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  667. @slowTest
  668. def test_publish_user_authorization_error(self):
  669. """
  670. Asserts that an AuthorizationError is returned when the user authenticates, but is not
  671. authorized.
  672. """
  673. load = {
  674. "user": "test",
  675. "fun": "test.arg",
  676. "tgt": "test_minion",
  677. "kwargs": {"user": "test"},
  678. "arg": "foo",
  679. }
  680. mock_ret = {
  681. "error": {
  682. "name": "AuthorizationError",
  683. "message": "Authorization error occurred.",
  684. }
  685. }
  686. with patch(
  687. "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False)
  688. ), patch(
  689. "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False)
  690. ), patch(
  691. "salt.auth.LoadAuth.authenticate_key",
  692. MagicMock(return_value="fake-user-key"),
  693. ), patch(
  694. "salt.utils.master.get_values_of_matching_keys",
  695. MagicMock(return_value=["test"]),
  696. ), patch(
  697. "salt.utils.minions.CkMinions.auth_check", MagicMock(return_value=False)
  698. ):
  699. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  700. class FakeCache(object):
  701. def __init__(self):
  702. self.data = {}
  703. def store(self, bank, key, value):
  704. self.data[bank, key] = value
  705. def fetch(self, bank, key):
  706. return self.data[bank, key]
  707. class RemoteFuncsTestCase(TestCase):
  708. """
  709. TestCase for salt.daemons.masterapi.RemoteFuncs class
  710. """
  711. def setUp(self):
  712. opts = salt.config.master_config(
  713. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master")
  714. )
  715. self.funcs = masterapi.RemoteFuncs(opts)
  716. self.funcs.cache = FakeCache()
  717. @slowTest
  718. def test_mine_get(self, tgt_type_key="tgt_type"):
  719. """
  720. Asserts that ``mine_get`` gives the expected results.
  721. Actually this only tests that:
  722. - the correct check minions method is called
  723. - the correct cache key is subsequently used
  724. """
  725. self.funcs.cache.store(
  726. "minions/webserver", "mine", dict(ip_addr="2001:db8::1:3")
  727. )
  728. with patch(
  729. "salt.utils.minions.CkMinions._check_compound_minions",
  730. MagicMock(return_value=(dict(minions=["webserver"], missing=[]))),
  731. ):
  732. ret = self.funcs._mine_get(
  733. {
  734. "id": "requester_minion",
  735. "tgt": "G@roles:web",
  736. "fun": "ip_addr",
  737. tgt_type_key: "compound",
  738. }
  739. )
  740. self.assertDictEqual(ret, dict(webserver="2001:db8::1:3"))
  741. @slowTest
  742. def test_mine_get_pre_nitrogen_compat(self):
  743. """
  744. Asserts that pre-Nitrogen API key ``expr_form`` is still accepted.
  745. This is what minions before Nitrogen would issue.
  746. """
  747. self.test_mine_get(tgt_type_key="expr_form")
  748. @slowTest
  749. def test_mine_get_dict_str(self, tgt_type_key="tgt_type"):
  750. """
  751. Asserts that ``mine_get`` gives the expected results when request
  752. is a comma-separated list.
  753. Actually this only tests that:
  754. - the correct check minions method is called
  755. - the correct cache key is subsequently used
  756. """
  757. self.funcs.cache.store(
  758. "minions/webserver",
  759. "mine",
  760. dict(ip_addr="2001:db8::1:3", ip4_addr="127.0.0.1"),
  761. )
  762. with patch(
  763. "salt.utils.minions.CkMinions._check_compound_minions",
  764. MagicMock(return_value=(dict(minions=["webserver"], missing=[]))),
  765. ):
  766. ret = self.funcs._mine_get(
  767. {
  768. "id": "requester_minion",
  769. "tgt": "G@roles:web",
  770. "fun": "ip_addr,ip4_addr",
  771. tgt_type_key: "compound",
  772. }
  773. )
  774. self.assertDictEqual(
  775. ret,
  776. dict(
  777. ip_addr=dict(webserver="2001:db8::1:3"),
  778. ip4_addr=dict(webserver="127.0.0.1"),
  779. ),
  780. )
  781. @slowTest
  782. def test_mine_get_dict_list(self, tgt_type_key="tgt_type"):
  783. """
  784. Asserts that ``mine_get`` gives the expected results when request
  785. is a list.
  786. Actually this only tests that:
  787. - the correct check minions method is called
  788. - the correct cache key is subsequently used
  789. """
  790. self.funcs.cache.store(
  791. "minions/webserver",
  792. "mine",
  793. dict(ip_addr="2001:db8::1:3", ip4_addr="127.0.0.1"),
  794. )
  795. with patch(
  796. "salt.utils.minions.CkMinions._check_compound_minions",
  797. MagicMock(return_value=(dict(minions=["webserver"], missing=[]))),
  798. ):
  799. ret = self.funcs._mine_get(
  800. {
  801. "id": "requester_minion",
  802. "tgt": "G@roles:web",
  803. "fun": ["ip_addr", "ip4_addr"],
  804. tgt_type_key: "compound",
  805. }
  806. )
  807. self.assertDictEqual(
  808. ret,
  809. dict(
  810. ip_addr=dict(webserver="2001:db8::1:3"),
  811. ip4_addr=dict(webserver="127.0.0.1"),
  812. ),
  813. )
  814. @slowTest
  815. def test_mine_get_acl_allowed(self):
  816. """
  817. Asserts that ``mine_get`` gives the expected results when this is allowed
  818. in the client-side ACL that was stored in the mine data.
  819. """
  820. self.funcs.cache.store(
  821. "minions/webserver",
  822. "mine",
  823. {
  824. "ip_addr": {
  825. salt.utils.mine.MINE_ITEM_ACL_DATA: "2001:db8::1:4",
  826. salt.utils.mine.MINE_ITEM_ACL_ID: salt.utils.mine.MINE_ITEM_ACL_VERSION,
  827. "allow_tgt": "requester_minion",
  828. "allow_tgt_type": "glob",
  829. },
  830. },
  831. )
  832. # The glob check is for the resolution of the allow_tgt
  833. # The compound check is for the resolution of the tgt in the mine_get request.
  834. with patch(
  835. "salt.utils.minions.CkMinions._check_glob_minions",
  836. MagicMock(return_value={"minions": ["requester_minion"], "missing": []}),
  837. ), patch(
  838. "salt.utils.minions.CkMinions._check_compound_minions",
  839. MagicMock(return_value={"minions": ["webserver"], "missing": []}),
  840. ):
  841. ret = self.funcs._mine_get(
  842. {
  843. "id": "requester_minion",
  844. "tgt": "anything",
  845. "tgt_type": "compound",
  846. "fun": ["ip_addr"],
  847. }
  848. )
  849. self.assertDictEqual(ret, {"ip_addr": {"webserver": "2001:db8::1:4"}})
  850. @slowTest
  851. def test_mine_get_acl_rejected(self):
  852. """
  853. Asserts that ``mine_get`` gives the expected results when this is rejected
  854. in the client-side ACL that was stored in the mine data. This results in
  855. no data being sent back (just as if the entry wouldn't exist).
  856. """
  857. self.funcs.cache.store(
  858. "minions/webserver",
  859. "mine",
  860. {
  861. "ip_addr": {
  862. salt.utils.mine.MINE_ITEM_ACL_DATA: "2001:db8::1:4",
  863. salt.utils.mine.MINE_ITEM_ACL_ID: salt.utils.mine.MINE_ITEM_ACL_VERSION,
  864. "allow_tgt": "not_requester_minion",
  865. "allow_tgt_type": "glob",
  866. }
  867. },
  868. )
  869. # The glob check is for the resolution of the allow_tgt
  870. # The compound check is for the resolution of the tgt in the mine_get request.
  871. with patch(
  872. "salt.utils.minions.CkMinions._check_glob_minions",
  873. MagicMock(
  874. return_value={"minions": ["not_requester_minion"], "missing": []}
  875. ),
  876. ), patch(
  877. "salt.utils.minions.CkMinions._check_compound_minions",
  878. MagicMock(return_value={"minions": ["webserver"], "missing": []}),
  879. ):
  880. ret = self.funcs._mine_get(
  881. {
  882. "id": "requester_minion",
  883. "tgt": "anything",
  884. "tgt_type": "compound",
  885. "fun": ["ip_addr"],
  886. }
  887. )
  888. self.assertDictEqual(ret, {})