Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions sdk/python/feast/offline_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

_FIPS_CIPHER_SUITES = ":".join(
[
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"AES128-GCM-SHA256",
"AES256-GCM-SHA384",
]
)


def _is_fips_enabled() -> bool:
try:
with open("/proc/sys/crypto/fips_enabled") as f:
return f.read().strip() == "1"
except (FileNotFoundError, PermissionError, OSError):
return False


def _configure_grpc_fips() -> None:
if _is_fips_enabled() and "GRPC_SSL_CIPHER_SUITES" not in os.environ:
os.environ["GRPC_SSL_CIPHER_SUITES"] = _FIPS_CIPHER_SUITES
logger.info("FIPS mode detected, configured FIPS-compliant gRPC cipher suites.")


class OfflineServer(fl.FlightServerBase):
def __init__(
Expand Down Expand Up @@ -596,6 +621,7 @@ def start_server(
tls_cert_path: str = "",
):
_init_auth_manager(store)
_configure_grpc_fips()

tls_certificates = []
scheme = "grpc+tcp"
Expand Down
55 changes: 53 additions & 2 deletions sdk/python/tests/unit/test_offline_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from unittest.mock import MagicMock, patch
import os
from unittest.mock import MagicMock, mock_open, patch

import assertpy

Expand All @@ -7,7 +8,11 @@
RemoteOfflineStoreConfig,
_create_retrieval_metadata,
)
from feast.offline_server import OfflineServer
from feast.offline_server import (
OfflineServer,
_configure_grpc_fips,
_is_fips_enabled,
)


def test_create_retrieval_metadata_with_sql_string():
Expand Down Expand Up @@ -86,3 +91,49 @@ def test_offline_server_get_historical_features_passes_sql_to_store():
assertpy.assert_that(result).is_equal_to(mock_job)
_, kwargs = mock_offline_store.get_historical_features.call_args
assertpy.assert_that(kwargs["entity_df"]).is_equal_to(sql)


def test_is_fips_enabled_returns_true():
with patch("builtins.open", mock_open(read_data="1\n")):
assert _is_fips_enabled() is True


def test_is_fips_enabled_returns_false():
with patch("builtins.open", mock_open(read_data="0\n")):
assert _is_fips_enabled() is False


def test_is_fips_enabled_missing_file():
with patch("builtins.open", side_effect=FileNotFoundError):
assert _is_fips_enabled() is False


def test_configure_grpc_fips_sets_cipher_suites():
with (
patch("feast.offline_server._is_fips_enabled", return_value=True),
patch.dict(os.environ, {}, clear=False),
):
os.environ.pop("GRPC_SSL_CIPHER_SUITES", None)
_configure_grpc_fips()
assert "GRPC_SSL_CIPHER_SUITES" in os.environ
assert "AES128-GCM-SHA256" in os.environ["GRPC_SSL_CIPHER_SUITES"]
del os.environ["GRPC_SSL_CIPHER_SUITES"]


def test_configure_grpc_fips_respects_existing_env():
with (
patch("feast.offline_server._is_fips_enabled", return_value=True),
patch.dict(os.environ, {"GRPC_SSL_CIPHER_SUITES": "custom"}, clear=False),
):
_configure_grpc_fips()
assert os.environ["GRPC_SSL_CIPHER_SUITES"] == "custom"


def test_configure_grpc_fips_noop_without_fips():
with (
patch("feast.offline_server._is_fips_enabled", return_value=False),
patch.dict(os.environ, {}, clear=False),
):
os.environ.pop("GRPC_SSL_CIPHER_SUITES", None)
_configure_grpc_fips()
assert "GRPC_SSL_CIPHER_SUITES" not in os.environ
Loading