This commit is contained in:
devin-ai-integration[bot] 2026-08-27 19:49:43 -05:00 committed by GitHub
commit 3a23fe8bfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 17 deletions

View file

@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Annotated, Any, Final, cast
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request, Response, WebSocket
from fastapi.responses import StreamingResponse
from google.auth.exceptions import GoogleAuthError, TransportError
from starlette.websockets import WebSocketState
import litellm
@ -1924,23 +1925,37 @@ async def _prepare_vertex_auth_headers(
else:
raise ValueError("No vertex credentials found")
_auth_header, vertex_project = await vertex_llm_base._ensure_access_token_async(
credentials=vertex_credentials_str,
project_id=vertex_project,
custom_llm_provider="vertex_ai_beta",
)
try:
_auth_header, vertex_project = await vertex_llm_base._ensure_access_token_async(
credentials=vertex_credentials_str,
project_id=vertex_project,
custom_llm_provider="vertex_ai_beta",
)
auth_header, _ = vertex_llm_base._get_token_and_url(
model="",
auth_header=_auth_header,
gemini_api_key=None,
vertex_credentials=vertex_credentials_str,
vertex_project=vertex_project,
vertex_location=vertex_location,
stream=False,
custom_llm_provider="vertex_ai_beta",
api_base="",
)
auth_header, _ = vertex_llm_base._get_token_and_url(
model="",
auth_header=_auth_header,
gemini_api_key=None,
vertex_credentials=vertex_credentials_str,
vertex_project=vertex_project,
vertex_location=vertex_location,
stream=False,
custom_llm_provider="vertex_ai_beta",
api_base="",
)
except TransportError:
raise
except (GoogleAuthError, ValueError) as e:
raise ProxyException(
message=(
f"Failed to get a Google access token for project={vertex_project} + location={vertex_location}: {e}. "
"Set `vertex_credentials` on the deployment marked `use_in_pass_through: true`, or on "
"`default_vertex_config` / `DEFAULT_GOOGLE_APPLICATION_CREDENTIALS`."
),
type=ProxyErrorTypes.auth_error,
param="vertex_credentials",
code=401,
) from e
# Use allowlist approach - only forward specific safe headers
headers = get_vertex_ai_allowed_incoming_headers(request)

View file

@ -1,12 +1,12 @@
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from google.auth.exceptions import DefaultCredentialsError, TransportError
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
_base_vertex_proxy_route,
)
from litellm.types.router import DeploymentTypedDict
@pytest.mark.asyncio
@ -348,6 +348,95 @@ async def test_vertex_passthrough_forwards_anthropic_beta_header():
assert headers_passed_through is False
@pytest.mark.asyncio
async def test_vertex_passthrough_credential_failure_raises_auth_error():
"""
A project/location configured for pass-through without usable Google credentials used to
bubble google.auth errors out of the route, so callers only saw an opaque 500
{"error": {"message": "Internal server error"}} with no hint about what to configure.
"""
from starlette.datastructures import Headers
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
from litellm.proxy._types import ProxyErrorTypes, ProxyException
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
_prepare_vertex_auth_headers,
)
mock_request = MagicMock()
mock_request.headers = Headers({"authorization": "Bearer sk-litellm-key"})
mock_request.state._cached_headers = None
mock_vertex_credentials = MagicMock()
mock_vertex_credentials.vertex_project = "test-project"
mock_vertex_credentials.vertex_location = "us-central1"
mock_vertex_credentials.vertex_credentials = None
with patch.object(
VertexBase,
"_ensure_access_token_async",
new_callable=AsyncMock,
side_effect=DefaultCredentialsError("Your default credentials were not found"),
):
with pytest.raises(ProxyException) as exc_info:
await _prepare_vertex_auth_headers(
request=mock_request,
vertex_credentials=mock_vertex_credentials,
router_credentials=None,
vertex_project="test-project",
vertex_location="us-central1",
base_target_url="https://us-central1-aiplatform.googleapis.com",
get_vertex_pass_through_handler=MagicMock(),
)
assert exc_info.value.code == "401"
assert exc_info.value.type == ProxyErrorTypes.auth_error.value
assert "test-project" in exc_info.value.message
assert "us-central1" in exc_info.value.message
assert "vertex_credentials" in exc_info.value.message
assert "Your default credentials were not found" in exc_info.value.message
@pytest.mark.asyncio
async def test_vertex_passthrough_transport_failure_is_not_reported_as_auth_error():
"""
Reaching Google's token endpoint can fail for reasons the operator cannot fix with
credentials, so those must not be relabelled as authentication errors.
"""
from starlette.datastructures import Headers
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
_prepare_vertex_auth_headers,
)
mock_request = MagicMock()
mock_request.headers = Headers({"authorization": "Bearer sk-litellm-key"})
mock_request.state._cached_headers = None
mock_vertex_credentials = MagicMock()
mock_vertex_credentials.vertex_project = "test-project"
mock_vertex_credentials.vertex_location = "us-central1"
mock_vertex_credentials.vertex_credentials = None
with patch.object(
VertexBase,
"_ensure_access_token_async",
new_callable=AsyncMock,
side_effect=TransportError("connection reset by peer"),
):
with pytest.raises(TransportError):
await _prepare_vertex_auth_headers(
request=mock_request,
vertex_credentials=mock_vertex_credentials,
router_credentials=None,
vertex_project="test-project",
vertex_location="us-central1",
base_target_url="https://us-central1-aiplatform.googleapis.com",
get_vertex_pass_through_handler=MagicMock(),
)
@pytest.mark.asyncio
async def test_vertex_passthrough_does_not_forward_litellm_auth_token():
"""