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:
DustHunter
2026-05-16 01:29:27 +00:00
committed by GitHub
co-authored by Qwen-Coder wang.yuqi
parent 87a2adcb43
commit 39c67d714e
3 changed files with 55 additions and 7 deletions
+4 -2
View File
@@ -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"],
+5 -2
View File
@@ -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)