mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-17 18:48:36 +00:00
chore(proxy): default sensitive routes to auth
This commit is contained in:
+1
-1
@@ -166,7 +166,7 @@ langfuse_default_tags: Optional[List[str]] = None
|
||||
langsmith_batch_size: Optional[int] = None
|
||||
prometheus_initialize_budget_metrics: Optional[bool] = False
|
||||
prometheus_latency_buckets: Optional[List[float]] = None
|
||||
require_auth_for_metrics_endpoint: Optional[bool] = False
|
||||
require_auth_for_metrics_endpoint: Optional[bool] = True
|
||||
argilla_batch_size: Optional[int] = None
|
||||
datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload.
|
||||
gcs_pub_sub_use_v1: Optional[bool] = (
|
||||
|
||||
@@ -613,13 +613,8 @@ class LiteLLMRoutes(enum.Enum):
|
||||
"/health/liveness",
|
||||
"/test",
|
||||
"/config/yaml",
|
||||
"/metrics",
|
||||
"/litellm/.well-known/litellm-ui-config",
|
||||
"/.well-known/litellm-ui-config",
|
||||
"/public/model_hub",
|
||||
"/public/agent_hub",
|
||||
"/public/mcp_hub",
|
||||
"/public/skill_hub",
|
||||
"/public/litellm_model_cost_map",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -108,10 +108,12 @@ def _route_requires_auth_despite_public(
|
||||
) -> bool:
|
||||
normalized_route = _normalize_public_auth_route(route)
|
||||
if normalized_route == "/metrics":
|
||||
return litellm.require_auth_for_metrics_endpoint is True
|
||||
return litellm.require_auth_for_metrics_endpoint is not False
|
||||
|
||||
if normalized_route in _PUBLIC_AI_HUB_ROUTES:
|
||||
return (general_settings or {}).get("require_auth_for_public_ai_hub") is True
|
||||
return (general_settings or {}).get(
|
||||
"require_auth_for_public_ai_hub", True
|
||||
) is True
|
||||
|
||||
return False
|
||||
|
||||
@@ -1671,7 +1673,7 @@ async def _run_centralized_common_checks(
|
||||
user_custom_auth,
|
||||
)
|
||||
|
||||
# Public routes (e.g. /health/liveness, /metrics) are exempt from
|
||||
# Public routes (e.g. /health/liveness) are exempt from
|
||||
# auth in the builder — the wrapper must not retroactively apply
|
||||
# authz on top, or k8s readiness probes and other unauthenticated
|
||||
# callers get 401.
|
||||
|
||||
@@ -19,13 +19,13 @@ class PrometheusAuthMiddleware:
|
||||
"""
|
||||
Middleware to authenticate requests to the metrics endpoint.
|
||||
|
||||
By default, auth is not run on the metrics endpoint.
|
||||
By default, auth is run on the metrics endpoint.
|
||||
|
||||
Enabled by setting the following in proxy_config.yaml:
|
||||
To allow unauthenticated metrics in proxy_config.yaml:
|
||||
|
||||
```yaml
|
||||
litellm_settings:
|
||||
require_auth_for_metrics_endpoint: true
|
||||
require_auth_for_metrics_endpoint: false
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -38,8 +38,8 @@ class PrometheusAuthMiddleware:
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
|
||||
# Only run auth if configured to do so
|
||||
if litellm.require_auth_for_metrics_endpoint is True:
|
||||
# Run auth by default; allow legacy public metrics only when explicitly disabled.
|
||||
if litellm.require_auth_for_metrics_endpoint is not False:
|
||||
# Construct Request only when auth is actually needed
|
||||
request = Request(scope, receive)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ router = APIRouter()
|
||||
async def public_ai_hub_auth_dependency(request: Request) -> UserAPIKeyAuth:
|
||||
from litellm.proxy.proxy_server import general_settings
|
||||
|
||||
if (general_settings or {}).get("require_auth_for_public_ai_hub") is True:
|
||||
if (general_settings or {}).get("require_auth_for_public_ai_hub", True) is True:
|
||||
return await user_api_key_auth(
|
||||
request=request,
|
||||
api_key=request.headers.get(SpecialHeaders.openai_authorization.value)
|
||||
|
||||
@@ -95,7 +95,7 @@ class UISettings(BaseModel):
|
||||
)
|
||||
|
||||
require_auth_for_public_ai_hub: bool = Field(
|
||||
default=False,
|
||||
default=True,
|
||||
description="If true, requires authentication for accessing the public AI Hub.",
|
||||
)
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ def test_route_requires_auth_despite_public_for_metrics(monkeypatch):
|
||||
def test_route_requires_auth_despite_public_for_public_ai_hub():
|
||||
settings = {"require_auth_for_public_ai_hub": True}
|
||||
|
||||
assert _route_requires_auth_despite_public("/public/model_hub", {}) is True
|
||||
assert _route_requires_auth_despite_public("/public/model_hub", settings) is True
|
||||
assert _route_requires_auth_despite_public("/public/model_hub/", settings) is True
|
||||
assert (
|
||||
@@ -74,7 +75,12 @@ def test_route_requires_auth_despite_public_for_public_ai_hub():
|
||||
assert _route_requires_auth_despite_public("/public/mcp_hub", settings) is True
|
||||
assert _route_requires_auth_despite_public("/public/skill_hub", settings) is True
|
||||
|
||||
assert _route_requires_auth_despite_public("/public/model_hub", {}) is False
|
||||
assert (
|
||||
_route_requires_auth_despite_public(
|
||||
"/public/model_hub", {"require_auth_for_public_ai_hub": False}
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_valid_auth_metrics(app_with_middleware, monkeypatch):
|
||||
Test that a request to /metrics (and /metrics/) with valid auth headers passes.
|
||||
"""
|
||||
# Enable auth on metrics endpoints.
|
||||
litellm.require_auth_for_metrics_endpoint = True
|
||||
monkeypatch.setattr(litellm, "require_auth_for_metrics_endpoint", True)
|
||||
# Patch the auth function to simulate a valid authentication.
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.middleware.prometheus_auth_middleware.user_api_key_auth",
|
||||
@@ -92,7 +92,7 @@ def test_invalid_auth_metrics(app_with_middleware, monkeypatch):
|
||||
"""
|
||||
Test that a request to /metrics with invalid auth headers fails with a 401.
|
||||
"""
|
||||
litellm.require_auth_for_metrics_endpoint = True
|
||||
monkeypatch.setattr(litellm, "require_auth_for_metrics_endpoint", True)
|
||||
# Patch the auth function to simulate a failed authentication.
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.middleware.prometheus_auth_middleware.user_api_key_auth",
|
||||
@@ -126,12 +126,29 @@ def test_metrics_auth_uses_real_auth_when_route_is_public(
|
||||
assert "Unauthorized access to metrics endpoint" in response.text
|
||||
|
||||
|
||||
def test_metrics_auth_is_required_by_default(app_with_middleware, monkeypatch):
|
||||
"""
|
||||
Metrics should require auth unless explicitly configured as public.
|
||||
"""
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.middleware.prometheus_auth_middleware.user_api_key_auth",
|
||||
fake_invalid_auth,
|
||||
)
|
||||
|
||||
client = TestClient(app_with_middleware)
|
||||
|
||||
response = client.get("/metrics")
|
||||
|
||||
assert response.status_code == 401, response.text
|
||||
assert "Unauthorized access to metrics endpoint" in response.text
|
||||
|
||||
|
||||
def test_no_auth_metrics_when_disabled(app_with_middleware, monkeypatch):
|
||||
"""
|
||||
Test that when require_auth_for_metrics_endpoint is False, requests to /metrics
|
||||
bypass the auth check.
|
||||
"""
|
||||
litellm.require_auth_for_metrics_endpoint = False
|
||||
monkeypatch.setattr(litellm, "require_auth_for_metrics_endpoint", False)
|
||||
|
||||
# To ensure auth is not run, patch the auth function with one that will raise if called.
|
||||
def should_not_be_called(*args, **kwargs):
|
||||
@@ -148,11 +165,11 @@ def test_no_auth_metrics_when_disabled(app_with_middleware, monkeypatch):
|
||||
assert response.json() == {"msg": "metrics OK"}
|
||||
|
||||
|
||||
def test_non_metrics_requests_pass_through(app_with_middleware):
|
||||
def test_non_metrics_requests_pass_through(app_with_middleware, monkeypatch):
|
||||
"""
|
||||
Test that non-metrics endpoints pass through the middleware unaffected.
|
||||
"""
|
||||
litellm.require_auth_for_metrics_endpoint = True
|
||||
monkeypatch.setattr(litellm, "require_auth_for_metrics_endpoint", True)
|
||||
|
||||
client = TestClient(app_with_middleware)
|
||||
|
||||
@@ -170,7 +187,7 @@ def test_non_metrics_requests_dont_trigger_auth(app_with_middleware, monkeypatch
|
||||
Test that non-metrics requests never trigger auth, even when auth is enabled
|
||||
and the auth function would reject the request.
|
||||
"""
|
||||
litellm.require_auth_for_metrics_endpoint = True
|
||||
monkeypatch.setattr(litellm, "require_auth_for_metrics_endpoint", True)
|
||||
|
||||
def should_not_be_called(*args, **kwargs):
|
||||
raise Exception("Auth should not be called for non-metrics requests")
|
||||
|
||||
@@ -12,7 +12,7 @@ from fastapi.testclient import TestClient
|
||||
from starlette.datastructures import URL
|
||||
from starlette.requests import Request
|
||||
|
||||
from litellm.proxy._types import ProxyException
|
||||
from litellm.proxy._types import LitellmUserRoles, ProxyException
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.public_endpoints import router
|
||||
from litellm.proxy.public_endpoints.public_endpoints import (
|
||||
@@ -97,7 +97,7 @@ def test_get_litellm_model_cost_map_returns_cost_map():
|
||||
)
|
||||
|
||||
|
||||
def test_public_ai_hub_info_stays_public_by_default(monkeypatch):
|
||||
def test_public_ai_hub_info_requires_auth_by_default(monkeypatch):
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
client = TestClient(app)
|
||||
@@ -105,16 +105,31 @@ def test_public_ai_hub_info_stays_public_by_default(monkeypatch):
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master")
|
||||
|
||||
with pytest.raises(ProxyException):
|
||||
client.get("/public/model_hub/info")
|
||||
|
||||
|
||||
def test_public_ai_hub_info_can_be_explicitly_public(monkeypatch):
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
client = TestClient(app)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{"require_auth_for_public_ai_hub": False},
|
||||
)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master")
|
||||
|
||||
response = client.get("/public/model_hub/info")
|
||||
|
||||
assert response.status_code == 200, response.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_public_ai_hub_info_requires_auth_when_configured(monkeypatch):
|
||||
async def test_public_ai_hub_info_requires_auth_by_default_dependency(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{"require_auth_for_public_ai_hub": True},
|
||||
{},
|
||||
)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master")
|
||||
request = Request(
|
||||
@@ -131,6 +146,27 @@ async def test_public_ai_hub_info_requires_auth_when_configured(monkeypatch):
|
||||
await public_ai_hub_auth_dependency(request)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_public_ai_hub_info_skips_auth_when_explicitly_disabled(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{"require_auth_for_public_ai_hub": False},
|
||||
)
|
||||
request = Request(
|
||||
scope={
|
||||
"type": "http",
|
||||
"method": "GET",
|
||||
"path": "/public/model_hub/info",
|
||||
"headers": [],
|
||||
}
|
||||
)
|
||||
request._url = URL(url="/public/model_hub/info")
|
||||
|
||||
auth = await public_ai_hub_auth_dependency(request)
|
||||
|
||||
assert auth.user_role == LitellmUserRoles.INTERNAL_USER_VIEW_ONLY
|
||||
|
||||
|
||||
def test_watsonx_provider_fields():
|
||||
"""Test that Watsonx provider has all required credential fields including multiple auth options."""
|
||||
app = FastAPI()
|
||||
|
||||
Reference in New Issue
Block a user