diff --git a/python/ray/includes/rpc_token_authentication.pxd b/python/ray/includes/rpc_token_authentication.pxd index 65b76dfecc6d..fe49e5ff0a71 100644 --- a/python/ray/includes/rpc_token_authentication.pxd +++ b/python/ray/includes/rpc_token_authentication.pxd @@ -26,9 +26,9 @@ cdef extern from "ray/rpc/authentication/authentication_token_loader.h" namespac cdef cppclass CAuthenticationTokenLoader "ray::rpc::AuthenticationTokenLoader": @staticmethod CAuthenticationTokenLoader& instance() - c_bool HasToken() + c_bool HasToken(c_bool ignore_auth_mode) void ResetCache() - optional[CAuthenticationToken] GetToken() + optional[CAuthenticationToken] GetToken(c_bool ignore_auth_mode) cdef extern from "ray/rpc/authentication/authentication_token_validator.h" namespace "ray::rpc" nogil: cdef cppclass CAuthenticationTokenValidator "ray::rpc::AuthenticationTokenValidator": diff --git a/python/ray/includes/rpc_token_authentication.pxi b/python/ray/includes/rpc_token_authentication.pxi index 53a1fdd9152d..837fa54b1ce0 100644 --- a/python/ray/includes/rpc_token_authentication.pxi +++ b/python/ray/includes/rpc_token_authentication.pxi @@ -43,7 +43,7 @@ def validate_authentication_token(provided_token: str) -> bool: cdef CAuthenticationToken provided if get_authentication_mode() == CAuthenticationMode.TOKEN: - expected_opt = CAuthenticationTokenLoader.instance().GetToken() + expected_opt = CAuthenticationTokenLoader.instance().GetToken(False) if not expected_opt.has_value(): return False @@ -64,13 +64,17 @@ class AuthenticationTokenLoader: """Get the singleton instance (returns a wrapper for convenience).""" return AuthenticationTokenLoader() - def has_token(self): + def has_token(self, ignore_auth_mode=False): """Check if an authentication token exists without crashing. + Args: + ignore_auth_mode: If True, bypass auth mode check and attempt to load token + regardless of RAY_AUTH_MODE setting. + Returns: bool: True if a token exists, False otherwise """ - return CAuthenticationTokenLoader.instance().HasToken() + return CAuthenticationTokenLoader.instance().HasToken(ignore_auth_mode) def reset_cache(self): """Reset the C++ authentication token cache. @@ -80,7 +84,7 @@ class AuthenticationTokenLoader: """ CAuthenticationTokenLoader.instance().ResetCache() - def get_token_for_http_header(self) -> dict: + def get_token_for_http_header(self, ignore_auth_mode=False) -> dict: """Get authentication token as a dictionary for HTTP headers. This method loads the token from C++ AuthenticationTokenLoader and returns it @@ -89,26 +93,39 @@ class AuthenticationTokenLoader: - A token does not exist - The token is empty + Args: + ignore_auth_mode: If True, bypass auth mode check and attempt to load token + regardless of RAY_AUTH_MODE setting. + Returns: dict: Empty dict or {"authorization": "Bearer "} """ - if not self.has_token(): + if not self.has_token(ignore_auth_mode): return {} # Get the token from C++ layer - cdef optional[CAuthenticationToken] token_opt = CAuthenticationTokenLoader.instance().GetToken() + cdef optional[CAuthenticationToken] token_opt = CAuthenticationTokenLoader.instance().GetToken(ignore_auth_mode) if not token_opt.has_value() or token_opt.value().empty(): return {} return {AUTHORIZATION_HEADER_NAME: token_opt.value().ToAuthorizationHeaderValue().decode('utf-8')} - def get_raw_token(self) -> str: - if not self.has_token(): + def get_raw_token(self, ignore_auth_mode=False) -> str: + """Get the raw authentication token value. + + Args: + ignore_auth_mode: If True, bypass auth mode check and attempt to load token + regardless of RAY_AUTH_MODE setting. + + Returns: + str: The raw token string, or empty string if no token exists + """ + if not self.has_token(ignore_auth_mode): return "" # Get the token from C++ layer - cdef optional[CAuthenticationToken] token_opt = CAuthenticationTokenLoader.instance().GetToken() + cdef optional[CAuthenticationToken] token_opt = CAuthenticationTokenLoader.instance().GetToken(ignore_auth_mode) if not token_opt.has_value() or token_opt.value().empty(): return "" diff --git a/python/ray/scripts/scripts.py b/python/ray/scripts/scripts.py index 183e270db9f5..936028ea7643 100644 --- a/python/ray/scripts/scripts.py +++ b/python/ray/scripts/scripts.py @@ -2723,7 +2723,7 @@ def shutdown_prometheus(): help="Generate a new token if none exists", ) def get_auth_token(generate): - """Prints the Ray authentication token to stdout when RAY_AUTH_MODE=token. + """Prints the Ray authentication token to stdout. If --generate is specified, a new token is created and saved to ~/.ray/auth_token if one does not exist. """ @@ -2731,21 +2731,13 @@ def get_auth_token(generate): generate_and_save_token, ) from ray._raylet import ( - AuthenticationMode, AuthenticationTokenLoader, - get_authentication_mode, ) - # Check if token auth mode is enabled and provide guidance if not - if get_authentication_mode() != AuthenticationMode.TOKEN: - raise click.ClickException( - "Token authentication is not currently enabled. To enable token authentication, set: export RAY_AUTH_MODE=token\n For more instructions, see: https://docs.ray.io/en/latest/ray-security/auth.html", - ) - # Try to load existing token loader = AuthenticationTokenLoader.instance() - if not loader.has_token(): + if not loader.has_token(ignore_auth_mode=True): if generate: click.echo("Generating new authentication token...", err=True) generate_and_save_token() @@ -2755,8 +2747,8 @@ def get_auth_token(generate): "No authentication token found. Use ray `get-auth-token --generate` to create one.", ) - # Get raw token value - token = loader.get_raw_token() + # Get raw token value (ignore auth mode - explicitly loading token) + token = loader.get_raw_token(ignore_auth_mode=True) # Print token to stdout (for piping) without newline click.echo(token, nl=False) diff --git a/python/ray/tests/test_token_auth_integration.py b/python/ray/tests/test_token_auth_integration.py index 86f4402233b5..fe7a0e4dda5f 100644 --- a/python/ray/tests/test_token_auth_integration.py +++ b/python/ray/tests/test_token_auth_integration.py @@ -425,7 +425,6 @@ def test_get_auth_token_cli(use_generate): test_token = "a" * 64 with authentication_env_guard(): - set_auth_mode("token") if use_generate: # Test --generate flag (no token set) clear_auth_token_sources(remove_default=True) @@ -473,7 +472,6 @@ def test_get_auth_token_cli(use_generate): def test_get_auth_token_cli_no_token_no_generate(): """Test ray get-auth-token fails without token and without --generate.""" with authentication_env_guard(): - set_auth_mode("token") reset_auth_token_state() clear_auth_token_sources(remove_default=True) env = os.environ.copy() @@ -500,7 +498,6 @@ def test_get_auth_token_cli_piping(): test_token = "b" * 64 with authentication_env_guard(): - set_auth_mode("token") set_env_auth_token(test_token) reset_auth_token_state() env = os.environ.copy() diff --git a/src/ray/rpc/authentication/authentication_token_loader.cc b/src/ray/rpc/authentication/authentication_token_loader.cc index c78b359f72e1..e16a1d550281 100644 --- a/src/ray/rpc/authentication/authentication_token_loader.cc +++ b/src/ray/rpc/authentication/authentication_token_loader.cc @@ -40,7 +40,8 @@ AuthenticationTokenLoader &AuthenticationTokenLoader::instance() { return instance; } -std::optional AuthenticationTokenLoader::GetToken() { +std::optional AuthenticationTokenLoader::GetToken( + bool ignore_auth_mode) { std::lock_guard lock(token_mutex_); // If already loaded, return cached value @@ -48,17 +49,17 @@ std::optional AuthenticationTokenLoader::GetToken() { return cached_token_; } - // If token or k8s auth is not enabled, return std::nullopt - if (!RequiresTokenAuthentication()) { + // If token or k8s auth is not enabled, return std::nullopt (unless ignoring auth mode) + if (!ignore_auth_mode && !RequiresTokenAuthentication()) { cached_token_ = std::nullopt; return std::nullopt; } - // Token auth is enabled, try to load from sources + // Token auth is enabled (or we're ignoring auth mode), try to load from sources AuthenticationToken token = LoadTokenFromSources(); // If no token found and auth is enabled, fail with RAY_CHECK - if (token.empty()) { + if (token.empty() && !ignore_auth_mode) { RAY_LOG(FATAL) << "Token authentication is enabled but Ray couldn't find an " "authentication token. " @@ -72,7 +73,7 @@ std::optional AuthenticationTokenLoader::GetToken() { return *cached_token_; } -bool AuthenticationTokenLoader::HasToken() { +bool AuthenticationTokenLoader::HasToken(bool ignore_auth_mode) { std::lock_guard lock(token_mutex_); // If already loaded, check if it's a valid token @@ -80,13 +81,13 @@ bool AuthenticationTokenLoader::HasToken() { return !cached_token_->empty(); } - // If token or k8s auth is not enabled, no token needed - if (!RequiresTokenAuthentication()) { + // If token or k8s auth is not enabled, no token needed (unless ignoring auth mode) + if (!ignore_auth_mode && !RequiresTokenAuthentication()) { cached_token_ = std::nullopt; return false; } - // Token auth is enabled, try to load from sources + // Token auth is enabled (or we're ignoring auth mode), try to load from sources AuthenticationToken token = LoadTokenFromSources(); // Cache the result diff --git a/src/ray/rpc/authentication/authentication_token_loader.h b/src/ray/rpc/authentication/authentication_token_loader.h index 1dc4972125d7..c34be6280b5a 100644 --- a/src/ray/rpc/authentication/authentication_token_loader.h +++ b/src/ray/rpc/authentication/authentication_token_loader.h @@ -37,13 +37,17 @@ class AuthenticationTokenLoader { /// Get the authentication token. /// If token authentication is enabled but no token is found, fails with RAY_CHECK. + /// \param ignore_auth_mode If true, bypass auth mode check and attempt to load token + /// regardless of RAY_AUTH_MODE setting. /// \return The authentication token, or std::nullopt if auth is disabled. - std::optional GetToken(); + std::optional GetToken(bool ignore_auth_mode = false); /// Check if a token exists without crashing. /// Caches the token if it loads it afresh. + /// \param ignore_auth_mode If true, bypass auth mode check and attempt to load token + /// regardless of RAY_AUTH_MODE setting. /// \return true if a token exists, false otherwise. - bool HasToken(); + bool HasToken(bool ignore_auth_mode = false); void ResetCache() { std::lock_guard lock(token_mutex_); diff --git a/src/ray/rpc/authentication/tests/BUILD.bazel b/src/ray/rpc/authentication/tests/BUILD.bazel new file mode 100644 index 000000000000..803db16018f3 --- /dev/null +++ b/src/ray/rpc/authentication/tests/BUILD.bazel @@ -0,0 +1,47 @@ +load("//bazel:ray.bzl", "ray_cc_test") + +ray_cc_test( + name = "authentication_token_test", + size = "small", + srcs = [ + "authentication_token_test.cc", + ], + tags = ["team:core"], + deps = [ + "//src/ray/rpc/authentication:authentication_token", + "@com_google_googletest//:gtest_main", + ], +) + +ray_cc_test( + name = "authentication_token_loader_test", + size = "small", + srcs = [ + "authentication_token_loader_test.cc", + ], + tags = ["team:core"], + deps = [ + "//src/ray/common:ray_config", + "//src/ray/rpc/authentication:authentication_token_loader", + "//src/ray/util:env", + "@com_google_googletest//:gtest_main", + ], +) + +ray_cc_test( + name = "grpc_auth_token_tests", + size = "small", + srcs = [ + "grpc_auth_token_tests.cc", + ], + tags = ["team:core"], + deps = [ + "//src/ray/protobuf:test_service_cc_grpc", + "//src/ray/rpc:grpc_client", + "//src/ray/rpc:grpc_server", + "//src/ray/rpc/authentication:authentication_token_loader", + "//src/ray/rpc/tests:grpc_test_common", + "//src/ray/util:env", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/ray/rpc/tests/authentication_token_loader_test.cc b/src/ray/rpc/authentication/tests/authentication_token_loader_test.cc similarity index 84% rename from src/ray/rpc/tests/authentication_token_loader_test.cc rename to src/ray/rpc/authentication/tests/authentication_token_loader_test.cc index 9869ff40cb49..bd2d1c76563b 100644 --- a/src/ray/rpc/tests/authentication_token_loader_test.cc +++ b/src/ray/rpc/authentication/tests/authentication_token_loader_test.cc @@ -325,6 +325,62 @@ TEST_F(AuthenticationTokenLoaderTest, TestWhitespaceHandling) { EXPECT_TRUE(token_opt->Equals(expected)); } +TEST_F(AuthenticationTokenLoaderTest, TestIgnoreAuthModeGetToken) { + // Disable auth mode + RayConfig::instance().initialize(R"({"AUTH_MODE": "disabled"})"); + AuthenticationTokenLoader::instance().ResetCache(); + + // Set token in environment + set_env_var("RAY_AUTH_TOKEN", "test-token-ignore-auth"); + + auto &loader = AuthenticationTokenLoader::instance(); + + // Without ignore_auth_mode, should return nullopt (auth is disabled) + auto token_opt_no_ignore = loader.GetToken(); + EXPECT_FALSE(token_opt_no_ignore.has_value()); + + // Reset cache to test ignore_auth_mode + loader.ResetCache(); + + // With ignore_auth_mode=true, should load token despite auth being disabled + auto token_opt_ignore = loader.GetToken(true); + ASSERT_TRUE(token_opt_ignore.has_value()); + AuthenticationToken expected("test-token-ignore-auth"); + EXPECT_TRUE(token_opt_ignore->Equals(expected)); + + // Re-enable auth for other tests + RayConfig::instance().initialize(R"({"AUTH_MODE": "token"})"); +} + +TEST_F(AuthenticationTokenLoaderTest, TestIgnoreAuthModeHasToken) { + // Disable auth mode + RayConfig::instance().initialize(R"({"AUTH_MODE": "disabled"})"); + AuthenticationTokenLoader::instance().ResetCache(); + + // Set token in environment + set_env_var("RAY_AUTH_TOKEN", "test-token-has-ignore"); + + auto &loader = AuthenticationTokenLoader::instance(); + + // Without ignore_auth_mode, should return false (auth is disabled) + EXPECT_FALSE(loader.HasToken()); + + // Reset cache to test ignore_auth_mode + loader.ResetCache(); + + // With ignore_auth_mode=true, should return true despite auth being disabled + EXPECT_TRUE(loader.HasToken(true)); + + // Also verify we can get the actual token value + auto token_opt = loader.GetToken(true); + ASSERT_TRUE(token_opt.has_value()); + AuthenticationToken expected("test-token-has-ignore"); + EXPECT_TRUE(token_opt->Equals(expected)); + + // Re-enable auth for other tests + RayConfig::instance().initialize(R"({"AUTH_MODE": "token"})"); +} + } // namespace rpc } // namespace ray diff --git a/src/ray/rpc/tests/authentication_token_test.cc b/src/ray/rpc/authentication/tests/authentication_token_test.cc similarity index 100% rename from src/ray/rpc/tests/authentication_token_test.cc rename to src/ray/rpc/authentication/tests/authentication_token_test.cc diff --git a/src/ray/rpc/tests/grpc_auth_token_tests.cc b/src/ray/rpc/authentication/tests/grpc_auth_token_tests.cc similarity index 100% rename from src/ray/rpc/tests/grpc_auth_token_tests.cc rename to src/ray/rpc/authentication/tests/grpc_auth_token_tests.cc diff --git a/src/ray/rpc/tests/BUILD.bazel b/src/ray/rpc/tests/BUILD.bazel index 2f82eafb15d9..5ca3ee88c853 100644 --- a/src/ray/rpc/tests/BUILD.bazel +++ b/src/ray/rpc/tests/BUILD.bazel @@ -39,24 +39,6 @@ ray_cc_test( ], ) -ray_cc_test( - name = "grpc_auth_token_tests", - size = "small", - srcs = [ - "grpc_auth_token_tests.cc", - ], - tags = ["team:core"], - deps = [ - ":grpc_test_common", - "//src/ray/protobuf:test_service_cc_grpc", - "//src/ray/rpc:grpc_client", - "//src/ray/rpc:grpc_server", - "//src/ray/rpc/authentication:authentication_token_loader", - "//src/ray/util:env", - "@com_google_googletest//:gtest_main", - ], -) - ray_cc_test( name = "metrics_agent_client_test", size = "small", @@ -69,31 +51,3 @@ ray_cc_test( "@com_google_googletest//:gtest_main", ], ) - -ray_cc_test( - name = "authentication_token_loader_test", - size = "small", - srcs = [ - "authentication_token_loader_test.cc", - ], - tags = ["team:core"], - deps = [ - "//src/ray/common:ray_config", - "//src/ray/rpc/authentication:authentication_token_loader", - "//src/ray/util:env", - "@com_google_googletest//:gtest_main", - ], -) - -ray_cc_test( - name = "authentication_token_test", - size = "small", - srcs = [ - "authentication_token_test.cc", - ], - tags = ["team:core"], - deps = [ - "//src/ray/rpc/authentication:authentication_token", - "@com_google_googletest//:gtest_main", - ], -)