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
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration storage and loading for SamiGPT.
|
|
3
|
+
|
|
4
|
+
This module provides functions to save and load configuration from both JSON and .env files,
|
|
5
|
+
allowing the web UI to manage configurations and supporting manual file editing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
from .config import (
|
|
17
|
+
CTIConfig,
|
|
18
|
+
ClickUpConfig,
|
|
19
|
+
EDRConfig,
|
|
20
|
+
ElasticConfig,
|
|
21
|
+
EngConfig,
|
|
22
|
+
GitHubConfig,
|
|
23
|
+
IrisConfig,
|
|
24
|
+
LoggingConfig,
|
|
25
|
+
SamiConfig,
|
|
26
|
+
TheHiveConfig,
|
|
27
|
+
TrelloConfig,
|
|
28
|
+
)
|
|
29
|
+
from .errors import ConfigError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
CONFIG_FILE = os.getenv("SAMIGPT_CONFIG_FILE", "config.json")
|
|
33
|
+
ENV_FILE = os.getenv("SAMIGPT_ENV_FILE", ".env")
|
|
34
|
+
STARTING_CONFIG_FILE = os.getenv("SAMIGPT_STARTING_CONFIG_FILE", "config.json.example")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _config_to_dict(config: SamiConfig) -> Dict[str, Any]:
|
|
38
|
+
"""Convert SamiConfig to a dictionary."""
|
|
39
|
+
result: Dict[str, Any] = {
|
|
40
|
+
"logging": {
|
|
41
|
+
"log_dir": config.logging.log_dir if config.logging else "logs",
|
|
42
|
+
"log_level": config.logging.log_level if config.logging else "INFO",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if config.thehive:
|
|
47
|
+
result["thehive"] = {
|
|
48
|
+
"base_url": config.thehive.base_url,
|
|
49
|
+
"api_key": config.thehive.api_key,
|
|
50
|
+
"timeout_seconds": config.thehive.timeout_seconds,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if config.iris:
|
|
54
|
+
result["iris"] = {
|
|
55
|
+
"base_url": config.iris.base_url,
|
|
56
|
+
"api_key": config.iris.api_key,
|
|
57
|
+
"timeout_seconds": config.iris.timeout_seconds,
|
|
58
|
+
"verify_ssl": config.iris.verify_ssl,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if config.elastic:
|
|
62
|
+
result["elastic"] = {
|
|
63
|
+
"base_url": config.elastic.base_url,
|
|
64
|
+
"api_key": config.elastic.api_key,
|
|
65
|
+
"username": config.elastic.username,
|
|
66
|
+
"password": config.elastic.password,
|
|
67
|
+
"timeout_seconds": config.elastic.timeout_seconds,
|
|
68
|
+
"verify_ssl": config.elastic.verify_ssl,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if config.edr:
|
|
72
|
+
result["edr"] = {
|
|
73
|
+
"edr_type": config.edr.edr_type,
|
|
74
|
+
"base_url": config.edr.base_url,
|
|
75
|
+
"api_key": config.edr.api_key,
|
|
76
|
+
"timeout_seconds": config.edr.timeout_seconds,
|
|
77
|
+
"verify_ssl": config.edr.verify_ssl,
|
|
78
|
+
"additional_params": config.edr.additional_params,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if config.cti:
|
|
82
|
+
result["cti"] = {
|
|
83
|
+
"cti_type": config.cti.cti_type,
|
|
84
|
+
"base_url": config.cti.base_url,
|
|
85
|
+
"api_key": config.cti.api_key,
|
|
86
|
+
"timeout_seconds": config.cti.timeout_seconds,
|
|
87
|
+
"verify_ssl": config.cti.verify_ssl,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if config.eng:
|
|
91
|
+
eng_dict: Dict[str, Any] = {
|
|
92
|
+
"provider": config.eng.provider,
|
|
93
|
+
}
|
|
94
|
+
if config.eng.trello:
|
|
95
|
+
eng_dict["trello"] = {
|
|
96
|
+
"api_key": config.eng.trello.api_key,
|
|
97
|
+
"api_token": config.eng.trello.api_token,
|
|
98
|
+
"fine_tuning_board_id": config.eng.trello.fine_tuning_board_id,
|
|
99
|
+
"engineering_board_id": config.eng.trello.engineering_board_id,
|
|
100
|
+
"timeout_seconds": config.eng.trello.timeout_seconds,
|
|
101
|
+
"verify_ssl": config.eng.trello.verify_ssl,
|
|
102
|
+
}
|
|
103
|
+
if config.eng.clickup:
|
|
104
|
+
eng_dict["clickup"] = {
|
|
105
|
+
"api_token": config.eng.clickup.api_token,
|
|
106
|
+
"fine_tuning_list_id": config.eng.clickup.fine_tuning_list_id,
|
|
107
|
+
"engineering_list_id": config.eng.clickup.engineering_list_id,
|
|
108
|
+
"timeout_seconds": config.eng.clickup.timeout_seconds,
|
|
109
|
+
"verify_ssl": config.eng.clickup.verify_ssl,
|
|
110
|
+
}
|
|
111
|
+
if config.eng.clickup.space_id:
|
|
112
|
+
eng_dict["clickup"]["space_id"] = config.eng.clickup.space_id
|
|
113
|
+
if config.eng.github:
|
|
114
|
+
eng_dict["github"] = {
|
|
115
|
+
"api_token": config.eng.github.api_token,
|
|
116
|
+
"fine_tuning_project_id": config.eng.github.fine_tuning_project_id,
|
|
117
|
+
"engineering_project_id": config.eng.github.engineering_project_id,
|
|
118
|
+
"timeout_seconds": config.eng.github.timeout_seconds,
|
|
119
|
+
"verify_ssl": config.eng.github.verify_ssl,
|
|
120
|
+
}
|
|
121
|
+
if eng_dict:
|
|
122
|
+
result["eng"] = eng_dict
|
|
123
|
+
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _dict_to_config(data: Dict[str, Any]) -> SamiConfig:
|
|
128
|
+
"""Convert a dictionary to SamiConfig."""
|
|
129
|
+
logging_data = data.get("logging", {})
|
|
130
|
+
logging_cfg = LoggingConfig(
|
|
131
|
+
log_dir=logging_data.get("log_dir", "logs"),
|
|
132
|
+
log_level=logging_data.get("log_level", "INFO"),
|
|
133
|
+
) if logging_data else LoggingConfig()
|
|
134
|
+
|
|
135
|
+
thehive_cfg: Optional[TheHiveConfig] = None
|
|
136
|
+
if "thehive" in data and data["thehive"]:
|
|
137
|
+
th_data = data["thehive"]
|
|
138
|
+
if th_data.get("base_url") and th_data.get("api_key"):
|
|
139
|
+
thehive_cfg = TheHiveConfig(
|
|
140
|
+
base_url=th_data["base_url"],
|
|
141
|
+
api_key=th_data["api_key"],
|
|
142
|
+
timeout_seconds=th_data.get("timeout_seconds", 30),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
iris_cfg: Optional[IrisConfig] = None
|
|
146
|
+
if "iris" in data and data["iris"]:
|
|
147
|
+
iris_data = data["iris"]
|
|
148
|
+
if iris_data.get("base_url") and iris_data.get("api_key"):
|
|
149
|
+
iris_cfg = IrisConfig(
|
|
150
|
+
base_url=iris_data["base_url"],
|
|
151
|
+
api_key=iris_data["api_key"],
|
|
152
|
+
timeout_seconds=iris_data.get("timeout_seconds", 30),
|
|
153
|
+
verify_ssl=iris_data.get("verify_ssl", True),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
elastic_cfg: Optional[ElasticConfig] = None
|
|
157
|
+
if "elastic" in data and data["elastic"]:
|
|
158
|
+
el_data = data["elastic"]
|
|
159
|
+
if el_data.get("base_url"):
|
|
160
|
+
elastic_cfg = ElasticConfig(
|
|
161
|
+
base_url=el_data["base_url"],
|
|
162
|
+
api_key=el_data.get("api_key"),
|
|
163
|
+
username=el_data.get("username"),
|
|
164
|
+
password=el_data.get("password"),
|
|
165
|
+
timeout_seconds=el_data.get("timeout_seconds", 30),
|
|
166
|
+
verify_ssl=el_data.get("verify_ssl", True),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
edr_cfg: Optional[EDRConfig] = None
|
|
170
|
+
if "edr" in data and data["edr"]:
|
|
171
|
+
edr_data = data["edr"]
|
|
172
|
+
if edr_data.get("base_url") and edr_data.get("api_key"):
|
|
173
|
+
edr_cfg = EDRConfig(
|
|
174
|
+
edr_type=edr_data.get("edr_type", "velociraptor"),
|
|
175
|
+
base_url=edr_data["base_url"],
|
|
176
|
+
api_key=edr_data["api_key"],
|
|
177
|
+
timeout_seconds=edr_data.get("timeout_seconds", 30),
|
|
178
|
+
verify_ssl=edr_data.get("verify_ssl", True),
|
|
179
|
+
additional_params=edr_data.get("additional_params"),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
cti_cfg: Optional[CTIConfig] = None
|
|
183
|
+
if "cti" in data and data["cti"]:
|
|
184
|
+
cti_data = data["cti"]
|
|
185
|
+
if cti_data.get("base_url"):
|
|
186
|
+
# Handle api_key - it's optional for local_tip but required for opencti
|
|
187
|
+
api_key = cti_data.get("api_key")
|
|
188
|
+
cti_cfg = CTIConfig(
|
|
189
|
+
cti_type=cti_data.get("cti_type", "local_tip"),
|
|
190
|
+
base_url=cti_data["base_url"],
|
|
191
|
+
api_key=api_key,
|
|
192
|
+
timeout_seconds=cti_data.get("timeout_seconds", 30),
|
|
193
|
+
verify_ssl=cti_data.get("verify_ssl", True),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
eng_cfg: Optional[EngConfig] = None
|
|
197
|
+
if "eng" in data and data["eng"]:
|
|
198
|
+
eng_data = data["eng"]
|
|
199
|
+
provider = eng_data.get("provider", "trello")
|
|
200
|
+
|
|
201
|
+
trello_cfg: Optional[TrelloConfig] = None
|
|
202
|
+
if eng_data.get("trello"):
|
|
203
|
+
trello_data = eng_data["trello"]
|
|
204
|
+
if trello_data.get("api_key") and trello_data.get("api_token"):
|
|
205
|
+
trello_cfg = TrelloConfig(
|
|
206
|
+
api_key=trello_data["api_key"],
|
|
207
|
+
api_token=trello_data["api_token"],
|
|
208
|
+
fine_tuning_board_id=trello_data["fine_tuning_board_id"],
|
|
209
|
+
engineering_board_id=trello_data["engineering_board_id"],
|
|
210
|
+
timeout_seconds=trello_data.get("timeout_seconds", 30),
|
|
211
|
+
verify_ssl=trello_data.get("verify_ssl", True),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
clickup_cfg: Optional[ClickUpConfig] = None
|
|
215
|
+
if eng_data.get("clickup"):
|
|
216
|
+
clickup_data = eng_data["clickup"]
|
|
217
|
+
if clickup_data.get("api_token"):
|
|
218
|
+
clickup_cfg = ClickUpConfig(
|
|
219
|
+
api_token=clickup_data["api_token"],
|
|
220
|
+
fine_tuning_list_id=clickup_data["fine_tuning_list_id"],
|
|
221
|
+
engineering_list_id=clickup_data["engineering_list_id"],
|
|
222
|
+
space_id=clickup_data.get("space_id"),
|
|
223
|
+
timeout_seconds=clickup_data.get("timeout_seconds", 30),
|
|
224
|
+
verify_ssl=clickup_data.get("verify_ssl", True),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
github_cfg: Optional[GitHubConfig] = None
|
|
228
|
+
if eng_data.get("github"):
|
|
229
|
+
github_data = eng_data["github"]
|
|
230
|
+
if github_data.get("api_token"):
|
|
231
|
+
github_cfg = GitHubConfig(
|
|
232
|
+
api_token=github_data["api_token"],
|
|
233
|
+
fine_tuning_project_id=github_data["fine_tuning_project_id"],
|
|
234
|
+
engineering_project_id=github_data["engineering_project_id"],
|
|
235
|
+
timeout_seconds=github_data.get("timeout_seconds", 30),
|
|
236
|
+
verify_ssl=github_data.get("verify_ssl", True),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if trello_cfg or clickup_cfg or github_cfg:
|
|
240
|
+
eng_cfg = EngConfig(
|
|
241
|
+
trello=trello_cfg,
|
|
242
|
+
clickup=clickup_cfg,
|
|
243
|
+
github=github_cfg,
|
|
244
|
+
provider=provider,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return SamiConfig(
|
|
248
|
+
thehive=thehive_cfg,
|
|
249
|
+
iris=iris_cfg,
|
|
250
|
+
elastic=elastic_cfg,
|
|
251
|
+
edr=edr_cfg,
|
|
252
|
+
cti=cti_cfg,
|
|
253
|
+
eng=eng_cfg,
|
|
254
|
+
logging=logging_cfg,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def load_config_from_env_file(env_path: str = ENV_FILE) -> Dict[str, Any]:
|
|
259
|
+
"""
|
|
260
|
+
Load configuration from a .env file.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
env_path: Path to the .env file.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Dictionary with configuration values.
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
ConfigError: If the file cannot be read or parsed.
|
|
270
|
+
"""
|
|
271
|
+
env_file = Path(env_path)
|
|
272
|
+
config_dict: Dict[str, Any] = {}
|
|
273
|
+
|
|
274
|
+
if not env_file.exists():
|
|
275
|
+
return config_dict
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
with open(env_file, "r") as f:
|
|
279
|
+
for line in f:
|
|
280
|
+
line = line.strip()
|
|
281
|
+
# Skip comments and empty lines
|
|
282
|
+
if not line or line.startswith("#"):
|
|
283
|
+
continue
|
|
284
|
+
# Parse KEY=VALUE
|
|
285
|
+
if "=" in line:
|
|
286
|
+
key, value = line.split("=", 1)
|
|
287
|
+
key = key.strip()
|
|
288
|
+
value = value.strip()
|
|
289
|
+
# Remove quotes if present
|
|
290
|
+
if value.startswith('"') and value.endswith('"'):
|
|
291
|
+
value = value[1:-1]
|
|
292
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
293
|
+
value = value[1:-1]
|
|
294
|
+
config_dict[key] = value
|
|
295
|
+
return config_dict
|
|
296
|
+
except Exception as e:
|
|
297
|
+
raise ConfigError(f"Failed to load .env file: {e}") from e
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _env_dict_to_config(env_dict: Dict[str, Any]) -> SamiConfig:
|
|
301
|
+
"""Convert .env dictionary to SamiConfig."""
|
|
302
|
+
logging_cfg = LoggingConfig(
|
|
303
|
+
log_dir=env_dict.get("SAMIGPT_LOG_DIR", "logs"),
|
|
304
|
+
log_level=env_dict.get("SAMIGPT_LOG_LEVEL", "INFO"),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
thehive_cfg: Optional[TheHiveConfig] = None
|
|
308
|
+
thehive_url = env_dict.get("SAMIGPT_THEHIVE_URL")
|
|
309
|
+
thehive_api_key = env_dict.get("SAMIGPT_THEHIVE_API_KEY")
|
|
310
|
+
if thehive_url and thehive_api_key:
|
|
311
|
+
timeout = int(env_dict.get("SAMIGPT_THEHIVE_TIMEOUT_SECONDS", "30"))
|
|
312
|
+
thehive_cfg = TheHiveConfig(
|
|
313
|
+
base_url=thehive_url,
|
|
314
|
+
api_key=thehive_api_key,
|
|
315
|
+
timeout_seconds=timeout,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
iris_cfg: Optional[IrisConfig] = None
|
|
319
|
+
iris_url = env_dict.get("SAMIGPT_IRIS_URL")
|
|
320
|
+
iris_api_key = env_dict.get("SAMIGPT_IRIS_API_KEY")
|
|
321
|
+
if iris_url and iris_api_key:
|
|
322
|
+
timeout = int(env_dict.get("SAMIGPT_IRIS_TIMEOUT_SECONDS", "30"))
|
|
323
|
+
verify_ssl = env_dict.get("SAMIGPT_IRIS_VERIFY_SSL", "true").lower() in ("true", "1", "yes")
|
|
324
|
+
iris_cfg = IrisConfig(
|
|
325
|
+
base_url=iris_url,
|
|
326
|
+
api_key=iris_api_key,
|
|
327
|
+
timeout_seconds=timeout,
|
|
328
|
+
verify_ssl=verify_ssl,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
elastic_cfg: Optional[ElasticConfig] = None
|
|
332
|
+
elastic_url = env_dict.get("SAMIGPT_ELASTIC_URL")
|
|
333
|
+
if elastic_url:
|
|
334
|
+
timeout = int(env_dict.get("SAMIGPT_ELASTIC_TIMEOUT_SECONDS", "30"))
|
|
335
|
+
verify_ssl = env_dict.get("SAMIGPT_ELASTIC_VERIFY_SSL", "true").lower() in ("true", "1", "yes")
|
|
336
|
+
elastic_cfg = ElasticConfig(
|
|
337
|
+
base_url=elastic_url,
|
|
338
|
+
api_key=env_dict.get("SAMIGPT_ELASTIC_API_KEY"),
|
|
339
|
+
username=env_dict.get("SAMIGPT_ELASTIC_USERNAME"),
|
|
340
|
+
password=env_dict.get("SAMIGPT_ELASTIC_PASSWORD"),
|
|
341
|
+
timeout_seconds=timeout,
|
|
342
|
+
verify_ssl=verify_ssl,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
edr_cfg: Optional[EDRConfig] = None
|
|
346
|
+
edr_url = env_dict.get("SAMIGPT_EDR_URL")
|
|
347
|
+
edr_api_key = env_dict.get("SAMIGPT_EDR_API_KEY")
|
|
348
|
+
if edr_url and edr_api_key:
|
|
349
|
+
timeout = int(env_dict.get("SAMIGPT_EDR_TIMEOUT_SECONDS", "30"))
|
|
350
|
+
verify_ssl = env_dict.get("SAMIGPT_EDR_VERIFY_SSL", "true").lower() in ("true", "1", "yes")
|
|
351
|
+
edr_cfg = EDRConfig(
|
|
352
|
+
edr_type=env_dict.get("SAMIGPT_EDR_TYPE", "velociraptor"),
|
|
353
|
+
base_url=edr_url,
|
|
354
|
+
api_key=edr_api_key,
|
|
355
|
+
timeout_seconds=timeout,
|
|
356
|
+
verify_ssl=verify_ssl,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return SamiConfig(
|
|
360
|
+
thehive=thehive_cfg,
|
|
361
|
+
iris=iris_cfg,
|
|
362
|
+
elastic=elastic_cfg,
|
|
363
|
+
edr=edr_cfg,
|
|
364
|
+
logging=logging_cfg,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _ensure_starting_config(config_path: str = CONFIG_FILE, starting_config_path: str = STARTING_CONFIG_FILE) -> None:
|
|
369
|
+
"""
|
|
370
|
+
Ensure config.json exists by copying from starting config if needed.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
config_path: Path to the JSON configuration file.
|
|
374
|
+
starting_config_path: Path to the starting/template configuration file.
|
|
375
|
+
"""
|
|
376
|
+
config_file = Path(config_path)
|
|
377
|
+
starting_config_file = Path(starting_config_path)
|
|
378
|
+
|
|
379
|
+
# If config.json doesn't exist, but starting config does, copy it
|
|
380
|
+
if not config_file.exists() and starting_config_file.exists():
|
|
381
|
+
try:
|
|
382
|
+
shutil.copy2(starting_config_file, config_file)
|
|
383
|
+
except Exception as e:
|
|
384
|
+
# If copy fails, continue - we'll use defaults
|
|
385
|
+
pass
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def load_config_from_file(config_path: str = CONFIG_FILE, env_path: str = ENV_FILE, starting_config_path: str = STARTING_CONFIG_FILE) -> SamiConfig:
|
|
389
|
+
"""
|
|
390
|
+
Load configuration from files. Tries .env file first, then JSON file.
|
|
391
|
+
|
|
392
|
+
Priority: .env file > JSON file > starting config > defaults
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
config_path: Path to the JSON configuration file.
|
|
396
|
+
env_path: Path to the .env file.
|
|
397
|
+
starting_config_path: Path to the starting/template configuration file.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
SamiConfig instance.
|
|
401
|
+
|
|
402
|
+
Raises:
|
|
403
|
+
ConfigError: If the files cannot be read or parsed.
|
|
404
|
+
"""
|
|
405
|
+
env_file = Path(env_path)
|
|
406
|
+
config_file = Path(config_path)
|
|
407
|
+
starting_config_file = Path(starting_config_path)
|
|
408
|
+
|
|
409
|
+
# Ensure config.json exists by copying from starting config if needed
|
|
410
|
+
_ensure_starting_config(config_path, starting_config_path)
|
|
411
|
+
|
|
412
|
+
# Try .env file first
|
|
413
|
+
if env_file.exists():
|
|
414
|
+
try:
|
|
415
|
+
env_dict = load_config_from_env_file(env_path)
|
|
416
|
+
if env_dict:
|
|
417
|
+
return _env_dict_to_config(env_dict)
|
|
418
|
+
except Exception as e:
|
|
419
|
+
# If .env parsing fails, try JSON
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
# Fall back to JSON file
|
|
423
|
+
if config_file.exists():
|
|
424
|
+
try:
|
|
425
|
+
with open(config_file, "r") as f:
|
|
426
|
+
data = json.load(f)
|
|
427
|
+
return _dict_to_config(data)
|
|
428
|
+
except json.JSONDecodeError as e:
|
|
429
|
+
raise ConfigError(f"Invalid JSON in config file: {e}") from e
|
|
430
|
+
except Exception as e:
|
|
431
|
+
raise ConfigError(f"Failed to load config file: {e}") from e
|
|
432
|
+
|
|
433
|
+
# Fall back to starting config file
|
|
434
|
+
if starting_config_file.exists():
|
|
435
|
+
try:
|
|
436
|
+
with open(starting_config_file, "r") as f:
|
|
437
|
+
data = json.load(f)
|
|
438
|
+
return _dict_to_config(data)
|
|
439
|
+
except json.JSONDecodeError as e:
|
|
440
|
+
raise ConfigError(f"Invalid JSON in starting config file: {e}") from e
|
|
441
|
+
except Exception as e:
|
|
442
|
+
# If starting config fails, continue to defaults
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
# Return default config if no files exist
|
|
446
|
+
return SamiConfig(
|
|
447
|
+
thehive=None,
|
|
448
|
+
elastic=None,
|
|
449
|
+
edr=None,
|
|
450
|
+
logging=LoggingConfig(),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def save_config_to_env_file(config: SamiConfig, env_path: str = ENV_FILE) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Save configuration to a .env file.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
config: SamiConfig instance to save.
|
|
460
|
+
env_path: Path to the .env file.
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
ConfigError: If the file cannot be written.
|
|
464
|
+
"""
|
|
465
|
+
env_file = Path(env_path)
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
# Create parent directories if needed
|
|
469
|
+
env_file.parent.mkdir(parents=True, exist_ok=True)
|
|
470
|
+
|
|
471
|
+
lines = [
|
|
472
|
+
"# SamiGPT Configuration File",
|
|
473
|
+
"# This file can be edited manually or via the web interface",
|
|
474
|
+
"# Changes are synchronized between this file and config.json",
|
|
475
|
+
"",
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
# Logging
|
|
479
|
+
lines.append("# Logging Configuration")
|
|
480
|
+
lines.append(f"SAMIGPT_LOG_DIR={config.logging.log_dir}")
|
|
481
|
+
lines.append(f"SAMIGPT_LOG_LEVEL={config.logging.log_level}")
|
|
482
|
+
lines.append("")
|
|
483
|
+
|
|
484
|
+
# TheHive
|
|
485
|
+
if config.thehive:
|
|
486
|
+
lines.append("# TheHive Case Management")
|
|
487
|
+
lines.append(f"SAMIGPT_THEHIVE_URL={config.thehive.base_url}")
|
|
488
|
+
lines.append(f'SAMIGPT_THEHIVE_API_KEY="{config.thehive.api_key}"')
|
|
489
|
+
lines.append(f"SAMIGPT_THEHIVE_TIMEOUT_SECONDS={config.thehive.timeout_seconds}")
|
|
490
|
+
lines.append("")
|
|
491
|
+
else:
|
|
492
|
+
lines.append("# TheHive Case Management (disabled)")
|
|
493
|
+
lines.append("# SAMIGPT_THEHIVE_URL=")
|
|
494
|
+
lines.append("# SAMIGPT_THEHIVE_API_KEY=")
|
|
495
|
+
lines.append("")
|
|
496
|
+
|
|
497
|
+
# IRIS
|
|
498
|
+
if config.iris:
|
|
499
|
+
lines.append("# IRIS Case Management")
|
|
500
|
+
lines.append(f"SAMIGPT_IRIS_URL={config.iris.base_url}")
|
|
501
|
+
lines.append(f'SAMIGPT_IRIS_API_KEY="{config.iris.api_key}"')
|
|
502
|
+
lines.append(f"SAMIGPT_IRIS_TIMEOUT_SECONDS={config.iris.timeout_seconds}")
|
|
503
|
+
lines.append(f"SAMIGPT_IRIS_VERIFY_SSL={'true' if config.iris.verify_ssl else 'false'}")
|
|
504
|
+
lines.append("")
|
|
505
|
+
else:
|
|
506
|
+
lines.append("# IRIS Case Management (disabled)")
|
|
507
|
+
lines.append("# SAMIGPT_IRIS_URL=")
|
|
508
|
+
lines.append("# SAMIGPT_IRIS_API_KEY=")
|
|
509
|
+
lines.append("")
|
|
510
|
+
|
|
511
|
+
# Elastic
|
|
512
|
+
if config.elastic:
|
|
513
|
+
lines.append("# Elastic (SIEM)")
|
|
514
|
+
lines.append(f"SAMIGPT_ELASTIC_URL={config.elastic.base_url}")
|
|
515
|
+
if config.elastic.api_key:
|
|
516
|
+
lines.append(f'SAMIGPT_ELASTIC_API_KEY="{config.elastic.api_key}"')
|
|
517
|
+
if config.elastic.username:
|
|
518
|
+
lines.append(f'SAMIGPT_ELASTIC_USERNAME="{config.elastic.username}"')
|
|
519
|
+
if config.elastic.password:
|
|
520
|
+
lines.append(f'SAMIGPT_ELASTIC_PASSWORD="{config.elastic.password}"')
|
|
521
|
+
lines.append(f"SAMIGPT_ELASTIC_TIMEOUT_SECONDS={config.elastic.timeout_seconds}")
|
|
522
|
+
lines.append(f"SAMIGPT_ELASTIC_VERIFY_SSL={'true' if config.elastic.verify_ssl else 'false'}")
|
|
523
|
+
lines.append("")
|
|
524
|
+
else:
|
|
525
|
+
lines.append("# Elastic (SIEM) (disabled)")
|
|
526
|
+
lines.append("# SAMIGPT_ELASTIC_URL=")
|
|
527
|
+
lines.append("")
|
|
528
|
+
|
|
529
|
+
# EDR
|
|
530
|
+
if config.edr:
|
|
531
|
+
lines.append("# EDR Configuration")
|
|
532
|
+
lines.append(f"SAMIGPT_EDR_TYPE={config.edr.edr_type}")
|
|
533
|
+
lines.append(f"SAMIGPT_EDR_URL={config.edr.base_url}")
|
|
534
|
+
lines.append(f'SAMIGPT_EDR_API_KEY="{config.edr.api_key}"')
|
|
535
|
+
lines.append(f"SAMIGPT_EDR_TIMEOUT_SECONDS={config.edr.timeout_seconds}")
|
|
536
|
+
lines.append(f"SAMIGPT_EDR_VERIFY_SSL={'true' if config.edr.verify_ssl else 'false'}")
|
|
537
|
+
lines.append("")
|
|
538
|
+
else:
|
|
539
|
+
lines.append("# EDR Configuration (disabled)")
|
|
540
|
+
lines.append("# SAMIGPT_EDR_URL=")
|
|
541
|
+
lines.append("# SAMIGPT_EDR_API_KEY=")
|
|
542
|
+
lines.append("")
|
|
543
|
+
|
|
544
|
+
with open(env_file, "w") as f:
|
|
545
|
+
f.write("\n".join(lines))
|
|
546
|
+
except Exception as e:
|
|
547
|
+
raise ConfigError(f"Failed to save .env file: {e}") from e
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def save_config_to_file(
|
|
551
|
+
config: SamiConfig, config_path: str = CONFIG_FILE, env_path: str = ENV_FILE, save_both: bool = True
|
|
552
|
+
) -> None:
|
|
553
|
+
"""
|
|
554
|
+
Save configuration to both JSON and .env files for synchronization.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
config: SamiConfig instance to save.
|
|
558
|
+
config_path: Path to the JSON configuration file.
|
|
559
|
+
env_path: Path to the .env file.
|
|
560
|
+
save_both: If True, save to both JSON and .env. If False, only save to JSON.
|
|
561
|
+
|
|
562
|
+
Raises:
|
|
563
|
+
ConfigError: If the files cannot be written.
|
|
564
|
+
"""
|
|
565
|
+
config_file = Path(config_path)
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
# Create parent directories if needed
|
|
569
|
+
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
570
|
+
|
|
571
|
+
data = _config_to_dict(config)
|
|
572
|
+
with open(config_file, "w") as f:
|
|
573
|
+
json.dump(data, f, indent=2)
|
|
574
|
+
|
|
575
|
+
# Also save to .env file for manual editing
|
|
576
|
+
if save_both:
|
|
577
|
+
save_config_to_env_file(config, env_path)
|
|
578
|
+
except Exception as e:
|
|
579
|
+
raise ConfigError(f"Failed to save config file: {e}") from e
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def get_config_dict(config_path: str = CONFIG_FILE, env_path: str = ENV_FILE) -> Dict[str, Any]:
|
|
583
|
+
"""
|
|
584
|
+
Get configuration as a dictionary (for API responses).
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
config_path: Path to the JSON configuration file.
|
|
588
|
+
env_path: Path to the .env file.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Dictionary representation of the configuration.
|
|
592
|
+
"""
|
|
593
|
+
config = load_config_from_file(config_path, env_path)
|
|
594
|
+
return _config_to_dict(config)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def update_config_dict(
|
|
598
|
+
updates: Dict[str, Any], config_path: str = CONFIG_FILE, env_path: str = ENV_FILE, save_both: bool = True
|
|
599
|
+
) -> SamiConfig:
|
|
600
|
+
"""
|
|
601
|
+
Update configuration with new values and save to both JSON and .env files.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
updates: Dictionary with configuration updates.
|
|
605
|
+
config_path: Path to the JSON configuration file.
|
|
606
|
+
env_path: Path to the .env file.
|
|
607
|
+
save_both: If True, save to both JSON and .env. If False, only save to JSON.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Updated SamiConfig instance.
|
|
611
|
+
|
|
612
|
+
Raises:
|
|
613
|
+
ConfigError: If the update fails.
|
|
614
|
+
"""
|
|
615
|
+
# Load existing config (checks both .env and JSON)
|
|
616
|
+
config = load_config_from_file(config_path, env_path)
|
|
617
|
+
|
|
618
|
+
# Update logging
|
|
619
|
+
if "logging" in updates:
|
|
620
|
+
logging_updates = updates["logging"]
|
|
621
|
+
if "log_dir" in logging_updates:
|
|
622
|
+
config.logging.log_dir = logging_updates["log_dir"]
|
|
623
|
+
if "log_level" in logging_updates:
|
|
624
|
+
config.logging.log_level = logging_updates["log_level"]
|
|
625
|
+
|
|
626
|
+
# Update TheHive
|
|
627
|
+
if "thehive" in updates:
|
|
628
|
+
th_updates = updates["thehive"]
|
|
629
|
+
if th_updates is None:
|
|
630
|
+
config.thehive = None
|
|
631
|
+
elif th_updates.get("base_url") and th_updates.get("api_key"):
|
|
632
|
+
config.thehive = TheHiveConfig(
|
|
633
|
+
base_url=th_updates["base_url"],
|
|
634
|
+
api_key=th_updates["api_key"],
|
|
635
|
+
timeout_seconds=th_updates.get("timeout_seconds", 30),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# Update IRIS
|
|
639
|
+
if "iris" in updates:
|
|
640
|
+
iris_updates = updates["iris"]
|
|
641
|
+
if iris_updates is None:
|
|
642
|
+
config.iris = None
|
|
643
|
+
elif iris_updates.get("base_url") and iris_updates.get("api_key"):
|
|
644
|
+
config.iris = IrisConfig(
|
|
645
|
+
base_url=iris_updates["base_url"],
|
|
646
|
+
api_key=iris_updates["api_key"],
|
|
647
|
+
timeout_seconds=iris_updates.get("timeout_seconds", 30),
|
|
648
|
+
verify_ssl=iris_updates.get("verify_ssl", True),
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# Update Elastic
|
|
652
|
+
if "elastic" in updates:
|
|
653
|
+
el_updates = updates["elastic"]
|
|
654
|
+
if el_updates is None:
|
|
655
|
+
config.elastic = None
|
|
656
|
+
elif el_updates.get("base_url"):
|
|
657
|
+
config.elastic = ElasticConfig(
|
|
658
|
+
base_url=el_updates["base_url"],
|
|
659
|
+
api_key=el_updates.get("api_key"),
|
|
660
|
+
username=el_updates.get("username"),
|
|
661
|
+
password=el_updates.get("password"),
|
|
662
|
+
timeout_seconds=el_updates.get("timeout_seconds", 30),
|
|
663
|
+
verify_ssl=el_updates.get("verify_ssl", True),
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Update EDR
|
|
667
|
+
if "edr" in updates:
|
|
668
|
+
edr_updates = updates["edr"]
|
|
669
|
+
if edr_updates is None:
|
|
670
|
+
config.edr = None
|
|
671
|
+
elif edr_updates.get("base_url") and edr_updates.get("api_key"):
|
|
672
|
+
config.edr = EDRConfig(
|
|
673
|
+
edr_type=edr_updates.get("edr_type", "velociraptor"),
|
|
674
|
+
base_url=edr_updates["base_url"],
|
|
675
|
+
api_key=edr_updates["api_key"],
|
|
676
|
+
timeout_seconds=edr_updates.get("timeout_seconds", 30),
|
|
677
|
+
verify_ssl=edr_updates.get("verify_ssl", True),
|
|
678
|
+
additional_params=edr_updates.get("additional_params"),
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Save updated config to both files
|
|
682
|
+
save_config_to_file(config, config_path, env_path, save_both=save_both)
|
|
683
|
+
return config
|
|
684
|
+
|