test_auth.py 30 KB

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