mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-26 18:06:00 +00:00
fix(auth): share internal gateway token across workers (#3184)
* fix(auth): share internal gateway token across workers * fix: restore deploy script executable bit * Update deploy.sh to skip the auth_token setup for the down command --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
@@ -50,6 +50,11 @@ INFOQUEST_API_KEY=your-infoquest-api-key
|
||||
# Set to "false" to disable Swagger UI, ReDoc, and OpenAPI schema in production
|
||||
# GATEWAY_ENABLE_DOCS=false
|
||||
|
||||
# Shared internal Gateway auth token for multi-worker deployments.
|
||||
# `make up` generates and persists this automatically; set it manually only
|
||||
# when you run Gateway workers outside the bundled deploy script.
|
||||
# DEER_FLOW_INTERNAL_AUTH_TOKEN=your-shared-internal-token
|
||||
|
||||
# ── Frontend SSR → Gateway wiring ─────────────────────────────────────────────
|
||||
# The Next.js server uses these to reach the Gateway during SSR (auth checks,
|
||||
# /api/* rewrites). They default to localhost values that match `make dev` and
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
"""Process-local authentication for Gateway internal callers."""
|
||||
"""Authentication for trusted Gateway internal callers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import secrets
|
||||
from types import SimpleNamespace
|
||||
|
||||
from deerflow.runtime.user_context import DEFAULT_USER_ID
|
||||
|
||||
INTERNAL_AUTH_HEADER_NAME = "X-DeerFlow-Internal-Token"
|
||||
_INTERNAL_AUTH_TOKEN = secrets.token_urlsafe(32)
|
||||
INTERNAL_AUTH_ENV_VAR = "DEER_FLOW_INTERNAL_AUTH_TOKEN"
|
||||
|
||||
|
||||
def _load_internal_auth_token() -> str:
|
||||
token = os.environ.get(INTERNAL_AUTH_ENV_VAR)
|
||||
if token:
|
||||
return token
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
_INTERNAL_AUTH_TOKEN = _load_internal_auth_token()
|
||||
|
||||
|
||||
def create_internal_auth_headers() -> dict[str, str]:
|
||||
"""Return headers that authenticate same-process Gateway internal calls."""
|
||||
"""Return headers that authenticate trusted Gateway internal calls."""
|
||||
return {INTERNAL_AUTH_HEADER_NAME: _INTERNAL_AUTH_TOKEN}
|
||||
|
||||
|
||||
def is_valid_internal_auth_token(token: str | None) -> bool:
|
||||
"""Return True when *token* matches the process-local internal token."""
|
||||
"""Return True when *token* matches this Gateway worker's internal token."""
|
||||
return bool(token) and secrets.compare_digest(token, _INTERNAL_AUTH_TOKEN)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Tests for Gateway internal auth token handling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
|
||||
|
||||
def test_internal_auth_uses_shared_env_token(monkeypatch):
|
||||
import app.gateway.internal_auth as internal_auth
|
||||
|
||||
monkeypatch.setenv("DEER_FLOW_INTERNAL_AUTH_TOKEN", "shared-token")
|
||||
reloaded = importlib.reload(internal_auth)
|
||||
try:
|
||||
headers = reloaded.create_internal_auth_headers()
|
||||
|
||||
assert headers[reloaded.INTERNAL_AUTH_HEADER_NAME] == "shared-token"
|
||||
assert reloaded.is_valid_internal_auth_token("shared-token") is True
|
||||
assert reloaded.is_valid_internal_auth_token("other-token") is False
|
||||
finally:
|
||||
monkeypatch.delenv("DEER_FLOW_INTERNAL_AUTH_TOKEN", raising=False)
|
||||
importlib.reload(reloaded)
|
||||
|
||||
|
||||
def test_internal_auth_generates_process_local_fallback(monkeypatch):
|
||||
import app.gateway.internal_auth as internal_auth
|
||||
|
||||
monkeypatch.delenv("DEER_FLOW_INTERNAL_AUTH_TOKEN", raising=False)
|
||||
reloaded = importlib.reload(internal_auth)
|
||||
try:
|
||||
token = reloaded.create_internal_auth_headers()[reloaded.INTERNAL_AUTH_HEADER_NAME]
|
||||
|
||||
assert token
|
||||
assert reloaded.is_valid_internal_auth_token(token) is True
|
||||
finally:
|
||||
importlib.reload(reloaded)
|
||||
@@ -168,6 +168,7 @@ services:
|
||||
- DEER_FLOW_HOME=/app/backend/.deer-flow
|
||||
- DEER_FLOW_CHANNELS_LANGGRAPH_URL=${DEER_FLOW_CHANNELS_LANGGRAPH_URL:-http://gateway:8001/api}
|
||||
- DEER_FLOW_CHANNELS_GATEWAY_URL=${DEER_FLOW_CHANNELS_GATEWAY_URL:-http://gateway:8001}
|
||||
- DEER_FLOW_INTERNAL_AUTH_TOKEN=${DEER_FLOW_INTERNAL_AUTH_TOKEN:-}
|
||||
- DEER_FLOW_HOST_BASE_DIR=${DEER_FLOW_ROOT}/backend/.deer-flow
|
||||
- DEER_FLOW_HOST_SKILLS_PATH=${DEER_FLOW_ROOT}/skills
|
||||
- DEER_FLOW_SANDBOX_HOST=host.docker.internal
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# DEER_FLOW_DOCKER_SOCKET — Docker socket path, default /var/run/docker.sock
|
||||
# DEER_FLOW_REPO_ROOT — repo root (used for skills host path in DooD)
|
||||
# BETTER_AUTH_SECRET — required for frontend auth/session security
|
||||
# DEER_FLOW_INTERNAL_AUTH_TOKEN — shared internal Gateway auth token for multi-worker IM channels
|
||||
#
|
||||
# LangSmith tracing is disabled by default (LANGSMITH_TRACING=false).
|
||||
# Set LANGSMITH_TRACING=true and LANGSMITH_API_KEY in .env to enable it.
|
||||
@@ -101,6 +102,7 @@ services:
|
||||
- DEER_FLOW_EXTENSIONS_CONFIG_PATH=/app/backend/extensions_config.json
|
||||
- DEER_FLOW_CHANNELS_LANGGRAPH_URL=${DEER_FLOW_CHANNELS_LANGGRAPH_URL:-http://gateway:8001/api}
|
||||
- DEER_FLOW_CHANNELS_GATEWAY_URL=${DEER_FLOW_CHANNELS_GATEWAY_URL:-http://gateway:8001}
|
||||
- DEER_FLOW_INTERNAL_AUTH_TOKEN=${DEER_FLOW_INTERNAL_AUTH_TOKEN}
|
||||
# DooD path/network translation
|
||||
- DEER_FLOW_HOST_BASE_DIR=${DEER_FLOW_HOME}
|
||||
- DEER_FLOW_HOST_SKILLS_PATH=${DEER_FLOW_REPO_ROOT}/skills
|
||||
|
||||
+34
-1
@@ -71,7 +71,7 @@ if [ -z "$DEER_FLOW_CONFIG_PATH" ]; then
|
||||
export DEER_FLOW_CONFIG_PATH="$REPO_ROOT/config.yaml"
|
||||
fi
|
||||
|
||||
if [ ! -f "$DEER_FLOW_CONFIG_PATH" ]; then
|
||||
if [ "$CMD" != "down" ] && [ ! -f "$DEER_FLOW_CONFIG_PATH" ]; then
|
||||
# Try to seed from repo (config.example.yaml is the canonical template)
|
||||
if [ -f "$REPO_ROOT/config.example.yaml" ]; then
|
||||
cp "$REPO_ROOT/config.example.yaml" "$DEER_FLOW_CONFIG_PATH"
|
||||
@@ -140,6 +140,38 @@ if [ -z "$BETTER_AUTH_SECRET" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── DEER_FLOW_INTERNAL_AUTH_TOKEN ────────────────────────────────────────────
|
||||
# Shared by all Gateway workers so channel workers can call internal Gateway
|
||||
# APIs even when the request is handled by a different Uvicorn worker.
|
||||
|
||||
_internal_auth_token_file="$DEER_FLOW_HOME/.internal-auth-token"
|
||||
if [ "$CMD" != "down" ] && [ -z "$DEER_FLOW_INTERNAL_AUTH_TOKEN" ]; then
|
||||
if [ -f "$_internal_auth_token_file" ]; then
|
||||
export DEER_FLOW_INTERNAL_AUTH_TOKEN
|
||||
DEER_FLOW_INTERNAL_AUTH_TOKEN="$(cat "$_internal_auth_token_file")"
|
||||
echo -e "${GREEN}✓ DEER_FLOW_INTERNAL_AUTH_TOKEN loaded from $_internal_auth_token_file${NC}"
|
||||
else
|
||||
export DEER_FLOW_INTERNAL_AUTH_TOKEN
|
||||
if command -v python3 > /dev/null 2>&1 && \
|
||||
DEER_FLOW_INTERNAL_AUTH_TOKEN="$(python3 -c 'import sys; sys.version_info >= (3, 6) or sys.exit(1); import secrets; print(secrets.token_urlsafe(32))' 2>/dev/null)"; then
|
||||
true
|
||||
elif command -v python > /dev/null 2>&1 && \
|
||||
DEER_FLOW_INTERNAL_AUTH_TOKEN="$(python -c 'import sys; sys.version_info >= (3, 6) or sys.exit(1); import secrets; print(secrets.token_urlsafe(32))' 2>/dev/null)"; then
|
||||
true
|
||||
elif command -v openssl > /dev/null 2>&1 && \
|
||||
DEER_FLOW_INTERNAL_AUTH_TOKEN="$(openssl rand -hex 32)"; then
|
||||
true
|
||||
else
|
||||
echo -e "${RED}✗ Cannot generate DEER_FLOW_INTERNAL_AUTH_TOKEN: python3, python, and openssl are all unavailable.${NC}" >&2
|
||||
echo -e "${RED} Set DEER_FLOW_INTERNAL_AUTH_TOKEN manually before running make up.${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$DEER_FLOW_INTERNAL_AUTH_TOKEN" > "$_internal_auth_token_file"
|
||||
chmod 600 "$_internal_auth_token_file"
|
||||
echo -e "${GREEN}✓ DEER_FLOW_INTERNAL_AUTH_TOKEN generated → $_internal_auth_token_file${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── detect_sandbox_mode ───────────────────────────────────────────────────────
|
||||
|
||||
detect_sandbox_mode() {
|
||||
@@ -186,6 +218,7 @@ if [ "$CMD" = "down" ]; then
|
||||
export DEER_FLOW_DOCKER_SOCKET="${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}"
|
||||
export DEER_FLOW_REPO_ROOT="${DEER_FLOW_REPO_ROOT:-$REPO_ROOT}"
|
||||
export BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET:-placeholder}"
|
||||
export DEER_FLOW_INTERNAL_AUTH_TOKEN="${DEER_FLOW_INTERNAL_AUTH_TOKEN:-placeholder}"
|
||||
"${COMPOSE_CMD[@]}" down
|
||||
exit 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user