| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- from fastapi import Depends, FastAPI, HTTPException
- from app.adapters.iris import IrisAdapter
- from app.adapters.pagerduty import PagerDutyAdapter
- from app.adapters.shuffle import ShuffleAdapter
- from app.adapters.wazuh import WazuhAdapter
- from app.config import settings
- from app.db import init_schema
- from app.models import (
- ActionCreateIncidentRequest,
- ApiResponse,
- ShuffleLoginRequest,
- ShuffleProxyRequest,
- TriggerShuffleRequest,
- WazuhIngestRequest,
- )
- from app.repositories.mvp_repo import MvpRepository
- from app.routes.mvp import build_mvp_router
- from app.security import require_internal_api_key
- from app.services.mvp_service import MvpService
- app = FastAPI(title=settings.app_name, version="0.1.0")
- wazuh_adapter = WazuhAdapter(
- base_url=settings.wazuh_base_url,
- username=settings.wazuh_username,
- password=settings.wazuh_password,
- indexer_url=settings.wazuh_indexer_url,
- indexer_username=settings.wazuh_indexer_username,
- indexer_password=settings.wazuh_indexer_password,
- )
- shuffle_adapter = ShuffleAdapter(
- base_url=settings.shuffle_base_url,
- api_key=settings.shuffle_api_key,
- )
- pagerduty_adapter = PagerDutyAdapter(
- base_url=settings.pagerduty_base_url,
- api_key=settings.pagerduty_api_key,
- )
- iris_adapter = IrisAdapter(
- base_url=settings.iris_base_url,
- api_key=settings.iris_api_key,
- )
- repo = MvpRepository()
- mvp_service = MvpService(
- repo=repo,
- wazuh_adapter=wazuh_adapter,
- shuffle_adapter=shuffle_adapter,
- iris_adapter=iris_adapter,
- pagerduty_adapter=pagerduty_adapter,
- )
- app.include_router(build_mvp_router(mvp_service, require_internal_api_key))
- @app.on_event("startup")
- async def startup() -> None:
- init_schema()
- repo.ensure_policy()
- @app.get("/health", response_model=ApiResponse)
- async def health() -> ApiResponse:
- return ApiResponse(
- data={
- "service": settings.app_name,
- "env": settings.app_env,
- "targets": {
- "wazuh": settings.wazuh_base_url,
- "shuffle": settings.shuffle_base_url,
- "pagerduty": settings.pagerduty_base_url,
- "iris": settings.iris_base_url,
- },
- }
- )
- @app.post("/ingest/wazuh-alert", response_model=ApiResponse)
- async def ingest_wazuh_alert(payload: WazuhIngestRequest) -> ApiResponse:
- normalized = {
- "source": payload.source,
- "alert_id": payload.alert_id,
- "rule_id": payload.rule_id,
- "severity": payload.severity,
- "title": payload.title,
- "payload": payload.payload,
- }
- return ApiResponse(data={"normalized": normalized})
- @app.post("/action/create-incident", response_model=ApiResponse)
- async def create_incident(payload: ActionCreateIncidentRequest) -> ApiResponse:
- incident_payload = {
- "title": payload.title,
- "urgency": payload.severity,
- "incident_key": payload.dedupe_key,
- "body": payload.payload,
- "source": payload.source,
- }
- try:
- pd_result = await pagerduty_adapter.create_incident(incident_payload)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"PagerDuty call failed: {exc}") from exc
- return ApiResponse(data={"pagerduty": pd_result})
- @app.post("/action/trigger-shuffle", response_model=ApiResponse)
- async def trigger_shuffle(payload: TriggerShuffleRequest) -> ApiResponse:
- try:
- shuffle_result = await shuffle_adapter.trigger_workflow(
- workflow_id=payload.workflow_id,
- payload=payload.execution_argument,
- )
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": shuffle_result})
- @app.get("/shuffle/health", response_model=ApiResponse)
- async def shuffle_health() -> ApiResponse:
- try:
- result = await shuffle_adapter.health()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.get("/shuffle/auth-test", response_model=ApiResponse)
- async def shuffle_auth_test() -> ApiResponse:
- try:
- result = await shuffle_adapter.auth_test()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.post("/shuffle/login", response_model=ApiResponse)
- async def shuffle_login(payload: ShuffleLoginRequest) -> ApiResponse:
- try:
- result = await shuffle_adapter.login(payload.username, payload.password)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.post("/shuffle/generate-apikey", response_model=ApiResponse)
- async def shuffle_generate_apikey(payload: ShuffleLoginRequest | None = None) -> ApiResponse:
- username = payload.username if payload else settings.shuffle_username
- password = payload.password if payload else settings.shuffle_password
- if not username or not password:
- raise HTTPException(
- status_code=400,
- detail="Missing shuffle credentials. Provide username/password in body or set SHUFFLE_USERNAME and SHUFFLE_PASSWORD.",
- )
- try:
- result = await shuffle_adapter.generate_apikey_from_login(username, password)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.get("/shuffle/workflows", response_model=ApiResponse)
- async def shuffle_workflows() -> ApiResponse:
- try:
- result = await shuffle_adapter.list_workflows()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.get("/shuffle/workflows/{workflow_id}", response_model=ApiResponse)
- async def shuffle_workflow(workflow_id: str) -> ApiResponse:
- try:
- result = await shuffle_adapter.get_workflow(workflow_id)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.post("/shuffle/workflows/{workflow_id}/execute", response_model=ApiResponse)
- async def shuffle_workflow_execute(
- workflow_id: str, payload: dict[str, object]
- ) -> ApiResponse:
- try:
- result = await shuffle_adapter.trigger_workflow(workflow_id=workflow_id, payload=payload)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.get("/shuffle/apps", response_model=ApiResponse)
- async def shuffle_apps() -> ApiResponse:
- try:
- result = await shuffle_adapter.list_apps()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.post("/shuffle/proxy", response_model=ApiResponse)
- async def shuffle_proxy(payload: ShuffleProxyRequest) -> ApiResponse:
- path = payload.path if payload.path.startswith("/api/") else f"/api/v1/{payload.path.lstrip('/')}"
- try:
- result = await shuffle_adapter.proxy(
- method=payload.method,
- path=path,
- params=payload.params,
- payload=payload.payload,
- )
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Shuffle call failed: {exc}") from exc
- return ApiResponse(data={"shuffle": result})
- @app.post("/action/create-iris-case", response_model=ApiResponse)
- async def create_iris_case(payload: ActionCreateIncidentRequest) -> ApiResponse:
- # IRIS v2 expects case_name, case_description, case_customer, case_soc_id.
- case_payload = {
- "case_name": payload.title,
- "case_description": payload.payload.get("description", "Created by soc-integrator"),
- "case_customer": payload.payload.get("case_customer", settings.iris_default_customer_id),
- "case_soc_id": payload.payload.get("case_soc_id", settings.iris_default_soc_id),
- }
- try:
- iris_result = await iris_adapter.create_case(case_payload)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"IRIS call failed: {exc}") from exc
- return ApiResponse(data={"iris": iris_result})
- @app.get("/sync/wazuh-version", response_model=ApiResponse)
- async def sync_wazuh_version() -> ApiResponse:
- try:
- wazuh_result = await wazuh_adapter.get_version()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh call failed: {exc}") from exc
- return ApiResponse(data={"wazuh": wazuh_result})
- @app.get("/wazuh/auth-test", response_model=ApiResponse)
- async def wazuh_auth_test() -> ApiResponse:
- try:
- result = await wazuh_adapter.auth_test()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh auth failed: {exc}") from exc
- return ApiResponse(data={"wazuh": result})
- @app.get("/wazuh/manager-info", response_model=ApiResponse)
- async def wazuh_manager_info() -> ApiResponse:
- try:
- result = await wazuh_adapter.get_manager_info()
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh call failed: {exc}") from exc
- return ApiResponse(data={"wazuh": result})
- @app.get("/wazuh/agents", response_model=ApiResponse)
- async def wazuh_agents(
- limit: int = 50,
- offset: int = 0,
- select: str | None = None,
- ) -> ApiResponse:
- try:
- result = await wazuh_adapter.list_agents(limit=limit, offset=offset, select=select)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh call failed: {exc}") from exc
- return ApiResponse(data={"wazuh": result})
- @app.get("/wazuh/alerts", response_model=ApiResponse)
- async def wazuh_alerts(
- limit: int = 50,
- offset: int = 0,
- q: str | None = None,
- sort: str | None = None,
- ) -> ApiResponse:
- try:
- # In this Wazuh build, API alerts are exposed via manager logs.
- result = await wazuh_adapter.list_manager_logs(
- limit=limit, offset=offset, q=q, sort=sort
- )
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh call failed: {exc}") from exc
- return ApiResponse(data={"wazuh": result})
- @app.get("/wazuh/manager-logs", response_model=ApiResponse)
- async def wazuh_manager_logs(
- limit: int = 50,
- offset: int = 0,
- q: str | None = None,
- sort: str | None = None,
- ) -> ApiResponse:
- try:
- result = await wazuh_adapter.list_manager_logs(
- limit=limit, offset=offset, q=q, sort=sort
- )
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh call failed: {exc}") from exc
- return ApiResponse(data={"wazuh": result})
- @app.post("/wazuh/sync-to-mvp", response_model=ApiResponse, dependencies=[Depends(require_internal_api_key)])
- async def wazuh_sync_to_mvp(
- limit: int = 50,
- minutes: int = 120,
- q: str = "soc_mvp_test=true OR event_type:*",
- ) -> ApiResponse:
- try:
- result = await mvp_service.sync_wazuh_alerts(query=q, limit=limit, minutes=minutes)
- except Exception as exc:
- raise HTTPException(status_code=502, detail=f"Wazuh sync failed: {exc}") from exc
- return ApiResponse(data={"sync": result})
|