fix(vault): key the secret cache by url and data field

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yassin 2026-09-17 02:08:58 +00:00
parent 15bfe8f28a
commit 4694bd0c63
2 changed files with 21 additions and 7 deletions

View file

@ -39,6 +39,7 @@ class _VaultSecretTarget(TypedDict):
url: ReadOnly[str]
data_key: ReadOnly[str]
secret_name: ReadOnly[str]
cache_key: ReadOnly[str]
class _VaultSecretDataBlock(TypedDict, total=False):
@ -368,6 +369,7 @@ class HashicorpSecretManager(BaseSecretManager):
"url": url,
"data_key": data_key,
"secret_name": secret_name,
"cache_key": f"{url}#{data_key}",
}
def _get_request_headers(self) -> dict:
@ -406,7 +408,7 @@ class HashicorpSecretManager(BaseSecretManager):
)
try:
target: Final = self._build_secret_target(secret_name, optional_params)
cached_value: Final = self.cache.get_cache(target["url"])
cached_value: Final = self.cache.get_cache(target["cache_key"])
if cached_value is not None:
return cached_value
@ -415,7 +417,7 @@ class HashicorpSecretManager(BaseSecretManager):
json_resp: Final = _json_object_body(response)
_value: Final = self._get_secret_value_from_json_response(json_resp, target["data_key"])
self.cache.set_cache(target["url"], _value)
self.cache.set_cache(target["cache_key"], _value)
return _value
except Exception as e:
@ -436,7 +438,7 @@ class HashicorpSecretManager(BaseSecretManager):
sync_client: Final = _get_httpx_client()
try:
target: Final = self._build_secret_target(secret_name, optional_params)
cached_value: Final = self.cache.get_cache(target["url"])
cached_value: Final = self.cache.get_cache(target["cache_key"])
if cached_value is not None:
return cached_value
@ -445,7 +447,7 @@ class HashicorpSecretManager(BaseSecretManager):
json_resp: Final = _json_object_body(response)
_value: Final = self._get_secret_value_from_json_response(json_resp, target["data_key"])
self.cache.set_cache(target["url"], _value)
self.cache.set_cache(target["cache_key"], _value)
return _value
except Exception as e:
@ -635,10 +637,10 @@ class HashicorpSecretManager(BaseSecretManager):
)
else:
# Clear cache for the old secret only if deletion was successful
self.cache.delete_cache(current_target["url"])
self.cache.delete_cache(current_target["cache_key"])
# Clear cache for the new secret (or updated secret if names are the same)
self.cache.delete_cache(new_target["url"])
self.cache.delete_cache(new_target["cache_key"])
return create_response
@ -679,7 +681,7 @@ class HashicorpSecretManager(BaseSecretManager):
response: Final = await async_client.delete(url=target["url"], headers=self._get_request_headers())
response.raise_for_status()
self.cache.delete_cache(target["url"])
self.cache.delete_cache(target["cache_key"])
return {
"status": "success",

View file

@ -120,6 +120,18 @@ def test_sync_read_caches_per_resolved_target(monkeypatch: pytest.MonkeyPatch) -
assert team_b_route.call_count == 1
@respx.mock
def test_sync_read_caches_per_data_key_for_the_same_secret_path(monkeypatch: pytest.MonkeyPatch) -> None:
manager: Final = _build_manager(monkeypatch, {"HCP_VAULT_SECRET_NAMESPACE": "teams/team-a"})
respx.post(f"{VAULT_ADDR}/v1/auth/approle/login").respond(json=LOGIN_RESPONSE)
respx.get(f"{VAULT_ADDR}/v1/teams/team-a/secret/data/DB_CREDS").respond(json=SECRET_RESPONSE)
password_params: Final = {"secret_manager_settings": {"data": "password"}}
assert manager.sync_read_secret("DB_CREDS") == "sk-from-vault"
assert manager.sync_read_secret("DB_CREDS", optional_params=password_params) == "pw-from-vault"
assert manager.sync_read_secret("DB_CREDS") == "sk-from-vault"
@pytest.mark.asyncio
@respx.mock
async def test_async_read_uses_secret_namespace_and_login_namespace(monkeypatch: pytest.MonkeyPatch) -> None: