mirror of
https://github.com/vllm-project/vllm.git
synced 2026-08-23 22:20:15 +00:00
fix: add API key authorization to /v2 endpoints (#42594)
Signed-off-by: DustHunter <[email protected]> Signed-off-by: wang.yuqi <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: wang.yuqi <[email protected]>
This commit is contained in:
co-authored by
Qwen-Coder
wang.yuqi
parent
87a2adcb43
commit
39c67d714e
@@ -128,7 +128,7 @@ firewall configuration instructions.
|
||||
|
||||
### Overview
|
||||
|
||||
The `--api-key` flag (or `VLLM_API_KEY` environment variable) provides authentication for vLLM's HTTP server, but **only for OpenAI-compatible API endpoints under the `/v1` path prefix**. Many other sensitive endpoints are exposed on the same HTTP server without any authentication enforcement.
|
||||
The `--api-key` flag (or `VLLM_API_KEY` environment variable) provides authentication for vLLM's HTTP server, but **only for OpenAI-compatible API endpoints under the `/v1` path prefix**, and other similar `/v2`, `/inference` path prefix**. Many other sensitive endpoints are exposed on the same HTTP server without any authentication enforcement.
|
||||
|
||||
**Important:** Do not rely exclusively on `--api-key` for securing access to vLLM. Additional security measures are required for production deployments.
|
||||
|
||||
@@ -154,6 +154,9 @@ When `--api-key` is configured, the following `/v1` endpoints require Bearer tok
|
||||
- `/v1/rerank` - Reranking API
|
||||
- `/v1/load_lora_adapter` - Load a LoRA adapter (can alter model behavior; only available when `--enable-lora` is set and `VLLM_ALLOW_RUNTIME_LORA_UPDATING=True`)
|
||||
- `/v1/unload_lora_adapter` - Unload a LoRA adapter (can alter model behavior; only available when `--enable-lora` is set and `VLLM_ALLOW_RUNTIME_LORA_UPDATING=True`)
|
||||
- `/inference/v1/generate` - Generate completions
|
||||
- `/v2/embed` - Cohere Embed API
|
||||
- `/v2/rerank` - Cohere Rerank API
|
||||
|
||||
### Unprotected Endpoints (No API Key Required)
|
||||
|
||||
@@ -162,7 +165,6 @@ The following endpoints **do not require authentication** even when `--api-key`
|
||||
**Inference endpoints:**
|
||||
|
||||
- `/invocations` - SageMaker-compatible endpoint (routes to the same inference functions as `/v1` endpoints)
|
||||
- `/inference/v1/generate` - Generate completions
|
||||
- `/generative_scoring` - Generative scoring API
|
||||
- `/pooling` - Pooling API
|
||||
- `/classify` - Classification API
|
||||
|
||||
@@ -86,13 +86,56 @@ async def test_passed_api_token(server: RemoteOpenAIServer):
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_not_v1_api_token(server: RemoteOpenAIServer):
|
||||
# Authorization check is skipped for any paths that
|
||||
# don't start with /v1 (e.g. /v1/chat/completions).
|
||||
async def test_not_v1_or_v2_path_skips_auth(server: RemoteOpenAIServer):
|
||||
# Authorization check is skipped for paths that
|
||||
# don't start with /v1 or /v2 (e.g. /health, /metrics).
|
||||
response = requests.get(server.url_for("health"))
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /v2 path authentication tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"server",
|
||||
[["--api-key", "test"]],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_v2_endpoint_rejects_missing_api_token(server: RemoteOpenAIServer):
|
||||
# /v2/embed should require authentication when --api-key is set.
|
||||
body = {
|
||||
"model": MODEL_NAME,
|
||||
"texts": ["hello"],
|
||||
"embedding_types": ["float"],
|
||||
}
|
||||
response = requests.post(server.url_for("/v2/embed"), json=body)
|
||||
assert response.status_code == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"server",
|
||||
[["--api-key", "test"]],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_v2_endpoint_accepts_valid_api_token(server: RemoteOpenAIServer):
|
||||
# /v2/embed should accept requests with a valid API key.
|
||||
body = {
|
||||
"model": MODEL_NAME,
|
||||
"texts": ["hello"],
|
||||
"embedding_types": ["float"],
|
||||
}
|
||||
response = requests.post(
|
||||
server.url_for("/v2/embed"),
|
||||
json=body,
|
||||
headers={"Authorization": "Bearer test"},
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"server",
|
||||
["--enable-request-id-headers"],
|
||||
|
||||
@@ -35,6 +35,9 @@ from vllm.v1.engine.exceptions import EngineDeadError, EngineGenerateError
|
||||
logger = init_logger("vllm.entrypoints.openai.server_utils")
|
||||
|
||||
|
||||
GUARDED_PREFIX = ("/v1", "/v2", "/inference")
|
||||
|
||||
|
||||
class AuthenticationMiddleware:
|
||||
"""
|
||||
Pure ASGI middleware that authenticates each request by checking
|
||||
@@ -44,7 +47,7 @@ class AuthenticationMiddleware:
|
||||
-----
|
||||
There are two cases in which authentication is skipped:
|
||||
1. The HTTP method is OPTIONS.
|
||||
2. The request path doesn't start with /v1 (e.g. /health).
|
||||
2. The request path doesn't start with GUARDED_PREFIX (e.g. /health).
|
||||
"""
|
||||
|
||||
def __init__(self, app: ASGIApp, tokens: list[str]) -> None:
|
||||
@@ -80,7 +83,7 @@ class AuthenticationMiddleware:
|
||||
url_path = URL(scope=scope).path.removeprefix(root_path)
|
||||
headers = Headers(scope=scope)
|
||||
# Type narrow to satisfy mypy.
|
||||
if url_path.startswith("/v1") and not self.verify_token(headers):
|
||||
if url_path.startswith(GUARDED_PREFIX) and not self.verify_token(headers):
|
||||
response = JSONResponse(content={"error": "Unauthorized"}, status_code=401)
|
||||
return response(scope, receive, send)
|
||||
return self.app(scope, receive, send)
|
||||
|
||||
Reference in New Issue
Block a user