Files
wehub-resource-sync e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

163 lines
6.2 KiB
Python

# Copyright 2025 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from fastapi import FastAPI
from fastapi.testclient import TestClient
from opensandbox_server.config import AppConfig, IngressConfig, RuntimeConfig, ServerConfig
from opensandbox_server.middleware.auth import AuthMiddleware
def _app_config_with_api_key() -> AppConfig:
return AppConfig(
server=ServerConfig(api_key="secret-key"),
runtime=RuntimeConfig(type="docker", execd_image="opensandbox/execd:latest"),
ingress=IngressConfig(mode="direct"),
)
def _build_test_app():
app = FastAPI()
config = _app_config_with_api_key()
app.add_middleware(AuthMiddleware, config=config)
@app.get("/secured")
def secured_endpoint():
return {"ok": True}
return app
def test_auth_middleware_rejects_missing_key():
app = _build_test_app()
client = TestClient(app)
response = client.get("/secured")
assert response.status_code == 401
assert response.json()["code"] == "MISSING_API_KEY"
def test_auth_middleware_accepts_valid_key():
app = _build_test_app()
client = TestClient(app)
response = client.get("/secured", headers={"OPEN-SANDBOX-API-KEY": "secret-key"})
assert response.status_code == 200
assert response.json() == {"ok": True}
def test_auth_middleware_skips_validation_for_proxy_to_sandbox():
"""Proxy-to-sandbox paths must not require API key; server only forwards to sandbox."""
app = _build_test_app()
@app.get("/sandboxes/{sandbox_id}/proxy/{port}/{full_path:path}")
def proxy_echo(sandbox_id: str, port: int, full_path: str):
return {"proxied": True, "sandbox_id": sandbox_id, "port": port, "path": full_path}
client = TestClient(app)
# No OPEN-SANDBOX-API-KEY header; should still succeed for proxy path
response = client.get("/sandboxes/abc-123/proxy/8080/foo/bar")
assert response.status_code == 200
assert response.json()["proxied"] is True
assert response.json()["sandbox_id"] == "abc-123"
assert response.json()["port"] == 8080
assert response.json()["path"] == "foo/bar"
def test_auth_middleware_v1_proxy_path_exempt():
"""V1 prefix proxy path is also exempt."""
app = _build_test_app()
@app.get("/v1/sandboxes/{sandbox_id}/proxy/{port}/{full_path:path}")
def proxy_echo(sandbox_id: str, port: int, full_path: str):
return {"proxied": True}
client = TestClient(app)
response = client.get("/v1/sandboxes/sid/proxy/443/")
assert response.status_code == 200
assert response.json()["proxied"] is True
def test_auth_middleware_requires_key_for_non_proxy_paths_containing_proxy_and_sandboxes():
"""Paths that contain both 'proxy' and 'sandboxes' but not in proxy-route shape still require auth."""
app = _build_test_app()
@app.get("/proxy/sandboxes/anything")
def fake_proxy():
return {"reached": True}
client = TestClient(app)
response = client.get("/proxy/sandboxes/anything")
assert response.status_code == 401
assert response.json()["code"] == "MISSING_API_KEY"
def test_auth_middleware_requires_key_for_malformed_proxy_port():
"""Malformed port (non-numeric) must get 401, not 422; limits unauthenticated surface."""
app = _build_test_app()
@app.get("/sandboxes/{sandbox_id}/proxy/{port}/{full_path:path}")
def proxy_echo(sandbox_id: str, port: int, full_path: str):
return {"proxied": True}
client = TestClient(app)
response = client.get("/sandboxes/s1/proxy/not-a-port/x")
assert response.status_code == 401
assert response.json()["code"] == "MISSING_API_KEY"
def test_auth_middleware_proxy_requires_auth_in_multi_tenant_mode():
"""In multi-tenant mode, proxy paths must require auth to establish tenant context."""
from unittest.mock import MagicMock
from opensandbox_server.tenants.models import TenantEntry
app = FastAPI()
config = _app_config_with_api_key()
mock_provider = MagicMock()
tenant = TenantEntry(name="tenant-a", namespace="ns-a", api_keys=["key-a"])
mock_provider.lookup.return_value = tenant
app.add_middleware(AuthMiddleware, config=config, tenant_provider=mock_provider)
@app.get("/sandboxes/{sandbox_id}/proxy/{port}/{full_path:path}")
def proxy_echo(sandbox_id: str, port: int, full_path: str):
return {"proxied": True}
client = TestClient(app)
response = client.get("/sandboxes/abc/proxy/8080/foo")
assert response.status_code == 401, "proxy must require auth in multi-tenant mode"
response = client.get(
"/sandboxes/abc/proxy/8080/foo",
headers={"OPEN-SANDBOX-API-KEY": "key-a"},
)
assert response.status_code == 200
def test_auth_middleware_is_proxy_path_rejects_traversal():
"""Paths containing '..' are never considered proxy (no auth bypass)."""
assert AuthMiddleware._is_proxy_path("/sandboxes/abc/proxy/8080/../other") is False
assert AuthMiddleware._is_proxy_path("/sandboxes/../admin/proxy/8080") is False
def test_auth_middleware_is_proxy_path_accepts_valid_shapes():
"""Only exact proxy route shape (including numeric port) is accepted."""
assert AuthMiddleware._is_proxy_path("/sandboxes/id/proxy/8080") is True
assert AuthMiddleware._is_proxy_path("/sandboxes/id/proxy/8080/") is True
assert AuthMiddleware._is_proxy_path("/v1/sandboxes/id/proxy/443/path") is True
assert AuthMiddleware._is_proxy_path("/proxy/sandboxes/x") is False
assert AuthMiddleware._is_proxy_path("/foo/sandboxes/id/proxy/8080") is False
# Non-numeric port must not skip auth (malformed path → 401, not 422)
assert AuthMiddleware._is_proxy_path("/sandboxes/s1/proxy/not-a-port/x") is False
assert AuthMiddleware._is_proxy_path("/sandboxes/s1/proxy/8080x/") is False