iflow-mcp-m507_ai-soc-agent 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
- src/__init__.py +8 -0
- src/ai_controller/README.md +139 -0
- src/ai_controller/__init__.py +12 -0
- src/ai_controller/agent_executor.py +596 -0
- src/ai_controller/cli/__init__.py +2 -0
- src/ai_controller/cli/main.py +243 -0
- src/ai_controller/session_manager.py +409 -0
- src/ai_controller/web/__init__.py +2 -0
- src/ai_controller/web/server.py +1181 -0
- src/ai_controller/web/static/css/README.md +102 -0
- src/api/__init__.py +13 -0
- src/api/case_management.py +271 -0
- src/api/edr.py +187 -0
- src/api/kb.py +136 -0
- src/api/siem.py +308 -0
- src/core/__init__.py +10 -0
- src/core/config.py +242 -0
- src/core/config_storage.py +684 -0
- src/core/dto.py +50 -0
- src/core/errors.py +36 -0
- src/core/logging.py +128 -0
- src/integrations/__init__.py +8 -0
- src/integrations/case_management/__init__.py +5 -0
- src/integrations/case_management/iris/__init__.py +11 -0
- src/integrations/case_management/iris/iris_client.py +885 -0
- src/integrations/case_management/iris/iris_http.py +274 -0
- src/integrations/case_management/iris/iris_mapper.py +263 -0
- src/integrations/case_management/iris/iris_models.py +128 -0
- src/integrations/case_management/thehive/__init__.py +8 -0
- src/integrations/case_management/thehive/thehive_client.py +193 -0
- src/integrations/case_management/thehive/thehive_http.py +147 -0
- src/integrations/case_management/thehive/thehive_mapper.py +190 -0
- src/integrations/case_management/thehive/thehive_models.py +125 -0
- src/integrations/cti/__init__.py +6 -0
- src/integrations/cti/local_tip/__init__.py +10 -0
- src/integrations/cti/local_tip/local_tip_client.py +90 -0
- src/integrations/cti/local_tip/local_tip_http.py +110 -0
- src/integrations/cti/opencti/__init__.py +10 -0
- src/integrations/cti/opencti/opencti_client.py +101 -0
- src/integrations/cti/opencti/opencti_http.py +418 -0
- src/integrations/edr/__init__.py +6 -0
- src/integrations/edr/elastic_defend/__init__.py +6 -0
- src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
- src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
- src/integrations/eng/__init__.py +10 -0
- src/integrations/eng/clickup/__init__.py +8 -0
- src/integrations/eng/clickup/clickup_client.py +513 -0
- src/integrations/eng/clickup/clickup_http.py +156 -0
- src/integrations/eng/github/__init__.py +8 -0
- src/integrations/eng/github/github_client.py +169 -0
- src/integrations/eng/github/github_http.py +158 -0
- src/integrations/eng/trello/__init__.py +8 -0
- src/integrations/eng/trello/trello_client.py +207 -0
- src/integrations/eng/trello/trello_http.py +162 -0
- src/integrations/kb/__init__.py +12 -0
- src/integrations/kb/fs_kb_client.py +313 -0
- src/integrations/siem/__init__.py +6 -0
- src/integrations/siem/elastic/__init__.py +6 -0
- src/integrations/siem/elastic/elastic_client.py +3319 -0
- src/integrations/siem/elastic/elastic_http.py +165 -0
- src/mcp/README.md +183 -0
- src/mcp/TOOLS.md +2827 -0
- src/mcp/__init__.py +13 -0
- src/mcp/__main__.py +18 -0
- src/mcp/agent_profiles.py +408 -0
- src/mcp/flow_agent_profiles.py +424 -0
- src/mcp/mcp_server.py +4086 -0
- src/mcp/rules_engine.py +487 -0
- src/mcp/runbook_manager.py +264 -0
- src/orchestrator/__init__.py +11 -0
- src/orchestrator/incident_workflow.py +244 -0
- src/orchestrator/tools_case.py +1085 -0
- src/orchestrator/tools_cti.py +359 -0
- src/orchestrator/tools_edr.py +315 -0
- src/orchestrator/tools_eng.py +378 -0
- src/orchestrator/tools_kb.py +156 -0
- src/orchestrator/tools_siem.py +1709 -0
- src/web/__init__.py +8 -0
- src/web/config_server.py +511 -0
src/web/__init__.py
ADDED
src/web/config_server.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Web server for SamiGPT configuration management.
|
|
3
|
+
|
|
4
|
+
This module provides a FastAPI server that exposes:
|
|
5
|
+
- REST API for managing configurations
|
|
6
|
+
- Web UI for configuring TheHive, Elastic (SIEM), and EDR integrations
|
|
7
|
+
|
|
8
|
+
The interface is protected with a secret/password that must be set via environment variable.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import hmac
|
|
15
|
+
import os
|
|
16
|
+
import secrets
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
from fastapi import FastAPI, HTTPException, Request, Response, status
|
|
21
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
22
|
+
from fastapi.staticfiles import StaticFiles
|
|
23
|
+
from pydantic import BaseModel
|
|
24
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
25
|
+
|
|
26
|
+
from ..core.config_storage import (
|
|
27
|
+
CONFIG_FILE,
|
|
28
|
+
ENV_FILE,
|
|
29
|
+
get_config_dict,
|
|
30
|
+
load_config_from_file,
|
|
31
|
+
update_config_dict,
|
|
32
|
+
)
|
|
33
|
+
from ..core.errors import ConfigError
|
|
34
|
+
|
|
35
|
+
# Get admin secret from environment or use default
|
|
36
|
+
ADMIN_SECRET = os.getenv("SAMIGPT_ADMIN_SECRET", "admin")
|
|
37
|
+
SESSION_SECRET = os.getenv("SAMIGPT_SESSION_SECRET", secrets.token_urlsafe(32))
|
|
38
|
+
|
|
39
|
+
# In-memory session store (for simple implementation)
|
|
40
|
+
# In production, consider using Redis or database-backed sessions
|
|
41
|
+
_sessions: Dict[str, Dict[str, Any]] = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SimpleSessionMiddleware(BaseHTTPMiddleware):
|
|
45
|
+
"""
|
|
46
|
+
Simple session middleware that doesn't require itsdangerous.
|
|
47
|
+
Uses signed cookies with HMAC for security.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, app, secret_key: str):
|
|
51
|
+
super().__init__(app)
|
|
52
|
+
self.secret_key = secret_key.encode()
|
|
53
|
+
|
|
54
|
+
async def dispatch(self, request: Request, call_next):
|
|
55
|
+
# Get session ID from cookie
|
|
56
|
+
session_id = request.cookies.get("session_id")
|
|
57
|
+
session_data = {}
|
|
58
|
+
|
|
59
|
+
if session_id and session_id in _sessions:
|
|
60
|
+
# Verify signature
|
|
61
|
+
expected_sig = self._sign(session_id)
|
|
62
|
+
provided_sig = request.cookies.get("session_sig", "")
|
|
63
|
+
if hmac.compare_digest(expected_sig, provided_sig):
|
|
64
|
+
session_data = _sessions[session_id].copy()
|
|
65
|
+
|
|
66
|
+
# Attach session to request state
|
|
67
|
+
request.state.session = session_data
|
|
68
|
+
|
|
69
|
+
# Process request
|
|
70
|
+
response = await call_next(request)
|
|
71
|
+
|
|
72
|
+
# Save session if modified
|
|
73
|
+
if hasattr(request.state, "session_modified") and request.state.session_modified:
|
|
74
|
+
if not session_id or session_id not in _sessions:
|
|
75
|
+
session_id = secrets.token_urlsafe(32)
|
|
76
|
+
_sessions[session_id] = {}
|
|
77
|
+
|
|
78
|
+
_sessions[session_id].update(request.state.session)
|
|
79
|
+
|
|
80
|
+
# Set cookie with signature
|
|
81
|
+
sig = self._sign(session_id)
|
|
82
|
+
response.set_cookie(
|
|
83
|
+
key="session_id",
|
|
84
|
+
value=session_id,
|
|
85
|
+
httponly=True,
|
|
86
|
+
samesite="lax",
|
|
87
|
+
max_age=86400, # 24 hours
|
|
88
|
+
)
|
|
89
|
+
response.set_cookie(
|
|
90
|
+
key="session_sig",
|
|
91
|
+
value=sig,
|
|
92
|
+
httponly=True,
|
|
93
|
+
samesite="lax",
|
|
94
|
+
max_age=86400,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return response
|
|
98
|
+
|
|
99
|
+
def _sign(self, data: str) -> str:
|
|
100
|
+
"""Create HMAC signature for session ID."""
|
|
101
|
+
return hmac.new(self.secret_key, data.encode(), hashlib.sha256).hexdigest()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Create FastAPI app
|
|
105
|
+
app = FastAPI(
|
|
106
|
+
title="SamiGPT Configuration Manager",
|
|
107
|
+
description="Web interface for configuring SamiGPT integrations",
|
|
108
|
+
version="1.0.0",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Add simple session middleware (no itsdangerous dependency)
|
|
112
|
+
app.add_middleware(SimpleSessionMiddleware, secret_key=SESSION_SECRET)
|
|
113
|
+
|
|
114
|
+
# Determine paths
|
|
115
|
+
WEB_DIR = Path(__file__).parent
|
|
116
|
+
STATIC_DIR = WEB_DIR / "static"
|
|
117
|
+
TEMPLATES_DIR = WEB_DIR / "templates"
|
|
118
|
+
|
|
119
|
+
# Create directories if they don't exist
|
|
120
|
+
STATIC_DIR.mkdir(exist_ok=True)
|
|
121
|
+
TEMPLATES_DIR.mkdir(exist_ok=True)
|
|
122
|
+
|
|
123
|
+
# Mount static files
|
|
124
|
+
if STATIC_DIR.exists():
|
|
125
|
+
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Helper function to check authentication
|
|
129
|
+
def is_authenticated(request: Request) -> bool:
|
|
130
|
+
"""Check if the request is authenticated."""
|
|
131
|
+
session_data = getattr(request.state, "session", {})
|
|
132
|
+
return session_data.get("authenticated", False) == True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def require_auth(request: Request) -> None:
|
|
136
|
+
"""Require authentication or raise 401."""
|
|
137
|
+
if not is_authenticated(request):
|
|
138
|
+
raise HTTPException(
|
|
139
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
140
|
+
detail="Authentication required",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Pydantic models for API requests
|
|
145
|
+
class LoginRequest(BaseModel):
|
|
146
|
+
secret: str
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TheHiveConfigUpdate(BaseModel):
|
|
150
|
+
base_url: Optional[str] = None
|
|
151
|
+
api_key: Optional[str] = None
|
|
152
|
+
timeout_seconds: Optional[int] = 30
|
|
153
|
+
enabled: bool = True
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class IrisConfigUpdate(BaseModel):
|
|
157
|
+
base_url: Optional[str] = None
|
|
158
|
+
api_key: Optional[str] = None
|
|
159
|
+
timeout_seconds: Optional[int] = 30
|
|
160
|
+
enabled: bool = True
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ElasticConfigUpdate(BaseModel):
|
|
164
|
+
base_url: Optional[str] = None
|
|
165
|
+
api_key: Optional[str] = None
|
|
166
|
+
username: Optional[str] = None
|
|
167
|
+
password: Optional[str] = None
|
|
168
|
+
timeout_seconds: Optional[int] = 30
|
|
169
|
+
verify_ssl: Optional[bool] = True
|
|
170
|
+
enabled: bool = True
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class EDRConfigUpdate(BaseModel):
|
|
174
|
+
edr_type: Optional[str] = "velociraptor"
|
|
175
|
+
base_url: Optional[str] = None
|
|
176
|
+
api_key: Optional[str] = None
|
|
177
|
+
timeout_seconds: Optional[int] = 30
|
|
178
|
+
enabled: bool = True
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class LoggingConfigUpdate(BaseModel):
|
|
182
|
+
log_dir: Optional[str] = "logs"
|
|
183
|
+
log_level: Optional[str] = "INFO"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ConfigUpdate(BaseModel):
|
|
187
|
+
thehive: Optional[TheHiveConfigUpdate] = None
|
|
188
|
+
iris: Optional[IrisConfigUpdate] = None
|
|
189
|
+
elastic: Optional[ElasticConfigUpdate] = None
|
|
190
|
+
edr: Optional[EDRConfigUpdate] = None
|
|
191
|
+
logging: Optional[LoggingConfigUpdate] = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.get("/", response_class=HTMLResponse)
|
|
195
|
+
async def root(request: Request):
|
|
196
|
+
"""Serve the login page or main configuration page if authenticated."""
|
|
197
|
+
session_data = getattr(request.state, "session", {})
|
|
198
|
+
if not session_data.get("authenticated", False):
|
|
199
|
+
login_path = TEMPLATES_DIR / "login.html"
|
|
200
|
+
if login_path.exists():
|
|
201
|
+
with open(login_path, "r") as f:
|
|
202
|
+
return HTMLResponse(content=f.read())
|
|
203
|
+
return HTMLResponse(
|
|
204
|
+
content="""
|
|
205
|
+
<!DOCTYPE html>
|
|
206
|
+
<html>
|
|
207
|
+
<head><title>Login - SamiGPT</title></head>
|
|
208
|
+
<body>
|
|
209
|
+
<h1>Login Required</h1>
|
|
210
|
+
<p>Please set SAMIGPT_ADMIN_SECRET environment variable and login page will appear.</p>
|
|
211
|
+
</body>
|
|
212
|
+
</html>
|
|
213
|
+
"""
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Authenticated - serve config page
|
|
217
|
+
html_path = TEMPLATES_DIR / "index.html"
|
|
218
|
+
if html_path.exists():
|
|
219
|
+
with open(html_path, "r") as f:
|
|
220
|
+
return HTMLResponse(content=f.read())
|
|
221
|
+
return HTMLResponse(
|
|
222
|
+
content="<h1>SamiGPT Configuration Manager</h1><p>index.html not found</p>"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.post("/api/auth/login")
|
|
227
|
+
async def login(request: Request, login_data: LoginRequest):
|
|
228
|
+
"""Authenticate with the admin secret."""
|
|
229
|
+
# Compare using constant-time comparison to prevent timing attacks
|
|
230
|
+
if secrets.compare_digest(login_data.secret, ADMIN_SECRET):
|
|
231
|
+
request.state.session = request.state.session if hasattr(request.state, "session") else {}
|
|
232
|
+
request.state.session["authenticated"] = True
|
|
233
|
+
request.state.session_modified = True
|
|
234
|
+
return JSONResponse(content={"success": True, "message": "Authentication successful"})
|
|
235
|
+
else:
|
|
236
|
+
raise HTTPException(
|
|
237
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
238
|
+
detail="Invalid secret",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@app.post("/api/auth/logout")
|
|
243
|
+
async def logout(request: Request, response: Response):
|
|
244
|
+
"""Logout and clear session."""
|
|
245
|
+
session_id = request.cookies.get("session_id")
|
|
246
|
+
if session_id and session_id in _sessions:
|
|
247
|
+
del _sessions[session_id]
|
|
248
|
+
|
|
249
|
+
# Clear cookies
|
|
250
|
+
response.delete_cookie("session_id")
|
|
251
|
+
response.delete_cookie("session_sig")
|
|
252
|
+
|
|
253
|
+
return JSONResponse(content={"success": True, "message": "Logged out successfully"})
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@app.get("/api/auth/status")
|
|
257
|
+
async def auth_status(request: Request):
|
|
258
|
+
"""Check authentication status."""
|
|
259
|
+
session_data = getattr(request.state, "session", {})
|
|
260
|
+
authenticated = session_data.get("authenticated", False) == True
|
|
261
|
+
return JSONResponse(
|
|
262
|
+
content={"authenticated": authenticated}
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@app.get("/api/config", response_model=Dict[str, Any])
|
|
267
|
+
async def get_config(request: Request):
|
|
268
|
+
"""Get current configuration."""
|
|
269
|
+
require_auth(request)
|
|
270
|
+
try:
|
|
271
|
+
config_dict = get_config_dict()
|
|
272
|
+
# Remove sensitive data from response
|
|
273
|
+
if "thehive" in config_dict and config_dict["thehive"]:
|
|
274
|
+
config_dict["thehive"]["api_key"] = "***" if config_dict["thehive"].get("api_key") else None
|
|
275
|
+
if "elastic" in config_dict and config_dict["elastic"]:
|
|
276
|
+
config_dict["elastic"]["api_key"] = "***" if config_dict["elastic"].get("api_key") else None
|
|
277
|
+
config_dict["elastic"]["password"] = "***" if config_dict["elastic"].get("password") else None
|
|
278
|
+
if "edr" in config_dict and config_dict["edr"]:
|
|
279
|
+
config_dict["edr"]["api_key"] = "***" if config_dict["edr"].get("api_key") else None
|
|
280
|
+
|
|
281
|
+
# Add file location information
|
|
282
|
+
from pathlib import Path
|
|
283
|
+
config_dict["_meta"] = {
|
|
284
|
+
"config_file": str(Path(CONFIG_FILE).absolute()),
|
|
285
|
+
"env_file": str(Path(ENV_FILE).absolute()),
|
|
286
|
+
"config_file_exists": Path(CONFIG_FILE).exists(),
|
|
287
|
+
"env_file_exists": Path(ENV_FILE).exists(),
|
|
288
|
+
}
|
|
289
|
+
return JSONResponse(content=config_dict)
|
|
290
|
+
except ConfigError as e:
|
|
291
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@app.post("/api/config", response_model=Dict[str, Any])
|
|
295
|
+
async def update_config(request: Request, config_update: ConfigUpdate):
|
|
296
|
+
"""Update configuration."""
|
|
297
|
+
require_auth(request)
|
|
298
|
+
try:
|
|
299
|
+
# Build update dict
|
|
300
|
+
updates: Dict[str, Any] = {}
|
|
301
|
+
|
|
302
|
+
if config_update.thehive is not None:
|
|
303
|
+
if config_update.thehive.enabled:
|
|
304
|
+
if not config_update.thehive.base_url or not config_update.thehive.api_key:
|
|
305
|
+
raise HTTPException(
|
|
306
|
+
status_code=400,
|
|
307
|
+
detail="TheHive base_url and api_key are required when enabled",
|
|
308
|
+
)
|
|
309
|
+
updates["thehive"] = {
|
|
310
|
+
"base_url": config_update.thehive.base_url,
|
|
311
|
+
"api_key": config_update.thehive.api_key,
|
|
312
|
+
"timeout_seconds": config_update.thehive.timeout_seconds or 30,
|
|
313
|
+
}
|
|
314
|
+
else:
|
|
315
|
+
updates["thehive"] = None
|
|
316
|
+
|
|
317
|
+
if config_update.iris is not None:
|
|
318
|
+
if config_update.iris.enabled:
|
|
319
|
+
if not config_update.iris.base_url or not config_update.iris.api_key:
|
|
320
|
+
raise HTTPException(
|
|
321
|
+
status_code=400,
|
|
322
|
+
detail="IRIS base_url and api_key are required when enabled",
|
|
323
|
+
)
|
|
324
|
+
updates["iris"] = {
|
|
325
|
+
"base_url": config_update.iris.base_url,
|
|
326
|
+
"api_key": config_update.iris.api_key,
|
|
327
|
+
"timeout_seconds": config_update.iris.timeout_seconds or 30,
|
|
328
|
+
}
|
|
329
|
+
else:
|
|
330
|
+
updates["iris"] = None
|
|
331
|
+
|
|
332
|
+
if config_update.elastic is not None:
|
|
333
|
+
if config_update.elastic.enabled:
|
|
334
|
+
if not config_update.elastic.base_url:
|
|
335
|
+
raise HTTPException(
|
|
336
|
+
status_code=400,
|
|
337
|
+
detail="Elastic base_url is required when enabled",
|
|
338
|
+
)
|
|
339
|
+
updates["elastic"] = {
|
|
340
|
+
"base_url": config_update.elastic.base_url,
|
|
341
|
+
"api_key": config_update.elastic.api_key,
|
|
342
|
+
"username": config_update.elastic.username,
|
|
343
|
+
"password": config_update.elastic.password,
|
|
344
|
+
"timeout_seconds": config_update.elastic.timeout_seconds or 30,
|
|
345
|
+
"verify_ssl": config_update.elastic.verify_ssl,
|
|
346
|
+
}
|
|
347
|
+
else:
|
|
348
|
+
updates["elastic"] = None
|
|
349
|
+
|
|
350
|
+
if config_update.edr is not None:
|
|
351
|
+
if config_update.edr.enabled:
|
|
352
|
+
if not config_update.edr.base_url or not config_update.edr.api_key:
|
|
353
|
+
raise HTTPException(
|
|
354
|
+
status_code=400,
|
|
355
|
+
detail="EDR base_url and api_key are required when enabled",
|
|
356
|
+
)
|
|
357
|
+
updates["edr"] = {
|
|
358
|
+
"edr_type": config_update.edr.edr_type or "velociraptor",
|
|
359
|
+
"base_url": config_update.edr.base_url,
|
|
360
|
+
"api_key": config_update.edr.api_key,
|
|
361
|
+
"timeout_seconds": config_update.edr.timeout_seconds or 30,
|
|
362
|
+
}
|
|
363
|
+
else:
|
|
364
|
+
updates["edr"] = None
|
|
365
|
+
|
|
366
|
+
if config_update.logging is not None:
|
|
367
|
+
updates["logging"] = {
|
|
368
|
+
"log_dir": config_update.logging.log_dir or "logs",
|
|
369
|
+
"log_level": config_update.logging.log_level or "INFO",
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# Update and save config
|
|
373
|
+
updated_config = update_config_dict(updates)
|
|
374
|
+
config_dict = get_config_dict()
|
|
375
|
+
|
|
376
|
+
return JSONResponse(content={"success": True, "config": config_dict})
|
|
377
|
+
except ConfigError as e:
|
|
378
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@app.get("/api/config/test/thehive")
|
|
382
|
+
async def test_thehive(request: Request):
|
|
383
|
+
"""Test TheHive connection."""
|
|
384
|
+
require_auth(request)
|
|
385
|
+
try:
|
|
386
|
+
config = load_config_from_file()
|
|
387
|
+
if not config.thehive:
|
|
388
|
+
raise HTTPException(status_code=400, detail="TheHive not configured")
|
|
389
|
+
|
|
390
|
+
from ..integrations.case_management.thehive.thehive_client import (
|
|
391
|
+
TheHiveCaseManagementClient,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
client = TheHiveCaseManagementClient.from_config(config)
|
|
395
|
+
is_connected = client.ping()
|
|
396
|
+
|
|
397
|
+
return JSONResponse(
|
|
398
|
+
content={
|
|
399
|
+
"success": is_connected,
|
|
400
|
+
"message": "Connected successfully" if is_connected else "Connection failed",
|
|
401
|
+
}
|
|
402
|
+
)
|
|
403
|
+
except Exception as e:
|
|
404
|
+
return JSONResponse(
|
|
405
|
+
content={"success": False, "message": str(e)}, status_code=500
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
@app.get("/api/config/test/iris")
|
|
410
|
+
async def test_iris(request: Request):
|
|
411
|
+
"""Test IRIS connection."""
|
|
412
|
+
require_auth(request)
|
|
413
|
+
try:
|
|
414
|
+
config = load_config_from_file()
|
|
415
|
+
if not config.iris:
|
|
416
|
+
raise HTTPException(status_code=400, detail="IRIS not configured")
|
|
417
|
+
|
|
418
|
+
from ..integrations.case_management.iris.iris_client import (
|
|
419
|
+
IRISCaseManagementClient,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
client = IRISCaseManagementClient.from_config(config)
|
|
423
|
+
is_connected = client.ping()
|
|
424
|
+
|
|
425
|
+
return JSONResponse(
|
|
426
|
+
content={
|
|
427
|
+
"success": is_connected,
|
|
428
|
+
"message": "Connected successfully" if is_connected else "Connection failed",
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
except Exception as e:
|
|
432
|
+
return JSONResponse(
|
|
433
|
+
content={"success": False, "message": str(e)}, status_code=500
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@app.get("/api/config/test/elastic")
|
|
438
|
+
async def test_elastic(request: Request):
|
|
439
|
+
"""Test Elastic connection."""
|
|
440
|
+
require_auth(request)
|
|
441
|
+
try:
|
|
442
|
+
config = load_config_from_file()
|
|
443
|
+
if not config.elastic:
|
|
444
|
+
raise HTTPException(status_code=400, detail="Elastic not configured")
|
|
445
|
+
|
|
446
|
+
# TODO: Implement Elastic client test when integration is available
|
|
447
|
+
return JSONResponse(
|
|
448
|
+
content={
|
|
449
|
+
"success": False,
|
|
450
|
+
"message": "Elastic integration not yet implemented",
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
except Exception as e:
|
|
454
|
+
return JSONResponse(
|
|
455
|
+
content={"success": False, "message": str(e)}, status_code=500
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@app.get("/api/config/test/edr")
|
|
460
|
+
async def test_edr(request: Request):
|
|
461
|
+
"""Test EDR connection."""
|
|
462
|
+
require_auth(request)
|
|
463
|
+
try:
|
|
464
|
+
config = load_config_from_file()
|
|
465
|
+
if not config.edr:
|
|
466
|
+
raise HTTPException(status_code=400, detail="EDR not configured")
|
|
467
|
+
|
|
468
|
+
# TODO: Implement EDR client test when integration is available
|
|
469
|
+
return JSONResponse(
|
|
470
|
+
content={
|
|
471
|
+
"success": False,
|
|
472
|
+
"message": "EDR integration not yet implemented",
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
except Exception as e:
|
|
476
|
+
return JSONResponse(
|
|
477
|
+
content={"success": False, "message": str(e)}, status_code=500
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@app.post("/api/config/reload")
|
|
482
|
+
async def reload_config(request: Request):
|
|
483
|
+
"""Reload configuration from files (sync from .env or config.json)."""
|
|
484
|
+
require_auth(request)
|
|
485
|
+
try:
|
|
486
|
+
# Force reload from files (priority: .env > config.json)
|
|
487
|
+
config = load_config_from_file()
|
|
488
|
+
# Save to both files to sync them
|
|
489
|
+
from ..core.config_storage import save_config_to_file
|
|
490
|
+
save_config_to_file(config, save_both=True)
|
|
491
|
+
|
|
492
|
+
config_dict = get_config_dict()
|
|
493
|
+
return JSONResponse(
|
|
494
|
+
content={
|
|
495
|
+
"success": True,
|
|
496
|
+
"message": "Configuration reloaded from files",
|
|
497
|
+
"config": config_dict,
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
except ConfigError as e:
|
|
501
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
if __name__ == "__main__":
|
|
505
|
+
import uvicorn
|
|
506
|
+
|
|
507
|
+
print(f"Starting SamiGPT Configuration Manager...")
|
|
508
|
+
print(f"Admin secret is set via SAMIGPT_ADMIN_SECRET environment variable")
|
|
509
|
+
if ADMIN_SECRET == "admin":
|
|
510
|
+
print("WARNING: Using default admin secret! Set SAMIGPT_ADMIN_SECRET environment variable.")
|
|
511
|
+
uvicorn.run(app, host="0.0.0.0", port=8080)
|