Skip to content
Merged
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
83 changes: 49 additions & 34 deletions sdk/python/feast/offline_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,6 @@
from datetime import datetime
from typing import Any, Dict, List, Optional, cast

import click
import pyarrow as pa
import pyarrow.flight as fl
from google.protobuf.json_format import Parse

from feast import FeatureStore, FeatureView, utils
from feast.arrow_error_handler import arrow_server_error_handling_decorator
from feast.data_source import DataSource
from feast.errors import FeatureViewNotFoundException
from feast.feature_logging import FeatureServiceLoggingSource
from feast.feature_view import DUMMY_ENTITY_NAME
from feast.infra.offline_stores.offline_utils import get_offline_store_from_config
from feast.permissions.action import AuthzedAction
from feast.permissions.security_manager import assert_permissions
from feast.permissions.server.arrow import (
AuthorizationMiddlewareFactory,
inject_user_details_decorator,
)
from feast.permissions.server.utils import (
AuthManagerType,
ServerType,
init_auth_manager,
init_security_manager,
str_to_auth_manager_type,
)
from feast.protos.feast.core.DataSource_pb2 import DataSource as DataSourceProto
from feast.saved_dataset import SavedDatasetStorage

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

_FIPS_CIPHER_SUITES = ":".join(
[
"ECDHE-RSA-AES128-GCM-SHA256",
Expand All @@ -55,14 +24,60 @@ def _is_fips_enabled() -> bool:
with open("/proc/sys/crypto/fips_enabled") as f:
return f.read().strip() == "1"
except (FileNotFoundError, PermissionError, OSError):
logger.debug("Could not detect FIPS mode (Linux-only feature)")
return False


def _configure_grpc_fips() -> None:
def _configure_grpc_fips() -> bool:
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.")
return True
return False


# On FIPS-enabled systems (notably IBM Power ppc64le), gRPC reads
# GRPC_SSL_CIPHER_SUITES during shared-library initialization. The env var
# must be set before any gRPC-linked module (pyarrow.flight) is imported.
_fips_configured = _configure_grpc_fips()

import click # noqa: E402
import pyarrow as pa # noqa: E402
import pyarrow.flight as fl # noqa: E402
from google.protobuf.json_format import Parse # noqa: E402

from feast import FeatureStore, FeatureView, utils # noqa: E402
from feast.arrow_error_handler import ( # noqa: E402
arrow_server_error_handling_decorator,
)
from feast.data_source import DataSource # noqa: E402
from feast.errors import FeatureViewNotFoundException # noqa: E402
from feast.feature_logging import FeatureServiceLoggingSource # noqa: E402
from feast.feature_view import DUMMY_ENTITY_NAME # noqa: E402
from feast.infra.offline_stores.offline_utils import ( # noqa: E402
get_offline_store_from_config,
)
from feast.permissions.action import AuthzedAction # noqa: E402
from feast.permissions.security_manager import assert_permissions # noqa: E402
from feast.permissions.server.arrow import ( # noqa: E402
AuthorizationMiddlewareFactory,
inject_user_details_decorator,
)
from feast.permissions.server.utils import ( # noqa: E402
AuthManagerType,
ServerType,
init_auth_manager,
init_security_manager,
str_to_auth_manager_type,
)
from feast.protos.feast.core.DataSource_pb2 import ( # noqa: E402
DataSource as DataSourceProto,
)
from feast.saved_dataset import SavedDatasetStorage # noqa: E402

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

if _fips_configured:
logger.info("FIPS mode detected, configured FIPS-compliant gRPC cipher suites.")


class OfflineServer(fl.FlightServerBase):
Expand Down
55 changes: 55 additions & 0 deletions sdk/python/tests/unit/test_offline_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import os
import subprocess
import sys
import textwrap
from unittest.mock import MagicMock, mock_open, patch

import assertpy
Expand Down Expand Up @@ -137,3 +140,55 @@ def test_configure_grpc_fips_noop_without_fips():
os.environ.pop("GRPC_SSL_CIPHER_SUITES", None)
_configure_grpc_fips()
assert "GRPC_SSL_CIPHER_SUITES" not in os.environ


def test_module_level_fips_sets_env_before_pyarrow_import():
"""GRPC_SSL_CIPHER_SUITES must be set at module load time,
before pyarrow.flight (which bundles gRPC) is imported.

Uses a subprocess so pyarrow.flight is not already cached in
sys.modules, which lets us verify the true import ordering.
"""
script = textwrap.dedent("""\
import io, os, sys

# Intercept only /proc/sys/crypto/fips_enabled to simulate FIPS
_real_open = open
def _fips_open(file, *args, **kwargs):
if str(file) == "/proc/sys/crypto/fips_enabled":
return io.StringIO("1\\n")
return _real_open(file, *args, **kwargs)

import builtins
builtins.open = _fips_open

# Track import order to verify env var is set before pyarrow.flight
original_import = builtins.__import__
def tracking_import(name, *args, **kwargs):
if name == "pyarrow.flight":
assert "GRPC_SSL_CIPHER_SUITES" in os.environ, (
"GRPC_SSL_CIPHER_SUITES not set before pyarrow.flight import"
)
return original_import(name, *args, **kwargs)

builtins.__import__ = tracking_import
try:
import feast.offline_server
assert "GRPC_SSL_CIPHER_SUITES" in os.environ
assert "AES128-GCM-SHA256" in os.environ["GRPC_SSL_CIPHER_SUITES"]
finally:
builtins.__import__ = original_import
builtins.open = _real_open
""")
env = os.environ.copy()
env.pop("GRPC_SSL_CIPHER_SUITES", None)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
env=env,
timeout=60,
)
assert result.returncode == 0, (
f"Subprocess failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
)
Loading