diff --git a/sdk/python/feast/registry_server.py b/sdk/python/feast/registry_server.py index 2cddf42c824..91ced2bc7f4 100644 --- a/sdk/python/feast/registry_server.py +++ b/sdk/python/feast/registry_server.py @@ -175,6 +175,25 @@ def _build_any_feature_view_proto(feature_view: BaseFeatureView): ) +def _warn_if_auth_disabled(auth_manager_type: AuthManagerType) -> None: + """Emit a startup warning when the registry server runs without authentication. + + The registry server accepts control-plane writes (e.g. applying feature + views), which materialize user-provided transformation code on this host. + When authentication is disabled it accepts those requests unauthenticated, + so it should only run inside a trusted network boundary. + """ + if auth_manager_type == AuthManagerType.NONE: + logger.warning( + "Registry server is starting with authentication disabled " + "(auth type 'no_auth'). It will accept unauthenticated requests, " + "including feature-view apply operations that load user-provided " + "transformation code on this host. Only run the registry server in " + "this mode inside a trusted network boundary, and enable " + "authentication before exposing it on an untrusted network." + ) + + class RegistryServer(RegistryServer_pb2_grpc.RegistryServerServicer): def __init__(self, registry: BaseRegistry) -> None: super().__init__() @@ -1353,6 +1372,7 @@ def start_server( tls_cert_path: str = "", ): auth_manager_type = str_to_auth_manager_type(store.config.auth_config.type) + _warn_if_auth_disabled(auth_manager_type) init_security_manager(auth_type=auth_manager_type, fs=store) init_auth_manager( auth_type=auth_manager_type, diff --git a/sdk/python/tests/unit/test_registry_server.py b/sdk/python/tests/unit/test_registry_server.py new file mode 100644 index 00000000000..79d9938534f --- /dev/null +++ b/sdk/python/tests/unit/test_registry_server.py @@ -0,0 +1,53 @@ +import logging +from types import SimpleNamespace + +import pytest + +from feast.permissions.server.utils import AuthManagerType +from feast.registry_server import _warn_if_auth_disabled, start_server + + +def test_warn_if_auth_disabled_emits_warning_for_none(caplog): + with caplog.at_level(logging.WARNING, logger="feast.registry_server"): + _warn_if_auth_disabled(AuthManagerType.NONE) + assert len(caplog.records) == 1 + assert caplog.records[0].levelno == logging.WARNING + assert "no_auth" in caplog.records[0].message + + +def test_warn_if_auth_disabled_no_warning_for_oidc(caplog): + with caplog.at_level(logging.WARNING, logger="feast.registry_server"): + _warn_if_auth_disabled(AuthManagerType.OIDC) + assert len(caplog.records) == 0 + + +def test_start_server_warns_when_auth_disabled(caplog, monkeypatch): + class Sentinel(Exception): + pass + + store = SimpleNamespace( + config=SimpleNamespace( + auth_config=SimpleNamespace(type=AuthManagerType.NONE.value) + ) + ) + + def fail_after_warning(auth_type, fs): + assert auth_type == AuthManagerType.NONE + assert fs is store + assert any( + record.levelno == logging.WARNING and "no_auth" in record.message + for record in caplog.records + ) + raise Sentinel + + monkeypatch.setattr( + "feast.registry_server.init_security_manager", fail_after_warning + ) + + with caplog.at_level(logging.WARNING, logger="feast.registry_server"): + with pytest.raises(Sentinel): + start_server(store, port=0) + + assert len(caplog.records) == 1 + assert caplog.records[0].levelno == logging.WARNING + assert "no_auth" in caplog.records[0].message