From 0422c14f4f75eb7ff91750ee6cc6c14b429badd3 Mon Sep 17 00:00:00 2001 From: "zhoujiahui.01" Date: Tue, 7 Apr 2026 14:52:18 +0800 Subject: [PATCH] feat(auth): Restrict trusted mode without API key to localhost --- docs/en/guides/01-configuration.md | 4 ++-- docs/en/guides/04-authentication.md | 4 ++-- docs/zh/guides/04-authentication.md | 4 ++-- openviking/console/README.md | 2 +- openviking/server/app.py | 5 +++-- openviking/server/config.py | 23 ++++++++++++++++------- tests/server/test_auth.py | 14 +++++++++++--- 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/docs/en/guides/01-configuration.md b/docs/en/guides/01-configuration.md index f088bd90f..5dc7d0970 100644 --- a/docs/en/guides/01-configuration.md +++ b/docs/en/guides/01-configuration.md @@ -854,12 +854,12 @@ When running OpenViking as an HTTP service, add a `server` section to `ov.conf`: | `host` | str | Bind address | `0.0.0.0` | | `port` | int | Bind port | `1933` | | `auth_mode` | str | Authentication mode: `"api_key"` or `"trusted"`. Default is `"api_key"` | `"api_key"` | -| `root_api_key` | str | Root API key for multi-tenant auth in `api_key` mode. In `trusted` mode it is optional extra protection, not the source of user identity | `null` | +| `root_api_key` | str | Root API key for multi-tenant auth in `api_key` mode. In `trusted` mode it is optional on localhost, but required for any non-localhost deployment; it does not become the source of user identity | `null` | | `cors_origins` | list | Allowed CORS origins | `["*"]` | `api_key` mode uses API keys and is the default. `trusted` mode trusts `X-OpenViking-Account` / `X-OpenViking-User` headers from a trusted gateway or internal caller. -When `root_api_key` is configured in `api_key` mode, the server enables multi-tenant authentication. Use the Admin API to create accounts and user keys. In `trusted` mode, ordinary requests do not require user registration first; each request is resolved as `USER` from the injected identity headers. Development mode only applies when `auth_mode = "api_key"` and `root_api_key` is not set. +When `root_api_key` is configured in `api_key` mode, the server enables multi-tenant authentication. Use the Admin API to create accounts and user keys. In `trusted` mode, ordinary requests do not require user registration first; each request is resolved as `USER` from the injected identity headers. However, skipping `root_api_key` in `trusted` mode is allowed only on localhost. Development mode only applies when `auth_mode = "api_key"` and `root_api_key` is not set. For startup and deployment details see [Deployment](./03-deployment.md), for authentication see [Authentication](./04-authentication.md). diff --git a/docs/en/guides/04-authentication.md b/docs/en/guides/04-authentication.md index 2343bb9cb..8adb5ccd6 100644 --- a/docs/en/guides/04-authentication.md +++ b/docs/en/guides/04-authentication.md @@ -18,9 +18,9 @@ All API keys are plain random tokens with no embedded identity. The server resol | Mode | `server.auth_mode` | Identity Source | Typical Use | |------|--------------------|-----------------|-------------| | API key mode | `"api_key"` | API key, with optional tenant headers for root requests | Standard multi-tenant deployment | -| Trusted mode | `"trusted"` | `X-OpenViking-Account` / `X-OpenViking-User` / optional `X-OpenViking-Agent` headers | Behind a trusted gateway or internal network boundary | +| Trusted mode | `"trusted"` | `X-OpenViking-Account` / `X-OpenViking-User` / optional `X-OpenViking-Agent` headers, plus `root_api_key` on non-localhost deployments | Behind a trusted gateway or internal network boundary | -`api_key` is the default and standard production mode. `trusted` is an alternative mode for deployments where an upstream gateway or trusted internal caller injects identity headers on every request. +`api_key` is the default and standard production mode. `trusted` is an alternative mode for deployments where an upstream gateway or trusted internal caller injects identity headers on every request. In `trusted` mode, running without `root_api_key` is allowed only when the server binds to localhost; non-localhost `trusted` deployments must configure `root_api_key`. ## Setting Up (Server Side) diff --git a/docs/zh/guides/04-authentication.md b/docs/zh/guides/04-authentication.md index 3101df057..40e6d3a1a 100644 --- a/docs/zh/guides/04-authentication.md +++ b/docs/zh/guides/04-authentication.md @@ -18,9 +18,9 @@ OpenViking 使用两层 API Key 体系: | 模式 | `server.auth_mode` | 身份来源 | 典型使用场景 | |------|--------------------|----------|--------------| | API Key 模式 | `"api_key"` | API Key,root 请求可附带租户请求头 | 标准多租户部署 | -| Trusted 模式 | `"trusted"` | `X-OpenViking-Account` / `X-OpenViking-User` / 可选 `X-OpenViking-Agent` 请求头 | 部署在受信网关或内网边界之后 | +| Trusted 模式 | `"trusted"` | `X-OpenViking-Account` / `X-OpenViking-User` / 可选 `X-OpenViking-Agent` 请求头;非 localhost 部署还必须配置 `root_api_key` | 部署在受信网关或内网边界之后 | -`api_key` 是默认模式,也是标准生产部署方式。`trusted` 是替代模式,适合由上游网关或受信内网调用方在每个请求里显式注入身份头。 +`api_key` 是默认模式,也是标准生产部署方式。`trusted` 是替代模式,适合由上游网关或受信内网调用方在每个请求里显式注入身份头。在 `trusted` 模式下,只有服务绑定到 localhost 时才允许不配置 `root_api_key`;只要是非 localhost 部署,就必须配置 `root_api_key`。 ## 服务端配置 diff --git a/openviking/console/README.md b/openviking/console/README.md index 09d19ba8d..0dd8be6de 100644 --- a/openviking/console/README.md +++ b/openviking/console/README.md @@ -30,7 +30,7 @@ http://127.0.0.1:8020/ ``` 4. In **Settings**, configure headers for your upstream auth mode. -`api_key` is the default server mode, so in that mode you normally paste `X-API-Key` and click **Save** (or press Enter). If the upstream server runs in `trusted` mode, you usually do not need `X-API-Key` for ordinary requests; instead set `X-OpenViking-Account` and `X-OpenViking-User` (and optionally `X-OpenViking-Agent`). +`api_key` is the default server mode, so in that mode you normally paste `X-API-Key` and click **Save** (or press Enter). If the upstream server runs in `trusted` mode, you can omit `X-API-Key` for ordinary requests only when that server is localhost-only and has no `root_api_key`; otherwise you still need `X-API-Key`, and you should also set `X-OpenViking-Account` and `X-OpenViking-User` (and optionally `X-OpenViking-Agent`). `X-API-Key` is stored locally in the browser and restored into the current tab. When the upstream server runs in `trusted` mode, ordinary access does not require user registration first. If you try account or user management actions against Admin API endpoints in `trusted` mode, the server now returns an explicit error explaining that `trusted` mode resolves requests as `USER` and that account/user management requires `api_key` mode with `root_api_key`. diff --git a/openviking/server/app.py b/openviking/server/app.py index fed0b128b..5e1a0e287 100644 --- a/openviking/server/app.py +++ b/openviking/server/app.py @@ -96,8 +96,9 @@ async def lifespan(app: FastAPI): else: logger.warning( "Trusted mode enabled: authentication uses X-OpenViking-Account/User/Agent " - "headers without API keys. Only expose this server behind a trusted " - "network boundary or identity-injecting gateway." + "headers without API keys. This is only allowed on localhost. " + "Only expose this server behind a trusted network boundary or " + "identity-injecting gateway after configuring server.root_api_key." ) else: app.state.api_key_manager = None diff --git a/openviking/server/config.py b/openviking/server/config.py index 5e68f1d58..551141b83 100644 --- a/openviking/server/config.py +++ b/openviking/server/config.py @@ -135,13 +135,22 @@ def validate_server_config(config: ServerConfig) -> None: sys.exit(1) if config.auth_mode == "trusted": - if not _is_localhost(config.host): - logger.warning( - "SECURITY: server.auth_mode='trusted' on non-localhost host '%s'. " - "Only use this behind a trusted network boundary or identity-injecting gateway.", - config.host, - ) - return + if config.root_api_key: + return + if _is_localhost(config.host): + return + logger.error( + "SECURITY: server.auth_mode='trusted' requires server.root_api_key when " + "server.host is '%s' (non-localhost). Only localhost trusted mode may run " + "without an API key.", + config.host, + ) + logger.error( + "To fix, either:\n" + " 1. Set server.root_api_key in ov.conf, or\n" + ' 2. Bind trusted mode to localhost (server.host = "127.0.0.1")' + ) + sys.exit(1) if config.root_api_key: return diff --git a/tests/server/test_auth.py b/tests/server/test_auth.py index dd36b1a90..aa3a7e152 100644 --- a/tests/server/test_auth.py +++ b/tests/server/test_auth.py @@ -784,7 +784,15 @@ def test_validate_with_key_any_host_passes(): validate_server_config(config) # should not raise -def test_validate_trusted_mode_without_key_non_localhost_passes(): - """Trusted mode should bypass the localhost-only dev-mode restriction.""" +def test_validate_trusted_mode_without_key_localhost_passes(): + """Trusted mode without root_api_key should still be allowed on localhost only.""" + for host in ("127.0.0.1", "localhost", "::1"): + config = ServerConfig(host=host, root_api_key=None, auth_mode="trusted") + validate_server_config(config) + + +def test_validate_trusted_mode_without_key_non_localhost_raises(): + """Trusted mode without root_api_key should be rejected off localhost.""" config = ServerConfig(host="0.0.0.0", root_api_key=None, auth_mode="trusted") - validate_server_config(config) + with pytest.raises(SystemExit): + validate_server_config(config)