import time import salt.config import salt.master from tests.support.helpers import slowTest from tests.support.mixins import AdaptedConfigurationTestCaseMixin from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase class TransportMethodsTest(TestCase): def test_transport_methods(self): class Foo(salt.master.TransportMethods): expose_methods = ["bar"] def bar(self): pass def bang(self): pass foo = Foo() assert foo.get_method("bar") is not None assert foo.get_method("bang") is None def test_aes_funcs_white(self): """ Validate methods exposed on AESFuncs exist and are callable """ opts = salt.config.master_config(None) aes_funcs = salt.master.AESFuncs(opts) for name in aes_funcs.expose_methods: func = getattr(aes_funcs, name, None) assert callable(func) def test_aes_funcs_black(self): """ Validate methods on AESFuncs that should not be called remotely """ opts = salt.config.master_config(None) aes_funcs = salt.master.AESFuncs(opts) # Any callable that should not explicitly be allowed should be added # here. blacklist_methods = [ "_AESFuncs__setup_fileserver", "_AESFuncs__verify_load", "_AESFuncs__verify_minion", "_AESFuncs__verify_minion_publish", "__class__", "__delattr__", "__dir__", "__eq__", "__format__", "__ge__", "__getattribute__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__le__", "__lt__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "get_method", "run_func", ] for name in dir(aes_funcs): if name in aes_funcs.expose_methods: continue if not callable(getattr(aes_funcs, name)): continue assert name in blacklist_methods, name def test_clear_funcs_white(self): """ Validate methods exposed on ClearFuncs exist and are callable """ opts = salt.config.master_config(None) clear_funcs = salt.master.ClearFuncs(opts, {}) for name in clear_funcs.expose_methods: func = getattr(clear_funcs, name, None) assert callable(func) def test_clear_funcs_black(self): """ Validate methods on ClearFuncs that should not be called remotely """ opts = salt.config.master_config(None) clear_funcs = salt.master.ClearFuncs(opts, {}) blacklist_methods = [ "__class__", "__delattr__", "__dir__", "__eq__", "__format__", "__ge__", "__getattribute__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__le__", "__lt__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "_prep_auth_info", "_prep_jid", "_prep_pub", "_send_pub", "_send_ssh_pub", "get_method", ] for name in dir(clear_funcs): if name in clear_funcs.expose_methods: continue if not callable(getattr(clear_funcs, name)): continue assert name in blacklist_methods, name class ClearFuncsTestCase(TestCase): """ TestCase for salt.master.ClearFuncs class """ @classmethod def setUpClass(cls): opts = salt.config.master_config(None) cls.clear_funcs = salt.master.ClearFuncs(opts, {}) @classmethod def tearDownClass(cls): del cls.clear_funcs def test_get_method(self): assert getattr(self.clear_funcs, "_send_pub", None) is not None assert self.clear_funcs.get_method("_send_pub") is None # runner tests @slowTest def test_runner_token_not_authenticated(self): """ Asserts that a TokenAuthenticationError is returned when the token can't authenticate. """ mock_ret = { "error": { "name": "TokenAuthenticationError", "message": 'Authentication failure of type "token" occurred.', } } ret = self.clear_funcs.runner({"token": "asdfasdfasdfasdf"}) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_token_authorization_error(self): """ Asserts that a TokenAuthenticationError is returned when the token authenticates, but is not authorized. """ token = "asdfasdfasdfasdf" clear_load = {"token": token, "fun": "test.arg"} mock_token = {"token": token, "eauth": "foo", "name": "test"} mock_ret = { "error": { "name": "TokenAuthenticationError", "message": 'Authentication failure of type "token" occurred ' "for user test.", } } with patch( "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token) ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])): ret = self.clear_funcs.runner(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_token_salt_invocation_error(self): """ Asserts that a SaltInvocationError is returned when the token authenticates, but the command is malformed. """ token = "asdfasdfasdfasdf" clear_load = {"token": token, "fun": "badtestarg"} mock_token = {"token": token, "eauth": "foo", "name": "test"} mock_ret = { "error": { "name": "SaltInvocationError", "message": "A command invocation error occurred: Check syntax.", } } with patch( "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"]) ): ret = self.clear_funcs.runner(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_eauth_not_authenticated(self): """ Asserts that an EauthAuthenticationError is returned when the user can't authenticate. """ mock_ret = { "error": { "name": "EauthAuthenticationError", "message": 'Authentication failure of type "eauth" occurred for ' "user UNKNOWN.", } } ret = self.clear_funcs.runner({"eauth": "foo"}) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_eauth_authorization_error(self): """ Asserts that an EauthAuthenticationError is returned when the user authenticates, but is not authorized. """ clear_load = {"eauth": "foo", "username": "test", "fun": "test.arg"} mock_ret = { "error": { "name": "EauthAuthenticationError", "message": 'Authentication failure of type "eauth" occurred for ' "user test.", } } with patch( "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True) ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])): ret = self.clear_funcs.runner(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_eauth_salt_invocation_error(self): """ Asserts that an EauthAuthenticationError is returned when the user authenticates, but the command is malformed. """ clear_load = {"eauth": "foo", "username": "test", "fun": "bad.test.arg.func"} mock_ret = { "error": { "name": "SaltInvocationError", "message": "A command invocation error occurred: Check syntax.", } } with patch( "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"]) ): ret = self.clear_funcs.runner(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_runner_user_not_authenticated(self): """ Asserts that an UserAuthenticationError is returned when the user can't authenticate. """ mock_ret = { "error": { "name": "UserAuthenticationError", "message": 'Authentication failure of type "user" occurred', } } ret = self.clear_funcs.runner({}) self.assertDictEqual(mock_ret, ret) # wheel tests @slowTest def test_wheel_token_not_authenticated(self): """ Asserts that a TokenAuthenticationError is returned when the token can't authenticate. """ mock_ret = { "error": { "name": "TokenAuthenticationError", "message": 'Authentication failure of type "token" occurred.', } } ret = self.clear_funcs.wheel({"token": "asdfasdfasdfasdf"}) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_token_authorization_error(self): """ Asserts that a TokenAuthenticationError is returned when the token authenticates, but is not authorized. """ token = "asdfasdfasdfasdf" clear_load = {"token": token, "fun": "test.arg"} mock_token = {"token": token, "eauth": "foo", "name": "test"} mock_ret = { "error": { "name": "TokenAuthenticationError", "message": 'Authentication failure of type "token" occurred ' "for user test.", } } with patch( "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token) ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])): ret = self.clear_funcs.wheel(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_token_salt_invocation_error(self): """ Asserts that a SaltInvocationError is returned when the token authenticates, but the command is malformed. """ token = "asdfasdfasdfasdf" clear_load = {"token": token, "fun": "badtestarg"} mock_token = {"token": token, "eauth": "foo", "name": "test"} mock_ret = { "error": { "name": "SaltInvocationError", "message": "A command invocation error occurred: Check syntax.", } } with patch( "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"]) ): ret = self.clear_funcs.wheel(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_eauth_not_authenticated(self): """ Asserts that an EauthAuthenticationError is returned when the user can't authenticate. """ mock_ret = { "error": { "name": "EauthAuthenticationError", "message": 'Authentication failure of type "eauth" occurred for ' "user UNKNOWN.", } } ret = self.clear_funcs.wheel({"eauth": "foo"}) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_eauth_authorization_error(self): """ Asserts that an EauthAuthenticationError is returned when the user authenticates, but is not authorized. """ clear_load = {"eauth": "foo", "username": "test", "fun": "test.arg"} mock_ret = { "error": { "name": "EauthAuthenticationError", "message": 'Authentication failure of type "eauth" occurred for ' "user test.", } } with patch( "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True) ), patch("salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[])): ret = self.clear_funcs.wheel(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_eauth_salt_invocation_error(self): """ Asserts that an EauthAuthenticationError is returned when the user authenticates, but the command is malformed. """ clear_load = {"eauth": "foo", "username": "test", "fun": "bad.test.arg.func"} mock_ret = { "error": { "name": "SaltInvocationError", "message": "A command invocation error occurred: Check syntax.", } } with patch( "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=["testing"]) ): ret = self.clear_funcs.wheel(clear_load) self.assertDictEqual(mock_ret, ret) @slowTest def test_wheel_user_not_authenticated(self): """ Asserts that an UserAuthenticationError is returned when the user can't authenticate. """ mock_ret = { "error": { "name": "UserAuthenticationError", "message": 'Authentication failure of type "user" occurred', } } ret = self.clear_funcs.wheel({}) self.assertDictEqual(mock_ret, ret) # publish tests @slowTest def test_publish_user_is_blacklisted(self): """ Asserts that an AuthorizationError is returned when the user has been blacklisted. """ mock_ret = { "error": { "name": "AuthorizationError", "message": "Authorization error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=True) ): self.assertEqual( mock_ret, self.clear_funcs.publish({"user": "foo", "fun": "test.arg"}) ) @slowTest def test_publish_cmd_blacklisted(self): """ Asserts that an AuthorizationError is returned when the command has been blacklisted. """ mock_ret = { "error": { "name": "AuthorizationError", "message": "Authorization error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=True) ): self.assertEqual( mock_ret, self.clear_funcs.publish({"user": "foo", "fun": "test.arg"}) ) @slowTest def test_publish_token_not_authenticated(self): """ Asserts that an AuthenticationError is returned when the token can't authenticate. """ mock_ret = { "error": { "name": "AuthenticationError", "message": "Authentication error occurred.", } } load = { "user": "foo", "fun": "test.arg", "tgt": "test_minion", "kwargs": {"token": "asdfasdfasdfasdf"}, } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_token_authorization_error(self): """ Asserts that an AuthorizationError is returned when the token authenticates, but is not authorized. """ token = "asdfasdfasdfasdf" load = { "user": "foo", "fun": "test.arg", "tgt": "test_minion", "arg": "bar", "kwargs": {"token": token}, } mock_token = {"token": token, "eauth": "foo", "name": "test"} mock_ret = { "error": { "name": "AuthorizationError", "message": "Authorization error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.auth.LoadAuth.authenticate_token", MagicMock(return_value=mock_token) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[]) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_eauth_not_authenticated(self): """ Asserts that an AuthenticationError is returned when the user can't authenticate. """ load = { "user": "test", "fun": "test.arg", "tgt": "test_minion", "kwargs": {"eauth": "foo"}, } mock_ret = { "error": { "name": "AuthenticationError", "message": "Authentication error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_eauth_authorization_error(self): """ Asserts that an AuthorizationError is returned when the user authenticates, but is not authorized. """ load = { "user": "test", "fun": "test.arg", "tgt": "test_minion", "kwargs": {"eauth": "foo"}, "arg": "bar", } mock_ret = { "error": { "name": "AuthorizationError", "message": "Authorization error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.auth.LoadAuth.authenticate_eauth", MagicMock(return_value=True) ), patch( "salt.auth.LoadAuth.get_auth_list", MagicMock(return_value=[]) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_user_not_authenticated(self): """ Asserts that an AuthenticationError is returned when the user can't authenticate. """ load = {"user": "test", "fun": "test.arg", "tgt": "test_minion"} mock_ret = { "error": { "name": "AuthenticationError", "message": "Authentication error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_user_authenticated_missing_auth_list(self): """ Asserts that an AuthenticationError is returned when the user has an effective user id and is authenticated, but the auth_list is empty. """ load = { "user": "test", "fun": "test.arg", "tgt": "test_minion", "kwargs": {"user": "test"}, "arg": "foo", } mock_ret = { "error": { "name": "AuthenticationError", "message": "Authentication error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.auth.LoadAuth.authenticate_key", MagicMock(return_value="fake-user-key"), ), patch( "salt.utils.master.get_values_of_matching_keys", MagicMock(return_value=[]) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) @slowTest def test_publish_user_authorization_error(self): """ Asserts that an AuthorizationError is returned when the user authenticates, but is not authorized. """ load = { "user": "test", "fun": "test.arg", "tgt": "test_minion", "kwargs": {"user": "test"}, "arg": "foo", } mock_ret = { "error": { "name": "AuthorizationError", "message": "Authorization error occurred.", } } with patch( "salt.acl.PublisherACL.user_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.acl.PublisherACL.cmd_is_blacklisted", MagicMock(return_value=False) ), patch( "salt.auth.LoadAuth.authenticate_key", MagicMock(return_value="fake-user-key"), ), patch( "salt.utils.master.get_values_of_matching_keys", MagicMock(return_value=["test"]), ), patch( "salt.utils.minions.CkMinions.auth_check", MagicMock(return_value=False) ): self.assertEqual(mock_ret, self.clear_funcs.publish(load)) class MaintenanceTestCase(TestCase, AdaptedConfigurationTestCaseMixin): """ TestCase for salt.master.Maintenance class """ def setUp(self): opts = self.get_temp_config("master", git_pillar_update_interval=180) self.main_class = salt.master.Maintenance(opts) self.main_class._after_fork_methods = self.main_class._finalize_methods = [] def tearDown(self): del self.main_class def test_run_func(self): """ Test the run function inside Maintenance class. """ class MockTime: def __init__(self, max_duration): self._start_time = time.time() self._current_duration = 0 self._max_duration = max_duration self._calls = [] def time(self): return self._start_time + self._current_duration def sleep(self, secs): self._calls += [secs] self._current_duration += secs if self._current_duration >= self._max_duration: raise RuntimeError("Time passes") mocked_time = MockTime(60 * 4) class MockTimedFunc: def __init__(self): self.call_times = [] def __call__(self, *args, **kwargs): self.call_times += [mocked_time._current_duration] mocked__post_fork_init = MockTimedFunc() mocked_clean_old_jobs = MockTimedFunc() mocked_clean_expired_tokens = MockTimedFunc() mocked_clean_pub_auth = MockTimedFunc() mocked_handle_git_pillar = MockTimedFunc() mocked_handle_schedule = MockTimedFunc() mocked_handle_key_cache = MockTimedFunc() mocked_handle_presence = MockTimedFunc() mocked_handle_key_rotate = MockTimedFunc() mocked_check_max_open_files = MockTimedFunc() with patch("salt.master.time", mocked_time), patch( "salt.utils.process", autospec=True ), patch( "salt.master.Maintenance._post_fork_init", mocked__post_fork_init ), patch( "salt.daemons.masterapi.clean_old_jobs", mocked_clean_old_jobs ), patch( "salt.daemons.masterapi.clean_expired_tokens", mocked_clean_expired_tokens ), patch( "salt.daemons.masterapi.clean_pub_auth", mocked_clean_pub_auth ), patch( "salt.master.Maintenance.handle_git_pillar", mocked_handle_git_pillar ), patch( "salt.master.Maintenance.handle_schedule", mocked_handle_schedule ), patch( "salt.master.Maintenance.handle_key_cache", mocked_handle_key_cache ), patch( "salt.master.Maintenance.handle_presence", mocked_handle_presence ), patch( "salt.master.Maintenance.handle_key_rotate", mocked_handle_key_rotate ), patch( "salt.utils.verify.check_max_open_files", mocked_check_max_open_files ): try: self.main_class.run() except RuntimeError as exc: self.assertEqual(str(exc), "Time passes") self.assertEqual(mocked_time._calls, [60] * 4) self.assertEqual(mocked__post_fork_init.call_times, [0]) self.assertEqual(mocked_clean_old_jobs.call_times, [60, 120, 180]) self.assertEqual(mocked_clean_expired_tokens.call_times, [60, 120, 180]) self.assertEqual(mocked_clean_pub_auth.call_times, [60, 120, 180]) self.assertEqual(mocked_handle_git_pillar.call_times, [0, 180]) self.assertEqual(mocked_handle_schedule.call_times, [0, 60, 120, 180]) self.assertEqual(mocked_handle_key_cache.call_times, [0, 60, 120, 180]) self.assertEqual(mocked_handle_presence.call_times, [0, 60, 120, 180]) self.assertEqual(mocked_handle_key_rotate.call_times, [0, 60, 120, 180]) self.assertEqual(mocked_check_max_open_files.call_times, [0, 60, 120, 180])