fix(oidc): guard naive STS expiration and wrap AWS ClientError as ValueError

This commit is contained in:
Devin AI 2026-07-14 17:41:17 +00:00
parent a983177a4f
commit af6ebf07ea
2 changed files with 61 additions and 5 deletions

View file

@ -126,7 +126,8 @@ def _get_aws_sts_client(region: str) -> "STSClient":
def _aws_oidc_cache_ttl(expiration: datetime | None) -> int | None:
if expiration is None:
return None
remaining = int((expiration - datetime.now(timezone.utc)).total_seconds()) - 60
aware_expiration = expiration if expiration.tzinfo is not None else expiration.replace(tzinfo=timezone.utc)
remaining = int((aware_expiration - datetime.now(timezone.utc)).total_seconds()) - 60
return remaining if remaining > 0 else None
@ -145,12 +146,17 @@ def _get_aws_oidc_token(
if isinstance(cached_token, str):
return cached_token
from botocore.exceptions import ClientError
build_client = sts_client_factory or _get_aws_sts_client
sts_client = build_client(_resolve_aws_region())
response = sts_client.get_web_identity_token(
Audience=[oidc_aud],
SigningAlgorithm="RS256",
)
try:
response = sts_client.get_web_identity_token(
Audience=[oidc_aud],
SigningAlgorithm="RS256",
)
except ClientError as e:
raise ValueError(f"AWS OIDC provider failed: {e}") from e
oidc_token = response["WebIdentityToken"]
ttl = _aws_oidc_cache_ttl(response.get("Expiration"))

View file

@ -339,6 +339,56 @@ def test_oidc_aws_no_expiration_skips_caching():
mock_cache.set_cache.assert_not_called()
def test_oidc_aws_handles_naive_expiration():
"""A naive Expiration from boto3 must not crash ttl computation."""
from datetime import datetime, timedelta, timezone
from litellm.secret_managers.main import _get_aws_oidc_token
naive_expiration = (datetime.now(timezone.utc) + timedelta(seconds=3600)).replace(tzinfo=None)
fake_client = _FakeSTSClient(token="aws_jwt", expiration=naive_expiration)
mock_cache = Mock()
mock_cache.get_cache.return_value = None
with patch("litellm.secret_managers.main.oidc_cache", mock_cache):
with patch("litellm.secret_managers.main._resolve_aws_region", return_value="us-east-1"):
token = _get_aws_oidc_token(
oidc_aud="aud",
cache_key="oidc/aws/aud",
sts_client_factory=Mock(return_value=fake_client),
)
assert token == "aws_jwt"
(cache_kwargs,) = mock_cache.set_cache.call_args_list
assert 3500 <= cache_kwargs.kwargs["ttl"] <= 3540
def test_oidc_aws_wraps_client_error():
"""A boto3 ClientError is surfaced as a descriptive ValueError."""
from botocore.exceptions import ClientError
from litellm.secret_managers.main import _get_aws_oidc_token
class _RaisingSTSClient:
def get_web_identity_token(self, **kwargs):
raise ClientError(
{"Error": {"Code": "AccessDenied", "Message": "not authorized"}},
"GetWebIdentityToken",
)
mock_cache = Mock()
mock_cache.get_cache.return_value = None
with patch("litellm.secret_managers.main.oidc_cache", mock_cache):
with patch("litellm.secret_managers.main._resolve_aws_region", return_value="us-east-1"):
with pytest.raises(ValueError, match="AWS OIDC provider failed"):
_get_aws_oidc_token(
oidc_aud="aud",
cache_key="oidc/aws/aud",
sts_client_factory=Mock(return_value=_RaisingSTSClient()),
)
def test_get_aws_sts_client_pins_regional_endpoint():
from litellm.secret_managers.main import _get_aws_sts_client