chore(proxy): default sensitive routes to auth

This commit is contained in:
user
2026-04-30 12:22:48 -07:00
parent b31182f62a
commit 35bbca60b0
9 changed files with 83 additions and 27 deletions
+1 -1
View File
@@ -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] = (
-5
View File
@@ -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",
]
)
+5 -3
View File
@@ -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()