feat: add --admin-api-key for finer-grained endpoint auth (#15908)
Co-authored-by: Simo Lin <linsimo.mark@gmail.com>
This commit is contained in:
@@ -141,7 +141,6 @@ from sglang.srt.parser.reasoning_parser import ReasoningParser
|
||||
from sglang.srt.server_args import PortArgs, ServerArgs
|
||||
from sglang.srt.tracing.trace import process_tracing_init, trace_set_thread_info
|
||||
from sglang.srt.utils import (
|
||||
add_api_key_middleware,
|
||||
add_prometheus_middleware,
|
||||
add_prometheus_track_response_middleware,
|
||||
delete_directory,
|
||||
@@ -149,6 +148,7 @@ from sglang.srt.utils import (
|
||||
kill_process_tree,
|
||||
set_uvicorn_logging_configs,
|
||||
)
|
||||
from sglang.srt.utils.auth import AuthLevel, app_has_admin_force_endpoints, auth_level
|
||||
from sglang.utils import get_exception_traceback
|
||||
from sglang.version import __version__
|
||||
|
||||
@@ -607,6 +607,7 @@ async def get_load():
|
||||
# example usage:
|
||||
# curl -s -X POST http://localhost:30000/set_internal_state -H "Content-Type: application/json" -d '{"server_args": {"pp_max_micro_batch_size": 8}}'
|
||||
@app.api_route("/set_internal_state", methods=["POST", "PUT"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def set_internal_state(obj: SetInternalStateReq, request: Request):
|
||||
res = await _global_state.tokenizer_manager.set_internal_state(obj)
|
||||
return res
|
||||
@@ -699,6 +700,7 @@ async def classify_request(obj: EmbeddingReqInput, request: Request):
|
||||
|
||||
|
||||
@app.api_route("/flush_cache", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def flush_cache():
|
||||
"""Flush the radix cache."""
|
||||
ret = await _global_state.tokenizer_manager.flush_cache()
|
||||
@@ -710,6 +712,7 @@ async def flush_cache():
|
||||
|
||||
|
||||
@app.api_route("/clear_hicache_storage_backend", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def clear_hicache_storage_backend():
|
||||
"""Clear the hierarchical cache storage backend."""
|
||||
ret = await _global_state.tokenizer_manager.clear_hicache_storage()
|
||||
@@ -720,6 +723,7 @@ async def clear_hicache_storage_backend():
|
||||
|
||||
|
||||
@app.api_route("/start_profile", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def start_profile_async(obj: Optional[ProfileReqInput] = None):
|
||||
"""Start profiling."""
|
||||
if obj is None:
|
||||
@@ -744,6 +748,7 @@ async def start_profile_async(obj: Optional[ProfileReqInput] = None):
|
||||
|
||||
|
||||
@app.api_route("/stop_profile", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def stop_profile_async():
|
||||
"""Stop profiling."""
|
||||
await _global_state.tokenizer_manager.stop_profile()
|
||||
@@ -754,6 +759,7 @@ async def stop_profile_async():
|
||||
|
||||
|
||||
@app.api_route("/freeze_gc", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def freeze_gc_async():
|
||||
"""
|
||||
See engine.freeze_gc for more details.
|
||||
@@ -766,6 +772,7 @@ async def freeze_gc_async():
|
||||
|
||||
|
||||
@app.api_route("/start_expert_distribution_record", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def start_expert_distribution_record_async():
|
||||
"""Start recording the expert distribution. Clear the previous record if any."""
|
||||
await _global_state.tokenizer_manager.start_expert_distribution_record()
|
||||
@@ -776,6 +783,7 @@ async def start_expert_distribution_record_async():
|
||||
|
||||
|
||||
@app.api_route("/stop_expert_distribution_record", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def stop_expert_distribution_record_async():
|
||||
"""Stop recording the expert distribution."""
|
||||
await _global_state.tokenizer_manager.stop_expert_distribution_record()
|
||||
@@ -786,6 +794,7 @@ async def stop_expert_distribution_record_async():
|
||||
|
||||
|
||||
@app.api_route("/dump_expert_distribution_record", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def dump_expert_distribution_record_async():
|
||||
"""Dump expert distribution record."""
|
||||
await _global_state.tokenizer_manager.dump_expert_distribution_record()
|
||||
@@ -796,6 +805,7 @@ async def dump_expert_distribution_record_async():
|
||||
|
||||
|
||||
@app.post("/update_weights_from_disk")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def update_weights_from_disk(obj: UpdateWeightFromDiskReqInput, request: Request):
|
||||
"""Update the weights from disk inplace without re-launching the server."""
|
||||
success, message, num_paused_requests = (
|
||||
@@ -820,6 +830,7 @@ async def update_weights_from_disk(obj: UpdateWeightFromDiskReqInput, request: R
|
||||
|
||||
|
||||
@app.post("/init_weights_send_group_for_remote_instance")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def init_weights_send_group_for_remote_instance(
|
||||
obj: InitWeightsSendGroupForRemoteInstanceReqInput, request: Request
|
||||
):
|
||||
@@ -836,6 +847,7 @@ async def init_weights_send_group_for_remote_instance(
|
||||
|
||||
|
||||
@app.post("/send_weights_to_remote_instance")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def send_weights_to_remote_instance(
|
||||
obj: SendWeightsToRemoteInstanceReqInput, request: Request
|
||||
):
|
||||
@@ -852,6 +864,7 @@ async def send_weights_to_remote_instance(
|
||||
|
||||
|
||||
@app.get("/get_remote_instance_transfer_engine_info")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def get_remote_instance_transfer_engine_info(rank: int = None):
|
||||
if rank is None or rank < 0:
|
||||
return Response(status_code=HTTPStatus.BAD_REQUEST)
|
||||
@@ -876,6 +889,7 @@ async def get_remote_instance_transfer_engine_info(rank: int = None):
|
||||
|
||||
|
||||
@app.post("/init_weights_update_group")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def init_weights_update_group(
|
||||
obj: InitWeightsUpdateGroupReqInput, request: Request
|
||||
):
|
||||
@@ -891,6 +905,7 @@ async def init_weights_update_group(
|
||||
|
||||
|
||||
@app.post("/destroy_weights_update_group")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def destroy_weights_update_group(
|
||||
obj: DestroyWeightsUpdateGroupReqInput, request: Request
|
||||
):
|
||||
@@ -905,6 +920,7 @@ async def destroy_weights_update_group(
|
||||
|
||||
|
||||
@app.post("/update_weights_from_tensor")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def update_weights_from_tensor(
|
||||
obj: UpdateWeightsFromTensorReqInput, request: Request
|
||||
):
|
||||
@@ -926,6 +942,7 @@ async def update_weights_from_tensor(
|
||||
|
||||
|
||||
@app.post("/update_weights_from_distributed")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def update_weights_from_distributed(
|
||||
obj: UpdateWeightsFromDistributedReqInput, request: Request
|
||||
):
|
||||
@@ -944,6 +961,7 @@ async def update_weights_from_distributed(
|
||||
|
||||
|
||||
@app.post("/update_weights_from_ipc")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def update_weights_from_ipc(obj: UpdateWeightsFromIPCReqInput, request: Request):
|
||||
"""Update the weights from IPC (Inter-Process Communication) for checkpoint-engine integration."""
|
||||
success, message = await _global_state.tokenizer_manager.update_weights_from_ipc(
|
||||
@@ -960,6 +978,7 @@ async def update_weights_from_ipc(obj: UpdateWeightsFromIPCReqInput, request: Re
|
||||
|
||||
|
||||
@app.post("/update_weight_version")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def update_weight_version(obj: UpdateWeightVersionReqInput, request: Request):
|
||||
"""Update the weight version. This operation requires no active requests."""
|
||||
if obj.abort_all_requests:
|
||||
@@ -990,6 +1009,7 @@ async def update_weight_version(obj: UpdateWeightVersionReqInput, request: Reque
|
||||
|
||||
|
||||
@app.api_route("/get_weights_by_name", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def get_weights_by_name(obj: GetWeightsByNameReqInput, request: Request):
|
||||
"""Get model parameter by name."""
|
||||
try:
|
||||
@@ -1003,6 +1023,7 @@ async def get_weights_by_name(obj: GetWeightsByNameReqInput, request: Request):
|
||||
|
||||
|
||||
@app.api_route("/release_memory_occupation", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def release_memory_occupation(
|
||||
obj: ReleaseMemoryOccupationReqInput, request: Request
|
||||
):
|
||||
@@ -1014,6 +1035,7 @@ async def release_memory_occupation(
|
||||
|
||||
|
||||
@app.api_route("/resume_memory_occupation", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def resume_memory_occupation(
|
||||
obj: ResumeMemoryOccupationReqInput, request: Request
|
||||
):
|
||||
@@ -1025,6 +1047,7 @@ async def resume_memory_occupation(
|
||||
|
||||
|
||||
@app.post("/weights_checker")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def check_weights(obj: CheckWeightsReqInput, request: Request):
|
||||
success, message = await _global_state.tokenizer_manager.check_weights(obj, request)
|
||||
return ORJSONResponse(
|
||||
@@ -1034,6 +1057,7 @@ async def check_weights(obj: CheckWeightsReqInput, request: Request):
|
||||
|
||||
|
||||
@app.api_route("/slow_down", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def slow_down(obj: SlowDownReqInput, request: Request):
|
||||
"""Slow down the system deliberately. Only for testing. Example scenario:
|
||||
when we want to test performance of D in large-scale PD disaggregation and have no enough nodes for P,
|
||||
@@ -1047,6 +1071,7 @@ async def slow_down(obj: SlowDownReqInput, request: Request):
|
||||
|
||||
|
||||
@app.api_route("/load_lora_adapter", methods=["POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def load_lora_adapter(obj: LoadLoRAAdapterReqInput, request: Request):
|
||||
"""Load a new LoRA adapter without re-launching the server."""
|
||||
result = await _global_state.tokenizer_manager.load_lora_adapter(obj, request)
|
||||
@@ -1079,6 +1104,7 @@ async def load_lora_adapter_from_tensors(
|
||||
|
||||
|
||||
@app.api_route("/unload_lora_adapter", methods=["POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def unload_lora_adapter(obj: UnloadLoRAAdapterReqInput, request: Request):
|
||||
"""Load a new LoRA adapter without re-launching the server."""
|
||||
result = await _global_state.tokenizer_manager.unload_lora_adapter(obj, request)
|
||||
@@ -1120,6 +1146,7 @@ async def close_session(obj: CloseSessionReqInput, request: Request):
|
||||
|
||||
|
||||
@app.api_route("/configure_logging", methods=["GET", "POST"])
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def configure_logging(obj: ConfigureLoggingReq, request: Request):
|
||||
"""Configure the request logging options."""
|
||||
_global_state.tokenizer_manager.configure_logging(obj)
|
||||
@@ -1127,6 +1154,7 @@ async def configure_logging(obj: ConfigureLoggingReq, request: Request):
|
||||
|
||||
|
||||
@app.post("/abort_request")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def abort_request(obj: AbortReq, request: Request):
|
||||
"""Abort a request."""
|
||||
try:
|
||||
@@ -1181,6 +1209,7 @@ async def separate_reasoning_request(obj: SeparateReasoningReqInput, request: Re
|
||||
|
||||
|
||||
@app.post("/pause_generation")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def pause_generation(obj: PauseGenerationReqInput, request: Request):
|
||||
"""Pause generation."""
|
||||
await _global_state.tokenizer_manager.pause_generation(obj)
|
||||
@@ -1191,6 +1220,7 @@ async def pause_generation(obj: PauseGenerationReqInput, request: Request):
|
||||
|
||||
|
||||
@app.post("/continue_generation")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
async def continue_generation(obj: ContinueGenerationReqInput, request: Request):
|
||||
"""Continue generation."""
|
||||
await _global_state.tokenizer_manager.continue_generation(obj)
|
||||
@@ -1750,8 +1780,23 @@ def launch_server(
|
||||
|
||||
# Add api key authorization
|
||||
# This is only supported in single tokenizer mode.
|
||||
if server_args.api_key:
|
||||
add_api_key_middleware(app, server_args.api_key)
|
||||
#
|
||||
# Backward compatibility:
|
||||
# - api_key only: behavior matches legacy (all endpoints require api_key)
|
||||
# - no keys: legacy had no restriction; ADMIN_FORCE endpoints must still be rejected when
|
||||
# admin_api_key is not configured.
|
||||
if (
|
||||
server_args.api_key
|
||||
or server_args.admin_api_key
|
||||
or app_has_admin_force_endpoints(app)
|
||||
):
|
||||
from sglang.srt.utils.auth import add_api_key_middleware
|
||||
|
||||
add_api_key_middleware(
|
||||
app,
|
||||
api_key=server_args.api_key,
|
||||
admin_api_key=server_args.admin_api_key,
|
||||
)
|
||||
else:
|
||||
# If it is multi-tokenizer mode, we need to write the arguments to shared memory
|
||||
# for other worker processes to read.
|
||||
|
||||
Reference in New Issue
Block a user