Skip to content

Commit 764df29

Browse files
sampan-s-nayaksampanedoakes
authored andcommitted
[Core] handling auth_mode=token in python ray.init() calls (ray-project#57835)
## Description builds atop of ray-project#58047, this pr ensures the following when `auth_mode` is `token`: calling `ray.init() `(without passing an existing cluster address) -> check if token is present, generate and store in default path if not present calling `ray.init(address="xyz")` (connecting to an existing cluster) -> check if token is present, raise exception if one is not present --------- Signed-off-by: sampan <sampan@anyscale.com> Signed-off-by: Sampan S Nayak <sampansnayak2@gmail.com> Co-authored-by: sampan <sampan@anyscale.com> Co-authored-by: Edward Oakes <ed.nmi.oakes@gmail.com>
1 parent fb68061 commit 764df29

File tree

11 files changed

+435
-0
lines changed

11 files changed

+435
-0
lines changed

python/ray/_private/authentication/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import uuid
2+
3+
4+
# TODO: this is a placeholder for the actual authentication token generator. Will be replaced with a proper implementation.
5+
def generate_new_authentication_token() -> str:
6+
return uuid.uuid4().hex
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Authentication token setup for Ray.
2+
3+
This module provides functions to generate and save authentication tokens
4+
for Ray's token-based authentication system. Token loading and caching is
5+
handled by the C++ AuthenticationTokenLoader.
6+
"""
7+
8+
import logging
9+
from pathlib import Path
10+
from typing import Any, Dict, Optional
11+
12+
from ray._private.authentication.authentication_token_generator import (
13+
generate_new_authentication_token,
14+
)
15+
from ray._raylet import (
16+
AuthenticationMode,
17+
AuthenticationTokenLoader,
18+
get_authentication_mode,
19+
)
20+
21+
logger = logging.getLogger(__name__)
22+
23+
TOKEN_AUTH_ENABLED_BUT_NO_TOKEN_FOUND_ERROR_MESSAGE = (
24+
"Token authentication is enabled but no authentication token was found. Please provide a token with one of these options:\n"
25+
+ " 1. RAY_AUTH_TOKEN environment variable\n"
26+
+ " 2. RAY_AUTH_TOKEN_PATH environment variable (path to token file)\n"
27+
+ " 3. Default token file: ~/.ray/auth_token"
28+
)
29+
30+
31+
def generate_and_save_token() -> None:
32+
"""Generate a new random token and save it in the default token path.
33+
34+
Returns:
35+
The newly generated authentication token.
36+
"""
37+
# Generate a UUID-based token
38+
token = generate_new_authentication_token()
39+
40+
token_path = _get_default_token_path()
41+
try:
42+
# Create directory if it doesn't exist
43+
token_path.parent.mkdir(parents=True, exist_ok=True)
44+
45+
# Write token to file with explicit flush and fsync
46+
with open(token_path, "w") as f:
47+
f.write(token)
48+
49+
logger.info(f"Generated new authentication token and saved to {token_path}")
50+
except Exception:
51+
raise
52+
53+
54+
def _get_default_token_path() -> Path:
55+
"""Get the default token file path (~/.ray/auth_token).
56+
57+
Returns:
58+
Path object pointing to ~/.ray/auth_token
59+
"""
60+
return Path.home() / ".ray" / "auth_token"
61+
62+
63+
def ensure_token_if_auth_enabled(
64+
system_config: Optional[Dict[str, Any]] = None, create_token_if_missing: bool = True
65+
) -> None:
66+
"""Check authentication settings and set up token resources if authentication is enabled.
67+
68+
Ray calls this early during ray.init() to do the following for token-based authentication:
69+
1. Check whether you enabled token-based authentication.
70+
2. Make sure a token is available if authentication is enabled.
71+
3. Generate and save a default token for new local clusters if one doesn't already exist.
72+
73+
Args:
74+
system_config: Ray raises an error if you set auth_mode in system_config instead of the environment.
75+
create_token_if_missing: Generate a new token if one doesn't already exist.
76+
77+
Raises:
78+
RuntimeError: Ray raises this error if authentication is enabled but no token is found when connecting
79+
to an existing cluster.
80+
"""
81+
82+
# Check if you enabled token authentication.
83+
if get_authentication_mode() != AuthenticationMode.TOKEN:
84+
if (
85+
system_config
86+
and "auth_mode" in system_config
87+
and system_config["auth_mode"] != "disabled"
88+
):
89+
raise RuntimeError(
90+
"Set authentication mode can only be set with the `RAY_auth_mode` environment variable, not using the system_config."
91+
)
92+
return
93+
94+
token_loader = AuthenticationTokenLoader.instance()
95+
96+
if not token_loader.has_token():
97+
if create_token_if_missing:
98+
# Generate a new token.
99+
generate_and_save_token()
100+
101+
# Reload the cache so subsequent calls to token_loader read the new token.
102+
token_loader.reset_cache()
103+
else:
104+
raise RuntimeError(TOKEN_AUTH_ENABLED_BUT_NO_TOKEN_FOUND_ERROR_MESSAGE)

python/ray/_private/worker.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
from ray._common import ray_option_utils
6363
from ray._common.constants import RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_ENV_VAR
6464
from ray._common.utils import load_class
65+
from ray._private.authentication.authentication_token_setup import (
66+
ensure_token_if_auth_enabled,
67+
)
6568
from ray._private.client_mode_hook import client_mode_hook
6669
from ray._private.custom_types import TensorTransportEnum
6770
from ray._private.function_manager import FunctionActorManager
@@ -1865,6 +1868,9 @@ def sigterm_handler(signum, frame):
18651868
if bootstrap_address is None:
18661869
# In this case, we need to start a new cluster.
18671870

1871+
# Setup and verify authentication for new cluster
1872+
ensure_token_if_auth_enabled(_system_config, create_token_if_missing=True)
1873+
18681874
# Don't collect usage stats in ray.init() unless it's a nightly wheel.
18691875
from ray._common.usage import usage_lib
18701876

@@ -1952,6 +1958,9 @@ def sigterm_handler(signum, frame):
19521958
"an existing cluster."
19531959
)
19541960

1961+
# Setup and verify authentication for connecting to existing cluster
1962+
ensure_token_if_auth_enabled(_system_config, create_token_if_missing=False)
1963+
19551964
# In this case, we only need to connect the node.
19561965
ray_params = ray._private.parameter.RayParams(
19571966
node_ip_address=_node_ip_address,

python/ray/_raylet.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ include "includes/metric.pxi"
201201
include "includes/setproctitle.pxi"
202202
include "includes/raylet_client.pxi"
203203
include "includes/gcs_subscriber.pxi"
204+
include "includes/rpc_token_authentication.pxi"
204205

205206
import ray
206207
from ray.exceptions import (
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from libcpp cimport bool as c_bool
2+
3+
cdef extern from "ray/rpc/authentication/authentication_mode.h" namespace "ray::rpc" nogil:
4+
cdef enum CAuthenticationMode "ray::rpc::AuthenticationMode":
5+
DISABLED "ray::rpc::AuthenticationMode::DISABLED"
6+
TOKEN "ray::rpc::AuthenticationMode::TOKEN"
7+
8+
CAuthenticationMode GetAuthenticationMode()
9+
10+
cdef extern from "ray/rpc/authentication/authentication_token_loader.h" namespace "ray::rpc" nogil:
11+
cdef cppclass CAuthenticationTokenLoader "ray::rpc::AuthenticationTokenLoader":
12+
@staticmethod
13+
CAuthenticationTokenLoader& instance()
14+
c_bool HasToken()
15+
void ResetCache()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from ray.includes.rpc_token_authentication cimport (
2+
CAuthenticationMode,
3+
GetAuthenticationMode,
4+
CAuthenticationTokenLoader,
5+
)
6+
7+
8+
# Authentication mode enum exposed to Python
9+
class AuthenticationMode:
10+
DISABLED = CAuthenticationMode.DISABLED
11+
TOKEN = CAuthenticationMode.TOKEN
12+
13+
14+
def get_authentication_mode():
15+
"""Get the current authentication mode.
16+
17+
Returns:
18+
AuthenticationMode enum value (DISABLED or TOKEN)
19+
"""
20+
return GetAuthenticationMode()
21+
22+
23+
class AuthenticationTokenLoader:
24+
"""Python wrapper for C++ AuthenticationTokenLoader singleton."""
25+
26+
@staticmethod
27+
def instance():
28+
"""Get the singleton instance (returns a wrapper for convenience)."""
29+
return AuthenticationTokenLoader()
30+
31+
def has_token(self):
32+
"""Check if an authentication token exists without crashing.
33+
34+
Returns:
35+
bool: True if a token exists, False otherwise
36+
"""
37+
return CAuthenticationTokenLoader.instance().HasToken()
38+
39+
def reset_cache(self):
40+
"""Reset the C++ authentication token cache.
41+
42+
This forces the token loader to reload the token from environment
43+
variables or files on the next request.
44+
"""
45+
CAuthenticationTokenLoader.instance().ResetCache()

python/ray/tests/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ py_test_module_list(
368368
"test_task_metrics.py",
369369
"test_tempdir.py",
370370
"test_tls_auth.py",
371+
"test_token_auth_integration.py",
371372
"test_traceback.py",
372373
"test_worker_capping.py",
373374
"test_worker_state.py",

0 commit comments

Comments
 (0)