mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(auth): exempt auth=false pass-through endpoints from centralized gate
User-configured pass-through endpoints with ``auth: false`` are explicitly unauthenticated — the builder short-circuits at check_api_key_for_custom_headers_or_pass_through_endpoints and returns a fresh empty UserAPIKeyAuth() without an api_key, user_id, or role. Pre-refactor, that empty token never reached common_checks. After the centralization, it does — and common_checks rejects it as admin-only, breaking every Langfuse / custom unauthenticated pass-through. This is the same regression class as the public-routes one: a builder fast-path whose return value cannot survive common_checks. Honor the same contract here — when the matched endpoint config has auth != True, skip the centralized gate. auth=True endpoints still run the full gate (covered by a companion test). No security regression: ``auth: false`` is the operator's explicit opt-out from LiteLLM auth on this path. The original commit closed seven authenticated bypasses; this exemption applies only to a path the operator has already declared unauthenticated.
This commit is contained in:
parent
3560823196
commit
7a91d80f9a
2 changed files with 106 additions and 0 deletions
|
|
@ -1638,6 +1638,22 @@ async def _run_centralized_common_checks(
|
|||
):
|
||||
return
|
||||
|
||||
# User-configured pass-through endpoints with ``auth: false`` are
|
||||
# explicitly unauthenticated — the builder returns an empty
|
||||
# UserAPIKeyAuth() and the request is forwarded as-is. Running
|
||||
# common_checks on the empty token would reject the request as
|
||||
# admin-only. The "auth" flag on the endpoint config is the
|
||||
# contract; honor it.
|
||||
pass_through_endpoints = general_settings.get("pass_through_endpoints", None)
|
||||
if pass_through_endpoints is not None:
|
||||
for endpoint in pass_through_endpoints:
|
||||
if (
|
||||
isinstance(endpoint, dict)
|
||||
and endpoint.get("path", "") == route
|
||||
and endpoint.get("auth") is not True
|
||||
):
|
||||
return
|
||||
|
||||
# No-auth dev mode: master_key unset AND no JWT/OAuth2 auth
|
||||
# configured. The builder returns an INTERNAL_USER token for any
|
||||
# api_key; the proxy is unauthenticated by configuration.
|
||||
|
|
|
|||
|
|
@ -2090,6 +2090,96 @@ async def test_centralized_common_checks_skips_public_routes():
|
|||
setattr(_proxy_server_mod, k, v)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_centralized_common_checks_skips_passthrough_endpoint_with_auth_false():
|
||||
"""Regression: user-configured pass-through endpoints with
|
||||
``auth: false`` are explicitly unauthenticated. The builder
|
||||
short-circuits and returns a fresh empty UserAPIKeyAuth(); running
|
||||
common_checks on that empty token would reject the request as
|
||||
admin-only. The "auth" flag on the endpoint config is the contract
|
||||
— when it's anything other than True, skip the gate."""
|
||||
import litellm.proxy.proxy_server as _proxy_server_mod
|
||||
from fastapi import Request
|
||||
from starlette.datastructures import URL
|
||||
|
||||
token = UserAPIKeyAuth()
|
||||
request = Request(scope={"type": "http"})
|
||||
request._url = URL(url="/api/public/ingestion")
|
||||
|
||||
attrs = _proxy_attrs_for_centralized_checks(user_custom_auth=None)
|
||||
attrs["general_settings"] = {
|
||||
"pass_through_endpoints": [
|
||||
{
|
||||
"path": "/api/public/ingestion",
|
||||
"target": "https://us.cloud.langfuse.com/api/public/ingestion",
|
||||
"auth": False,
|
||||
}
|
||||
]
|
||||
}
|
||||
originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs}
|
||||
try:
|
||||
for k, v in attrs.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
with patch(
|
||||
"litellm.proxy.auth.user_api_key_auth.common_checks",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_checks:
|
||||
await _run_centralized_common_checks(
|
||||
user_api_key_auth_obj=token,
|
||||
request=request,
|
||||
request_data={},
|
||||
route="/api/public/ingestion",
|
||||
)
|
||||
mock_checks.assert_not_awaited()
|
||||
finally:
|
||||
for k, v in originals.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_centralized_common_checks_runs_for_passthrough_endpoint_with_auth_true():
|
||||
"""Companion to the auth=False test: when a pass-through endpoint
|
||||
has ``auth: true``, the builder runs full authentication and the
|
||||
centralized gate must run too. Skipping based on path-match alone
|
||||
would re-open every ``auth: true`` pass-through endpoint."""
|
||||
import litellm.proxy.proxy_server as _proxy_server_mod
|
||||
from fastapi import Request
|
||||
from starlette.datastructures import URL
|
||||
|
||||
token = UserAPIKeyAuth(api_key="sk-test", user_id="u1")
|
||||
request = Request(scope={"type": "http"})
|
||||
request._url = URL(url="/api/public/ingestion")
|
||||
|
||||
attrs = _proxy_attrs_for_centralized_checks(user_custom_auth=None)
|
||||
attrs["general_settings"] = {
|
||||
"pass_through_endpoints": [
|
||||
{
|
||||
"path": "/api/public/ingestion",
|
||||
"target": "https://us.cloud.langfuse.com/api/public/ingestion",
|
||||
"auth": True,
|
||||
}
|
||||
]
|
||||
}
|
||||
originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs}
|
||||
try:
|
||||
for k, v in attrs.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
with patch(
|
||||
"litellm.proxy.auth.user_api_key_auth.common_checks",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_checks:
|
||||
await _run_centralized_common_checks(
|
||||
user_api_key_auth_obj=token,
|
||||
request=request,
|
||||
request_data={},
|
||||
route="/api/public/ingestion",
|
||||
)
|
||||
mock_checks.assert_awaited_once()
|
||||
finally:
|
||||
for k, v in originals.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_centralized_common_checks_master_key_admin_overrides_db_user_role():
|
||||
"""Regression: master_key tokens have user_id=litellm_proxy_admin_name
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue