mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
fix(proxy): break ties between equivalent login limit overrides deterministically
Two spellings of one network share a prefix length, so the exemption wins the tie, then the higher limit, regardless of mapping order Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
65765d6550
commit
cfdf4fa5dc
4 changed files with 28 additions and 5 deletions
|
|
@ -2759,7 +2759,7 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
|
|||
)
|
||||
max_failed_login_attempts_per_source_overrides: dict[str, int] | None = Field(
|
||||
None,
|
||||
description="Per-address overrides of `max_failed_login_attempts_per_source`, keyed by IP address or CIDR range, e.g. {'1.2.3.4': 200, '5.6.0.0/24': 500}. The most specific matching range wins, and the per-username allowance for that address follows as half the override. A value of 0 exempts the address from both limits. Set under `general_settings` in config.yaml",
|
||||
description="Per-address overrides of `max_failed_login_attempts_per_source`, keyed by IP address or CIDR range, e.g. {'1.2.3.4': 200, '5.6.0.0/24': 500}. The most specific matching range wins (between equivalent keys such as '1.2.3.4' and '1.2.3.4/32', an exemption wins, then the higher limit), and the per-username allowance for that address follows as half the override. A value of 0 exempts the address from both limits. Set under `general_settings` in config.yaml",
|
||||
)
|
||||
failed_login_window_seconds: int | None = Field(
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -186,10 +186,16 @@ def _parse_network(raw_range: str) -> _Network | None:
|
|||
return None
|
||||
|
||||
|
||||
def _precedence(network: _Network, limit: int) -> tuple[int, bool, int]:
|
||||
"""Sort key for competing overrides: the longest prefix wins, then an exemption, then the higher limit."""
|
||||
return (network.prefixlen, limit == EXEMPT, limit)
|
||||
|
||||
|
||||
def _source_limit(settings: Mapping[str, object], client_ip: str) -> int:
|
||||
"""Failure allowance for this address: the most specific configured range containing it, else the default.
|
||||
|
||||
``EXEMPT`` (0) means the operator opted this address out of both limits.
|
||||
``EXEMPT`` (0) means the operator opted this address out of both limits. Between equivalent keys such as
|
||||
``1.2.3.4`` and ``1.2.3.4/32`` an exemption wins, then the higher limit.
|
||||
"""
|
||||
default: Final = _int_setting(settings, SOURCE_LIMIT_KEY, DEFAULT_MAX_FAILED_LOGIN_ATTEMPTS_PER_SOURCE)
|
||||
raw_overrides: Final = settings.get(SOURCE_LIMIT_OVERRIDES_KEY)
|
||||
|
|
@ -206,11 +212,11 @@ def _source_limit(settings: Mapping[str, object], client_ip: str) -> int:
|
|||
if address is None:
|
||||
return default
|
||||
matches: Final = sorted(
|
||||
(network.prefixlen, _override_limit(raw_limit, default))
|
||||
_precedence(network, _override_limit(raw_limit, default))
|
||||
for raw_range, raw_limit in overrides.items()
|
||||
if (network := _parse_network(raw_range)) is not None and address in network
|
||||
)
|
||||
return matches[-1][1] if matches else default
|
||||
return matches[-1][-1] if matches else default
|
||||
|
||||
|
||||
def user_limit_for(source_limit: int) -> int:
|
||||
|
|
|
|||
|
|
@ -1023,6 +1023,23 @@ def test_source_overrides_pick_the_most_specific_matching_range():
|
|||
assert _limit("::ffff:203.0.113.10") == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("overrides", "expected"),
|
||||
[
|
||||
({"203.0.113.7": 0, "203.0.113.7/32": 5}, None),
|
||||
({"203.0.113.7/32": 5, "203.0.113.7": 0}, None),
|
||||
({"203.0.113.0/24": 3, "203.0.113.9/24": 8}, 8),
|
||||
({"203.0.113.9/24": 8, "203.0.113.0/24": 3}, 8),
|
||||
],
|
||||
ids=["exact-then-slash32", "slash32-then-exact", "low-then-high", "high-then-low"],
|
||||
)
|
||||
def test_equivalent_override_keys_resolve_to_the_exemption_then_the_higher_limit(overrides, expected):
|
||||
"""Two spellings of the same network are a config mistake, so precedence must not depend on dict order."""
|
||||
settings = {"trusted_proxy_ranges": ["10.0.0.0/8"], "max_failed_login_attempts_per_source_overrides": overrides}
|
||||
|
||||
assert _throttle_behind_trusted_proxy("203.0.113.7", settings).source_limit == expected
|
||||
|
||||
|
||||
def test_ipv6_sources_are_grouped_by_their_64_bit_prefix():
|
||||
"""A /64 holder has 2^64 addresses; counting each one separately would hand them unlimited fresh buckets."""
|
||||
from litellm.proxy.auth.login_throttle import source_group
|
||||
|
|
|
|||
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -26571,7 +26571,7 @@ export interface components {
|
|||
max_failed_login_attempts_per_source?: number | null;
|
||||
/**
|
||||
* Max Failed Login Attempts Per Source Overrides
|
||||
* @description Per-address overrides of `max_failed_login_attempts_per_source`, keyed by IP address or CIDR range, e.g. {'1.2.3.4': 200, '5.6.0.0/24': 500}. The most specific matching range wins, and the per-username allowance for that address follows as half the override. A value of 0 exempts the address from both limits. Set under `general_settings` in config.yaml
|
||||
* @description Per-address overrides of `max_failed_login_attempts_per_source`, keyed by IP address or CIDR range, e.g. {'1.2.3.4': 200, '5.6.0.0/24': 500}. The most specific matching range wins (between equivalent keys such as '1.2.3.4' and '1.2.3.4/32', an exemption wins, then the higher limit), and the per-username allowance for that address follows as half the override. A value of 0 exempts the address from both limits. Set under `general_settings` in config.yaml
|
||||
*/
|
||||
max_failed_login_attempts_per_source_overrides?: {
|
||||
[key: string]: number;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue