Skip to content

Commit c09643a

Browse files
committed
fix(redis): never hand a data-node credential provider to the Sentinel monitors
The monitors are separate servers with their own password, so the data node's Entra or IAM token has no standing there. Dropping the provider only when a Sentinel password was configured left it in place for unauthenticated monitors, where redis-py sends it as an AUTH the monitor rejects and async Sentinel discovery fails.
1 parent 09b391d commit c09643a

2 files changed

Lines changed: 20 additions & 11 deletions

File tree

litellm/_redis.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,11 @@ def _init_redis_sentinel(redis_kwargs) -> redis.Redis:
552552

553553

554554
def _sentinel_auth_kwargs(connection_kwargs: dict, sentinel_password: str | None) -> dict:
555-
"""The Sentinel monitors are separate servers with their own password, and redis-py refuses a
556-
password passed alongside a credential provider, so the data node's provider stays behind once
557-
a Sentinel password is configured."""
558-
superseded: Final = frozenset({"credential_provider"}) if sentinel_password else frozenset()
559-
kept: Final = ((k, v) for k, v in connection_kwargs.items() if k not in superseded)
555+
"""The Sentinel monitors are separate servers that authenticate with their own password, so the
556+
data node's credential provider never belongs on them: leaving it there makes redis-py send the
557+
data node's token to a monitor, which fails whether the monitor is unauthenticated or has its
558+
own password."""
559+
kept: Final = ((k, v) for k, v in connection_kwargs.items() if k != "credential_provider")
560560
return dict(kept, password=sentinel_password)
561561

562562

tests/test_litellm/test_redis.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,13 +1034,19 @@ async def connect(connection):
10341034
],
10351035
ids=["azure_ad", "gcp_iam"],
10361036
)
1037-
def test_async_sentinel_keeps_the_credential_provider_off_the_monitors(markers, provider_cls):
1038-
"""The Sentinel monitors authenticate with their own password, and redis-py refuses a password
1039-
passed alongside a credential provider, so only the data node may carry the provider.
1037+
@pytest.mark.parametrize(
1038+
"sentinel_password",
1039+
[None, "sentinel-secret"],
1040+
ids=["unauthenticated_monitors", "password_protected_monitors"],
1041+
)
1042+
def test_async_sentinel_keeps_the_credential_provider_off_the_monitors(markers, provider_cls, sentinel_password):
1043+
"""The Sentinel monitors are separate servers with their own password, so the data node's token
1044+
never belongs on them: redis-py refuses it next to a Sentinel password, and sends it to an
1045+
unauthenticated monitor as an AUTH the monitor rejects.
10401046
"""
10411047
redis_kwargs = {
10421048
"sentinel_nodes": [("sentinel-1", 26379)],
1043-
"sentinel_password": "sentinel-secret",
1049+
"sentinel_password": sentinel_password,
10441050
"service_name": "mymaster",
10451051
"redis_connect_func": SimpleNamespace(**markers),
10461052
}
@@ -1050,9 +1056,12 @@ def test_async_sentinel_keeps_the_credential_provider_off_the_monitors(markers,
10501056
get_redis_async_client()
10511057

10521058
sentinel_kwargs = mock_sentinel_cls.call_args[1]["sentinel_kwargs"]
1053-
assert sentinel_kwargs["password"] == "sentinel-secret"
1059+
assert sentinel_kwargs["password"] == sentinel_password
10541060
assert "credential_provider" not in sentinel_kwargs
1055-
async_redis.Connection(host="sentinel-1", port=26379, **sentinel_kwargs)
1061+
1062+
monitor_connection = async_redis.Connection(host="sentinel-1", port=26379, **sentinel_kwargs)
1063+
assert monitor_connection.credential_provider is None
1064+
assert bool(monitor_connection.username or monitor_connection.password) is bool(sentinel_password)
10561065

10571066
master_kwargs = mock_sentinel_cls.return_value.master_for.call_args[1]
10581067
assert isinstance(master_kwargs["credential_provider"], provider_cls)

0 commit comments

Comments
 (0)