From 6bae94fa0bfeb8961e5f88f28a52172f45d4de87 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 11 Jun 2026 11:46:43 +0800 Subject: [PATCH] fix(api): restore Alertmanager project context --- apps/api/src/main.py | 30 +++++++--- .../test_alertmanager_project_context.py | 55 +++++++++++++++++++ 2 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 apps/api/tests/test_alertmanager_project_context.py diff --git a/apps/api/src/main.py b/apps/api/src/main.py index 67809ede..e357289e 100644 --- a/apps/api/src/main.py +++ b/apps/api/src/main.py @@ -120,6 +120,26 @@ from src.workers import close_signal_worker, init_signal_worker setup_logging() logger = get_logger("awoooi.api") +ALERTMANAGER_WEBHOOK_PATH = "/api/v1/webhooks/alertmanager" +ALERTMANAGER_DEFAULT_PROJECT_ID = "awoooi" + + +def _resolve_request_project_context(request: Request) -> tuple[str | None, str]: + """Resolve tenant context for RLS while keeping non-webhook routes fail-closed.""" + for candidate in ( + request.headers.get("X-Project-ID"), + request.headers.get("X-Tenant-ID"), + request.query_params.get("project_id"), + ): + project_id = candidate.strip() if candidate else None + if project_id: + return project_id, "request.header_or_query" + + if request.url.path == ALERTMANAGER_WEBHOOK_PATH: + return ALERTMANAGER_DEFAULT_PROJECT_ID, "request.alertmanager.default_project" + + return None, "request.project_id.missing" + # ============================================================================= # Sentry SDK Initialization (Error Tracking - 補強 SignOz) # Self-Hosted @ 192.168.0.110 @@ -905,15 +925,7 @@ async def request_logging_middleware(request: Request, call_next): from src.core.context import clear_project_context, get_current_project_context, set_project_context request_id = request.headers.get("X-Request-ID") or str(uuid4()) - project_id = ( - request.headers.get("X-Project-ID") - or request.headers.get("X-Tenant-ID") - or request.query_params.get("project_id") - ) - project_id = project_id.strip() if project_id else None - source = "request.project_id.missing" - if project_id: - source = "request.header_or_query" + project_id, source = _resolve_request_project_context(request) context_tokens = set_project_context( project_id=project_id, diff --git a/apps/api/tests/test_alertmanager_project_context.py b/apps/api/tests/test_alertmanager_project_context.py new file mode 100644 index 00000000..3c6d1c3b --- /dev/null +++ b/apps/api/tests/test_alertmanager_project_context.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from starlette.requests import Request + +from src.main import _resolve_request_project_context + + +def _request( + path: str, + *, + query_string: bytes = b"", + headers: list[tuple[bytes, bytes]] | None = None, +) -> Request: + return Request( + { + "type": "http", + "method": "POST", + "path": path, + "scheme": "http", + "server": ("testserver", 80), + "client": ("127.0.0.1", 50000), + "query_string": query_string, + "headers": headers or [], + } + ) + + +def test_alertmanager_webhook_without_header_uses_awoooi_project_context(): + project_id, source = _resolve_request_project_context( + _request("/api/v1/webhooks/alertmanager") + ) + + assert project_id == "awoooi" + assert source == "request.alertmanager.default_project" + + +def test_alertmanager_webhook_keeps_explicit_project_context_priority(): + project_id, source = _resolve_request_project_context( + _request( + "/api/v1/webhooks/alertmanager", + headers=[(b"x-project-id", b"tenant-a")], + ) + ) + + assert project_id == "tenant-a" + assert source == "request.header_or_query" + + +def test_non_alertmanager_route_without_header_still_fails_closed(): + project_id, source = _resolve_request_project_context( + _request("/api/v1/security/db-context-guard") + ) + + assert project_id is None + assert source == "request.project_id.missing"