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