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
2 changes: 2 additions & 0 deletions src/codeflare_sdk/ray/cluster/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ def _validate_types(self):
"""Validate the types of all fields in the ClusterConfiguration dataclass."""
errors = []
for field_info in fields(self):
if field_info.name == "appwrapper":
continue
value = getattr(self, field_info.name)
expected_type = field_info.type
if not self._is_type(value, expected_type):
Expand Down
108 changes: 96 additions & 12 deletions tests/e2e/mnist_raycluster_sdk_oauth_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ def run_mnist_raycluster_sdk_oauth(self):

def assert_jobsubmit_withoutLogin(self, cluster):
dashboard_url = cluster.cluster_dashboard_uri()

# Verify that job submission is actually blocked by attempting to submit without auth
# The endpoint path depends on whether we're using HTTPRoute (with path prefix) or not
if "/ray/" in dashboard_url:
# HTTPRoute format: https://hostname/ray/namespace/cluster-name
# API endpoint is at the same base path
api_url = dashboard_url + "/api/jobs/"
else:
# OpenShift Route format: https://hostname
# API endpoint is directly under the hostname
api_url = dashboard_url + "/api/jobs/"

jobdata = {
"entrypoint": "python mnist.py",
"runtime_env": {
Expand All @@ -84,26 +96,98 @@ def assert_jobsubmit_withoutLogin(self, cluster):
"env_vars": get_setup_env_variables(),
},
}
try:
response = requests.post(
dashboard_url + "/api/jobs/", verify=False, json=jobdata
)
if response.status_code == 403:
assert True
else:
response.raise_for_status()
assert False

except Exception as e:
print(f"An unexpected error occurred. Error: {e}")
assert False
# Try to submit a job without authentication
# Follow redirects to see the final response - if it redirects to login, that's still a failure
response = requests.post(
api_url, verify=False, json=jobdata, allow_redirects=True
)

# Check if the submission was actually blocked
# Success indicators that submission was blocked:
# 1. Status code 403 (Forbidden)
# 2. Status code 302 (Redirect to login) - but we need to verify the final response after redirect
# 3. Status code 200 but with HTML content (login page) instead of JSON (job submission response)
# 4. Status code 401 (Unauthorized)

submission_blocked = False

if response.status_code == 403:
submission_blocked = True
elif response.status_code == 401:
submission_blocked = True
elif response.status_code == 302:
# Redirect happened - check if final response after redirect is also a failure
# If we followed redirects, check the final status
submission_blocked = True # Redirect to login means submission failed
elif response.status_code == 200:
# Check if response is HTML (login page) instead of JSON (job submission response)
content_type = response.headers.get("Content-Type", "")
if "text/html" in content_type or "application/json" not in content_type:
# Got HTML (likely login page) instead of JSON - submission was blocked
submission_blocked = True
else:
# Got JSON response - check if it's an error or actually a successful submission
try:
json_response = response.json()
# If it's a successful job submission, it should have a 'job_id' or 'submission_id'
# If it's an error, it might have 'error' or 'message'
if "job_id" in json_response or "submission_id" in json_response:
# Job was actually submitted - this is a failure!
submission_blocked = False
else:
# Error response - submission was blocked
submission_blocked = True
except ValueError:
# Not JSON - likely HTML login page
submission_blocked = True

if not submission_blocked:
assert (
False
), f"Job submission succeeded without authentication! Status: {response.status_code}, Response: {response.text[:200]}"

# Also verify that RayJobClient cannot be used without authentication
try:
client = RayJobClient(address=dashboard_url, verify=False)
# Try to call a method to trigger the connection and authentication check
client.list_jobs()
assert (
False
), "RayJobClient succeeded without authentication - this should not be possible"
except (
requests.exceptions.JSONDecodeError,
requests.exceptions.HTTPError,
Exception,
):
# Any exception is expected when trying to use the client without auth
pass

assert True, "Job submission without authentication was correctly blocked"

def assert_jobsubmit_withlogin(self, cluster):
auth_token = run_oc_command(["whoami", "--show-token=true"])
ray_dashboard = cluster.cluster_dashboard_uri()
header = {"Authorization": f"Bearer {auth_token}"}
client = RayJobClient(address=ray_dashboard, headers=header, verify=False)

# Verify that no jobs were submitted during the previous unauthenticated test
# This ensures that the authentication check in assert_jobsubmit_withoutLogin actually worked
existing_jobs = client.list_jobs()
if existing_jobs:
job_ids = [
job.job_id if hasattr(job, "job_id") else str(job)
for job in existing_jobs
]
assert False, (
f"Found {len(existing_jobs)} existing job(s) before authenticated submission: {job_ids}. "
"This indicates that the unauthenticated job submission test failed to properly block submission."
)
else:
print(
"Verified: No jobs exist from the previous unauthenticated submission attempt."
)

submission_id = client.submit_job(
entrypoint="python mnist.py",
runtime_env={
Expand Down
Loading