fix(router): separate runtime and backend credentials - #5396
Conversation
There was a problem hiding this comment.
Code Review
This pull request separates internal and external credentials in the token router by introducing a custom x-xinference-backend-authorization header to forward external credentials when an internal token is configured. It also updates the backend router to promote this header and adds corresponding unit tests. However, a security review identified a potential leak where the secret internal token could be forwarded to the backend if no external authorization or backend API key is provided. A code suggestion has been provided to prevent this leak.
| headers: dict[str, str] = {} | ||
| backend_authorization = "" | ||
| for key_bytes, value_bytes in incoming: | ||
| key = key_bytes.decode("latin-1").lower() | ||
| if key in FORWARDED_REQUEST_HEADERS: | ||
| headers[key] = value_bytes.decode("latin-1") | ||
| value = value_bytes.decode("latin-1") | ||
| if key == TOKEN_ROUTER_BACKEND_AUTHORIZATION_HEADER: | ||
| backend_authorization = value | ||
| elif key in FORWARDED_REQUEST_HEADERS: | ||
| headers[key] = value | ||
| if backend_api_key: | ||
| headers["authorization"] = f"Bearer {backend_api_key}" | ||
| elif backend_authorization: | ||
| headers["authorization"] = backend_authorization |
There was a problem hiding this comment.
If internal_token is configured, the incoming Authorization header to the Runtime is the internal token. If the external user did not provide an authorization header (meaning backend_authorization is empty) and backend_api_key is also empty, the current implementation will forward the internal token to the backend because "authorization" is in FORWARDED_REQUEST_HEADERS. This leaks the secret internal token to the backend.
To prevent this, we should check if the incoming Authorization header matches the configured internal token, and if so, avoid forwarding it.
import os
headers: dict[str, str] = {}
backend_authorization = ""
internal_token = os.getenv("XINFERENCE_TOKEN_ROUTER_DATA_PLANE_TOKEN") or os.getenv(
"XINFERENCE_TOKEN_ROUTER_INTERNAL_TOKEN"
)
for key_bytes, value_bytes in incoming:
key = key_bytes.decode("latin-1").lower()
value = value_bytes.decode("latin-1")
if key == TOKEN_ROUTER_BACKEND_AUTHORIZATION_HEADER:
backend_authorization = value
elif key in FORWARDED_REQUEST_HEADERS:
if key == "authorization" and internal_token and value == f"Bearer {internal_token}":
continue
headers[key] = value
if backend_api_key:
headers["authorization"] = f"Bearer {backend_api_key}"
elif backend_authorization:
headers["authorization"] = backend_authorization|
Fixed in commit
Validation: |
Summary
backend_api_keyand never forward the internal header upstreamDependency
Validation
pytest -q xinference/router/tests/test_app.py xinference/api/tests/test_token_router_dispatch.pypre-commit run --files xinference/router/constants.py xinference/router/backend.py xinference/router/tests/test_app.py xinference/api/restful_api.py xinference/api/tests/test_token_router_dispatch.py