Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/auth/trusted_proxy_utils.py: 39%
31 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1from typing import Any, Final
3from fastapi import Request
5from litellm._logging import verbose_proxy_logger
6from litellm.proxy.auth.network import (
7 ip_in_networks,
8 normalize_cidr_ranges,
9 parse_trusted_proxy_ranges,
10)
12TRUSTED_PROXY_RANGES_KEY: Final = "trusted_proxy_ranges"
15def _get_proxy_general_settings() -> dict[str, Any]:
16 try:
17 from litellm.proxy.proxy_server import general_settings
19 return general_settings or {}
20 except ImportError:
21 return {}
24def get_trusted_proxy_cidrs(
25 general_settings: dict[str, Any] | None = None,
26) -> list[str]:
27 """Operator-configured trusted reverse-proxy CIDRs, normalized to strings.
29 Empty when none are configured, in which case X-Forwarded-For must not be
30 trusted and only the direct peer is authoritative.
31 """
32 if general_settings is None: 32 ↛ 34line 32 didn't jump to line 34 because the condition on line 32 was always true
33 general_settings = _get_proxy_general_settings()
34 return normalize_cidr_ranges(
35 general_settings.get(TRUSTED_PROXY_RANGES_KEY),
36 setting_name=TRUSTED_PROXY_RANGES_KEY,
37 )
40def _get_direct_client_ip(request: Request) -> str | None:
41 client: Final = getattr(request, "client", None)
42 client_host: Final = getattr(client, "host", None)
43 if isinstance(client_host, str):
44 return client_host
45 return None
48def require_trusted_proxy_request(
49 *,
50 request: Request,
51 general_settings: dict[str, Any] | None = None,
52 feature_name: str,
53 setting_name: str = TRUSTED_PROXY_RANGES_KEY,
54) -> None:
55 """
56 Fail closed unless the direct TCP peer is one of the configured
57 trusted reverse proxies.
59 Header-based auth paths must validate the direct peer, not
60 X-Forwarded-For, because the direct peer is the actor supplying the
61 identity headers.
62 """
63 if general_settings is None:
64 general_settings = _get_proxy_general_settings()
66 trusted_networks: Final = parse_trusted_proxy_ranges(general_settings.get(setting_name), setting_name=setting_name)
67 if not trusted_networks:
68 raise ValueError(
69 f"{feature_name} requires general_settings.{setting_name} before "
70 "trusting identity headers from an upstream proxy."
71 )
73 direct_client_ip: Final = _get_direct_client_ip(request)
74 if not ip_in_networks(direct_client_ip, trusted_networks):
75 verbose_proxy_logger.warning(
76 "%s rejected identity headers from untrusted direct client IP %r",
77 feature_name,
78 direct_client_ip,
79 )
80 raise ValueError(
81 f"{feature_name} only accepts identity headers from configured "
82 f"trusted proxy ranges. Direct client IP {direct_client_ip!r} "
83 "is not trusted."
84 )