mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-28 06:23:07 +00:00
fix(tls): ignore LITELLM_SSL_CIPHERS override under FIPS mode with a warning
This commit is contained in:
parent
f62150d5fc
commit
93ec12a477
4 changed files with 96 additions and 7 deletions
|
|
@ -364,11 +364,12 @@ MODEL_GROUP_ALIAS_RESOLVED_SCOPE_KEY: Final = "litellm.model_group_alias_resolve
|
|||
REALTIME_SESSION_SUCCESS_LOGGED_KEY: Final = "realtime_session_success_logged"
|
||||
REALTIME_SESSION_FAILURE_LOGGED_KEY: Final = "realtime_session_failure_logged"
|
||||
|
||||
FIPS_SSL_CIPHERS: Final = os.getenv(
|
||||
"LITELLM_SSL_CIPHERS",
|
||||
# Certified GCM-only list: deliberately NOT overridable via LITELLM_SSL_CIPHERS so a
|
||||
# custom override cannot silently weaken the FIPS posture when LITELLM_FIPS_MODE is set.
|
||||
FIPS_SSL_CIPHERS: Final = (
|
||||
"TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256"
|
||||
)
|
||||
DEFAULT_SSL_CIPHERS: Final = os.getenv(
|
||||
"LITELLM_SSL_CIPHERS",
|
||||
|
|
|
|||
|
|
@ -286,7 +286,13 @@ _ssl_context_cache: Final[dict[tuple[str | None, str | None, str | None, str], s
|
|||
def _outbound_ssl_ciphers() -> str:
|
||||
from litellm.proxy.common_utils.fips import is_fips_mode
|
||||
|
||||
return FIPS_SSL_CIPHERS if is_fips_mode() else DEFAULT_SSL_CIPHERS
|
||||
if is_fips_mode():
|
||||
if os.getenv("LITELLM_SSL_CIPHERS"):
|
||||
verbose_logger.warning(
|
||||
"LITELLM_SSL_CIPHERS override ignored in FIPS mode; using FIPS_SSL_CIPHERS"
|
||||
)
|
||||
return FIPS_SSL_CIPHERS
|
||||
return DEFAULT_SSL_CIPHERS
|
||||
|
||||
|
||||
def _create_ssl_context(
|
||||
|
|
|
|||
|
|
@ -366,6 +366,80 @@ def test_get_ssl_configuration_uses_fips_cipher_list_only_in_fips_mode(monkeypat
|
|||
assert get_ssl_configuration() is fips_context
|
||||
|
||||
|
||||
_CHACHA_OVERRIDE: Final = "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA256"
|
||||
|
||||
|
||||
def _simulate_cipher_override_at_import(monkeypatch: pytest.MonkeyPatch, http_handler) -> None:
|
||||
"""Simulate a process that started with LITELLM_SSL_CIPHERS set: litellm.constants
|
||||
re-reads the env at import and http_handler binds those values at import. Reloading
|
||||
http_handler itself would rebind its classes and break isinstance in later tests, so
|
||||
only constants is re-executed and the handler's bindings are swapped explicitly."""
|
||||
from litellm import constants
|
||||
|
||||
importlib.reload(constants)
|
||||
monkeypatch.setattr(http_handler, "DEFAULT_SSL_CIPHERS", constants.DEFAULT_SSL_CIPHERS)
|
||||
monkeypatch.setattr(http_handler, "FIPS_SSL_CIPHERS", constants.FIPS_SSL_CIPHERS)
|
||||
|
||||
|
||||
def _restore_cipher_constants(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from litellm import constants
|
||||
|
||||
monkeypatch.delenv("LITELLM_SSL_CIPHERS", raising=False)
|
||||
importlib.reload(constants)
|
||||
|
||||
|
||||
def test_get_ssl_configuration_ignores_ssl_ciphers_override_in_fips_mode(
|
||||
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
import logging
|
||||
|
||||
from litellm.llms.custom_httpx import http_handler
|
||||
|
||||
monkeypatch.setattr(http_handler, "_ssl_context_cache", {})
|
||||
monkeypatch.setenv("LITELLM_SSL_CIPHERS", _CHACHA_OVERRIDE)
|
||||
monkeypatch.setenv("LITELLM_FIPS_MODE", "true")
|
||||
monkeypatch.delenv("SSL_SECURITY_LEVEL", raising=False)
|
||||
monkeypatch.delenv("SSL_ECDH_CURVE", raising=False)
|
||||
|
||||
_simulate_cipher_override_at_import(monkeypatch, http_handler)
|
||||
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
|
||||
context = http_handler.get_ssl_configuration()
|
||||
_restore_cipher_constants(monkeypatch)
|
||||
|
||||
assert isinstance(context, ssl.SSLContext)
|
||||
names = _context_tls12_cipher_names(context)
|
||||
supported = _supported_tls12_cipher_names()
|
||||
assert names == (_tls12_cipher_names(http_handler.FIPS_SSL_CIPHERS) & supported)
|
||||
assert not any("CHACHA" in name for name in names)
|
||||
assert "LITELLM_SSL_CIPHERS override ignored" in caplog.text
|
||||
|
||||
|
||||
def test_get_ssl_configuration_applies_ssl_ciphers_override_outside_fips_mode(
|
||||
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
import logging
|
||||
|
||||
from litellm.llms.custom_httpx import http_handler
|
||||
|
||||
monkeypatch.setattr(http_handler, "_ssl_context_cache", {})
|
||||
monkeypatch.setenv("LITELLM_SSL_CIPHERS", _CHACHA_OVERRIDE)
|
||||
monkeypatch.delenv("LITELLM_FIPS_MODE", raising=False)
|
||||
monkeypatch.delenv("SSL_SECURITY_LEVEL", raising=False)
|
||||
monkeypatch.delenv("SSL_ECDH_CURVE", raising=False)
|
||||
|
||||
_simulate_cipher_override_at_import(monkeypatch, http_handler)
|
||||
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
|
||||
context = http_handler.get_ssl_configuration()
|
||||
_restore_cipher_constants(monkeypatch)
|
||||
|
||||
assert isinstance(context, ssl.SSLContext)
|
||||
names = _context_tls12_cipher_names(context)
|
||||
supported = _supported_tls12_cipher_names()
|
||||
assert names == (_tls12_cipher_names(_CHACHA_OVERRIDE) & supported)
|
||||
assert any("CHACHA" in name for name in names)
|
||||
assert "LITELLM_SSL_CIPHERS override ignored" not in caplog.text
|
||||
|
||||
|
||||
# Session Reuse Tests
|
||||
class MockClientSession:
|
||||
"""Mock ClientSession that is not callable"""
|
||||
|
|
@ -1532,7 +1606,6 @@ async def test_connection_error_retry_forwards_content(method: str):
|
|||
await handler.close()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def forward_proxy_server():
|
||||
"""Plain HTTP forward proxy that records the absolute URIs it is asked to fetch."""
|
||||
|
|
@ -1927,6 +2000,7 @@ def _vcr_outcome_gate(request, vcr):
|
|||
yield
|
||||
record_vcr_outcome(request, vcr)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def isolate_litellm_state():
|
||||
"""
|
||||
|
|
@ -1979,6 +2053,7 @@ def isolate_litellm_state():
|
|||
setattr(litellm, attr, original_value)
|
||||
_invalidate_model_cost_lowercase_map()
|
||||
|
||||
|
||||
_SCALAR_DEFAULTS = {
|
||||
"num_retries": getattr(litellm, "num_retries", None),
|
||||
"num_retries_per_request": getattr(litellm, "num_retries_per_request", None),
|
||||
|
|
@ -1999,6 +2074,7 @@ _SCALAR_DEFAULTS = {
|
|||
"api_key": getattr(litellm, "api_key", None),
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def setup_and_teardown():
|
||||
"""
|
||||
|
|
@ -2021,12 +2097,14 @@ def setup_and_teardown():
|
|||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
yield
|
||||
|
||||
|
||||
_SERVER_DELAY_S = 5
|
||||
|
||||
_PER_REQUEST_TIMEOUT_S = 1.0
|
||||
|
||||
_CLIENT_DEFAULT_TIMEOUT_S = 60.0
|
||||
|
||||
|
||||
class _SlowHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
time.sleep(_SERVER_DELAY_S)
|
||||
|
|
@ -2040,6 +2118,7 @@ class _SlowHandler(BaseHTTPRequestHandler):
|
|||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_vcr_outcome_gate", "isolate_litellm_state", "setup_and_teardown")
|
||||
def test_post_delay_exceeds_per_request_timeout_raises():
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), _SlowHandler)
|
||||
|
|
|
|||
|
|
@ -96,12 +96,15 @@ def test_fips_ssl_ciphers_tls12_suites_are_all_gcm():
|
|||
assert all("-GCM-" in suite for suite in tls12_suites)
|
||||
|
||||
|
||||
def test_ssl_ciphers_env_override_applies_to_both_lists(monkeypatch: pytest.MonkeyPatch):
|
||||
def test_ssl_ciphers_env_override_applies_to_default_list_only(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("LITELLM_SSL_CIPHERS", "ECDHE-RSA-AES128-GCM-SHA256")
|
||||
importlib.reload(constants)
|
||||
try:
|
||||
assert constants.DEFAULT_SSL_CIPHERS == "ECDHE-RSA-AES128-GCM-SHA256"
|
||||
assert constants.FIPS_SSL_CIPHERS == "ECDHE-RSA-AES128-GCM-SHA256"
|
||||
# The FIPS list must stay certified regardless of the override so FIPS mode
|
||||
# cannot be silently weakened by LITELLM_SSL_CIPHERS.
|
||||
assert constants.FIPS_SSL_CIPHERS != "ECDHE-RSA-AES128-GCM-SHA256"
|
||||
assert all("-GCM-" in suite for suite in _cipher_suites(constants.FIPS_SSL_CIPHERS) if not suite.startswith("TLS_"))
|
||||
finally:
|
||||
monkeypatch.delenv("LITELLM_SSL_CIPHERS")
|
||||
importlib.reload(constants)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue