mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-06 07:48:06 +00:00
feat(proxy): refuse FIPS mode when cryptography's OpenSSL is not FIPS
The stock cryptography wheel bundles its own non-FIPS OpenSSL, which serves JWT, Fernet and AES-GCM even when Python's OpenSSL enforces FIPS.
This commit is contained in:
parent
db7c9aa546
commit
75c6d07a8f
3 changed files with 52 additions and 12 deletions
|
|
@ -30,9 +30,19 @@ def openssl_enforces_fips() -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def cryptography_enforces_fips() -> bool:
|
||||
"""The cryptography library's own OpenSSL runs with FIPS-only defaults."""
|
||||
try:
|
||||
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
|
||||
|
||||
return rust_openssl.is_fips_enabled()
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
def enforce_fips_mode() -> None:
|
||||
"""Refuse to start when LITELLM_FIPS_MODE is not a boolean, or is on but TLS verification is off
|
||||
or OpenSSL does not enforce FIPS."""
|
||||
or Python's or the cryptography library's OpenSSL does not enforce FIPS."""
|
||||
value: Final = os.getenv(FIPS_MODE_ENV_VAR)
|
||||
if value and str_to_bool(value) is None:
|
||||
raise FipsModeError(f"{FIPS_MODE_ENV_VAR}={value!r} is not a boolean. Set it to true or false, or unset it.")
|
||||
|
|
@ -51,3 +61,8 @@ def enforce_fips_mode() -> None:
|
|||
f"{FIPS_MODE_ENV_VAR} is on but OpenSSL does not enforce FIPS. Run the proxy on an OpenSSL with "
|
||||
f"its FIPS provider active and default_properties = fips=yes, or unset {FIPS_MODE_ENV_VAR}."
|
||||
)
|
||||
if not cryptography_enforces_fips():
|
||||
raise FipsModeError(
|
||||
f"{FIPS_MODE_ENV_VAR} is on but the cryptography library's OpenSSL does not enforce FIPS. Build it against "
|
||||
f"the system OpenSSL (pip install --no-binary cryptography cryptography), or unset {FIPS_MODE_ENV_VAR}."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@ import _hashlib
|
|||
from contextlib import nullcontext
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
|
||||
|
||||
import litellm
|
||||
from litellm.proxy.common_utils.fips import FipsModeError, enforce_fips_mode, is_fips_mode, openssl_enforces_fips
|
||||
from litellm.proxy.common_utils.fips import (
|
||||
FipsModeError,
|
||||
cryptography_enforces_fips,
|
||||
enforce_fips_mode,
|
||||
is_fips_mode,
|
||||
openssl_enforces_fips,
|
||||
)
|
||||
|
||||
|
||||
def _fake_openssl(monkeypatch, *, fips_flag: int, md5_refused: bool) -> None:
|
||||
|
|
@ -43,23 +50,36 @@ def test_openssl_without_get_fips_mode_does_not_enforce_fips(monkeypatch):
|
|||
assert openssl_enforces_fips() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fips_enabled", [True, False])
|
||||
def test_cryptography_enforces_fips_reads_its_openssl_fips_flag(monkeypatch, fips_enabled):
|
||||
monkeypatch.setattr(rust_openssl, "is_fips_enabled", lambda: fips_enabled)
|
||||
assert cryptography_enforces_fips() is fips_enabled
|
||||
|
||||
|
||||
def test_cryptography_without_is_fips_enabled_does_not_enforce_fips(monkeypatch):
|
||||
monkeypatch.delattr(rust_openssl, "is_fips_enabled")
|
||||
assert cryptography_enforces_fips() is False
|
||||
|
||||
|
||||
def test_this_runners_openssl_does_not_enforce_fips():
|
||||
if _hashlib.get_fips_mode() == 1:
|
||||
pytest.skip("this runner's OpenSSL is in FIPS mode")
|
||||
assert openssl_enforces_fips() is False
|
||||
assert cryptography_enforces_fips() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fips_mode, ssl_verify_env, ssl_verify_setting, openssl_fips, refusal",
|
||||
"fips_mode, ssl_verify_env, ssl_verify_setting, openssl_fips, cryptography_fips, refusal",
|
||||
[
|
||||
(None, "false", False, False, None),
|
||||
("", "false", False, False, None),
|
||||
("false", "false", False, False, None),
|
||||
("true", None, True, True, None),
|
||||
("true", "false", True, True, "TLS certificate verification is disabled"),
|
||||
("true", None, False, True, "TLS certificate verification is disabled"),
|
||||
("true", None, True, False, "OpenSSL does not enforce FIPS"),
|
||||
("enforced", None, True, True, "LITELLM_FIPS_MODE='enforced' is not a boolean"),
|
||||
(None, "false", False, False, False, None),
|
||||
("", "false", False, False, False, None),
|
||||
("false", "false", False, False, False, None),
|
||||
("true", None, True, True, True, None),
|
||||
("true", "false", True, True, True, "TLS certificate verification is disabled"),
|
||||
("true", None, False, True, True, "TLS certificate verification is disabled"),
|
||||
("true", None, True, False, True, "is on but OpenSSL does not enforce FIPS"),
|
||||
("true", None, True, True, False, "cryptography library's OpenSSL does not enforce FIPS"),
|
||||
("enforced", None, True, True, True, "LITELLM_FIPS_MODE='enforced' is not a boolean"),
|
||||
],
|
||||
ids=[
|
||||
"unset",
|
||||
|
|
@ -69,10 +89,13 @@ def test_this_runners_openssl_does_not_enforce_fips():
|
|||
"ssl-verify-env-off",
|
||||
"ssl-verify-setting-off",
|
||||
"openssl-not-fips",
|
||||
"cryptography-not-fips",
|
||||
"not-a-boolean",
|
||||
],
|
||||
)
|
||||
def test_enforce_fips_mode(monkeypatch, fips_mode, ssl_verify_env, ssl_verify_setting, openssl_fips, refusal):
|
||||
def test_enforce_fips_mode(
|
||||
monkeypatch, fips_mode, ssl_verify_env, ssl_verify_setting, openssl_fips, cryptography_fips, refusal
|
||||
):
|
||||
for name, value in (("LITELLM_FIPS_MODE", fips_mode), ("SSL_VERIFY", ssl_verify_env)):
|
||||
if value is None:
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
|
|
@ -80,6 +103,7 @@ def test_enforce_fips_mode(monkeypatch, fips_mode, ssl_verify_env, ssl_verify_se
|
|||
monkeypatch.setenv(name, value)
|
||||
monkeypatch.setattr(litellm, "ssl_verify", ssl_verify_setting)
|
||||
_fake_openssl(monkeypatch, fips_flag=int(openssl_fips), md5_refused=openssl_fips)
|
||||
monkeypatch.setattr(rust_openssl, "is_fips_enabled", lambda: cryptography_fips)
|
||||
|
||||
with pytest.raises(FipsModeError, match=refusal) if refusal else nullcontext():
|
||||
enforce_fips_mode()
|
||||
|
|
|
|||
|
|
@ -1801,6 +1801,7 @@ async def test_proxy_startup_refuses_fips_mode_when_the_password_migration_fails
|
|||
monkeypatch.delenv("SSL_VERIFY", raising=False)
|
||||
monkeypatch.setenv("LITELLM_FIPS_MODE", "true")
|
||||
monkeypatch.setattr("litellm.proxy.common_utils.fips.openssl_enforces_fips", lambda: True)
|
||||
monkeypatch.setattr("litellm.proxy.common_utils.fips.cryptography_enforces_fips", lambda: True)
|
||||
monkeypatch.setattr(ProxyStartupEvent, "setup_prisma_client", AsyncMock(return_value=MockPrisma()))
|
||||
scrypt_refused = ValueError("[digital envelope routines] unsupported")
|
||||
monkeypatch.setattr(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue