diff --git a/litellm/secret_managers/hashicorp_secret_manager.py b/litellm/secret_managers/hashicorp_secret_manager.py index d2fb9ff70f..90705de02c 100644 --- a/litellm/secret_managers/hashicorp_secret_manager.py +++ b/litellm/secret_managers/hashicorp_secret_manager.py @@ -24,8 +24,13 @@ class HashicorpSecretManager(BaseSecretManager): # Vault-specific config self.vault_addr = os.getenv("HCP_VAULT_ADDR", "http://127.0.0.1:8200") self.vault_token = os.getenv("HCP_VAULT_TOKEN", "") - # If your KV engine is mounted somewhere other than "secret", adjust here: + # Vault namespace (for X-Vault-Namespace header) self.vault_namespace = os.getenv("HCP_VAULT_NAMESPACE", None) + # KV engine mount name (default: "secret") + # If your KV engine is mounted somewhere other than "secret", set HCP_VAULT_MOUNT_NAME + self.vault_mount_name = os.getenv("HCP_VAULT_MOUNT_NAME", "secret") + # Optional path prefix for secrets (e.g., "myapp" -> secret/data/myapp/{secret_name}) + self.vault_path_prefix = os.getenv("HCP_VAULT_PATH_PREFIX", None) # Optional config for TLS cert auth self.tls_cert_path = os.getenv("HCP_VAULT_CLIENT_CERT", "") @@ -118,10 +123,24 @@ class HashicorpSecretManager(BaseSecretManager): return {"name": self.vault_cert_role} def get_url(self, secret_name: str) -> str: + """ + Constructs the Vault URL for KV v2 secrets. + + Format: {VAULT_ADDR}/v1/{NAMESPACE}/{MOUNT_NAME}/data/{PATH_PREFIX}/{SECRET_NAME} + + Examples: + - Default: http://127.0.0.1:8200/v1/secret/data/mykey + - With namespace: http://127.0.0.1:8200/v1/mynamespace/secret/data/mykey + - With custom mount: http://127.0.0.1:8200/v1/kv/data/mykey + - With path prefix: http://127.0.0.1:8200/v1/secret/data/myapp/mykey + """ _url = f"{self.vault_addr}/v1/" if self.vault_namespace: _url += f"{self.vault_namespace}/" - _url += f"secret/data/{secret_name}" + _url += f"{self.vault_mount_name}/data/" + if self.vault_path_prefix: + _url += f"{self.vault_path_prefix}/" + _url += secret_name return _url def _get_request_headers(self) -> dict: diff --git a/tests/litellm_utils_tests/test_hashicorp.py b/tests/litellm_utils_tests/test_hashicorp.py index 055112d88c..ef8aa889ff 100644 --- a/tests/litellm_utils_tests/test_hashicorp.py +++ b/tests/litellm_utils_tests/test_hashicorp.py @@ -17,6 +17,10 @@ from litellm._uuid import uuid verbose_logger.setLevel(logging.DEBUG) +# Minimal setup for module-level instantiation +import litellm.proxy.proxy_server +litellm.proxy.proxy_server.premium_user = True + from litellm.secret_managers.hashicorp_secret_manager import HashicorpSecretManager hashicorp_secret_manager = HashicorpSecretManager() @@ -211,3 +215,41 @@ def test_hashicorp_secret_manager_tls_cert_auth(monkeypatch): # Verify the token was cached assert test_manager.cache.get_cache("hcp_vault_token") == "test-client-token-12345" + + +def test_hashicorp_custom_mount_and_prefix(): + """Test URL construction with custom mount name and path prefix using get_url method.""" + # Save original values + original_mount = hashicorp_secret_manager.vault_mount_name + original_prefix = hashicorp_secret_manager.vault_path_prefix + original_namespace = hashicorp_secret_manager.vault_namespace + + try: + # Test that existing manager uses default "secret" mount and namespace "admin" + url = hashicorp_secret_manager.get_url("my-secret") + assert "/secret/data/" in url + assert "my-secret" in url + + # Test custom mount name + hashicorp_secret_manager.vault_mount_name = "kv" + hashicorp_secret_manager.vault_path_prefix = None + hashicorp_secret_manager.vault_namespace = None + url = hashicorp_secret_manager.get_url("my-secret") + assert url == f"{hashicorp_secret_manager.vault_addr}/v1/kv/data/my-secret" + + # Test path prefix + hashicorp_secret_manager.vault_mount_name = "secret" + hashicorp_secret_manager.vault_path_prefix = "myapp" + url = hashicorp_secret_manager.get_url("my-secret") + assert url == f"{hashicorp_secret_manager.vault_addr}/v1/secret/data/myapp/my-secret" + + # Test both custom mount and prefix + hashicorp_secret_manager.vault_mount_name = "kv" + hashicorp_secret_manager.vault_path_prefix = "production" + url = hashicorp_secret_manager.get_url("my-secret") + assert url == f"{hashicorp_secret_manager.vault_addr}/v1/kv/data/production/my-secret" + finally: + # Restore original values + hashicorp_secret_manager.vault_mount_name = original_mount + hashicorp_secret_manager.vault_path_prefix = original_prefix + hashicorp_secret_manager.vault_namespace = original_namespace