feat: Add configurable mount name and path prefix for HashiCorp Vault (#16253)

- Add HCP_VAULT_MOUNT_NAME env var to override default 'secret' mount
- Add HCP_VAULT_PATH_PREFIX env var to add prefix to secret paths
- Update get_url() method to construct URLs with configurable mount and prefix
- Add test coverage for custom mount names and path prefixes
- Maintain backward compatibility with existing configurations

This allows users to configure Vault paths like:
- Custom mount: {VAULT_ADDR}/v1/{MOUNT_NAME}/data/{SECRET}
- With prefix: {VAULT_ADDR}/v1/secret/data/{PREFIX}/{SECRET}
- Both: {VAULT_ADDR}/v1/{MOUNT_NAME}/data/{PREFIX}/{SECRET}

Resolves issue where mount name was hardcoded and path prefixes weren't supported.
This commit is contained in:
Alexsander Hamir
2025-11-05 16:06:07 -08:00
committed by GitHub
parent f1191578ae
commit 8ee9b1bc93
2 changed files with 63 additions and 2 deletions
@@ -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:
@@ -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