diff --git a/changelog/95.fixed.md b/changelog/95.fixed.md new file mode 100644 index 00000000..270906de --- /dev/null +++ b/changelog/95.fixed.md @@ -0,0 +1 @@ +Fixed the client used for unwrapping authentication credentials not respecting `client` configuration when no cached configuration is available diff --git a/src/saltext/vault/utils/vault/factory.py b/src/saltext/vault/utils/vault/factory.py index 55488863..1696205d 100644 --- a/src/saltext/vault/utils/vault/factory.py +++ b/src/saltext/vault/utils/vault/factory.py @@ -500,8 +500,14 @@ def _get_connection_config(cbank, opts, context, force_local=False, pre_flush=Fa config_cache.flush(cbank=False) config_cache.store(new_config) - if unwrap_client is None: - unwrap_client = vclient.VaultClient(**new_config["server"], **new_config["client"]) + # Always reinitialize client in case the unwrap_client `client` config is + # out of sync with the new config. This is a theoretical precaution since + # the client should be instantiated in _query_master using the new config. + unwrap_client = vclient.VaultClient( + **new_config["server"], + **new_config["client"], + session=unwrap_client.session if unwrap_client is not None else None, + ) return new_config, embedded_token, unwrap_client @@ -710,7 +716,7 @@ def check_result( if result.get("wrap_info") or result.get("wrap_info_nested"): if unwrap_client is None: - unwrap_client = vclient.VaultClient(**result["server"]) + unwrap_client = vclient.VaultClient(**result["server"], **result.get("client", {})) for key in [""] + result.get("wrap_info_nested", []): if key: diff --git a/tests/unit/pillar/test_vault.py b/tests/unit/pillar/test_vault.py index afbfcf8e..72549ba7 100644 --- a/tests/unit/pillar/test_vault.py +++ b/tests/unit/pillar/test_vault.py @@ -101,9 +101,7 @@ def test_get_paths(pattern, expected): previous_pillar = { "role": "foo", } - result = vault._get_paths( # pylint: disable=protected-access - pattern, "test-minion", previous_pillar - ) + result = vault._get_paths(pattern, "test-minion", previous_pillar) assert result == expected diff --git a/tests/unit/runners/vault/test_vault.py b/tests/unit/runners/vault/test_vault.py index fd8b629f..41aaf5ff 100644 --- a/tests/unit/runners/vault/test_vault.py +++ b/tests/unit/runners/vault/test_vault.py @@ -370,14 +370,12 @@ def test_generate_token( token_serialized, wrapped_serialized, metadata_secret_default, -): # pylint: disable-msg=too-many-arguments +): """ Ensure _generate_token calls the API as expected """ wrap = config("issue:wrap") - res_token, res_num_uses = vault._generate_token( # pylint: disable=protected-access - "test-minion", issue_params=None, wrap=wrap - ) + res_token, res_num_uses = vault._generate_token("test-minion", issue_params=None, wrap=wrap) endpoint = "auth/token/create" role_name = config("issue:token:role_name") payload = {} @@ -407,17 +405,13 @@ def test_generate_token_no_policies_denied(): Ensure generated tokens need at least one attached policy """ with pytest.raises(salt.exceptions.SaltRunnerError, match=".*No policies matched minion.*"): - vault._generate_token( # pylint: disable=protected-access - "test-minion", issue_params=None, wrap=False - ) + vault._generate_token("test-minion", issue_params=None, wrap=False) @pytest.mark.parametrize("ttl", [None, 1337]) @pytest.mark.parametrize("uses", [None, 1, 30]) @pytest.mark.parametrize("config", [{}, {"issue:type": "approle"}], indirect=True) -def test_generate_token_deprecated( - ttl, uses, token_serialized, config, validate_signature, caplog -): # pylint: disable-msg=too-many-arguments +def test_generate_token_deprecated(ttl, uses, token_serialized, config, validate_signature, caplog): """ Ensure the deprecated generate_token function returns data in the old format """ @@ -715,15 +709,13 @@ def test_get_role_id( manage_entity, manage_entity_alias, wrapped_serialized, - ): # pylint: disable-msg=too-many-arguments + ): """ Ensure _get_role_id returns data in the expected format and does not try to generate a new AppRole if it exists and is configured correctly """ wrap = config("issue:wrap") - res = vault._get_role_id( # pylint: disable=protected-access - "test-minion", issue_params=None, wrap=wrap - ) + res = vault._get_role_id("test-minion", issue_params=None, wrap=wrap) lookup_approle.assert_called_with("test-minion") lookup_roleid.assert_called_with("test-minion", wrap=wrap) manage_approle.assert_not_called() @@ -750,7 +742,7 @@ def test_get_role_id( ) def test_get_role_id_generate_new( self, - config, # pylint: disable=unused-argument + config, lookup_approle, lookup_roleid, manage_approle, @@ -758,25 +750,24 @@ def test_get_role_id_generate_new( manage_entity_alias, wrapped_serialized, issue_params, - ): # pylint: disable-msg=too-many-arguments + ): """ Ensure _get_role_id returns data in the expected format and does not try to generate a new AppRole if it exists and is configured correctly """ lookup_approle.return_value = False wrap = config("issue:wrap") - res = vault._get_role_id( # pylint: disable=protected-access - "test-minion", issue_params=issue_params, wrap=wrap - ) + res = vault._get_role_id("test-minion", issue_params=issue_params, wrap=wrap) assert res == wrapped_serialized lookup_roleid.assert_called_with("test-minion", wrap=wrap) manage_approle.assert_called_once_with("test-minion", issue_params) manage_entity.assert_called_once_with("test-minion") manage_entity_alias.assert_called_once_with("test-minion") + @pytest.mark.usefixtures("config") @pytest.mark.parametrize("config", [{"issue:type": "approle"}], indirect=True) def test_get_role_id_generate_new_errors_on_generation_failure( - self, config, lookup_approle, lookup_roleid # pylint: disable=unused-argument + self, lookup_approle, lookup_roleid ): """ Ensure _get_role_id returns an error if the AppRole generation failed @@ -787,9 +778,7 @@ def test_get_role_id_generate_new_errors_on_generation_failure( salt.exceptions.SaltRunnerError, match="Failed to create AppRole for minion.*", ): - vault._get_role_id( # pylint: disable=protected-access - "test-minion", issue_params=None, wrap=False - ) + vault._get_role_id("test-minion", issue_params=None, wrap=False) @pytest.mark.parametrize( @@ -951,9 +940,7 @@ def test_get_policies(expected, grains, pillar): "saltext.vault.utils.vault.helpers.expand_pattern_lists", Mock(side_effect=lambda x, *args, **kwargs: [x]), ): - res = vault._get_policies( # pylint: disable=protected-access - "test-minion", refresh_pillar=False - ) + res = vault._get_policies("test-minion", refresh_pillar=False) assert res == expected @@ -979,9 +966,7 @@ def test_get_policies_does_not_render_pillar_unnecessarily(config, grains, pilla ): with patch("salt.pillar.get_pillar", autospec=True) as get_pillar: get_pillar.return_value.compile_pillar.return_value = pillar - vault._get_policies( # pylint: disable=protected-access - "test-minion", refresh_pillar=True - ) + vault._get_policies("test-minion", refresh_pillar=True) assert get_pillar.call_count == int("pillar" in config("policies:assign")[0]) @@ -1005,9 +990,7 @@ def test_get_policies_for_nonexisting_minions(expected): "saltext.vault.utils.vault.helpers.expand_pattern_lists", Mock(side_effect=lambda x, *args, **kwargs: [x]), ): - res = vault._get_policies( # pylint: disable=protected-access - "test-minion", refresh_pillar=False - ) + res = vault._get_policies("test-minion", refresh_pillar=False) assert res == expected @@ -1047,9 +1030,7 @@ def test_get_metadata(metadata_patterns, expected, pillar): "saltext.vault.utils.vault.helpers.expand_pattern_lists", Mock(side_effect=lambda x, *args, **kwargs: [x]), ): - res = vault._get_metadata( # pylint: disable=protected-access - "test-minion", metadata_patterns, refresh_pillar=False - ) + res = vault._get_metadata("test-minion", metadata_patterns, refresh_pillar=False) assert res == expected @@ -1065,7 +1046,7 @@ def test_get_metadata_list(): "saltext.vault.utils.vault.helpers.expand_pattern_lists", autospec=True ) as expand: expand.return_value = ["salt_role_foo", "salt_role_bar"] - res = vault._get_metadata( # pylint: disable=protected-access + res = vault._get_metadata( "test-minion", {"salt_role": "salt_role_{pillar[roles]}"}, refresh_pillar=False, @@ -1277,7 +1258,7 @@ def test_parse_issue_params(issue_params, expected): Ensure all known parameters can only be overridden if it was configured on the master. Also ensure the mapping to API requests is correct (for tokens). """ - res = vault._parse_issue_params(issue_params) # pylint: disable=protected-access + res = vault._parse_issue_params(issue_params) assert res == expected @@ -1315,7 +1296,7 @@ def test_parse_issue_params_does_not_allow_bind_secret_id_override(issue_params, """ Ensure bind_secret_id can only be set on the master. """ - res = vault._parse_issue_params(issue_params) # pylint: disable=protected-access + res = vault._parse_issue_params(issue_params) assert res.get("bind_secret_id", False) == expected @@ -1324,7 +1305,7 @@ def test_manage_approle(approle_api, policies_default): """ Ensure _manage_approle calls the API as expected. """ - vault._manage_approle("test-minion", None) # pylint: disable=protected-access + vault._manage_approle("test-minion", None) approle_api.write_approle.assert_called_once_with( "test-minion", mount="salt-minions", @@ -1339,7 +1320,7 @@ def test_delete_approle(approle_api): """ Ensure _delete_approle calls the API as expected. """ - vault._delete_approle("test-minion") # pylint: disable=protected-access + vault._delete_approle("test-minion") approle_api.delete_approle.assert_called_once_with("test-minion", mount="salt-minions") @@ -1349,7 +1330,7 @@ def test_lookup_approle(approle_api, approle_meta): Ensure _lookup_approle calls the API as expected. """ approle_api.read_approle.return_value = approle_meta - res = vault._lookup_approle("test-minion") # pylint: disable=protected-access + res = vault._lookup_approle("test-minion") assert res == approle_meta approle_api.read_approle.assert_called_once_with("test-minion", mount="salt-minions") @@ -1360,7 +1341,7 @@ def test_lookup_approle_nonexistent(approle_api): Ensure _lookup_approle catches VaultNotFoundErrors and returns False. """ approle_api.read_approle.side_effect = vaultutil.VaultNotFoundError - res = vault._lookup_approle("test-minion") # pylint: disable=protected-access + res = vault._lookup_approle("test-minion") assert res is False @@ -1370,7 +1351,7 @@ def test_lookup_role_id(approle_api, wrap): """ Ensure _lookup_role_id calls the API as expected. """ - vault._lookup_role_id("test-minion", wrap=wrap) # pylint: disable=protected-access + vault._lookup_role_id("test-minion", wrap=wrap) approle_api.read_role_id.assert_called_once_with("test-minion", mount="salt-minions", wrap=wrap) @@ -1380,7 +1361,7 @@ def test_lookup_role_id_nonexistent(approle_api): Ensure _lookup_role_id catches VaultNotFoundErrors and returns False. """ approle_api.read_role_id.side_effect = vaultutil.VaultNotFoundError - res = vault._lookup_role_id("test-minion", wrap=False) # pylint: disable=protected-access + res = vault._lookup_role_id("test-minion", wrap=False) assert res is False @@ -1390,7 +1371,7 @@ def test_get_secret_id(approle_api, wrap): """ Ensure _get_secret_id calls the API as expected. """ - vault._get_secret_id("test-minion", wrap=wrap) # pylint: disable=protected-access + vault._get_secret_id("test-minion", wrap=wrap) approle_api.generate_secret_id.assert_called_once_with( "test-minion", metadata=ANY, @@ -1405,7 +1386,7 @@ def test_lookup_entity_by_alias(identity_api): Ensure _lookup_entity_by_alias calls the API as expected. """ with patch("saltext.vault.runners.vault._lookup_role_id", return_value="test-role-id"): - vault._lookup_entity_by_alias("test-minion") # pylint: disable=protected-access + vault._lookup_entity_by_alias("test-minion") identity_api.read_entity_by_alias.assert_called_once_with( alias="test-role-id", mount="salt-minions" ) @@ -1418,7 +1399,7 @@ def test_lookup_entity_by_alias_failed(identity_api): """ with patch("saltext.vault.runners.vault._lookup_role_id", return_value="test-role-id"): identity_api.read_entity_by_alias.side_effect = vaultutil.VaultNotFoundError - res = vault._lookup_entity_by_alias("test-minion") # pylint: disable=protected-access + res = vault._lookup_entity_by_alias("test-minion") assert res is False @@ -1427,7 +1408,7 @@ def test_fetch_entity_by_name(identity_api): """ Ensure _fetch_entity_by_name calls the API as expected. """ - vault._fetch_entity_by_name("test-minion") # pylint: disable=protected-access + vault._fetch_entity_by_name("test-minion") identity_api.read_entity.assert_called_once_with(name="salt_minion_test-minion") @@ -1437,18 +1418,16 @@ def test_fetch_entity_by_name_failed(identity_api): Ensure _fetch_entity_by_name returns False if the lookup fails. """ identity_api.read_entity.side_effect = vaultutil.VaultNotFoundError - res = vault._fetch_entity_by_name("test-minion") # pylint: disable=protected-access + res = vault._fetch_entity_by_name("test-minion") assert res is False -@pytest.mark.usefixtures("config") -def test_manage_entity( - identity_api, metadata, metadata_entity_default -): # pylint: disable=unused-argument +@pytest.mark.usefixtures("config", "metadata") +def test_manage_entity(identity_api, metadata_entity_default): """ Ensure _manage_entity calls the API as expected. """ - vault._manage_entity("test-minion") # pylint: disable=protected-access + vault._manage_entity("test-minion") identity_api.write_entity.assert_called_with( "salt_minion_test-minion", metadata=metadata_entity_default ) @@ -1459,7 +1438,7 @@ def test_delete_entity(identity_api): """ Ensure _delete_entity calls the API as expected. """ - vault._delete_entity("test-minion") # pylint: disable=protected-access + vault._delete_entity("test-minion") identity_api.delete_entity.assert_called_with("salt_minion_test-minion") @@ -1469,7 +1448,7 @@ def test_manage_entity_alias(identity_api): Ensure _manage_entity_alias calls the API as expected. """ with patch("saltext.vault.runners.vault._lookup_role_id", return_value="test-role-id"): - vault._manage_entity_alias("test-minion") # pylint: disable=protected-access + vault._manage_entity_alias("test-minion") identity_api.write_entity_alias.assert_called_with( "salt_minion_test-minion", alias_name="test-role-id", mount="salt-minions" ) @@ -1486,14 +1465,14 @@ def test_manage_entity_alias_raises_errors(identity_api): salt.exceptions.SaltRunnerError, match="Cannot create alias.* no entity found.", ): - vault._manage_entity_alias("test-minion") # pylint: disable=protected-access + vault._manage_entity_alias("test-minion") def test_revoke_token_by_token(client): """ Ensure _revoke_token calls the API as expected. """ - vault._revoke_token(token="test-token") # pylint: disable=protected-access + vault._revoke_token(token="test-token") client.post.assert_called_once_with("auth/token/revoke", payload={"token": "test-token"}) @@ -1501,7 +1480,7 @@ def test_revoke_token_by_accessor(client): """ Ensure _revoke_token calls the API as expected. """ - vault._revoke_token(accessor="test-accessor") # pylint: disable=protected-access + vault._revoke_token(accessor="test-accessor") client.post.assert_called_once_with( "auth/token/revoke-accessor", payload={"accessor": "test-accessor"} ) diff --git a/tests/unit/utils/vault/conftest.py b/tests/unit/utils/vault/conftest.py index aa120267..d7c15634 100644 --- a/tests/unit/utils/vault/conftest.py +++ b/tests/unit/utils/vault/conftest.py @@ -38,20 +38,9 @@ def _mock_json_response(data, status_code=200, reason=""): return response -@pytest.fixture(params=[{}]) -def server_config(request): - conf = { - "url": "http://127.0.0.1:8200", - "namespace": None, - "verify": None, - } - conf.update(request.param) - return conf - - -@pytest.fixture(params=["token", "approle"]) -def test_config(server_config, request): - defaults = { +@pytest.fixture +def config_defaults(): + return { "auth": { "approle_mount": "approle", "approle_name": "salt-master", @@ -68,6 +57,7 @@ def test_config(server_config, request): "clear_on_unauthorized": True, "config": 3600, "expire_events": False, + "kv_metadata": "connection", "secret": "ttl", }, "client": { @@ -123,61 +113,59 @@ def test_config(server_config, request): "cache_time": 60, "refresh_pillar": None, }, - "server": server_config, + "server": { + "namespace": None, + "verify": None, + }, } + +@pytest.fixture(params=[{}]) +def server_config(request): + conf = { + "url": "http://127.0.0.1:8200", + "namespace": None, + "verify": None, + } + conf.update(request.param) + return conf + + +@pytest.fixture(params=["token", "approle"]) +def test_config(config_defaults, server_config, request): + # avoid mutating default config + defaults = config_defaults.copy() + auth = defaults["auth"].copy() + if request.param == "token": - defaults["auth"]["token"] = "test-token" + auth["token"] = "test-token" if request.param == "wrapped_token": - defaults["auth"]["method"] = "wrapped_token" - defaults["auth"]["token"] = "test-wrapped-token" + auth["method"] = "wrapped_token" + auth["token"] = "test-wrapped-token" if request.param == "approle": - defaults["auth"]["method"] = "approle" - defaults["auth"]["role_id"] = "test-role-id" - defaults["auth"]["secret_id"] = "test-secret-id" + auth["method"] = "approle" + auth["role_id"] = "test-role-id" + auth["secret_id"] = "test-secret-id" if request.param == "approle_no_secretid": - defaults["auth"]["method"] = "approle" - defaults["auth"]["role_id"] = "test-role-id" + auth["method"] = "approle" + auth["role_id"] = "test-role-id" + auth["secret_id"] = False + + defaults["auth"] = auth + defaults["server"] = server_config return defaults @pytest.fixture(params=["token", "approle"]) -def test_remote_config(server_config, request): +def test_remote_config(config_defaults, server_config, request): + # avoid mutating default config defaults = { - "auth": { - "approle_mount": "approle", - "approle_name": "salt-master", - "method": "token", - "secret_id": None, - "token_lifecycle": { - "minimum_ttl": 10, - "renew_increment": None, - }, - }, - "cache": { - "backend": "session", - "clear_attempt_revocation": 60, - "clear_on_unauthorized": True, - "config": 3600, - "expire_events": False, - "kv_metadata": "connection", - "secret": "ttl", - }, - "client": { - "connect_timeout": vclient.DEFAULT_CONNECT_TIMEOUT, - "read_timeout": vclient.DEFAULT_READ_TIMEOUT, - "max_retries": vclient.DEFAULT_MAX_RETRIES, - "backoff_factor": vclient.DEFAULT_BACKOFF_FACTOR, - "backoff_max": vclient.DEFAULT_BACKOFF_MAX, - "backoff_jitter": vclient.DEFAULT_BACKOFF_JITTER, - "retry_post": vclient.DEFAULT_RETRY_POST, - "retry_status": vclient.DEFAULT_RETRY_STATUS, - "respect_retry_after": vclient.DEFAULT_RESPECT_RETRY_AFTER, - "retry_after_max": vclient.DEFAULT_RETRY_AFTER_MAX, - }, + "auth": config_defaults["auth"].copy(), + "cache": config_defaults["cache"].copy(), + "client": config_defaults["client"].copy(), "server": server_config, } @@ -201,6 +189,7 @@ def test_remote_config(server_config, request): if request.param == "approle_no_secretid": defaults["auth"]["method"] = "approle" defaults["auth"]["role_id"] = "test-role-id" + defaults["auth"]["secret_id"] = False # this happens when wrapped role_ids are merged by _query_master if request.param == "approle_wrapped_roleid": @@ -585,11 +574,9 @@ def events(): @pytest.fixture(params=["MASTER", "MASTER_IMPERSONATING", "MINION_LOCAL", "MINION_REMOTE"]) def salt_runtype(request): - runtype = Mock(spec=hlp._get_salt_run_type) # pylint: disable=protected-access + runtype = Mock(spec=hlp._get_salt_run_type) runtype.return_value = getattr(hlp, f"SALT_RUNTYPE_{request.param}") - with patch( - "saltext.vault.utils.vault.helpers._get_salt_run_type", runtype - ): # pylint: disable=protected-access + with patch("saltext.vault.utils.vault.helpers._get_salt_run_type", runtype): yield diff --git a/tests/unit/utils/vault/test_api.py b/tests/unit/utils/vault/test_api.py index 93ee2405..66a2a4e5 100644 --- a/tests/unit/utils/vault/test_api.py +++ b/tests/unit/utils/vault/test_api.py @@ -314,7 +314,7 @@ def test_lookup_mount_accessor(client, identity_api, lookup_mount_response): Ensure _lookup_mount_accessor calls the API as expected. """ client.get.return_value = lookup_mount_response - res = identity_api._lookup_mount_accessor("salt-minions") # pylint: disable=protected-access + res = identity_api._lookup_mount_accessor("salt-minions") client.get.assert_called_once_with("sys/auth/salt-minions") assert res == "auth_approle_cafebabe" diff --git a/tests/unit/utils/vault/test_auth.py b/tests/unit/utils/vault/test_auth.py index a5b8d92c..6d624f30 100644 --- a/tests/unit/utils/vault/test_auth.py +++ b/tests/unit/utils/vault/test_auth.py @@ -270,7 +270,7 @@ def test_approle_auth_get_token_login(approle, mount, client, token_store_empty_ @pytest.mark.parametrize("num_uses", [0, 1, 10]) def test_approle_auth_used_num_uses( token_store_empty_first, approle, client, uncached, num_uses, token -): # pylint: disable-msg=too-many-arguments +): """ Ensure that cache writes for use count are only done when num_uses is not 0 (= unlimited) diff --git a/tests/unit/utils/vault/test_cache.py b/tests/unit/utils/vault/test_cache.py index ff091089..6e4f97ef 100644 --- a/tests/unit/utils/vault/test_cache.py +++ b/tests/unit/utils/vault/test_cache.py @@ -71,6 +71,7 @@ def time_stopped(request): yield +@pytest.mark.usefixtures("salt_runtype") @pytest.mark.parametrize("connection", [True, False]) @pytest.mark.parametrize( "salt_runtype,force_local,expected", @@ -83,16 +84,12 @@ def time_stopped(request): ], indirect=["salt_runtype"], ) -def test_get_cache_bank( - connection, salt_runtype, force_local, expected -): # pylint: disable=unused-argument +def test_get_cache_bank(connection, force_local, expected): """ Ensure the cache banks are mapped as expected, depending on run type """ opts = {"grains": {"id": "test-minion"}} - cbank = vcache._get_cache_bank( # pylint: disable=protected-access - opts, force_local=force_local, connection=connection - ) # pylint: disable=protected-access + cbank = vcache._get_cache_bank(opts, force_local=force_local, connection=connection) if connection: expected += "/connection" assert cbank == expected @@ -116,9 +113,7 @@ def test_get_uncached(self, config, uncached, cbank, ckey): if config != "session": uncached.contains.assert_called_once_with(cbank, ckey) - def test_get_cached_from_context( - self, context, cached, cbank, ckey, data - ): # pylint: disable-msg=too-many-arguments + def test_get_cached_from_context(self, context, cached, cbank, ckey, data): """ Ensure that cached data in __context__ is respected, regardless of cache backend. @@ -152,9 +147,7 @@ def test_get_cached_outdated(self, cached_outdated, cbank, ckey): cached_outdated.fetch.assert_not_called() @pytest.mark.parametrize("config", ["session", "other"]) - def test_flush( - self, config, context, cached, cbank, ckey - ): # pylint: disable-msg=too-many-arguments + def test_flush(self, config, context, cached, cbank, ckey): """ Ensure that flushing clears the context key only and, if a cache backend is in use, it is also cleared. @@ -168,9 +161,7 @@ def test_flush( cached.flush.assert_called_once_with(cbank, ckey) @pytest.mark.parametrize("config", ["session", "other"]) - def test_flush_cbank( - self, config, context, cached, cbank, ckey - ): # pylint: disable-msg=too-many-arguments + def test_flush_cbank(self, config, context, cached, cbank, ckey): """ Ensure that flushing with cbank=True clears the context bank and, if a cache backend is in use, it is also cleared. @@ -185,9 +176,7 @@ def test_flush_cbank( @pytest.mark.parametrize("context", [{}, {"vault/connection": {}}]) @pytest.mark.parametrize("config", ["session", "other"]) - def test_store( - self, config, context, uncached, cbank, ckey, data - ): # pylint: disable-msg=too-many-arguments + def test_store(self, config, context, uncached, cbank, ckey, data): """ Ensure that storing data in cache always updates the context and, if a cache backend is in use, it is also stored there. @@ -236,16 +225,14 @@ def test_get_config_cache_uncached(self, cbank, ckey): """ Ensure an uninitialized instance is returned when there is no cache """ - res = vault.cache._get_config_cache({}, {}, cbank, ckey) # pylint: disable=protected-access + res = vault.cache._get_config_cache({}, {}, cbank, ckey) assert res.config is None def test_get_config_context_cached(self, uncached, cbank, ckey, context): """ Ensure cached data in context wins """ - res = vault.cache._get_config_cache( # pylint: disable=protected-access - {}, context, cbank, ckey - ) # pylint: disable=protected-access + res = vault.cache._get_config_cache({}, context, cbank, ckey) assert res.config == context[cbank][ckey] uncached.contains.assert_not_called() @@ -253,7 +240,7 @@ def test_get_config_other_cached(self, cached, cbank, ckey, data): """ Ensure cached data from other sources is respected """ - res = vault.cache._get_config_cache({}, {}, cbank, ckey) # pylint: disable=protected-access + res = vault.cache._get_config_cache({}, {}, cbank, ckey) assert res.config == data cached.contains.assert_called_once_with(cbank, ckey) cached.fetch.assert_called_once_with(cbank, ckey) @@ -274,7 +261,7 @@ def test_reload(self, config, data, cbank, ckey): else: assert cache.ttl is None assert cache.cache is None - cache._load(data) # pylint: disable=protected-access + cache._load(data) assert cache.ttl == data["cache"]["config"] assert cache.cache is not None if config is not None and config["cache"]["backend"] != "session": @@ -289,9 +276,7 @@ def test_exists(self, config, context, cbank, ckey): res = cache.exists() assert res is bool(config) - def test_get( - self, config, cached, context, cbank, ckey, data - ): # pylint: disable-msg=too-many-arguments + def test_get(self, config, cached, context, cbank, ckey, data): """ Ensure cached data is returned and backend settings honored, unless the instance has not been initialized yet @@ -313,9 +298,7 @@ def test_get( cached.contains.assert_not_called() assert res is None - def test_flush( - self, config, context, cached, cbank, ckey - ): # pylint: disable-msg=too-many-arguments + def test_flush(self, config, context, cached, cbank, ckey): """ Ensure flushing deletes the whole cache bank (=connection scope), unless the configuration has not been initialized. @@ -500,9 +483,8 @@ def test_flush_exceptions_with_flush(self, cached, cbank, ckey): with pytest.raises(vault.VaultAuthExpired): cache.flush() - def test_flush_exceptions_with_get( - self, cached_outdated, cbank, ckey - ): # pylint: disable=unused-argument + @pytest.mark.usefixtures("cached_outdated") + def test_flush_exceptions_with_get(self, cbank, ckey): """ Ensure internal flushing is disabled when the object is initialized with a reference to an exception class. @@ -617,9 +599,7 @@ def test_store(self, lease): @pytest.mark.parametrize("lease", ({}, {"meta": {"foo": "bar"}}), indirect=True) @pytest.mark.usefixtures("cached_outdated") - def test_expire_events_with_get( - self, events, lease - ): # pylint: disable=unused-argument, disable-msg=too-many-arguments + def test_expire_events_with_get(self, events, lease): """ Ensure internal flushing is disabled when the object is initialized with a reference to an exception class. diff --git a/tests/unit/utils/vault/test_client.py b/tests/unit/utils/vault/test_client.py index 88830c5f..f75edfbb 100644 --- a/tests/unit/utils/vault/test_client.py +++ b/tests/unit/utils/vault/test_client.py @@ -120,6 +120,7 @@ def test_vault_client_request_raw_does_not_raise_http_exception(client): res.raise_for_status() +@pytest.mark.usefixtures("req_failed") @pytest.mark.parametrize( "req_failed,expected", [ @@ -137,9 +138,7 @@ def test_vault_client_request_raw_does_not_raise_http_exception(client): indirect=["req_failed"], ) @pytest.mark.parametrize("raise_error", [True, False]) -def test_vault_client_request_respects_raise_error( - raise_error, req_failed, expected, client -): # pylint: disable=unused-argument +def test_vault_client_request_respects_raise_error(raise_error, expected, client): """ request should inspect the response object and raise appropriate errors or fall back to raise_for_status if raise_error is true @@ -241,12 +240,11 @@ def test_vault_client_wrap_info_only_data(wrapped_role_id_lookup_response, clien assert res == wrapped_role_id_lookup_response["data"] +@pytest.mark.usefixtures("req_failed") @pytest.mark.parametrize( "req_failed,expected", [(502, vault.VaultServerError)], indirect=["req_failed"] ) -def test_vault_client_wrap_info_should_fail_with_sensible_response( - expected, req_failed, client -): # pylint: disable=unused-argument +def test_vault_client_wrap_info_should_fail_with_sensible_response(expected, client): """ wrap_info should return sensible Exceptions, not KeyError etc """ @@ -282,6 +280,7 @@ def test_vault_client_unwrap_should_default_to_token_header_before_payload( assert headers.get("X-Vault-Token") == token +@pytest.mark.usefixtures("req_failed") @pytest.mark.parametrize("func", ["unwrap", "token_lookup"]) @pytest.mark.parametrize( "req_failed,expected", @@ -294,9 +293,7 @@ def test_vault_client_unwrap_should_default_to_token_header_before_payload( ], indirect=["req_failed"], ) -def test_vault_client_unwrap_should_raise_appropriate_errors( - func, req_failed, expected, client -): # pylint: disable=unused-argument +def test_vault_client_unwrap_should_raise_appropriate_errors(func, expected, client): """ unwrap/token_lookup should raise exceptions the same way request does """ @@ -419,15 +416,14 @@ def test_vault_client_request_raw_increases_use_count_when_necessary_depending_o assert client.auth.used.called is use +@pytest.mark.usefixtures("req_failed") @pytest.mark.parametrize("client", ["valid_token"], indirect=True) @pytest.mark.parametrize( "req_failed", [400, 403, 404, 405, 412, 500, 502, 503, 401], indirect=True, ) -def test_vault_client_request_raw_increases_use_count_when_necessary_depending_on_response( - client, req_failed # pylint: disable=unused-argument -): +def test_vault_client_request_raw_increases_use_count_when_necessary_depending_on_response(client): """ When a request is issued to an endpoint that consumes a use, make sure that this is registered regardless of status code: @@ -595,10 +591,7 @@ def test_get_expected_creation_path(secret, config, expected): """ Ensure expected creation paths are resolved as expected """ - assert ( - vclient._get_expected_creation_path(secret, config) # pylint: disable=protected-access - == expected - ) + assert vclient._get_expected_creation_path(secret, config) == expected def test_get_expected_creation_path_fails_for_unknown_type(): @@ -606,7 +599,7 @@ def test_get_expected_creation_path_fails_for_unknown_type(): Ensure unknown source types result in an exception """ with pytest.raises(salt.exceptions.SaltInvocationError): - vclient._get_expected_creation_path("nonexistent") # pylint: disable=protected-access + vclient._get_expected_creation_path("nonexistent") @pytest.fixture diff --git a/tests/unit/utils/vault/test_factory.py b/tests/unit/utils/vault/test_factory.py index 4efad23c..c012815a 100644 --- a/tests/unit/utils/vault/test_factory.py +++ b/tests/unit/utils/vault/test_factory.py @@ -14,6 +14,40 @@ from tests.unit.utils.vault.conftest import _mock_json_response # pylint: disable=import-error +@pytest.fixture(params=("token", "approle")) +def get_config_response(config_defaults, server_config, wrapped_role_id_response, request): + # avoid mutating default config + res = { + "auth": config_defaults["auth"].copy(), + "cache": config_defaults["cache"].copy(), + "client": config_defaults["client"].copy(), + "server": server_config, + } + + if request.param == "token": + res["auth"]["token"] = "test-token" + + if request.param == "wrapped_token": + res["auth"]["method"] = "wrapped_token" + res["auth"]["token"] = "test-wrapped-token" + + if request.param == "approle": + res["auth"]["method"] = "approle" + res["auth"]["role_id"] = "test-role-id" + + if request.param == "approle_no_secretid": + res["auth"]["method"] = "approle" + res["auth"]["role_id"] = "test-role-id" + res["auth"]["secret_id"] = False + + if request.param == "approle_wrapped_roleid": + res["auth"]["method"] = "approle" + res["auth"]["role_id"] = {"wrap_info": wrapped_role_id_response["wrap_info"]} + res["auth"]["secret_id"] = True + res["wrap_info_nested"] = ["auth:role_id"] + return res + + class TestGetAuthdClient: """ Tests for Vault Get Authd Client @@ -259,9 +293,7 @@ def test_get_authd_client_unrenewable_new_token(self, clear_cache): clear_cache.assert_called_once() @pytest.mark.usefixtures("build_renewable_max_ttl") - def test_get_authd_client_renewable_token_max_ttl_insufficient( - self, build_renewable_max_ttl, clear_cache # pylint: disable=unused-argument - ): + def test_get_authd_client_renewable_token_max_ttl_insufficient(self, clear_cache): """ Ensure minimum_ttl is respected when a token can be renewed, but the new ttl does not satisfy it. @@ -339,13 +371,11 @@ def test_build_authd_client(self, test_remote_config, conn_config, fetch_secret_ ( client, config, # pylint: disable=unused-variable - ) = vfactory._build_authd_client( # pylint: disable=protected-access, unused-variable - {}, {} - ) + ) = vfactory._build_authd_client({}, {}) assert client.token_valid(remote=False) if test_remote_config["auth"]["method"] == "approle": if ( - not test_remote_config["auth"]["secret_id"] + test_remote_config["auth"]["secret_id"] is False or cached(None, None, vfactory.TOKEN_CKEY).get() or cached(None, None, "secret_id").get() ): @@ -390,6 +420,7 @@ def remote(self, test_remote_config, unauthd_client_mock): query.return_value = (test_remote_config, unauthd_client_mock) yield query + @pytest.mark.usefixtures("salt_runtype") @pytest.mark.parametrize( "salt_runtype,force_local", [ @@ -399,18 +430,14 @@ def remote(self, test_remote_config, unauthd_client_mock): ], indirect=["salt_runtype"], ) - def test_get_connection_config_local( - self, salt_runtype, force_local, local - ): # pylint: disable=unused-argument + def test_get_connection_config_local(self, force_local, local): """ Ensure the local configuration is used when a) running on master b) running on master impersonating a minion when called from runner c) running on minion in local mode """ - vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, force_local=force_local - ) # pylint: disable=protected-access + vfactory._get_connection_config("vault", {}, {}, force_local=force_local) local.assert_called_once() def test_get_connection_config_cached(self, cached, remote, test_remote_config): @@ -421,9 +448,7 @@ def test_get_connection_config_cached(self, cached, remote, test_remote_config): res, embedded_token, _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {} - ) # pylint: disable=protected-access + ) = vfactory._get_connection_config("vault", {}, {}) assert res == test_remote_config assert embedded_token is None cached.store.assert_not_called() @@ -438,9 +463,7 @@ def test_get_connection_config_uncached(self, uncached, remote): res, embedded_token, _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {} - ) + ) = vfactory._get_connection_config("vault", {}, {}) uncached.store.assert_called_once() remote.assert_called_once() data, _ = remote() @@ -465,11 +488,9 @@ def test_get_connection_config_location(self, conf_location, called, remote): salt.exceptions.InvalidConfigError, match=".*config_location must be either local or master.*", ): - vfactory._get_connection_config( # pylint: disable=protected-access - "vault", opts, {} - ) + vfactory._get_connection_config("vault", opts, {}) else: - vfactory._get_connection_config("vault", opts, {}) # pylint: disable=protected-access + vfactory._get_connection_config("vault", opts, {}) if called: remote.assert_called() else: @@ -486,9 +507,7 @@ def test_get_connection_config_update(self, cached, remote, test_remote_config): res, embedded_token, _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) assert res == updated_remote_config assert embedded_token is None remote.assert_called_once_with("get_config", {}, issue_params=None, config_only=True) @@ -509,9 +528,7 @@ def test_get_connection_config_update_server(self, cached, remote, test_remote_c res, # pylint: disable=unused-variable embedded_token, # pylint: disable=unused-variable _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) self._assert_not_updated(remote, cached) @pytest.mark.parametrize("test_remote_config", ["token"], indirect=True) @@ -529,9 +546,7 @@ def test_get_connection_config_update_auth_method(self, cached, remote, test_rem res, # pylint: disable=unused-variable embedded_token, # pylint: disable=unused-variable _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) self._assert_not_updated(remote, cached) @pytest.mark.parametrize("test_remote_config", ["token"], indirect=True) @@ -548,9 +563,7 @@ def test_get_connection_config_update_cache_backend(self, cached, remote, test_r res, # pylint: disable=unused-variable embedded_token, # pylint: disable=unused-variable _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) self._assert_not_updated(remote, cached) @pytest.mark.parametrize("test_remote_config", ["approle"], indirect=True) @@ -567,9 +580,7 @@ def test_get_connection_config_update_role_id(self, cached, remote, test_remote_ res, # pylint: disable=unused-variable embedded_token, # pylint: disable=unused-variable _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) self._assert_not_updated(remote, cached) @pytest.mark.parametrize("test_remote_config", ["approle"], indirect=True) @@ -587,9 +598,7 @@ def test_get_connection_config_update_bind_secret_id(self, cached, remote, test_ res, # pylint: disable=unused-variable embedded_token, # pylint: disable=unused-variable _, - ) = vfactory._get_connection_config( # pylint: disable=protected-access - "vault", {}, {}, update=True - ) + ) = vfactory._get_connection_config("vault", {}, {}, update=True) self._assert_not_updated(remote, cached) def _assert_not_updated(self, remote, cached): @@ -638,6 +647,7 @@ def secret_id(self, secret_id_response, wrapped_secret_id_response, request): "dict": secret_id_response["data"], }[request.param] + @pytest.mark.usefixtures("salt_runtype") @pytest.mark.parametrize("test_remote_config", ["approle"], indirect=True) @pytest.mark.parametrize( "salt_runtype,force_local", @@ -650,7 +660,6 @@ def secret_id(self, secret_id_response, wrapped_secret_id_response, request): ) def test_fetch_secret_id_local( self, - salt_runtype, # pylint: disable=unused-argument force_local, uncached, test_remote_config, @@ -667,7 +676,7 @@ def test_fetch_secret_id_local( """ test_remote_config["auth"]["secret_id"] = secret_id unauthd_client_mock.unwrap.return_value = secret_id_response - res = vfactory._fetch_secret_id( # pylint: disable=protected-access + res = vfactory._fetch_secret_id( test_remote_config, {}, uncached, @@ -694,9 +703,7 @@ def test_fetch_secret_id_cached(self, test_remote_config, cached, remote, unauth """ Ensure cache is respected """ - res = vfactory._fetch_secret_id( # pylint: disable=protected-access - test_remote_config, {}, cached, unauthd_client_mock - ) + res = vfactory._fetch_secret_id(test_remote_config, {}, cached, unauthd_client_mock) assert res == cached.get() cached.store.assert_not_called() remote.assert_not_called() @@ -708,9 +715,7 @@ def test_fetch_secret_id_uncached( """ Ensure requested credentials are cached and returned as data objects """ - res = vfactory._fetch_secret_id( # pylint: disable=protected-access - test_remote_config, {}, uncached, unauthd_client_mock - ) + res = vfactory._fetch_secret_id(test_remote_config, {}, uncached, unauthd_client_mock) uncached.store.assert_called_once() remote.assert_called_once() data, _ = remote() @@ -737,9 +742,7 @@ def test_fetch_secret_id_uncached_single_use( }, unauthd_client_mock, ) - res = vfactory._fetch_secret_id( # pylint: disable=protected-access - test_remote_config, {}, uncached, unauthd_client_mock - ) + res = vfactory._fetch_secret_id(test_remote_config, {}, uncached, unauthd_client_mock) uncached.store.assert_not_called() remote.assert_called_once() data, _ = remote() @@ -770,13 +773,9 @@ def test_fetch_secret_id_config_location( salt.exceptions.InvalidConfigError, match=".*config_location must be either local or master.*", ): - vfactory._fetch_secret_id( # pylint: disable=protected-access - test_remote_config, opts, uncached, unauthd_client_mock - ) + vfactory._fetch_secret_id(test_remote_config, opts, uncached, unauthd_client_mock) else: - vfactory._fetch_secret_id( # pylint: disable=protected-access - test_remote_config, opts, uncached, unauthd_client_mock - ) + vfactory._fetch_secret_id(test_remote_config, opts, uncached, unauthd_client_mock) if called: remote.assert_called() else: @@ -823,6 +822,7 @@ def token(self, token_auth, wrapped_token_auth_response, request): "dict": token_auth["auth"], }[request.param] + @pytest.mark.usefixtures("salt_runtype") @pytest.mark.parametrize("test_remote_config", ["token", "wrapped_token"], indirect=True) @pytest.mark.parametrize( "salt_runtype,force_local", @@ -835,7 +835,6 @@ def token(self, token_auth, wrapped_token_auth_response, request): ) def test_fetch_token_local( self, - salt_runtype, # pylint: disable=unused-argument force_local, uncached, test_remote_config, @@ -858,7 +857,7 @@ def test_fetch_token_local( unauthd_client_mock.token_lookup.return_value = _mock_json_response( token_lookup_self_response, status_code=200 ) - res = vfactory._fetch_token( # pylint: disable=protected-access + res = vfactory._fetch_token( test_remote_config, {}, uncached, @@ -893,6 +892,7 @@ def test_fetch_token_local( uncached.get.assert_called_once() uncached.store.assert_called_once() + @pytest.mark.usefixtures("salt_runtype") @pytest.mark.parametrize("test_remote_config", ["token", "token_changed"], indirect=True) @pytest.mark.parametrize( "salt_runtype,force_local", @@ -905,7 +905,6 @@ def test_fetch_token_local( ) def test_fetch_token_local_cached_changed( self, - salt_runtype, # pylint: disable=unused-argument force_local, cached, test_remote_config, @@ -921,7 +920,7 @@ def test_fetch_token_local_cached_changed( unauthd_client_mock.token_lookup.return_value = _mock_json_response( token_lookup_self_response, status_code=200 ) - res = vfactory._fetch_token( # pylint: disable=protected-access + res = vfactory._fetch_token( test_remote_config, {}, cached, @@ -945,9 +944,7 @@ def test_fetch_token_cached(self, test_remote_config, cached, remote, unauthd_cl """ Ensure that cache is respected """ - res = vfactory._fetch_token( # pylint: disable=protected-access - test_remote_config, {}, cached, unauthd_client_mock - ) + res = vfactory._fetch_token(test_remote_config, {}, cached, unauthd_client_mock) assert res == cached.get() cached.store.assert_not_called() remote.assert_not_called() @@ -961,7 +958,7 @@ def test_fetch_token_uncached_embedded( are used when no cached token is available """ test_remote_config["auth"].pop("token", None) - res = vfactory._fetch_token( # pylint: disable=protected-access + res = vfactory._fetch_token( test_remote_config, {}, uncached, @@ -979,9 +976,7 @@ def test_fetch_token_uncached(self, test_remote_config, uncached, remote, unauth are used when no cached token is available """ test_remote_config["auth"].pop("token", None) - res = vfactory._fetch_token( # pylint: disable=protected-access - test_remote_config, {}, uncached, unauthd_client_mock - ) + res = vfactory._fetch_token(test_remote_config, {}, uncached, unauthd_client_mock) uncached.store.assert_called_once() remote.assert_called_once() assert res == vault.VaultToken(**remote.return_value[0]["auth"]) @@ -1004,9 +999,7 @@ def test_fetch_token_uncached_single_use( {"auth": token_auth["auth"], "server": server_config}, unauthd_client_mock, ) - res = vfactory._fetch_token( # pylint: disable=protected-access - test_remote_config, {}, uncached, unauthd_client_mock - ) + res = vfactory._fetch_token(test_remote_config, {}, uncached, unauthd_client_mock) uncached.store.assert_not_called() remote.assert_called_once() assert res == vault.VaultToken(**remote.return_value[0]["auth"]) @@ -1038,7 +1031,7 @@ def test_fetch_token_config_location( salt.exceptions.InvalidConfigError, match=".*config_location must be either local or master.*", ): - vfactory._fetch_token( # pylint: disable=protected-access + vfactory._fetch_token( test_remote_config, opts, uncached, @@ -1046,7 +1039,7 @@ def test_fetch_token_config_location( embedded_token=embedded_token, ) else: - vfactory._fetch_token( # pylint: disable=protected-access + vfactory._fetch_token( test_remote_config, opts, uncached, @@ -1129,7 +1122,7 @@ def test_query_master_uses_correct_module( minion - publish.runner master impersonating - saltutil.runner """ - out, _ = vfactory._query_master("func", opts) # pylint: disable=protected-access + out, _ = vfactory._query_master("func", opts) assert out == {"success": True} if expected == "saltutil": publish_runner.assert_not_called() @@ -1147,10 +1140,10 @@ def test_query_master_validates_response(self, opts, response, publish_runner, s publish_runner.return_value = saltutil_runner.return_value = response if not response: with pytest.raises(vault.VaultConfigExpired): - vfactory._query_master("func", opts) # pylint: disable=protected-access + vfactory._query_master("func", opts) else: with pytest.raises(salt.exceptions.CommandExecutionError): - vfactory._query_master("func", opts) # pylint: disable=protected-access + vfactory._query_master("func", opts) @pytest.mark.parametrize( "response", [{"expire_cache": True}, {"error": {"error"}, "expire_cache": True}] @@ -1163,7 +1156,7 @@ def test_query_master_invalidates_cache_when_requested_by_master( """ publish_runner.return_value = saltutil_runner.return_value = response with pytest.raises(vault.VaultConfigExpired): - vfactory._query_master("func", opts) # pylint: disable=protected-access + vfactory._query_master("func", opts) @pytest.mark.parametrize( "url,verify,namespace", @@ -1179,7 +1172,6 @@ def test_query_master_invalidates_cache_when_expected_server_differs( url, verify, namespace, - server_config, # pylint: disable=unused-argument wrapped_role_id_response, unauthd_client_mock, unwrap_client, @@ -1198,9 +1190,7 @@ def test_query_master_invalidates_cache_when_expected_server_differs( } publish_runner.return_value = saltutil_runner.return_value = ret with pytest.raises(vault.VaultConfigExpired): - vfactory._query_master( # pylint: disable=protected-access - "func", opts, unwrap_client=unauthd_client_mock - ) + vfactory._query_master("func", opts, unwrap_client=unauthd_client_mock) assert "Mismatch of cached and reported server data detected" in caplog.text # this one gets discarded because it's outdated unauthd_client_mock.unwrap.assert_not_called() @@ -1241,9 +1231,7 @@ def test_query_master_local_verify_does_not_interfere_with_expected_server( unauthd_client_mock.get_config.return_value = expected_server unauthd_client_mock.unwrap.return_value = role_id_response - ret, _ = vfactory._query_master( # pylint: disable=protected-access - "func", opts, unwrap_client=unauthd_client_mock - ) + ret, _ = vfactory._query_master("func", opts, unwrap_client=unauthd_client_mock) assert "Mismatch of cached and reported server data detected" not in caplog.text # ensure the client was not replaced unwrap_client.assert_not_called() @@ -1253,6 +1241,32 @@ def test_query_master_local_verify_does_not_interfere_with_expected_server( "server": expected_server, } + @pytest.mark.parametrize( + "get_config_response", + ("approle_wrapped_roleid",), + indirect=True, + ) + def test_query_master_respects_client_config_when_unwrapping_without_cached_config( + self, + opts, + publish_runner, + saltutil_runner, + server_config, + get_config_response, + ): + """ + Ensure that when no cached configuration is available, the client used + to unwrap token/RoleID (and transitively, SecretID) respects custom + ``client`` configuration. + """ + get_config_response["client"]["retry_post"] = True + publish_runner.return_value = saltutil_runner.return_value = get_config_response + with patch("saltext.vault.utils.vault.client.VaultClient", autospec=True) as client_init: + client_init.return_value.get_config.return_value = server_config + vfactory._query_master("get_config", opts) + assert "retry_post" in client_init.call_args.kwargs + assert client_init.call_args.kwargs["retry_post"] is True + @pytest.mark.parametrize( "unauthd_client_mock,key", [ @@ -1278,9 +1292,7 @@ def test_query_master_merges_unwrapped_result( "server": server_config, "wrap_info": wrapped_role_id_response["wrap_info"], } - out, _ = vfactory._query_master( # pylint: disable=protected-access - "func", opts, unwrap_client=unauthd_client_mock - ) # pylint: disable=protected-access + out, _ = vfactory._query_master("func", opts, unwrap_client=unauthd_client_mock) assert "wrap_info" not in out assert key in out assert out[key] == {"bar": "baz"} @@ -1304,9 +1316,7 @@ def test_query_master_merges_nested_unwrapped_result( "wrap_info_nested": ["auth:role_id"], "auth": {"role_id": {"wrap_info": wrapped_role_id_response["wrap_info"]}}, } - out, _ = vfactory._query_master( # pylint: disable=protected-access - "func", opts, unwrap_client=unauthd_client_mock - ) # pylint: disable=protected-access + out, _ = vfactory._query_master("func", opts, unwrap_client=unauthd_client_mock) assert "wrap_info_nested" not in out assert "wrap_info" not in out["auth"]["role_id"] assert out["auth"]["role_id"] == {"bar": "baz"} @@ -1329,7 +1339,7 @@ def test_query_master_merges_misc_data( "misc_data": {misc_data: "merged"}, } publish_runner.return_value = saltutil_runner.return_value = copy.deepcopy(response) - out, _ = vfactory._query_master("func", opts) # pylint: disable=protected-access + out, _ = vfactory._query_master("func", opts) assert misc_data in out[key] assert "misc_data" not in out if misc_data in secret_id_response["data"]: @@ -1355,7 +1365,7 @@ def test_query_master_merges_misc_data_recursively( "misc_data": {misc_data: "merged"}, } publish_runner.return_value = saltutil_runner.return_value = copy.deepcopy(response) - out, _ = vfactory._query_master("func", opts) # pylint: disable=protected-access + out, _ = vfactory._query_master("func", opts) nested_key = misc_data.split(":")[1] assert nested_key in out[key]["nested"] assert "misc_data" not in out @@ -1392,13 +1402,13 @@ def token(self, token_auth, request): def test_build_revocation_client_never_calls_master_for_config(self): with patch("saltext.vault.utils.vault.factory._query_master") as query: - vfactory._build_revocation_client({}, {}) # pylint: disable=protected-access + vfactory._build_revocation_client({}, {}) query.assert_not_called() @pytest.mark.parametrize("config", [True], indirect=True) def test_build_revocation_client_never_calls_master_for_token(self, token, test_remote_config): with patch("saltext.vault.utils.vault.factory._query_master") as query: - res = vfactory._build_revocation_client({}, {}) # pylint: disable=protected-access + res = vfactory._build_revocation_client({}, {}) query.assert_not_called() if token.return_value.get.return_value is not None: assert isinstance(res[0], vclient.AuthenticatedVaultClient) @@ -1434,12 +1444,11 @@ def test_clear_cache(ckey, connection, session, cache_factory): assert cbank not in context +@pytest.mark.usefixtures("cache_factory") @pytest.mark.parametrize("ckey", ["token", None]) @pytest.mark.parametrize("connection", [True, False]) @pytest.mark.parametrize("session", [True, False]) -def test_clear_cache_clears_client_from_context( - ckey, connection, session, cache_factory -): # pylint: disable=unused-argument +def test_clear_cache_clears_client_from_context(ckey, connection, session): """ Ensure the cached client is removed when the connection cache is altered only """ @@ -1478,6 +1487,7 @@ def test_clear_cache_clears_client_from_context( "clear_on_unauthorized": True, "config": 3600, "expire_events": False, + "kv_metadata": "connection", "secret": "ttl", }, "client": { @@ -1520,6 +1530,7 @@ def test_clear_cache_clears_client_from_context( "clear_on_unauthorized": True, "config": 3600, "expire_events": False, + "kv_metadata": "connection", "secret": "ttl", }, "client": { @@ -1551,7 +1562,7 @@ def test_use_local_config(test_config, expected_config, expected_token): and pops an embedded token, if present """ with patch("saltext.vault.utils.vault.factory.parse_config", Mock(return_value=test_config)): - output, token, _ = vfactory._use_local_config({}) # pylint: disable=protected-access + output, token, _ = vfactory._use_local_config({}) assert output == expected_config assert token == expected_token @@ -1637,7 +1648,7 @@ def test_get_config_recognizes_old_config(old, new): def rec( # pylint: disable=inconsistent-return-statements config, path, val=None - ): # pylint: disable=too-many-arguments, duplicate-code, inconsistent-return-statements + ): # pylint: disable=duplicate-code, inconsistent-return-statements ptr = config parts = path.split(":") while parts: diff --git a/tests/unit/utils/vault/test_helpers.py b/tests/unit/utils/vault/test_helpers.py index 22692e8b..4dfd1975 100644 --- a/tests/unit/utils/vault/test_helpers.py +++ b/tests/unit/utils/vault/test_helpers.py @@ -24,7 +24,7 @@ def test_get_salt_run_type(opts_runtype, expected): """ Ensure run types are detected as expected """ - assert hlp._get_salt_run_type(opts_runtype) == expected # pylint: disable=protected-access + assert hlp._get_salt_run_type(opts_runtype) == expected @pytest.mark.parametrize( diff --git a/tests/unit/utils/vault/test_kv.py b/tests/unit/utils/vault/test_kv.py index 587fcca0..11f50c33 100644 --- a/tests/unit/utils/vault/test_kv.py +++ b/tests/unit/utils/vault/test_kv.py @@ -244,7 +244,7 @@ def kvv2(kvv2_info, kvv2_response, metadata_nocache, kv_list_response): @pytest.mark.parametrize("clear_unauthd,token_valid", [(False, False), (True, False), (True, True)]) def test_kv_wrapper_handles_perm_exceptions( wrapper, param, result, test_remote_config, clear_unauthd, token_valid -): # pylint: disable-msg=too-many-arguments +): """ Test that *_kv wrappers retry with a new client if a) the current configuration might be invalid @@ -405,9 +405,7 @@ def test_vault_kv_write(self, kvv1, path): ), ], ) - def test_vault_kv_patch( - self, kvv1, path, existing, data, expected - ): # pylint: disable-msg=too-many-arguments + def test_vault_kv_patch(self, kvv1, path, existing, data, expected): """ Ensure that VaultKV.patch works for KV v1. This also tests the internal JSON merge patch implementation. @@ -475,14 +473,14 @@ def test_parse_versions(self, kvv2, versions, expected): Ensure parsing versions works as expected: single integer/number string or list of those are allowed """ - assert kvv2._parse_versions(versions) == expected # pylint: disable=protected-access + assert kvv2._parse_versions(versions) == expected def test_parse_versions_raises_exception_when_unparsable(self, kvv2): """ Ensure unparsable versions raise an exception """ with pytest.raises(vault.VaultInvocationError): - kvv2._parse_versions("four") # pylint: disable=protected-access + kvv2._parse_versions("four") def test_get_secret_path_metadata_lookup_unexpected_response(self, kvv2, caplog, path): """ @@ -494,7 +492,7 @@ def test_get_secret_path_metadata_lookup_unexpected_response(self, kvv2, caplog, resp_mm.status_code = 200 resp_mm.reason = "" kvv2.client.get.return_value = resp_mm - res = kvv2._get_secret_path_metadata(path) # pylint: disable=protected-access + res = kvv2._get_secret_path_metadata(path) assert res is None assert "Unexpected response to metadata query" in caplog.text @@ -503,7 +501,7 @@ def test_get_secret_path_metadata_lookup_request_error(self, kvv2, caplog, path) Ensure HTTP error status codes are treated as not KV """ kvv2.client.get.side_effect = vault.VaultPermissionDeniedError - res = kvv2._get_secret_path_metadata(path) # pylint: disable=protected-access + res = kvv2._get_secret_path_metadata(path) assert res is None assert "VaultPermissionDeniedError:" in caplog.text diff --git a/tests/unit/utils/vault/test_leases.py b/tests/unit/utils/vault/test_leases.py index cdd5c49c..2d8e7f37 100644 --- a/tests/unit/utils/vault/test_leases.py +++ b/tests/unit/utils/vault/test_leases.py @@ -420,7 +420,7 @@ def test_get_valid_renew_valid_for( valid_for, lease_renewed_response, lease_renewed_extended_response, - ): # pylint: disable-msg=too-many-arguments + ): """ Ensure that, if renew_increment was not set and the default period does not yield valid_for, a second renewal is attempted by valid_for.