Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions litellm-rust/crates/python-bridge/src/secrets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use crate::{coercion::FieldSpec, python_settings::PythonSettings};

const NATIVE: FieldSpec<bool> = FieldSpec::new("native", |field| field.schema_bool());

/// Where a Rust route reads provider secrets from. Python's `get_secret_str` until a
/// `SecretManagerRule` in `catalog.py` moves the configured system off `PYTHON_ONLY`, then the
/// native secret manager. A bare extension module without the litellm package reads the process
/// environment.
/// Where a Rust route reads provider secrets from. Python's `get_secret_str` when the settings
/// projection reports no configured secret manager, otherwise the resolved manager: the native
/// backend when the configured client captures one, the Python callback when it does not. A bare
/// extension module without the litellm package reads the process environment.
pub(crate) fn source(py: Python<'_>) -> PyResult<Arc<dyn SecretSource>> {
let Some(snapshot) = PythonSettings::SecretManager.read_or_unset(py)? else {
return Ok(Arc::new(EnvironmentSecrets::python_compatible(
Expand Down
30 changes: 3 additions & 27 deletions litellm/rust_bridge/catalog.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Ordered rollout policy for routes, loggers, and secret managers.
"""Ordered rollout policy for routes and loggers.

The first matching rule wins; unmatched contexts stay on Python. Native
admission separately decides whether the selected implementation can execute.
Expand All @@ -12,7 +12,6 @@

from litellm.rust_bridge.configuration import Decision, Rollout
from litellm.rust_bridge.configuration import decision as _decision
from litellm.types.secret_managers.main import KeyManagementSystem


class Route(str, Enum):
Expand Down Expand Up @@ -49,20 +48,6 @@ def matches(self, context: Context) -> bool:
)


@dataclass(frozen=True, slots=True)
class SecretManagerContext:
system: str


@dataclass(frozen=True, slots=True)
class SecretManagerRule:
rollout: Rollout
systems: frozenset[str] | None = None

def matches(self, context: Context) -> bool:
return isinstance(context, SecretManagerContext) and (self.systems is None or context.system in self.systems)


@dataclass(frozen=True, slots=True)
class LoggerContext:
pass
Expand All @@ -76,8 +61,8 @@ def matches(self, context: Context) -> bool:
return isinstance(context, LoggerContext)


Context: TypeAlias = RouteContext | SecretManagerContext | LoggerContext
Rule: TypeAlias = RouteRule | SecretManagerRule | LoggerRule
Context: TypeAlias = RouteContext | LoggerContext
Rule: TypeAlias = RouteRule | LoggerRule
Rules: TypeAlias = tuple[Rule, ...]

RULES: Final[Rules] = (
Expand All @@ -91,15 +76,6 @@ def matches(self, context: Context) -> bool:
RouteRule(Route.TOKEN_COUNTER, Rollout.PYTHON_ONLY),
RouteRule(Route.TOKENIZER, Rollout.PYTHON_ONLY),
RouteRule(Route.TRANSCRIPTION, Rollout.RUST_REQUIRED, providers=frozenset({"bedrock"})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.GOOGLE_KMS.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.AZURE_KEY_VAULT.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.AWS_SECRET_MANAGER.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.GOOGLE_SECRET_MANAGER.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.HASHICORP_VAULT.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.CYBERARK.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.LOCAL.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.AWS_KMS.value})),
SecretManagerRule(Rollout.PYTHON_ONLY, systems=frozenset({KeyManagementSystem.CUSTOM.value})),
)


Expand Down
130 changes: 2 additions & 128 deletions litellm/rust_bridge/secret_manager.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
from __future__ import annotations

import os
from collections.abc import Awaitable, Mapping
from collections.abc import Mapping
from dataclasses import dataclass, field
from importlib import import_module
from typing import Final, Protocol, runtime_checkable
from typing import Final

import httpx
from pydantic import JsonValue

from litellm.rust_bridge.bindings import NativeBinding
from litellm.rust_bridge.catalog import Rules, SecretManagerContext, decision
from litellm.rust_bridge.configuration import Decision
from litellm.types.secret_managers.main import KeyManagementSettings, KeyManagementSystem


Expand Down Expand Up @@ -163,123 +157,3 @@ def native_secret_manager_config(client: object) -> NativeSecretManagerConfig |
):
return _capture(client, adapter)
return None


class NativeSecretManagerRuntime(Protocol):
@property
def system(self) -> str: ...

def read_secret(self, name: str, settings: Mapping[str, object] | None = None) -> JsonValue: ...


@runtime_checkable
class NativeSecretManagerFactory(Protocol):
@staticmethod
def from_client(client: object) -> NativeSecretManagerRuntime | None: ...


def _factory(value: object) -> NativeSecretManagerFactory | None:
return value if isinstance(value, NativeSecretManagerFactory) and callable(value.from_client) else None


NATIVE_SECRET_MANAGER: Final = NativeBinding("_SecretManagerRuntime", validate=_factory)


def resolve_native_secret_manager(
client: object,
system: str,
rules: Rules | None = None,
*,
binding: NativeBinding[NativeSecretManagerFactory] = NATIVE_SECRET_MANAGER,
) -> NativeSecretManagerRuntime | None:
if system in ("custom", "local"):
return None
selected: Final = decision(SecretManagerContext(system=system), rules)
if selected is Decision.PYTHON:
return None
factory: Final = binding.load()
if factory is None:
if selected is Decision.RUST_REQUIRED:
raise RuntimeError("Rust secret manager runtime is unavailable")
return None
runtime: Final = factory.from_client(client)
if runtime is not None and runtime.system != system:
raise ValueError("Native secret manager system does not match configuration")
return runtime


@runtime_checkable
class NativeProviderReader(Protocol):
def sync_read_secret(
self,
secret_name: str,
optional_params: Mapping[str, object] | None = None,
timeout: float | httpx.Timeout | None = None,
) -> str | None: ...

def async_read_secret(
self,
secret_name: str,
optional_params: Mapping[str, object] | None = None,
timeout: float | httpx.Timeout | None = None,
) -> Awaitable[str | None]: ...


def resolve_native_provider_reader(
client: object,
system: str,
rules: Rules | None = None,
*,
binding: NativeBinding[NativeSecretManagerFactory] = NATIVE_SECRET_MANAGER,
) -> NativeProviderReader | None:
runtime: Final = resolve_native_secret_manager(client, system, rules, binding=binding)
if runtime is None:
return None
if not isinstance(runtime, NativeProviderReader):
raise TypeError("Rust secret manager provider reads are unavailable")
return runtime


@runtime_checkable
class NativeProviderWriter(Protocol):
def async_write_secret(
self,
secret_name: str,
secret_value: str,
description: str | None = None,
optional_params: Mapping[str, object] | None = None,
timeout: float | httpx.Timeout | None = None,
tags: object = None,
) -> Awaitable[dict[str, JsonValue]]: ...

def async_delete_secret(
self,
secret_name: str,
recovery_window_in_days: int | None = 7,
optional_params: Mapping[str, object] | None = None,
timeout: float | httpx.Timeout | None = None,
) -> Awaitable[dict[str, JsonValue]]: ...

def async_rotate_secret(
self,
current_secret_name: str,
new_secret_name: str,
new_secret_value: str,
optional_params: Mapping[str, object] | None = None,
timeout: float | httpx.Timeout | None = None,
) -> Awaitable[dict[str, JsonValue]]: ...


def resolve_native_provider_writer(
client: object,
system: str,
rules: Rules | None = None,
*,
binding: NativeBinding[NativeSecretManagerFactory] = NATIVE_SECRET_MANAGER,
) -> NativeProviderWriter | None:
runtime: Final = resolve_native_secret_manager(client, system, rules, binding=binding)
if runtime is None:
return None
if not isinstance(runtime, NativeProviderWriter):
raise TypeError("Rust secret manager provider writes are unavailable")
return runtime
16 changes: 3 additions & 13 deletions litellm/rust_bridge/settings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Final

if TYPE_CHECKING:
from litellm.rust_bridge.catalog import Rules
from typing import Final


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -62,10 +59,8 @@ class SecretManagerBinding:
settings_object: object


def secret_manager(rules: Rules | None = None) -> SecretManager:
def secret_manager() -> SecretManager:
import litellm
from litellm.rust_bridge.catalog import SecretManagerContext, decision
from litellm.rust_bridge.configuration import Decision
from litellm.secret_managers.main import (
_should_read_secret_from_secret_manager, # pyright: ignore[reportPrivateUsage] # canonical resolver is private
)
Expand All @@ -74,12 +69,7 @@ def secret_manager(rules: Rules | None = None) -> SecretManager:
system: Final = (
litellm._key_management_system # pyright: ignore[reportPrivateUsage] # canonical key management globals are private
)
native: Final = (
readable
and system is not None
and decision(SecretManagerContext(system=system.value), rules) is not Decision.PYTHON
)
return SecretManager(readable=readable, native=native)
return SecretManager(readable=readable, native=readable and system is not None)
Comment thread
yujonglee-berri marked this conversation as resolved.
Resolved


def secret_manager_binding() -> SecretManagerBinding:
Expand Down
9 changes: 0 additions & 9 deletions litellm/secret_managers/aws_secret_manager_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
_get_httpx_client,
get_async_httpx_client,
)
from litellm.rust_bridge.secret_manager import resolve_native_provider_reader
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.custom_http import httpxSpecialProvider
from litellm.types.secret_managers.main import KeyManagementSettings, KeyManagementSystem
Expand Down Expand Up @@ -140,10 +139,6 @@ async def async_read_secret(
secret_name=secret_name, primary_secret_name=primary_secret_name
)

native: Final = resolve_native_provider_reader(self, "aws_secret_manager")
if native is not None:
return await native.async_read_secret(secret_name, optional_params, timeout)

endpoint_url, headers, body = self._prepare_request(
action="GetSecretValue",
secret_name=secret_name,
Expand Down Expand Up @@ -196,10 +191,6 @@ def sync_read_secret(
secret_name=secret_name, primary_secret_name=primary_secret_name
)

native: Final = resolve_native_provider_reader(self, "aws_secret_manager")
if native is not None:
return native.sync_read_secret(secret_name, optional_params, timeout)

endpoint_url, headers, body = self._prepare_request(
action="GetSecretValue",
secret_name=secret_name,
Expand Down
28 changes: 0 additions & 28 deletions litellm/secret_managers/cyberark_secret_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
get_async_httpx_client,
httpxSpecialProvider,
)
from litellm.rust_bridge.secret_manager import resolve_native_provider_reader, resolve_native_provider_writer
from litellm.types.secret_managers.main import KeyManagementSystem

from .base_secret_manager import BaseSecretManager, raise_if_unsafe_secret_name
Expand Down Expand Up @@ -199,10 +198,6 @@ async def async_read_secret(
Returns:
Optional[str]: The secret value if found, None otherwise
"""
native: Final = resolve_native_provider_reader(self, "cyberark")
if native is not None:
return await native.async_read_secret(secret_name, optional_params, timeout)

# Check cache first
if self.cache.get_cache(secret_name) is not None:
return self.cache.get_cache(secret_name)
Expand Down Expand Up @@ -249,10 +244,6 @@ def sync_read_secret(
Returns:
Optional[str]: The secret value if found, None otherwise
"""
native: Final = resolve_native_provider_reader(self, "cyberark")
if native is not None:
return native.sync_read_secret(secret_name, optional_params, timeout)

# Check cache first
if self.cache.get_cache(secret_name) is not None:
return self.cache.get_cache(secret_name)
Expand Down Expand Up @@ -302,12 +293,6 @@ async def async_write_secret(
Returns:
dict: Response containing status and details of the operation
"""
native: Final = resolve_native_provider_writer(self, "cyberark")
if native is not None:
return await native.async_write_secret(
secret_name, secret_value, description, optional_params, timeout, tags
)

async_client: Final = get_async_httpx_client(
llm_provider=httpxSpecialProvider.SecretManager,
params={"ssl_verify": self.ssl_verify},
Expand Down Expand Up @@ -353,10 +338,6 @@ async def async_delete_secret(
Returns:
dict: Response indicating operation not supported
"""
native: Final = resolve_native_provider_writer(self, "cyberark")
if native is not None:
return await native.async_delete_secret(secret_name, recovery_window_in_days, optional_params, timeout)

verbose_logger.warning(
"CyberArk Conjur does not support direct secret deletion. Secrets must be removed through policy updates."
)
Expand All @@ -377,15 +358,6 @@ async def async_rotate_secret(
optional_params: dict | None = None,
timeout: float | httpx.Timeout | None = None,
) -> dict:
native: Final = resolve_native_provider_writer(self, "cyberark")
if native is not None:
return await native.async_rotate_secret(
current_secret_name,
new_secret_name,
new_secret_value,
optional_params,
timeout,
)
return await super().async_rotate_secret(
current_secret_name,
new_secret_name,
Expand Down
30 changes: 0 additions & 30 deletions litellm/secret_managers/dispatch.py

This file was deleted.

Loading
Loading