Skip to content

Commit 56f3fd0

Browse files
sampan-s-nayaksampanedoakes
authored andcommitted
[Core] Support X-Ray-Authorization fallback header for accepting auth token in dashboard (ray-project#58819)
support `X-Ray-Authorization` header for accepting auth token. this is used by kuberay to pass auth token when it is making requests to ray dashboard through Kubernetes API via proxy. this only affects api's using the middleware (dashboard head and runtime env agent server) --------- Signed-off-by: sampan <sampan@anyscale.com> Signed-off-by: Edward Oakes <ed.nmi.oakes@gmail.com> Co-authored-by: sampan <sampan@anyscale.com> Co-authored-by: Edward Oakes <ed.nmi.oakes@gmail.com>
1 parent 36e1e4a commit 56f3fd0

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

python/ray/_private/authentication/authentication_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
AUTHORIZATION_HEADER_NAME = "authorization"
2626
AUTHORIZATION_BEARER_PREFIX = "Bearer "
27+
RAY_AUTHORIZATION_HEADER_NAME = "x-ray-authorization"
2728

2829
AUTHENTICATION_TOKEN_COOKIE_NAME = "ray-authentication-token"
2930
AUTHENTICATION_TOKEN_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 # 30 days

python/ray/_private/authentication/http_token_authentication.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,20 @@ async def token_auth_middleware(request, handler):
4343
):
4444
return await handler(request)
4545

46-
# Check Authorization header first (for API clients)
46+
# Try to get authentication token from multiple sources (in priority order):
47+
# 1. Standard "Authorization" header (for API clients, SDKs)
48+
# 2. Fallback "X-Ray-Authorization" header (for proxies and KubeRay)
49+
# 3. Cookie (for web dashboard sessions)
50+
4751
auth_header = request.headers.get(
4852
authentication_constants.AUTHORIZATION_HEADER_NAME, ""
4953
)
5054

51-
# If no Authorization header, check cookie (for web dashboard)
55+
if not auth_header:
56+
auth_header = request.headers.get(
57+
authentication_constants.RAY_AUTHORIZATION_HEADER_NAME, ""
58+
)
59+
5260
if not auth_header:
5361
token = request.cookies.get(
5462
authentication_constants.AUTHENTICATION_TOKEN_COOKIE_NAME

python/ray/dashboard/tests/test_dashboard_auth.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,54 @@ def test_dashboard_request_requires_auth_invalid_token(setup_cluster_with_token_
5050
assert response.status_code == 403
5151

5252

53+
def test_dashboard_request_with_ray_auth_header(setup_cluster_with_token_auth):
54+
"""Test that requests succeed with valid token in X-Ray-Authorization header."""
55+
56+
cluster_info = setup_cluster_with_token_auth
57+
headers = {"X-Ray-Authorization": f"Bearer {cluster_info['token']}"}
58+
59+
response = requests.get(
60+
f"{cluster_info['dashboard_url']}/api/component_activities",
61+
headers=headers,
62+
)
63+
64+
assert response.status_code == 200
65+
66+
67+
def test_authorization_header_takes_precedence(setup_cluster_with_token_auth):
68+
"""Test that standard Authorization header takes precedence over X-Ray-Authorization."""
69+
70+
cluster_info = setup_cluster_with_token_auth
71+
72+
# Provide both headers: valid token in Authorization, invalid in X-Ray-Authorization
73+
headers = {
74+
"Authorization": f"Bearer {cluster_info['token']}",
75+
"X-Ray-Authorization": "Bearer invalid_token_000000000000000000000000",
76+
}
77+
78+
# Should succeed because Authorization header takes precedence
79+
response = requests.get(
80+
f"{cluster_info['dashboard_url']}/api/component_activities",
81+
headers=headers,
82+
)
83+
84+
assert response.status_code == 200
85+
86+
# Now test with invalid Authorization but valid X-Ray-Authorization
87+
headers = {
88+
"Authorization": "Bearer invalid_token_000000000000000000000000",
89+
"X-Ray-Authorization": f"Bearer {cluster_info['token']}",
90+
}
91+
92+
# Should fail because Authorization header takes precedence (even though it's invalid)
93+
response = requests.get(
94+
f"{cluster_info['dashboard_url']}/api/component_activities",
95+
headers=headers,
96+
)
97+
98+
assert response.status_code == 403
99+
100+
53101
def test_dashboard_auth_disabled(setup_cluster_without_token_auth):
54102
"""Test that auth is not enforced when AUTH_MODE is disabled."""
55103

0 commit comments

Comments
 (0)