mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-25 12:01:04 +02:00
fix: add a devops secret header
This commit is contained in:
parent
2750b770f7
commit
ae2663fcc4
10 changed files with 214 additions and 11 deletions
|
|
@ -12,6 +12,11 @@ UI_APP_URL="http://localhost:3000"
|
|||
DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/postgres"
|
||||
REDIS_URL="redis://:redissecret@localhost:6379"
|
||||
|
||||
# Internal devops secret for deployment scripts and lifecycle hooks.
|
||||
# scripts/rolling_update.sh sends this to protected operational endpoints via
|
||||
# X-Dograh-Devops-Secret. Use a unique random value in production.
|
||||
DOGRAH_DEVOPS_SECRET="change-me-dograh-devops-secret"
|
||||
|
||||
# AWS S3 Configuration
|
||||
ENABLE_AWS_S3="false"
|
||||
# AWS_ACCESS_KEY_ID=""
|
||||
|
|
|
|||
|
|
@ -14,4 +14,6 @@ UI_APP_URL=http://localhost:3000
|
|||
DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/test_db"
|
||||
REDIS_URL="redis://:redissecret@localhost:6379/0"
|
||||
|
||||
DOGRAH_DEVOPS_SECRET="test-dograh-devops-secret"
|
||||
|
||||
MINIO_PUBLIC_ENDPOINT=http://localhost:9000
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ STACK_AUTH_PROJECT_ID = os.getenv("STACK_AUTH_PROJECT_ID")
|
|||
STACK_PUBLISHABLE_CLIENT_KEY = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
|
||||
DOGRAH_MPS_SECRET_KEY = os.getenv("DOGRAH_MPS_SECRET_KEY", None)
|
||||
MPS_API_URL = os.getenv("MPS_API_URL", "https://services.dograh.com")
|
||||
DOGRAH_DEVOPS_SECRET = os.getenv("DOGRAH_DEVOPS_SECRET") or None
|
||||
|
||||
# Storage Configuration
|
||||
ENABLE_AWS_S3 = os.getenv("ENABLE_AWS_S3", "false").lower() == "true"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from fastapi import APIRouter
|
||||
import secrets
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException, status
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -131,8 +134,35 @@ class ActiveCallsResponse(BaseModel):
|
|||
active_calls: int
|
||||
|
||||
|
||||
DOGRAH_DEVOPS_SECRET_HEADER = "X-Dograh-Devops-Secret"
|
||||
|
||||
|
||||
def _verify_devops_secret(
|
||||
configured_secret: str | None,
|
||||
provided_secret: str | None,
|
||||
) -> None:
|
||||
if not configured_secret:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Devops secret is not configured",
|
||||
)
|
||||
if not provided_secret or not secrets.compare_digest(
|
||||
provided_secret,
|
||||
configured_secret,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Forbidden",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/health/active-calls", response_model=ActiveCallsResponse)
|
||||
async def active_calls() -> ActiveCallsResponse:
|
||||
async def active_calls(
|
||||
x_dograh_devops_secret: Annotated[
|
||||
str | None,
|
||||
Header(alias=DOGRAH_DEVOPS_SECRET_HEADER),
|
||||
] = None,
|
||||
) -> ActiveCallsResponse:
|
||||
"""In-flight call count for THIS worker — the drain signal for deploys.
|
||||
|
||||
A deploy orchestrator polls this per worker and waits for zero before
|
||||
|
|
@ -141,6 +171,8 @@ async def active_calls() -> ActiveCallsResponse:
|
|||
count is per-process: one uvicorn per VM port (scripts/rolling_update.sh)
|
||||
or per Kubernetes pod (preStop hook). See api/services/pipecat/active_calls.py.
|
||||
"""
|
||||
from api.constants import DOGRAH_DEVOPS_SECRET
|
||||
from api.services.pipecat.active_calls import active_call_count
|
||||
|
||||
_verify_devops_secret(DOGRAH_DEVOPS_SECRET, x_dograh_devops_secret)
|
||||
return ActiveCallsResponse(active_calls=active_call_count())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ worker. The guarantees that matter for draining: register/unregister are
|
|||
idempotent, and the count only reaches zero when every registered run is gone.
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from api.routes import main as main_routes
|
||||
from api.services.pipecat import active_calls
|
||||
|
||||
|
||||
|
|
@ -14,6 +18,21 @@ def setup_function():
|
|||
active_calls._active_run_ids.clear()
|
||||
|
||||
|
||||
def _make_active_calls_client(
|
||||
monkeypatch,
|
||||
configured_secret: str | None = "test-dograh-devops-secret",
|
||||
) -> TestClient:
|
||||
monkeypatch.setattr("api.constants.DOGRAH_DEVOPS_SECRET", configured_secret)
|
||||
app = FastAPI()
|
||||
app.add_api_route(
|
||||
"/api/v1/health/active-calls",
|
||||
main_routes.active_calls,
|
||||
methods=["GET"],
|
||||
response_model=main_routes.ActiveCallsResponse,
|
||||
)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_starts_empty():
|
||||
assert active_calls.active_call_count() == 0
|
||||
|
||||
|
|
@ -51,3 +70,46 @@ def test_full_lifecycle_drains_to_zero():
|
|||
assert active_calls.active_call_count() == 1
|
||||
active_calls.unregister_active_call(42)
|
||||
assert active_calls.active_call_count() == 0
|
||||
|
||||
|
||||
def test_active_calls_route_requires_configured_secret(monkeypatch):
|
||||
client = _make_active_calls_client(monkeypatch, configured_secret=None)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/health/active-calls",
|
||||
headers={"X-Dograh-Devops-Secret": "test-dograh-devops-secret"},
|
||||
)
|
||||
|
||||
assert response.status_code == 503
|
||||
|
||||
|
||||
def test_active_calls_route_rejects_missing_secret_header(monkeypatch):
|
||||
client = _make_active_calls_client(monkeypatch)
|
||||
|
||||
response = client.get("/api/v1/health/active-calls")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_active_calls_route_rejects_wrong_secret(monkeypatch):
|
||||
client = _make_active_calls_client(monkeypatch)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/health/active-calls",
|
||||
headers={"X-Dograh-Devops-Secret": "wrong"},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_active_calls_route_returns_count_with_secret(monkeypatch):
|
||||
active_calls.register_active_call(42)
|
||||
client = _make_active_calls_client(monkeypatch)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/health/active-calls",
|
||||
headers={"X-Dograh-Devops-Secret": "test-dograh-devops-secret"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"active_calls": 1}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue