diff --git a/docs/commands.md b/docs/commands.md index b9e5290..d0203a6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -430,6 +430,14 @@ This page contains example commands to help you choose models and configure Open --load-models model1 model2 ``` + To require API key authentication: + + ``` + openarc serve start --use-api-key + ``` + + When `--use-api-key` is passed, clients must authenticate with a `Bearer` token matching `OPENARC_API_KEY`. If the environment variable is not set, the server will not start. Without the flag, no authentication is required. + === "load" After using `openarc add` you can use `openarc load` to read the added configuration and load models onto the OpenArc server. diff --git a/docs/install.md b/docs/install.md index a601be2..cb0f4f8 100644 --- a/docs/install.md +++ b/docs/install.md @@ -34,11 +34,13 @@ Visit [OpenVINO System Requirments](https://docs.openvino.ai/2025/about-openvino uv pip install --pre -U openvino-genai --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly ``` - 4. Set your API key as an environment variable: + 4. Optionally, set an API key to authenticate clients connecting to the server: ``` export OPENARC_API_KEY=api-key ``` + Pass `--use-api-key` to `openarc serve start` to enforce authentication. See [serve](commands.md#serve) for details. + 5. To get started, run: ``` @@ -70,11 +72,13 @@ Visit [OpenVINO System Requirments](https://docs.openvino.ai/2025/about-openvino uv pip install --pre -U openvino-genai --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly ``` - 4. **Set your API key as an environment variable:** + 4. **Optionally, set an API key to authenticate clients connecting to the server:** ``` setx OPENARC_API_KEY openarc-api-key ``` + Pass `--use-api-key` to `openarc serve start` to enforce authentication. See [serve](commands.md#serve) for details. + 5. To get started, run: ``` @@ -103,11 +107,13 @@ Visit [OpenVINO System Requirments](https://docs.openvino.ai/2025/about-openvino Environment Variables ```bash - export OPENARC_API_KEY="openarc-api-key" # default, set it to whatever you want + export OPENARC_API_KEY="openarc-api-key" # optional — pass --use-api-key to openarc serve start to enforce export OPENARC_AUTOLOAD_MODEL="model_name" # model_name to load on startup export MODEL_PATH="/path/to/your/models" # mount your models to `/models` inside the container docker-compose up --build -d -``` + ``` + + Pass `--use-api-key` to `openarc serve start` to require clients to authenticate. See [serve](commands.md#serve) for details. Take a look at the [Dockerfile](Dockerfile) and [docker-compose](docker-compose.yaml) for more details. diff --git a/src/cli/groups/serve.py b/src/cli/groups/serve.py index a142281..dd45aa9 100644 --- a/src/cli/groups/serve.py +++ b/src/cli/groups/serve.py @@ -31,9 +31,11 @@ def serve(): @click.option("--load-models", "--lm", required=False, help="Load models on startup. Specify once followed by space-separated model names.") +@click.option("--use-api-key", is_flag=True, default=False, + help="Require OPENARC_API_KEY for all requests.") @click.argument('startup_models', nargs=-1, required=False) @click.pass_context -def start(ctx, host, port, load_models, startup_models): +def start(ctx, host, port, load_models, use_api_key, startup_models): """ - 'start' reads --host and --port from config or defaults to 0.0.0.0:8000 @@ -68,5 +70,15 @@ def start(ctx, host, port, load_models, startup_models): os.environ["OPENARC_STARTUP_MODELS"] = ",".join(models_to_load) console.print(f"[blue]Models to load on startup:[/blue] {', '.join(models_to_load)}\n") + if use_api_key: + if not os.getenv("OPENARC_API_KEY"): + console.print("[red]Error: You chose to require an API key but OPENARC_API_KEY has not been set.[/red]") + raise SystemExit(1) + os.environ["OPENARC_API_KEY_REQUIRED"] = "true" + console.print("[blue]OPENARC_API_KEY_REQUIRED=[/blue][green]True[/green] [dim][Clients connecting to the server must authenticate with OPENARC_API_KEY][/dim]") + else: + os.environ["OPENARC_API_KEY_REQUIRED"] = "false" + console.print("[blue]OPENARC_API_KEY_REQUIRED=[/blue][yellow]False[/yellow] [dim][Clients do not need to authenticate.][/dim]") + console.print(f"[green]Starting OpenArc server on {host}:{port}[/green]") start_server(host=host, port=port) diff --git a/src/server/main.py b/src/server/main.py index f39846c..5e46bf3 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -97,6 +97,7 @@ async def lifespan(app: FastAPI): except Exception as e: logger.error(f"Startup: failed to load '{name}': {e}") + logger.info(f"OPENARC_API_KEY_REQUIRED={AUTH_REQUIRED}") yield # Shutdown: (add cleanup here if needed) @@ -104,7 +105,8 @@ async def lifespan(app: FastAPI): # API key authentication API_KEY = os.getenv("OPENARC_API_KEY") -security = HTTPBearer() +AUTH_REQUIRED = os.getenv("OPENARC_API_KEY_REQUIRED", "false").lower() == "true" +security = HTTPBearer(auto_error=False) # Add request logging middleware (before CORS so it logs all requests) app.add_middleware(RequestLoggingMiddleware) @@ -120,14 +122,15 @@ async def lifespan(app: FastAPI): async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)): """Verify the API key provided in the Authorization header""" - if credentials.credentials != API_KEY: - logger.error(f"Invalid API key: {credentials.credentials}") + if not AUTH_REQUIRED: + return None + if credentials is None or credentials.credentials != API_KEY: + logger.error(f"Invalid API key: {credentials.credentials if credentials else 'missing'}") raise HTTPException( status_code=401, detail="Invalid API key", headers={"WWW-Authenticate": "Bearer"}, ) - return credentials.credentials @app.exception_handler(RequestValidationError)