From 7d38fe0b25e1724b97b665f74a5d3d129e6363c9 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Wed, 28 Jun 2023 13:50:37 -0700 Subject: [PATCH] Migrate master unit tests to pytest --- tests/pytests/unit/test_master.py | 784 +++++++++++++++++++++++++++++- tests/unit/test_master.py | 773 ----------------------------- 2 files changed, 783 insertions(+), 774 deletions(-) delete mode 100644 tests/unit/test_master.py diff --git a/tests/pytests/unit/test_master.py b/tests/pytests/unit/test_master.py index 502767d3e34..451d92b8c84 100644 --- a/tests/pytests/unit/test_master.py +++ b/tests/pytests/unit/test_master.py @@ -4,7 +4,50 @@ import pytest import salt.master import salt.utils.platform -from tests.support.mock import patch +from tests.support.mock import MagicMock, patch + + +@pytest.fixture +def master_opts(): + """ + Master options + """ + yield salt.config.master_config(None) + + +@pytest.fixture +def maintenence_opts(master_opts): + """ + Options needed for master's Maintenence class + """ + opts = master_opts.copy() + opts.update(git_pillar_update_interval=180, maintenance_interval=181) + yield opts + + + +@pytest.fixture +def maintenence(maintenence_opts): + """ + The master's Maintenence class + """ + maintenence = salt.master.Maintenance(maintenence_opts) + try: + yield maintenence + finally: + pass + + +@pytest.fixture +def clear_funcs(master_opts): + """ + The Master's ClearFuncs object + """ + clear_funcs = salt.master.ClearFuncs(master_opts, {}) + try: + yield clear_funcs + finally: + clear_funcs.destroy() @pytest.fixture @@ -160,3 +203,742 @@ def test_when_syndic_return_processes_load_then_correct_values_should_be_returne with patch.object(encrypted_requests, "_return", autospec=True) as fake_return: encrypted_requests._syndic_return(payload) fake_return.assert_called_with(expected_return) + +def test_aes_funcs_white(): + """ + Validate methods exposed on AESFuncs exist and are callable + """ + opts = salt.config.master_config(None) + aes_funcs = salt.master.AESFuncs(opts) + try: + for name in aes_funcs.expose_methods: + func = getattr(aes_funcs, name, None) + assert callable(func) + finally: + aes_funcs.destroy() + +def test_transport_methods(): + 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_black(): + """ + 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__", + "__getstate__", + "__gt__", + "__hash__", + "__init__", + "__init_subclass__", + "__le__", + "__lt__", + "__ne__", + "__new__", + "__reduce__", + "__reduce_ex__", + "__repr__", + "__setattr__", + "__sizeof__", + "__str__", + "__subclasshook__", + "destroy", + "get_method", + "run_func", + ] + try: + 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 + finally: + aes_funcs.destroy() + +def test_clear_funcs_white(): + """ + Validate methods exposed on ClearFuncs exist and are callable + """ + opts = salt.config.master_config(None) + clear_funcs = salt.master.ClearFuncs(opts, {}) + try: + for name in clear_funcs.expose_methods: + func = getattr(clear_funcs, name, None) + assert callable(func) + finally: + clear_funcs.destroy() + +def test_clear_funcs_black(): + """ + 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__", + "__getstate__", + "__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", + "connect", + "destroy", + "get_method", + ] + try: + 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 + finally: + clear_funcs.destroy() + +def test_clear_funcs_get_method(clear_funcs): + assert getattr(clear_funcs, "_send_pub", None) is not None + assert clear_funcs.get_method("_send_pub") is None + + +@pytest.mark.slow_test +def test_runner_token_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.runner({"token": "asdfasdfasdfasdf"}) + assert mock_ret == ret + +@pytest.mark.slow_test +def test_runner_token_authorization_error(clear_funcs): + """ + 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 = clear_funcs.runner(clear_load) + + assert mock_ret == ret + + +@pytest.mark.slow_test +def test_runner_token_salt_invocation_error(clear_funcs): + """ + 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 = clear_funcs.runner(clear_load) + + assert mock_ret == ret + + +@pytest.mark.slow_test +def test_runner_eauth_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.runner({"eauth": "foo"}) + assert mock_ret == ret + +@pytest.mark.slow_test +def test_runner_eauth_authorization_error(clear_funcs): + """ + 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 = clear_funcs.runner(clear_load) + + assert mock_ret == ret + + +@pytest.mark.slow_test +def test_runner_eauth_salt_invocation_error(clear_funcs): + """ + 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 = clear_funcs.runner(clear_load) + + assert mock_ret == ret + +@pytest.mark.slow_test +def test_runner_user_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.runner({}) + assert mock_ret == ret + +# wheel tests + +@pytest.mark.slow_test +def test_wheel_token_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.wheel({"token": "asdfasdfasdfasdf"}) + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_token_authorization_error(clear_funcs): + """ + 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 = clear_funcs.wheel(clear_load) + + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_token_salt_invocation_error(clear_funcs): + """ + 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 = clear_funcs.wheel(clear_load) + + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_eauth_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.wheel({"eauth": "foo"}) + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_eauth_authorization_error(clear_funcs): + """ + 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 = clear_funcs.wheel(clear_load) + + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_eauth_salt_invocation_error(clear_funcs): + """ + 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 = clear_funcs.wheel(clear_load) + + assert mock_ret == ret + +@pytest.mark.slow_test +def test_wheel_user_not_authenticated(clear_funcs): + """ + 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 = clear_funcs.wheel({}) + assert mock_ret == ret + +# publish tests + +@pytest.mark.slow_test +async def test_publish_user_is_blacklisted(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish({"user": "foo", "fun": "test.arg"}) + +@pytest.mark.slow_test +async def test_publish_cmd_blacklisted(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish({"user": "foo", "fun": "test.arg"}) + +@pytest.mark.slow_test +async def test_publish_token_not_authenticated(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish(load) + +@pytest.mark.slow_test +async def test_publish_token_authorization_error(clear_funcs): + """ + 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=[]) + ): + assert mock_ret == await clear_funcs.publish(load) + +@pytest.mark.slow_test +async def test_publish_eauth_not_authenticated(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish(load) + +@pytest.mark.slow_test +async def test_publish_eauth_authorization_error(clear_funcs): + """ + 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=[]) + ): + assert mock_ret == await clear_funcs.publish(load) + +@pytest.mark.slow_test +async def test_publish_user_not_authenticated(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish(load) + +@pytest.mark.slow_test +async def test_publish_user_authenticated_missing_auth_list(clear_funcs): + """ + 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=[]) + ): + assert mock_ret == await clear_funcs.publish(load) + + +@pytest.mark.slow_test +async def test_publish_user_authorization_error(clear_funcs): + """ + 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) + ): + assert mock_ret == await clear_funcs.publish(load) + + +def test_run_func(maintenence): + """ + 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: + maintenence.run() + except RuntimeError as exc: + assert str(exc) == "Time passes" + assert mocked_time._calls == [60] * 4 + assert mocked__post_fork_init.call_times == [0] + assert mocked_clean_old_jobs.call_times == [0, 120, 180] + assert mocked_clean_expired_tokens.call_times == [0, 120, 180] + assert mocked_clean_pub_auth.call_times == [0, 120, 180] + assert mocked_handle_git_pillar.call_times == [0] + assert mocked_handle_schedule.call_times == [0, 60, 120, 180] + assert mocked_handle_key_cache.call_times == [0, 60, 120, 180] + assert mocked_handle_presence.call_times == [0, 60, 120, 180] + assert mocked_handle_key_rotate.call_times == [0, 60, 120, 180] + assert mocked_check_max_open_files.call_times == [0, 60, 120, 180] diff --git a/tests/unit/test_master.py b/tests/unit/test_master.py deleted file mode 100644 index 96fe2a54595..00000000000 --- a/tests/unit/test_master.py +++ /dev/null @@ -1,773 +0,0 @@ -import time - -import pytest - -import salt.config -import salt.master -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) - self.addCleanup(aes_funcs.destroy) - 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) - self.addCleanup(aes_funcs.destroy) - # 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__", - "__getstate__", - "__gt__", - "__hash__", - "__init__", - "__init_subclass__", - "__le__", - "__lt__", - "__ne__", - "__new__", - "__reduce__", - "__reduce_ex__", - "__repr__", - "__setattr__", - "__sizeof__", - "__str__", - "__subclasshook__", - "destroy", - "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, {}) - self.addCleanup(clear_funcs.destroy) - 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, {}) - self.addCleanup(clear_funcs.destroy) - blacklist_methods = [ - "__class__", - "__delattr__", - "__dir__", - "__eq__", - "__format__", - "__ge__", - "__getattribute__", - "__getstate__", - "__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", - "connect", - "destroy", - "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): - cls.clear_funcs.destroy() - 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 - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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 - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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) - - @pytest.mark.slow_test - 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 - - @pytest.mark.slow_test - 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"}) - ) - - @pytest.mark.slow_test - 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"}) - ) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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)) - - @pytest.mark.slow_test - 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, maintenance_interval=181 - ) - 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, [0, 120, 180]) - self.assertEqual(mocked_clean_expired_tokens.call_times, [0, 120, 180]) - self.assertEqual(mocked_clean_pub_auth.call_times, [0, 120, 180]) - self.assertEqual(mocked_handle_git_pillar.call_times, [0]) - 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])