test_masterapi.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. # -*- coding: utf-8 -*-
  2. # Import Python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. from functools import wraps
  5. import os
  6. import io
  7. import stat
  8. # Import Salt libs
  9. import salt.config
  10. import salt.daemons.masterapi as masterapi
  11. import salt.utils.platform
  12. # Import Salt Testing Libs
  13. from tests.support.runtests import RUNTIME_VARS
  14. from tests.support.unit import TestCase, skipIf
  15. from tests.support.mock import (
  16. patch,
  17. MagicMock,
  18. NO_MOCK,
  19. NO_MOCK_REASON
  20. )
  21. def gen_permissions(owner='', group='', others=''):
  22. '''
  23. Helper method to generate file permission bits
  24. Usage: gen_permissions('rw', 'r', 'r')
  25. '''
  26. ret = 0
  27. for c in owner:
  28. ret |= getattr(stat, 'S_I{}USR'.format(c.upper()), 0)
  29. for c in group:
  30. ret |= getattr(stat, 'S_I{}GRP'.format(c.upper()), 0)
  31. for c in others:
  32. ret |= getattr(stat, 'S_I{}OTH'.format(c.upper()), 0)
  33. return ret
  34. def patch_check_permissions(uid=1, groups=None, is_windows=False, permissive_pki=False):
  35. if not groups:
  36. groups = [uid]
  37. def decorator(func):
  38. @wraps(func)
  39. def wrapper(self):
  40. self.auto_key.opts['permissive_pki_access'] = permissive_pki
  41. if salt.utils.platform.is_windows():
  42. with patch('salt.utils.platform.is_windows', MagicMock(return_value=True)):
  43. func(self)
  44. else:
  45. with patch('os.stat', self.os_stat_mock), \
  46. patch('os.getuid', MagicMock(return_value=uid)), \
  47. patch('salt.utils.user.get_gid_list', MagicMock(return_value=groups)), \
  48. patch('salt.utils.platform.is_windows', MagicMock(return_value=is_windows)):
  49. func(self)
  50. return wrapper
  51. return decorator
  52. @skipIf(NO_MOCK, NO_MOCK_REASON)
  53. class AutoKeyTest(TestCase):
  54. '''
  55. Tests for the salt.daemons.masterapi.AutoKey class
  56. '''
  57. def setUp(self):
  58. opts = salt.config.master_config(None)
  59. opts['user'] = 'test_user'
  60. self.auto_key = masterapi.AutoKey(opts)
  61. self.stats = {}
  62. def os_stat_mock(self, filename):
  63. fmode = MagicMock()
  64. fstats = self.stats.get(filename, {})
  65. fmode.st_mode = fstats.get('mode', 0)
  66. fmode.st_gid = fstats.get('gid', 0)
  67. return fmode
  68. @patch_check_permissions(uid=0, is_windows=True)
  69. def test_check_permissions_windows(self):
  70. '''
  71. Assert that all files are accepted on windows
  72. '''
  73. self.stats['testfile'] = {'mode': gen_permissions('rwx', 'rwx', 'rwx'), 'gid': 2}
  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 perkissive_pki_access=False
  89. '''
  90. self.stats['testfile'] = {'mode': gen_permissions('w', 'w', ''), 'gid': 1}
  91. if salt.utils.platform.is_windows():
  92. self.assertTrue(self.auto_key.check_permissions('testfile'))
  93. else:
  94. self.assertFalse(self.auto_key.check_permissions('testfile'))
  95. @patch_check_permissions(permissive_pki=True)
  96. def test_check_permissions_group_can_write_permissive(self):
  97. '''
  98. Assert that a file is accepted, when group can write to it and perkissive_pki_access=True
  99. '''
  100. self.stats['testfile'] = {'mode': gen_permissions('w', 'w', ''), 'gid': 1}
  101. self.assertTrue(self.auto_key.check_permissions('testfile'))
  102. @patch_check_permissions(uid=0, permissive_pki=True)
  103. def test_check_permissions_group_can_write_permissive_root_in_group(self):
  104. '''
  105. Assert that a file is accepted, when group can write to it, perkissive_pki_access=False,
  106. salt is root and in the file owning group
  107. '''
  108. self.stats['testfile'] = {'mode': gen_permissions('w', 'w', ''), 'gid': 0}
  109. self.assertTrue(self.auto_key.check_permissions('testfile'))
  110. @patch_check_permissions(uid=0, permissive_pki=True)
  111. def test_check_permissions_group_can_write_permissive_root_not_in_group(self):
  112. '''
  113. Assert that no file is accepted, when group can write to it, perkissive_pki_access=False,
  114. salt is root and **not** in the file owning group
  115. '''
  116. self.stats['testfile'] = {'mode': gen_permissions('w', 'w', ''), 'gid': 1}
  117. if salt.utils.platform.is_windows():
  118. self.assertTrue(self.auto_key.check_permissions('testfile'))
  119. else:
  120. self.assertFalse(self.auto_key.check_permissions('testfile'))
  121. @patch_check_permissions()
  122. def test_check_permissions_only_owner_can_write(self):
  123. '''
  124. Assert that a file is accepted, when only the owner can write to it
  125. '''
  126. self.stats['testfile'] = {'mode': gen_permissions('w', '', ''), 'gid': 1}
  127. self.assertTrue(self.auto_key.check_permissions('testfile'))
  128. @patch_check_permissions(uid=0)
  129. def test_check_permissions_only_owner_can_write_root(self):
  130. '''
  131. Assert that a file is accepted, when only the owner can write to it and salt is root
  132. '''
  133. self.stats['testfile'] = {'mode': gen_permissions('w', '', ''), 'gid': 0}
  134. self.assertTrue(self.auto_key.check_permissions('testfile'))
  135. def _test_check_autosign_grains(self,
  136. test_func,
  137. file_content='test_value',
  138. file_name='test_grain',
  139. autosign_grains_dir='test_dir',
  140. permissions_ret=True):
  141. '''
  142. Helper function for testing autosign_grains().
  143. Patches ``os.walk`` to return only ``file_name`` and ``salt.utils.files.fopen`` to open a
  144. mock file with ``file_content`` as content. Optionally sets ``opts`` values.
  145. Then executes test_func. The ``os.walk`` and ``salt.utils.files.fopen`` mock objects
  146. are passed to the function as arguments.
  147. '''
  148. if autosign_grains_dir:
  149. self.auto_key.opts['autosign_grains_dir'] = autosign_grains_dir
  150. mock_file = io.StringIO(file_content)
  151. mock_dirs = [(None, None, [file_name])]
  152. with patch('os.walk', MagicMock(return_value=mock_dirs)) as mock_walk, \
  153. patch('salt.utils.files.fopen', MagicMock(return_value=mock_file)) as mock_open, \
  154. patch('salt.daemons.masterapi.AutoKey.check_permissions',
  155. MagicMock(return_value=permissions_ret)) as mock_permissions:
  156. test_func(mock_walk, mock_open, mock_permissions)
  157. def test_check_autosign_grains_no_grains(self):
  158. '''
  159. Asserts that autosigning from grains fails when no grain values are passed.
  160. '''
  161. def test_func(mock_walk, mock_open, mock_permissions):
  162. self.assertFalse(self.auto_key.check_autosign_grains(None))
  163. self.assertEqual(mock_walk.call_count, 0)
  164. self.assertEqual(mock_open.call_count, 0)
  165. self.assertEqual(mock_permissions.call_count, 0)
  166. self.assertFalse(self.auto_key.check_autosign_grains({}))
  167. self.assertEqual(mock_walk.call_count, 0)
  168. self.assertEqual(mock_open.call_count, 0)
  169. self.assertEqual(mock_permissions.call_count, 0)
  170. self._test_check_autosign_grains(test_func)
  171. def test_check_autosign_grains_no_autosign_grains_dir(self):
  172. '''
  173. Asserts that autosigning from grains fails when the \'autosign_grains_dir\' config option
  174. is undefined.
  175. '''
  176. def test_func(mock_walk, mock_open, mock_permissions):
  177. self.assertFalse(self.auto_key.check_autosign_grains({'test_grain': 'test_value'}))
  178. self.assertEqual(mock_walk.call_count, 0)
  179. self.assertEqual(mock_open.call_count, 0)
  180. self.assertEqual(mock_permissions.call_count, 0)
  181. self._test_check_autosign_grains(test_func, autosign_grains_dir=None)
  182. def test_check_autosign_grains_accept(self):
  183. '''
  184. Asserts that autosigning from grains passes when a matching grain value is in an
  185. autosign_grain file.
  186. '''
  187. def test_func(*args):
  188. self.assertTrue(self.auto_key.check_autosign_grains({'test_grain': 'test_value'}))
  189. file_content = '#test_ignore\ntest_value'
  190. self._test_check_autosign_grains(test_func, file_content=file_content)
  191. def test_check_autosign_grains_accept_not(self):
  192. '''
  193. Asserts that autosigning from grains fails when the grain value is not in the
  194. autosign_grain files.
  195. '''
  196. def test_func(*args):
  197. self.assertFalse(self.auto_key.check_autosign_grains({'test_grain': 'test_invalid'}))
  198. file_content = '#test_invalid\ntest_value'
  199. self._test_check_autosign_grains(test_func, file_content=file_content)
  200. def test_check_autosign_grains_invalid_file_permissions(self):
  201. '''
  202. Asserts that autosigning from grains fails when the grain file has the wrong permissions.
  203. '''
  204. def test_func(*args):
  205. self.assertFalse(self.auto_key.check_autosign_grains({'test_grain': 'test_value'}))
  206. file_content = '#test_ignore\ntest_value'
  207. self._test_check_autosign_grains(test_func, file_content=file_content, permissions_ret=False)
  208. @skipIf(NO_MOCK, NO_MOCK_REASON)
  209. class LocalFuncsTestCase(TestCase):
  210. '''
  211. TestCase for salt.daemons.masterapi.LocalFuncs class
  212. '''
  213. def setUp(self):
  214. opts = salt.config.master_config(None)
  215. self.local_funcs = masterapi.LocalFuncs(opts, 'test-key')
  216. # runner tests
  217. def test_runner_token_not_authenticated(self):
  218. '''
  219. Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
  220. '''
  221. mock_ret = {'error': {'name': 'TokenAuthenticationError',
  222. 'message': 'Authentication failure of type "token" occurred.'}}
  223. ret = self.local_funcs.runner({'token': 'asdfasdfasdfasdf'})
  224. self.assertDictEqual(mock_ret, ret)
  225. def test_runner_token_authorization_error(self):
  226. '''
  227. Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
  228. not authorized.
  229. '''
  230. token = 'asdfasdfasdfasdf'
  231. load = {'token': token, 'fun': 'test.arg', 'kwarg': {}}
  232. mock_token = {'token': token, 'eauth': 'foo', 'name': 'test'}
  233. mock_ret = {'error': {'name': 'TokenAuthenticationError',
  234. 'message': 'Authentication failure of type "token" occurred '
  235. 'for user test.'}}
  236. with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
  237. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  238. ret = self.local_funcs.runner(load)
  239. self.assertDictEqual(mock_ret, ret)
  240. def test_runner_token_salt_invocation_error(self):
  241. '''
  242. Asserts that a SaltInvocationError is returned when the token authenticates, but the
  243. command is malformed.
  244. '''
  245. token = 'asdfasdfasdfasdf'
  246. load = {'token': token, 'fun': 'badtestarg', 'kwarg': {}}
  247. mock_token = {'token': token, 'eauth': 'foo', 'name': 'test'}
  248. mock_ret = {'error': {'name': 'SaltInvocationError',
  249. 'message': 'A command invocation error occurred: Check syntax.'}}
  250. with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
  251. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=['testing'])):
  252. ret = self.local_funcs.runner(load)
  253. self.assertDictEqual(mock_ret, ret)
  254. def test_runner_eauth_not_authenticated(self):
  255. '''
  256. Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
  257. '''
  258. mock_ret = {'error': {'name': 'EauthAuthenticationError',
  259. 'message': 'Authentication failure of type "eauth" occurred for '
  260. 'user UNKNOWN.'}}
  261. ret = self.local_funcs.runner({'eauth': 'foo'})
  262. self.assertDictEqual(mock_ret, ret)
  263. def test_runner_eauth_authorization_error(self):
  264. '''
  265. Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
  266. not authorized.
  267. '''
  268. load = {'eauth': 'foo', 'username': 'test', 'fun': 'test.arg', 'kwarg': {}}
  269. mock_ret = {'error': {'name': 'EauthAuthenticationError',
  270. 'message': 'Authentication failure of type "eauth" occurred for '
  271. 'user test.'}}
  272. with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
  273. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  274. ret = self.local_funcs.runner(load)
  275. self.assertDictEqual(mock_ret, ret)
  276. def test_runner_eauth_salt_invocation_error(self):
  277. '''
  278. Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
  279. command is malformed.
  280. '''
  281. load = {'eauth': 'foo', 'username': 'test', 'fun': 'bad.test.arg.func', 'kwarg': {}}
  282. mock_ret = {'error': {'name': 'SaltInvocationError',
  283. 'message': 'A command invocation error occurred: Check syntax.'}}
  284. with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
  285. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=['testing'])):
  286. ret = self.local_funcs.runner(load)
  287. self.assertDictEqual(mock_ret, ret)
  288. # wheel tests
  289. def test_wheel_token_not_authenticated(self):
  290. '''
  291. Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
  292. '''
  293. mock_ret = {'error': {'name': 'TokenAuthenticationError',
  294. 'message': 'Authentication failure of type "token" occurred.'}}
  295. ret = self.local_funcs.wheel({'token': 'asdfasdfasdfasdf'})
  296. self.assertDictEqual(mock_ret, ret)
  297. def test_wheel_token_authorization_error(self):
  298. '''
  299. Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
  300. not authorized.
  301. '''
  302. token = 'asdfasdfasdfasdf'
  303. load = {'token': token, 'fun': 'test.arg', 'kwarg': {}}
  304. mock_token = {'token': token, 'eauth': 'foo', 'name': 'test'}
  305. mock_ret = {'error': {'name': 'TokenAuthenticationError',
  306. 'message': 'Authentication failure of type "token" occurred '
  307. 'for user test.'}}
  308. with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
  309. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  310. ret = self.local_funcs.wheel(load)
  311. self.assertDictEqual(mock_ret, ret)
  312. def test_wheel_token_salt_invocation_error(self):
  313. '''
  314. Asserts that a SaltInvocationError is returned when the token authenticates, but the
  315. command is malformed.
  316. '''
  317. token = 'asdfasdfasdfasdf'
  318. load = {'token': token, 'fun': 'badtestarg', 'kwarg': {}}
  319. mock_token = {'token': token, 'eauth': 'foo', 'name': 'test'}
  320. mock_ret = {'error': {'name': 'SaltInvocationError',
  321. 'message': 'A command invocation error occurred: Check syntax.'}}
  322. with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
  323. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=['testing'])):
  324. ret = self.local_funcs.wheel(load)
  325. self.assertDictEqual(mock_ret, ret)
  326. def test_wheel_eauth_not_authenticated(self):
  327. '''
  328. Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
  329. '''
  330. mock_ret = {'error': {'name': 'EauthAuthenticationError',
  331. 'message': 'Authentication failure of type "eauth" occurred for '
  332. 'user UNKNOWN.'}}
  333. ret = self.local_funcs.wheel({'eauth': 'foo'})
  334. self.assertDictEqual(mock_ret, ret)
  335. def test_wheel_eauth_authorization_error(self):
  336. '''
  337. Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
  338. not authorized.
  339. '''
  340. load = {'eauth': 'foo', 'username': 'test', 'fun': 'test.arg', 'kwarg': {}}
  341. mock_ret = {'error': {'name': 'EauthAuthenticationError',
  342. 'message': 'Authentication failure of type "eauth" occurred for '
  343. 'user test.'}}
  344. with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
  345. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  346. ret = self.local_funcs.wheel(load)
  347. self.assertDictEqual(mock_ret, ret)
  348. def test_wheel_eauth_salt_invocation_error(self):
  349. '''
  350. Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
  351. command is malformed.
  352. '''
  353. load = {'eauth': 'foo', 'username': 'test', 'fun': 'bad.test.arg.func', 'kwarg': {}}
  354. mock_ret = {'error': {'name': 'SaltInvocationError',
  355. 'message': 'A command invocation error occurred: Check syntax.'}}
  356. with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
  357. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=['testing'])):
  358. ret = self.local_funcs.wheel(load)
  359. self.assertDictEqual(mock_ret, ret)
  360. def test_wheel_user_not_authenticated(self):
  361. '''
  362. Asserts that an UserAuthenticationError is returned when the user can't authenticate.
  363. '''
  364. mock_ret = {'error': {'name': 'UserAuthenticationError',
  365. 'message': 'Authentication failure of type "user" occurred for '
  366. 'user UNKNOWN.'}}
  367. ret = self.local_funcs.wheel({})
  368. self.assertDictEqual(mock_ret, ret)
  369. # publish tests
  370. def test_publish_user_is_blacklisted(self):
  371. '''
  372. Asserts that an AuthorizationError is returned when the user has been blacklisted.
  373. '''
  374. mock_ret = {'error': {'name': 'AuthorizationError',
  375. 'message': 'Authorization error occurred.'}}
  376. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=True)):
  377. self.assertEqual(mock_ret, self.local_funcs.publish({'user': 'foo', 'fun': 'test.arg'}))
  378. def test_publish_cmd_blacklisted(self):
  379. '''
  380. Asserts that an AuthorizationError is returned when the command has been blacklisted.
  381. '''
  382. mock_ret = {'error': {'name': 'AuthorizationError',
  383. 'message': 'Authorization error occurred.'}}
  384. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  385. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=True)):
  386. self.assertEqual(mock_ret, self.local_funcs.publish({'user': 'foo', 'fun': 'test.arg'}))
  387. def test_publish_token_not_authenticated(self):
  388. '''
  389. Asserts that an AuthenticationError is returned when the token can't authenticate.
  390. '''
  391. load = {'user': 'foo', 'fun': 'test.arg', 'tgt': 'test_minion',
  392. 'kwargs': {'token': 'asdfasdfasdfasdf'}}
  393. mock_ret = {'error': {'name': 'AuthenticationError',
  394. 'message': 'Authentication error occurred.'}}
  395. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  396. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)):
  397. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  398. def test_publish_token_authorization_error(self):
  399. '''
  400. Asserts that an AuthorizationError is returned when the token authenticates, but is not
  401. authorized.
  402. '''
  403. token = 'asdfasdfasdfasdf'
  404. load = {'user': 'foo', 'fun': 'test.arg', 'tgt': 'test_minion',
  405. 'arg': 'bar', 'kwargs': {'token': token}}
  406. mock_token = {'token': token, 'eauth': 'foo', 'name': 'test'}
  407. mock_ret = {'error': {'name': 'AuthorizationError',
  408. 'message': 'Authorization error occurred.'}}
  409. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  410. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)), \
  411. patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
  412. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  413. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  414. def test_publish_eauth_not_authenticated(self):
  415. '''
  416. Asserts that an AuthenticationError is returned when the user can't authenticate.
  417. '''
  418. load = {'user': 'test', 'fun': 'test.arg', 'tgt': 'test_minion',
  419. 'kwargs': {'eauth': 'foo'}}
  420. mock_ret = {'error': {'name': 'AuthenticationError',
  421. 'message': 'Authentication error occurred.'}}
  422. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  423. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)):
  424. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  425. def test_publish_eauth_authorization_error(self):
  426. '''
  427. Asserts that an AuthorizationError is returned when the user authenticates, but is not
  428. authorized.
  429. '''
  430. load = {'user': 'test', 'fun': 'test.arg', 'tgt': 'test_minion',
  431. 'kwargs': {'eauth': 'foo'}, 'arg': 'bar'}
  432. mock_ret = {'error': {'name': 'AuthorizationError',
  433. 'message': 'Authorization error occurred.'}}
  434. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  435. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)), \
  436. patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
  437. patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
  438. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  439. def test_publish_user_not_authenticated(self):
  440. '''
  441. Asserts that an AuthenticationError is returned when the user can't authenticate.
  442. '''
  443. load = {'user': 'test', 'fun': 'test.arg', 'tgt': 'test_minion'}
  444. mock_ret = {'error': {'name': 'AuthenticationError',
  445. 'message': 'Authentication error occurred.'}}
  446. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  447. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)):
  448. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  449. def test_publish_user_authenticated_missing_auth_list(self):
  450. '''
  451. Asserts that an AuthenticationError is returned when the user has an effective user id and is
  452. authenticated, but the auth_list is empty.
  453. '''
  454. load = {'user': 'test', 'fun': 'test.arg', 'tgt': 'test_minion',
  455. 'kwargs': {'user': 'test'}, 'arg': 'foo'}
  456. mock_ret = {'error': {'name': 'AuthenticationError',
  457. 'message': 'Authentication error occurred.'}}
  458. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  459. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)), \
  460. patch('salt.auth.LoadAuth.authenticate_key', MagicMock(return_value='fake-user-key')), \
  461. patch('salt.utils.master.get_values_of_matching_keys', MagicMock(return_value=[])):
  462. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  463. def test_publish_user_authorization_error(self):
  464. '''
  465. Asserts that an AuthorizationError is returned when the user authenticates, but is not
  466. authorized.
  467. '''
  468. load = {'user': 'test', 'fun': 'test.arg', 'tgt': 'test_minion',
  469. 'kwargs': {'user': 'test'}, 'arg': 'foo'}
  470. mock_ret = {'error': {'name': 'AuthorizationError',
  471. 'message': 'Authorization error occurred.'}}
  472. with patch('salt.acl.PublisherACL.user_is_blacklisted', MagicMock(return_value=False)), \
  473. patch('salt.acl.PublisherACL.cmd_is_blacklisted', MagicMock(return_value=False)), \
  474. patch('salt.auth.LoadAuth.authenticate_key', MagicMock(return_value='fake-user-key')), \
  475. patch('salt.utils.master.get_values_of_matching_keys', MagicMock(return_value=['test'])), \
  476. patch('salt.utils.minions.CkMinions.auth_check', MagicMock(return_value=False)):
  477. self.assertEqual(mock_ret, self.local_funcs.publish(load))
  478. class FakeCache(object):
  479. def __init__(self):
  480. self.data = {}
  481. def store(self, bank, key, value):
  482. self.data[bank, key] = value
  483. def fetch(self, bank, key):
  484. return self.data[bank, key]
  485. class RemoteFuncsTestCase(TestCase):
  486. '''
  487. TestCase for salt.daemons.masterapi.RemoteFuncs class
  488. '''
  489. def setUp(self):
  490. opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'master'))
  491. self.funcs = masterapi.RemoteFuncs(opts)
  492. self.funcs.cache = FakeCache()
  493. def test_mine_get(self, tgt_type_key='tgt_type'):
  494. '''
  495. Asserts that ``mine_get`` gives the expected results.
  496. Actually this only tests that:
  497. - the correct check minions method is called
  498. - the correct cache key is subsequently used
  499. '''
  500. self.funcs.cache.store('minions/webserver', 'mine',
  501. dict(ip_addr='2001:db8::1:3'))
  502. with patch('salt.utils.minions.CkMinions._check_compound_minions',
  503. MagicMock(return_value=(dict(
  504. minions=['webserver'],
  505. missing=[])))):
  506. ret = self.funcs._mine_get(
  507. {
  508. 'id': 'requester_minion',
  509. 'tgt': 'G@roles:web',
  510. 'fun': 'ip_addr',
  511. tgt_type_key: 'compound',
  512. }
  513. )
  514. self.assertDictEqual(ret, dict(webserver='2001:db8::1:3'))
  515. def test_mine_get_pre_nitrogen_compat(self):
  516. '''
  517. Asserts that pre-Nitrogen API key ``expr_form`` is still accepted.
  518. This is what minions before Nitrogen would issue.
  519. '''
  520. self.test_mine_get(tgt_type_key='expr_form')