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,1709 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM-callable tools for SIEM operations.
|
|
3
|
+
|
|
4
|
+
These functions wrap the generic SIEMClient interface and provide
|
|
5
|
+
LLM-friendly error handling and return values.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from ..api.siem import SIEMClient
|
|
13
|
+
from ..core.errors import IntegrationError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def search_security_events(
|
|
17
|
+
query: str,
|
|
18
|
+
limit: int = 100,
|
|
19
|
+
client: SIEMClient = None, # type: ignore
|
|
20
|
+
) -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Search security events across all environments.
|
|
23
|
+
|
|
24
|
+
Tool schema:
|
|
25
|
+
- name: search_security_events
|
|
26
|
+
- description: Search security events and logs across all environments
|
|
27
|
+
using a query string. Returns matching events with details.
|
|
28
|
+
- parameters:
|
|
29
|
+
- query (str, required): Search query (vendor-specific query language).
|
|
30
|
+
- limit (int, optional): Maximum number of events to return (default: 100).
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
query: Search query string.
|
|
34
|
+
limit: Maximum number of events to return.
|
|
35
|
+
client: The SIEM client.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary containing search results with events.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
IntegrationError: If search fails.
|
|
42
|
+
"""
|
|
43
|
+
if client is None:
|
|
44
|
+
raise IntegrationError("SIEM client not provided")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
result = client.search_security_events(query=query, limit=limit)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"success": True,
|
|
51
|
+
"query": result.query,
|
|
52
|
+
"total_count": result.total_count,
|
|
53
|
+
"returned_count": len(result.events),
|
|
54
|
+
"events": [
|
|
55
|
+
{
|
|
56
|
+
"id": event.id,
|
|
57
|
+
"timestamp": event.timestamp.isoformat(),
|
|
58
|
+
"source_type": event.source_type.value,
|
|
59
|
+
"message": event.message,
|
|
60
|
+
"host": event.host,
|
|
61
|
+
"username": event.username,
|
|
62
|
+
"ip": event.ip,
|
|
63
|
+
"process_name": event.process_name,
|
|
64
|
+
"file_hash": event.file_hash,
|
|
65
|
+
}
|
|
66
|
+
for event in result.events
|
|
67
|
+
],
|
|
68
|
+
}
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise IntegrationError(f"Failed to search security events: {str(e)}") from e
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_file_report(
|
|
74
|
+
file_hash: str,
|
|
75
|
+
client: SIEMClient = None, # type: ignore
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Get a report about a file by its hash.
|
|
79
|
+
|
|
80
|
+
Tool schema:
|
|
81
|
+
- name: get_file_report
|
|
82
|
+
- description: Retrieve an aggregated report about a file identified by
|
|
83
|
+
its hash, including when it was first/last seen, detection count, and
|
|
84
|
+
affected hosts.
|
|
85
|
+
- parameters:
|
|
86
|
+
- file_hash (str, required): The file hash (MD5, SHA256, etc.).
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
file_hash: The file hash.
|
|
90
|
+
client: The SIEM client.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary containing file report details.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
IntegrationError: If retrieving report fails.
|
|
97
|
+
"""
|
|
98
|
+
if client is None:
|
|
99
|
+
raise IntegrationError("SIEM client not provided")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
report = client.get_file_report(file_hash)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"success": True,
|
|
106
|
+
"file_hash": report.file_hash,
|
|
107
|
+
"first_seen": report.first_seen.isoformat() if report.first_seen else None,
|
|
108
|
+
"last_seen": report.last_seen.isoformat() if report.last_seen else None,
|
|
109
|
+
"detection_count": report.detection_count,
|
|
110
|
+
"affected_hosts": report.affected_hosts or [],
|
|
111
|
+
}
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise IntegrationError(f"Failed to get file report for {file_hash}: {str(e)}") from e
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_file_behavior_summary(
|
|
117
|
+
file_hash: str,
|
|
118
|
+
client: SIEMClient = None, # type: ignore
|
|
119
|
+
) -> Dict[str, Any]:
|
|
120
|
+
"""
|
|
121
|
+
Get a behavior summary for a file.
|
|
122
|
+
|
|
123
|
+
Tool schema:
|
|
124
|
+
- name: get_file_behavior_summary
|
|
125
|
+
- description: Retrieve a high-level behavior summary for a file,
|
|
126
|
+
including process trees, network activity, and persistence mechanisms.
|
|
127
|
+
- parameters:
|
|
128
|
+
- file_hash (str, required): The file hash.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
file_hash: The file hash.
|
|
132
|
+
client: The SIEM client.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary containing behavior summary.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
IntegrationError: If retrieving summary fails.
|
|
139
|
+
"""
|
|
140
|
+
if client is None:
|
|
141
|
+
raise IntegrationError("SIEM client not provided")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
summary = client.get_file_behavior_summary(file_hash)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"success": True,
|
|
148
|
+
"file_hash": summary.file_hash,
|
|
149
|
+
"process_trees": summary.process_trees or [],
|
|
150
|
+
"network_activity": summary.network_activity or [],
|
|
151
|
+
"persistence_mechanisms": summary.persistence_mechanisms or [],
|
|
152
|
+
"notes": summary.notes,
|
|
153
|
+
}
|
|
154
|
+
except Exception as e:
|
|
155
|
+
raise IntegrationError(
|
|
156
|
+
f"Failed to get file behavior summary for {file_hash}: {str(e)}"
|
|
157
|
+
) from e
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_entities_related_to_file(
|
|
161
|
+
file_hash: str,
|
|
162
|
+
client: SIEMClient = None, # type: ignore
|
|
163
|
+
) -> Dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Get entities related to a file.
|
|
166
|
+
|
|
167
|
+
Tool schema:
|
|
168
|
+
- name: get_entities_related_to_file
|
|
169
|
+
- description: Retrieve entities related to a file hash, such as hosts
|
|
170
|
+
where it was seen, users who executed it, related processes, and alerts.
|
|
171
|
+
- parameters:
|
|
172
|
+
- file_hash (str, required): The file hash.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
file_hash: The file hash.
|
|
176
|
+
client: The SIEM client.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dictionary containing related entities.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
IntegrationError: If retrieving entities fails.
|
|
183
|
+
"""
|
|
184
|
+
if client is None:
|
|
185
|
+
raise IntegrationError("SIEM client not provided")
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
entities = client.get_entities_related_to_file(file_hash)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"success": True,
|
|
192
|
+
"indicator": entities.indicator,
|
|
193
|
+
"hosts": entities.hosts or [],
|
|
194
|
+
"users": entities.users or [],
|
|
195
|
+
"processes": entities.processes or [],
|
|
196
|
+
"alerts": entities.alerts or [],
|
|
197
|
+
}
|
|
198
|
+
except Exception as e:
|
|
199
|
+
raise IntegrationError(
|
|
200
|
+
f"Failed to get entities related to file {file_hash}: {str(e)}"
|
|
201
|
+
) from e
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_ip_address_report(
|
|
205
|
+
ip: str,
|
|
206
|
+
client: SIEMClient = None, # type: ignore
|
|
207
|
+
) -> Dict[str, Any]:
|
|
208
|
+
"""
|
|
209
|
+
Get a report about an IP address.
|
|
210
|
+
|
|
211
|
+
Tool schema:
|
|
212
|
+
- name: get_ip_address_report
|
|
213
|
+
- description: Retrieve an aggregated report about an IP address,
|
|
214
|
+
including reputation, geolocation, and related alerts.
|
|
215
|
+
- parameters:
|
|
216
|
+
- ip (str, required): The IP address.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
ip: The IP address.
|
|
220
|
+
client: The SIEM client.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dictionary containing IP report details.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
IntegrationError: If retrieving report fails.
|
|
227
|
+
"""
|
|
228
|
+
if client is None:
|
|
229
|
+
raise IntegrationError("SIEM client not provided")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
report = client.get_ip_address_report(ip)
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"success": True,
|
|
236
|
+
"ip": report.ip,
|
|
237
|
+
"reputation": report.reputation,
|
|
238
|
+
"geo": report.geo or {},
|
|
239
|
+
"related_alerts": report.related_alerts or [],
|
|
240
|
+
}
|
|
241
|
+
except Exception as e:
|
|
242
|
+
raise IntegrationError(f"Failed to get IP address report for {ip}: {str(e)}") from e
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def search_user_activity(
|
|
246
|
+
username: str,
|
|
247
|
+
limit: int = 100,
|
|
248
|
+
client: SIEMClient = None, # type: ignore
|
|
249
|
+
) -> Dict[str, Any]:
|
|
250
|
+
"""
|
|
251
|
+
Search for user activity in security logs.
|
|
252
|
+
|
|
253
|
+
Tool schema:
|
|
254
|
+
- name: search_user_activity
|
|
255
|
+
- description: Search for security events related to a specific user,
|
|
256
|
+
including authentication events, file access, and other activities.
|
|
257
|
+
- parameters:
|
|
258
|
+
- username (str, required): The username to search for.
|
|
259
|
+
- limit (int, optional): Maximum number of events to return (default: 100).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
username: The username.
|
|
263
|
+
limit: Maximum number of events.
|
|
264
|
+
client: The SIEM client.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Dictionary containing user activity events.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
IntegrationError: If search fails.
|
|
271
|
+
"""
|
|
272
|
+
if client is None:
|
|
273
|
+
raise IntegrationError("SIEM client not provided")
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
result = client.search_user_activity(username=username, limit=limit)
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
"success": True,
|
|
280
|
+
"username": username,
|
|
281
|
+
"total_count": result.total_count,
|
|
282
|
+
"returned_count": len(result.events),
|
|
283
|
+
"events": [
|
|
284
|
+
{
|
|
285
|
+
"id": event.id,
|
|
286
|
+
"timestamp": event.timestamp.isoformat(),
|
|
287
|
+
"source_type": event.source_type.value,
|
|
288
|
+
"message": event.message,
|
|
289
|
+
"host": event.host,
|
|
290
|
+
"username": event.username,
|
|
291
|
+
"ip": event.ip,
|
|
292
|
+
"process_name": event.process_name,
|
|
293
|
+
"file_hash": event.file_hash,
|
|
294
|
+
}
|
|
295
|
+
for event in result.events
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
except Exception as e:
|
|
299
|
+
raise IntegrationError(f"Failed to search user activity for {username}: {str(e)}") from e
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def pivot_on_indicator(
|
|
303
|
+
indicator: str,
|
|
304
|
+
limit: int = 200,
|
|
305
|
+
client: SIEMClient = None, # type: ignore
|
|
306
|
+
) -> Dict[str, Any]:
|
|
307
|
+
"""
|
|
308
|
+
Pivot on an indicator of compromise (IOC).
|
|
309
|
+
|
|
310
|
+
Tool schema:
|
|
311
|
+
- name: pivot_on_indicator
|
|
312
|
+
- description: Given an IOC (file hash, IP address, domain, etc.),
|
|
313
|
+
search for all related security events across environments for
|
|
314
|
+
further investigation.
|
|
315
|
+
- parameters:
|
|
316
|
+
- indicator (str, required): The IOC (hash, IP, domain, etc.).
|
|
317
|
+
- limit (int, optional): Maximum number of events to return (default: 200).
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
indicator: The IOC value.
|
|
321
|
+
limit: Maximum number of events.
|
|
322
|
+
client: The SIEM client.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Dictionary containing related events.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
IntegrationError: If pivot search fails.
|
|
329
|
+
"""
|
|
330
|
+
if client is None:
|
|
331
|
+
raise IntegrationError("SIEM client not provided")
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
result = client.pivot_on_indicator(indicator=indicator, limit=limit)
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"success": True,
|
|
338
|
+
"indicator": indicator,
|
|
339
|
+
"query": result.query,
|
|
340
|
+
"total_count": result.total_count,
|
|
341
|
+
"returned_count": len(result.events),
|
|
342
|
+
"events": [
|
|
343
|
+
{
|
|
344
|
+
"id": event.id,
|
|
345
|
+
"timestamp": event.timestamp.isoformat(),
|
|
346
|
+
"source_type": event.source_type.value,
|
|
347
|
+
"message": event.message,
|
|
348
|
+
"host": event.host,
|
|
349
|
+
"username": event.username,
|
|
350
|
+
"ip": event.ip,
|
|
351
|
+
"process_name": event.process_name,
|
|
352
|
+
"file_hash": event.file_hash,
|
|
353
|
+
}
|
|
354
|
+
for event in result.events
|
|
355
|
+
],
|
|
356
|
+
}
|
|
357
|
+
except Exception as e:
|
|
358
|
+
raise IntegrationError(f"Failed to pivot on indicator {indicator}: {str(e)}") from e
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def get_recent_alerts(
|
|
362
|
+
hours_back: int = 1,
|
|
363
|
+
max_alerts: int = 100,
|
|
364
|
+
status_filter: Optional[str] = None,
|
|
365
|
+
severity: Optional[str] = None,
|
|
366
|
+
hostname: Optional[str] = None,
|
|
367
|
+
client: SIEMClient = None, # type: ignore
|
|
368
|
+
) -> Dict[str, Any]:
|
|
369
|
+
"""
|
|
370
|
+
Summarize and smart-group recent alerts from the SIEM.
|
|
371
|
+
|
|
372
|
+
Tool schema:
|
|
373
|
+
- name: get_recent_alerts
|
|
374
|
+
- description: Get recent SIEM alerts (last N hours) and group similar
|
|
375
|
+
alerts together to help the AI decide what to investigate first.
|
|
376
|
+
**CRITICAL: Automatically excludes alerts that have already been investigated**
|
|
377
|
+
(alerts with signal.ai.verdict field) to prevent duplicate work. SOC1 should
|
|
378
|
+
never re-investigate alerts that have already been triaged.
|
|
379
|
+
- parameters:
|
|
380
|
+
- hours_back (int, optional): How many hours to look back (default: 1)
|
|
381
|
+
- max_alerts (int, optional): Maximum number of alerts to retrieve (default: 100)
|
|
382
|
+
- status_filter (str, optional): Filter by status (implementation-specific)
|
|
383
|
+
- severity (str, optional): Filter by severity (low, medium, high, critical)
|
|
384
|
+
- hostname (str, optional): Filter alerts by hostname (matches host.name field)
|
|
385
|
+
|
|
386
|
+
**Important:** This tool automatically filters out alerts that have a `verdict` field
|
|
387
|
+
(signal.ai.verdict in Elasticsearch). Alerts with verdicts have already been investigated
|
|
388
|
+
and should not be re-triaged by SOC1. This prevents duplicate work and ensures SOC1 only
|
|
389
|
+
processes new, uninvestigated alerts.
|
|
390
|
+
|
|
391
|
+
The tool groups alerts by a composite of title, severity, status, rule ID,
|
|
392
|
+
and alert type/category. For each group it returns:
|
|
393
|
+
- a stable group_id
|
|
394
|
+
- title and primary_severity
|
|
395
|
+
- count of alerts in the group
|
|
396
|
+
- list of alert_ids in the group
|
|
397
|
+
- statuses and severities seen in the group
|
|
398
|
+
- earliest_created_at and latest_created_at
|
|
399
|
+
- up to 3 example_alerts with key fields (id, title, severity, status, timestamps).
|
|
400
|
+
"""
|
|
401
|
+
if client is None:
|
|
402
|
+
raise IntegrationError("SIEM client not provided")
|
|
403
|
+
|
|
404
|
+
if not hasattr(client, "get_security_alerts"):
|
|
405
|
+
raise IntegrationError("SIEM client does not support get_security_alerts")
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
alerts = client.get_security_alerts(
|
|
409
|
+
hours_back=hours_back,
|
|
410
|
+
max_alerts=max_alerts,
|
|
411
|
+
status_filter=status_filter,
|
|
412
|
+
severity=severity,
|
|
413
|
+
hostname=hostname,
|
|
414
|
+
)
|
|
415
|
+
except Exception as e:
|
|
416
|
+
raise IntegrationError(f"Failed to get recent alerts: {str(e)}") from e
|
|
417
|
+
|
|
418
|
+
if not isinstance(alerts, list):
|
|
419
|
+
raise IntegrationError(
|
|
420
|
+
"SIEM client get_security_alerts returned unexpected type "
|
|
421
|
+
f"{type(alerts).__name__}, expected list"
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
def _severity_rank(value: Optional[str]) -> int:
|
|
425
|
+
mapping = {"critical": 4, "high": 3, "medium": 2, "low": 1}
|
|
426
|
+
if value is None:
|
|
427
|
+
return 0
|
|
428
|
+
return mapping.get(str(value).lower(), 0)
|
|
429
|
+
|
|
430
|
+
# First, filter out investigated alerts and limit to max_alerts uninvestigated alerts.
|
|
431
|
+
# While doing this, also track the single oldest uninvestigated alert so we can
|
|
432
|
+
# expose it as a suggested starting point for triage.
|
|
433
|
+
uninvestigated_alerts = []
|
|
434
|
+
suggested_alert = None
|
|
435
|
+
suggested_alert_created_at = None
|
|
436
|
+
|
|
437
|
+
for alert in alerts:
|
|
438
|
+
if not isinstance(alert, dict):
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
# CRITICAL: Skip alerts that have already been investigated (have verdict field)
|
|
442
|
+
# The verdict field comes from signal.ai.verdict in Elasticsearch and indicates the alert has already been triaged
|
|
443
|
+
# SOC1 should never re-investigate alerts that have already been processed
|
|
444
|
+
# Check both direct verdict field and signal.ai.verdict path for compatibility
|
|
445
|
+
verdict = alert.get("verdict") or alert.get("signal", {}).get("ai", {}).get("verdict")
|
|
446
|
+
if verdict:
|
|
447
|
+
continue # Skip this alert - it has already been investigated (has signal.ai.verdict)
|
|
448
|
+
|
|
449
|
+
# Limit to max_alerts uninvestigated alerts
|
|
450
|
+
if len(uninvestigated_alerts) >= max_alerts:
|
|
451
|
+
break
|
|
452
|
+
|
|
453
|
+
uninvestigated_alerts.append(alert)
|
|
454
|
+
|
|
455
|
+
# Track the oldest uninvestigated alert based on created_at / @timestamp
|
|
456
|
+
created_at_value = alert.get("created_at") or alert.get("@timestamp")
|
|
457
|
+
if created_at_value is not None and (
|
|
458
|
+
suggested_alert_created_at is None or created_at_value < suggested_alert_created_at
|
|
459
|
+
):
|
|
460
|
+
suggested_alert = alert
|
|
461
|
+
suggested_alert_created_at = created_at_value
|
|
462
|
+
|
|
463
|
+
# If no uninvestigated alerts, return early with a clear message
|
|
464
|
+
if not uninvestigated_alerts:
|
|
465
|
+
return {
|
|
466
|
+
"success": True,
|
|
467
|
+
"hours_back": hours_back,
|
|
468
|
+
"max_alerts": max_alerts,
|
|
469
|
+
"status_filter": status_filter,
|
|
470
|
+
"severity": severity,
|
|
471
|
+
"hostname": hostname,
|
|
472
|
+
"total_alerts": len(alerts),
|
|
473
|
+
"uninvestigated_alerts": 0,
|
|
474
|
+
"group_count": 0,
|
|
475
|
+
"groups": [],
|
|
476
|
+
"message": "No recent security alerts to investigate. All alerts in the specified timeframe have already been investigated (have verdict field).",
|
|
477
|
+
"suggested_alert_to_triage": None,
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
groups: Dict[str, Dict[str, Any]] = {}
|
|
481
|
+
|
|
482
|
+
for alert in uninvestigated_alerts:
|
|
483
|
+
alert_id = alert.get("id")
|
|
484
|
+
|
|
485
|
+
# Skip alerts without a title or name
|
|
486
|
+
title = alert.get("title") or alert.get("name")
|
|
487
|
+
if not title or not title.strip():
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
severity_value = (alert.get("severity") or "unknown").lower()
|
|
491
|
+
status_value = (alert.get("status") or "unknown").lower()
|
|
492
|
+
|
|
493
|
+
rule_id = alert.get("rule_id") or alert.get("detection_rule_id")
|
|
494
|
+
rule = alert.get("rule")
|
|
495
|
+
if isinstance(rule, dict):
|
|
496
|
+
rule_id = rule_id or rule.get("id")
|
|
497
|
+
|
|
498
|
+
alert_type = (
|
|
499
|
+
alert.get("type")
|
|
500
|
+
or alert.get("category")
|
|
501
|
+
or (rule.get("name") if isinstance(rule, dict) else None)
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
key_parts = [
|
|
505
|
+
title.strip().lower(),
|
|
506
|
+
severity_value,
|
|
507
|
+
status_value,
|
|
508
|
+
]
|
|
509
|
+
if rule_id:
|
|
510
|
+
key_parts.append(f"rule:{rule_id}")
|
|
511
|
+
if alert_type:
|
|
512
|
+
key_parts.append(f"type:{str(alert_type).lower()}")
|
|
513
|
+
|
|
514
|
+
group_key = "|".join(key_parts)
|
|
515
|
+
group = groups.get(group_key)
|
|
516
|
+
if group is None:
|
|
517
|
+
group = {
|
|
518
|
+
"group_key": group_key,
|
|
519
|
+
"title": title,
|
|
520
|
+
"primary_severity": severity_value,
|
|
521
|
+
"primary_status": status_value,
|
|
522
|
+
"rule_id": rule_id,
|
|
523
|
+
"alert_type": alert_type,
|
|
524
|
+
"count": 0,
|
|
525
|
+
"alert_ids": [],
|
|
526
|
+
"statuses": set(),
|
|
527
|
+
"severities": set(),
|
|
528
|
+
"earliest_created_at": None,
|
|
529
|
+
"latest_created_at": None,
|
|
530
|
+
"example_alerts": [],
|
|
531
|
+
}
|
|
532
|
+
groups[group_key] = group
|
|
533
|
+
|
|
534
|
+
group["count"] += 1
|
|
535
|
+
if alert_id is not None:
|
|
536
|
+
group["alert_ids"].append(alert_id)
|
|
537
|
+
|
|
538
|
+
group["statuses"].add(status_value)
|
|
539
|
+
group["severities"].add(severity_value)
|
|
540
|
+
|
|
541
|
+
created_at = alert.get("created_at") or alert.get("@timestamp")
|
|
542
|
+
if created_at is not None:
|
|
543
|
+
earliest = group["earliest_created_at"]
|
|
544
|
+
latest = group["latest_created_at"]
|
|
545
|
+
if earliest is None or created_at < earliest:
|
|
546
|
+
group["earliest_created_at"] = created_at
|
|
547
|
+
if latest is None or created_at > latest:
|
|
548
|
+
group["latest_created_at"] = created_at
|
|
549
|
+
|
|
550
|
+
if _severity_rank(severity_value) > _severity_rank(group.get("primary_severity")):
|
|
551
|
+
group["primary_severity"] = severity_value
|
|
552
|
+
|
|
553
|
+
examples = group["example_alerts"]
|
|
554
|
+
if len(examples) < 3:
|
|
555
|
+
examples.append(
|
|
556
|
+
{
|
|
557
|
+
"id": alert_id,
|
|
558
|
+
"title": title,
|
|
559
|
+
"severity": severity_value,
|
|
560
|
+
"status": status_value,
|
|
561
|
+
"created_at": created_at,
|
|
562
|
+
"source": alert.get("source"),
|
|
563
|
+
"rule_id": rule_id,
|
|
564
|
+
"type": alert_type,
|
|
565
|
+
"description": alert.get("description"),
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
grouped_list = []
|
|
570
|
+
for idx, group in enumerate(groups.values(), start=1):
|
|
571
|
+
# Sort example_alerts within each group from oldest to most recent
|
|
572
|
+
example_alerts_sorted = sorted(
|
|
573
|
+
group["example_alerts"],
|
|
574
|
+
key=lambda a: a.get("created_at") or "",
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
grouped_list.append(
|
|
578
|
+
{
|
|
579
|
+
"group_id": f"alert_group_{idx}",
|
|
580
|
+
"title": group["title"],
|
|
581
|
+
"primary_severity": group["primary_severity"],
|
|
582
|
+
"primary_status": group["primary_status"],
|
|
583
|
+
"rule_id": group["rule_id"],
|
|
584
|
+
"alert_type": group["alert_type"],
|
|
585
|
+
"count": group["count"],
|
|
586
|
+
"alert_ids": group["alert_ids"],
|
|
587
|
+
"statuses": sorted(s for s in group["statuses"] if s),
|
|
588
|
+
"severities": sorted(s for s in group["severities"] if s),
|
|
589
|
+
"earliest_created_at": group["earliest_created_at"],
|
|
590
|
+
"latest_created_at": group["latest_created_at"],
|
|
591
|
+
"example_alerts": example_alerts_sorted,
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# Sort groups from oldest to most recent based on their earliest_created_at.
|
|
596
|
+
# If timestamps are missing, they will naturally fall to the start of the list.
|
|
597
|
+
grouped_list.sort(
|
|
598
|
+
key=lambda g: g.get("earliest_created_at") or "",
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Build a compact "suggested alert to triage" view based on the oldest uninvestigated alert
|
|
602
|
+
suggested_alert_view = None
|
|
603
|
+
if suggested_alert is not None:
|
|
604
|
+
suggested_title = suggested_alert.get("title") or suggested_alert.get("name")
|
|
605
|
+
suggested_severity = (suggested_alert.get("severity") or "unknown").lower()
|
|
606
|
+
suggested_status = (suggested_alert.get("status") or "unknown").lower()
|
|
607
|
+
suggested_rule_id = (
|
|
608
|
+
suggested_alert.get("rule_id")
|
|
609
|
+
or suggested_alert.get("detection_rule_id")
|
|
610
|
+
)
|
|
611
|
+
suggested_alert_view = {
|
|
612
|
+
"id": suggested_alert.get("id"),
|
|
613
|
+
"title": suggested_title,
|
|
614
|
+
"severity": suggested_severity,
|
|
615
|
+
"status": suggested_status,
|
|
616
|
+
"created_at": suggested_alert_created_at,
|
|
617
|
+
"rule_id": suggested_rule_id,
|
|
618
|
+
"source": suggested_alert.get("source"),
|
|
619
|
+
"type": suggested_alert.get("type")
|
|
620
|
+
or suggested_alert.get("category"),
|
|
621
|
+
"description": suggested_alert.get("description"),
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
"success": True,
|
|
626
|
+
"hours_back": hours_back,
|
|
627
|
+
"max_alerts": max_alerts,
|
|
628
|
+
"status_filter": status_filter,
|
|
629
|
+
"severity": severity,
|
|
630
|
+
"hostname": hostname,
|
|
631
|
+
"total_alerts": len(alerts),
|
|
632
|
+
"uninvestigated_alerts": len(uninvestigated_alerts),
|
|
633
|
+
"group_count": len(grouped_list),
|
|
634
|
+
"groups": grouped_list,
|
|
635
|
+
"suggested_alert_to_triage": suggested_alert_view,
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def get_security_alerts(
|
|
640
|
+
hours_back: int = 24,
|
|
641
|
+
max_alerts: int = 10,
|
|
642
|
+
status_filter: Optional[str] = None,
|
|
643
|
+
severity: Optional[str] = None,
|
|
644
|
+
client: SIEMClient = None, # type: ignore
|
|
645
|
+
) -> Dict[str, Any]:
|
|
646
|
+
"""
|
|
647
|
+
Get security alerts from the SIEM platform.
|
|
648
|
+
|
|
649
|
+
Tool schema:
|
|
650
|
+
- name: get_security_alerts
|
|
651
|
+
- description: Get security alerts directly from the SIEM platform
|
|
652
|
+
- parameters:
|
|
653
|
+
- hours_back (int, optional): How many hours to look back (default: 24)
|
|
654
|
+
- max_alerts (int, optional): Maximum number of alerts to return (default: 10)
|
|
655
|
+
- status_filter (str, optional): Filter by status
|
|
656
|
+
- severity (str, optional): Filter by severity (low, medium, high, critical)
|
|
657
|
+
"""
|
|
658
|
+
if client is None:
|
|
659
|
+
raise IntegrationError("SIEM client not provided")
|
|
660
|
+
|
|
661
|
+
# Check if client has this method
|
|
662
|
+
if not hasattr(client, "get_security_alerts"):
|
|
663
|
+
raise IntegrationError("SIEM client does not support get_security_alerts")
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
alerts = client.get_security_alerts(
|
|
667
|
+
hours_back=hours_back,
|
|
668
|
+
max_alerts=max_alerts,
|
|
669
|
+
status_filter=status_filter,
|
|
670
|
+
severity=severity,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
return {
|
|
674
|
+
"success": True,
|
|
675
|
+
"count": len(alerts),
|
|
676
|
+
"alerts": alerts,
|
|
677
|
+
}
|
|
678
|
+
except Exception as e:
|
|
679
|
+
raise IntegrationError(f"Failed to get security alerts: {str(e)}") from e
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def get_security_alert_by_id(
|
|
683
|
+
alert_id: str,
|
|
684
|
+
include_detections: bool = True,
|
|
685
|
+
client: SIEMClient = None, # type: ignore
|
|
686
|
+
) -> Dict[str, Any]:
|
|
687
|
+
"""
|
|
688
|
+
Get detailed information about a specific security alert.
|
|
689
|
+
|
|
690
|
+
Tool schema:
|
|
691
|
+
- name: get_security_alert_by_id
|
|
692
|
+
- description: Get detailed information about a specific security alert by its ID
|
|
693
|
+
- parameters:
|
|
694
|
+
- alert_id (str, required): The ID of the alert
|
|
695
|
+
- include_detections (bool, optional): Whether to include detection details (default: true)
|
|
696
|
+
"""
|
|
697
|
+
if client is None:
|
|
698
|
+
raise IntegrationError("SIEM client not provided")
|
|
699
|
+
|
|
700
|
+
if not hasattr(client, "get_security_alert_by_id"):
|
|
701
|
+
raise IntegrationError("SIEM client does not support get_security_alert_by_id")
|
|
702
|
+
|
|
703
|
+
try:
|
|
704
|
+
alert = client.get_security_alert_by_id(
|
|
705
|
+
alert_id=alert_id,
|
|
706
|
+
include_detections=include_detections,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
"success": True,
|
|
711
|
+
"alert": alert,
|
|
712
|
+
}
|
|
713
|
+
except Exception as e:
|
|
714
|
+
raise IntegrationError(f"Failed to get security alert: {str(e)}") from e
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def get_siem_event_by_id(
|
|
718
|
+
event_id: str,
|
|
719
|
+
client: SIEMClient = None, # type: ignore
|
|
720
|
+
) -> Dict[str, Any]:
|
|
721
|
+
"""
|
|
722
|
+
Get detailed information about a specific security event by its ID.
|
|
723
|
+
|
|
724
|
+
Tool schema:
|
|
725
|
+
- name: get_siem_event_by_id
|
|
726
|
+
- description: Retrieve a specific security event by its unique identifier (event ID).
|
|
727
|
+
This tool allows you to get the exact event details when you know the event ID.
|
|
728
|
+
- parameters:
|
|
729
|
+
- event_id (str, required): The unique identifier of the event to retrieve
|
|
730
|
+
"""
|
|
731
|
+
if client is None:
|
|
732
|
+
raise IntegrationError("SIEM client not provided")
|
|
733
|
+
|
|
734
|
+
if not hasattr(client, "get_siem_event_by_id"):
|
|
735
|
+
raise IntegrationError("SIEM client does not support get_siem_event_by_id")
|
|
736
|
+
|
|
737
|
+
try:
|
|
738
|
+
event = client.get_siem_event_by_id(event_id=event_id)
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
"success": True,
|
|
742
|
+
"event": {
|
|
743
|
+
"id": event.id,
|
|
744
|
+
"timestamp": event.timestamp.isoformat(),
|
|
745
|
+
"source_type": event.source_type.value,
|
|
746
|
+
"message": event.message,
|
|
747
|
+
"host": event.host,
|
|
748
|
+
"username": event.username,
|
|
749
|
+
"ip": event.ip,
|
|
750
|
+
"process_name": event.process_name,
|
|
751
|
+
"file_hash": event.file_hash,
|
|
752
|
+
"raw": event.raw,
|
|
753
|
+
},
|
|
754
|
+
}
|
|
755
|
+
except Exception as e:
|
|
756
|
+
raise IntegrationError(f"Failed to get event by ID: {str(e)}") from e
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def lookup_entity(
|
|
760
|
+
entity_value: str,
|
|
761
|
+
entity_type: Optional[str] = None,
|
|
762
|
+
hours_back: int = 24,
|
|
763
|
+
client: SIEMClient = None, # type: ignore
|
|
764
|
+
) -> Dict[str, Any]:
|
|
765
|
+
"""
|
|
766
|
+
Look up an entity for enrichment.
|
|
767
|
+
|
|
768
|
+
Tool schema:
|
|
769
|
+
- name: lookup_entity
|
|
770
|
+
- description: Look up an entity (IP address, domain, hash, user, etc.) in the SIEM for enrichment
|
|
771
|
+
- parameters:
|
|
772
|
+
- entity_value (str, required): Value to look up
|
|
773
|
+
- entity_type (str, optional): Type of entity (ip, domain, hash, user, etc.)
|
|
774
|
+
- hours_back (int, optional): How many hours of historical data (default: 24)
|
|
775
|
+
"""
|
|
776
|
+
if client is None:
|
|
777
|
+
raise IntegrationError("SIEM client not provided")
|
|
778
|
+
|
|
779
|
+
if not hasattr(client, "lookup_entity"):
|
|
780
|
+
raise IntegrationError("SIEM client does not support lookup_entity")
|
|
781
|
+
|
|
782
|
+
try:
|
|
783
|
+
result = client.lookup_entity(
|
|
784
|
+
entity_value=entity_value,
|
|
785
|
+
entity_type=entity_type,
|
|
786
|
+
hours_back=hours_back,
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
return {
|
|
790
|
+
"success": True,
|
|
791
|
+
**result,
|
|
792
|
+
}
|
|
793
|
+
except Exception as e:
|
|
794
|
+
raise IntegrationError(f"Failed to lookup entity: {str(e)}") from e
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def get_ioc_matches(
|
|
798
|
+
hours_back: int = 24,
|
|
799
|
+
max_matches: int = 20,
|
|
800
|
+
ioc_type: Optional[str] = None,
|
|
801
|
+
severity: Optional[str] = None,
|
|
802
|
+
client: SIEMClient = None, # type: ignore
|
|
803
|
+
) -> Dict[str, Any]:
|
|
804
|
+
"""
|
|
805
|
+
Get Indicators of Compromise (IoC) matches.
|
|
806
|
+
|
|
807
|
+
Tool schema:
|
|
808
|
+
- name: get_ioc_matches
|
|
809
|
+
- description: Get Indicators of Compromise (IoC) matches from the SIEM
|
|
810
|
+
- parameters:
|
|
811
|
+
- hours_back (int, optional): How many hours back to look (default: 24)
|
|
812
|
+
- max_matches (int, optional): Maximum number of matches (default: 20)
|
|
813
|
+
- ioc_type (str, optional): Filter by IoC type (ip, domain, hash, url, etc.)
|
|
814
|
+
- severity (str, optional): Filter by severity level
|
|
815
|
+
"""
|
|
816
|
+
if client is None:
|
|
817
|
+
raise IntegrationError("SIEM client not provided")
|
|
818
|
+
|
|
819
|
+
if not hasattr(client, "get_ioc_matches"):
|
|
820
|
+
raise IntegrationError("SIEM client does not support get_ioc_matches")
|
|
821
|
+
|
|
822
|
+
try:
|
|
823
|
+
matches = client.get_ioc_matches(
|
|
824
|
+
hours_back=hours_back,
|
|
825
|
+
max_matches=max_matches,
|
|
826
|
+
ioc_type=ioc_type,
|
|
827
|
+
severity=severity,
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
"success": True,
|
|
832
|
+
"count": len(matches),
|
|
833
|
+
"matches": matches,
|
|
834
|
+
}
|
|
835
|
+
except Exception as e:
|
|
836
|
+
raise IntegrationError(f"Failed to get IoC matches: {str(e)}") from e
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def get_threat_intel(
|
|
840
|
+
query: str,
|
|
841
|
+
context: Optional[Dict[str, Any]] = None,
|
|
842
|
+
client: SIEMClient = None, # type: ignore
|
|
843
|
+
) -> Dict[str, Any]:
|
|
844
|
+
"""
|
|
845
|
+
Get threat intelligence answers.
|
|
846
|
+
|
|
847
|
+
Tool schema:
|
|
848
|
+
- name: get_threat_intel
|
|
849
|
+
- description: Get answers to security questions using integrated threat intelligence
|
|
850
|
+
- parameters:
|
|
851
|
+
- query (str, required): The security or threat intelligence question
|
|
852
|
+
- context (object, optional): Additional context (indicators, events, etc.)
|
|
853
|
+
"""
|
|
854
|
+
if client is None:
|
|
855
|
+
raise IntegrationError("SIEM client not provided")
|
|
856
|
+
|
|
857
|
+
if not hasattr(client, "get_threat_intel"):
|
|
858
|
+
raise IntegrationError("SIEM client does not support get_threat_intel")
|
|
859
|
+
|
|
860
|
+
try:
|
|
861
|
+
result = client.get_threat_intel(
|
|
862
|
+
query=query,
|
|
863
|
+
context=context,
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
return {
|
|
867
|
+
"success": True,
|
|
868
|
+
**result,
|
|
869
|
+
}
|
|
870
|
+
except Exception as e:
|
|
871
|
+
raise IntegrationError(f"Failed to get threat intelligence: {str(e)}") from e
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def list_security_rules(
|
|
875
|
+
enabled_only: bool = False,
|
|
876
|
+
limit: int = 100,
|
|
877
|
+
client: SIEMClient = None, # type: ignore
|
|
878
|
+
) -> Dict[str, Any]:
|
|
879
|
+
"""
|
|
880
|
+
List security detection rules.
|
|
881
|
+
|
|
882
|
+
Tool schema:
|
|
883
|
+
- name: list_security_rules
|
|
884
|
+
- description: List all security detection rules configured in the SIEM platform
|
|
885
|
+
- parameters:
|
|
886
|
+
- enabled_only (bool, optional): Only return enabled rules (default: false)
|
|
887
|
+
- limit (int, optional): Maximum number of rules (default: 100)
|
|
888
|
+
"""
|
|
889
|
+
if client is None:
|
|
890
|
+
raise IntegrationError("SIEM client not provided")
|
|
891
|
+
|
|
892
|
+
if not hasattr(client, "list_security_rules"):
|
|
893
|
+
raise IntegrationError("SIEM client does not support list_security_rules")
|
|
894
|
+
|
|
895
|
+
try:
|
|
896
|
+
rules = client.list_security_rules(
|
|
897
|
+
enabled_only=enabled_only,
|
|
898
|
+
limit=limit,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
return {
|
|
902
|
+
"success": True,
|
|
903
|
+
"count": len(rules),
|
|
904
|
+
"rules": rules,
|
|
905
|
+
}
|
|
906
|
+
except Exception as e:
|
|
907
|
+
raise IntegrationError(f"Failed to list security rules: {str(e)}") from e
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def search_security_rules(
|
|
911
|
+
query: str,
|
|
912
|
+
category: Optional[str] = None,
|
|
913
|
+
enabled_only: bool = False,
|
|
914
|
+
client: SIEMClient = None, # type: ignore
|
|
915
|
+
) -> Dict[str, Any]:
|
|
916
|
+
"""
|
|
917
|
+
Search for security detection rules.
|
|
918
|
+
|
|
919
|
+
Tool schema:
|
|
920
|
+
- name: search_security_rules
|
|
921
|
+
- description: Search for security detection rules by name, description, or other criteria
|
|
922
|
+
- parameters:
|
|
923
|
+
- query (str, required): Search query (supports regex patterns)
|
|
924
|
+
- category (str, optional): Filter by rule category
|
|
925
|
+
- enabled_only (bool, optional): Only search enabled rules (default: false)
|
|
926
|
+
"""
|
|
927
|
+
if client is None:
|
|
928
|
+
raise IntegrationError("SIEM client not provided")
|
|
929
|
+
|
|
930
|
+
if not hasattr(client, "search_security_rules"):
|
|
931
|
+
raise IntegrationError("SIEM client does not support search_security_rules")
|
|
932
|
+
|
|
933
|
+
try:
|
|
934
|
+
rules = client.search_security_rules(
|
|
935
|
+
query=query,
|
|
936
|
+
category=category,
|
|
937
|
+
enabled_only=enabled_only,
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
return {
|
|
941
|
+
"success": True,
|
|
942
|
+
"count": len(rules),
|
|
943
|
+
"rules": rules,
|
|
944
|
+
}
|
|
945
|
+
except Exception as e:
|
|
946
|
+
raise IntegrationError(f"Failed to search security rules: {str(e)}") from e
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def get_rule_detections(
|
|
950
|
+
rule_id: str,
|
|
951
|
+
alert_state: Optional[str] = None,
|
|
952
|
+
hours_back: int = 24,
|
|
953
|
+
limit: int = 50,
|
|
954
|
+
client: SIEMClient = None, # type: ignore
|
|
955
|
+
) -> Dict[str, Any]:
|
|
956
|
+
"""
|
|
957
|
+
Get historical detections from a specific rule.
|
|
958
|
+
|
|
959
|
+
Tool schema:
|
|
960
|
+
- name: get_rule_detections
|
|
961
|
+
- description: Retrieve historical detections generated by a specific security detection rule
|
|
962
|
+
- parameters:
|
|
963
|
+
- rule_id (str, required): Unique ID of the rule
|
|
964
|
+
- alert_state (str, optional): Filter by alert state
|
|
965
|
+
- hours_back (int, optional): How many hours back (default: 24)
|
|
966
|
+
- limit (int, optional): Maximum number of detections (default: 50)
|
|
967
|
+
"""
|
|
968
|
+
if client is None:
|
|
969
|
+
raise IntegrationError("SIEM client not provided")
|
|
970
|
+
|
|
971
|
+
if not hasattr(client, "get_rule_detections"):
|
|
972
|
+
raise IntegrationError("SIEM client does not support get_rule_detections")
|
|
973
|
+
|
|
974
|
+
try:
|
|
975
|
+
detections = client.get_rule_detections(
|
|
976
|
+
rule_id=rule_id,
|
|
977
|
+
alert_state=alert_state,
|
|
978
|
+
hours_back=hours_back,
|
|
979
|
+
limit=limit,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
"success": True,
|
|
984
|
+
"rule_id": rule_id,
|
|
985
|
+
"count": len(detections),
|
|
986
|
+
"detections": detections,
|
|
987
|
+
}
|
|
988
|
+
except Exception as e:
|
|
989
|
+
raise IntegrationError(f"Failed to get rule detections: {str(e)}") from e
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def list_rule_errors(
|
|
993
|
+
rule_id: str,
|
|
994
|
+
hours_back: int = 24,
|
|
995
|
+
client: SIEMClient = None, # type: ignore
|
|
996
|
+
) -> Dict[str, Any]:
|
|
997
|
+
"""
|
|
998
|
+
List execution errors for a specific rule.
|
|
999
|
+
|
|
1000
|
+
Tool schema:
|
|
1001
|
+
- name: list_rule_errors
|
|
1002
|
+
- description: List execution errors for a specific security detection rule
|
|
1003
|
+
- parameters:
|
|
1004
|
+
- rule_id (str, required): Unique ID of the rule
|
|
1005
|
+
- hours_back (int, optional): How many hours back to look (default: 24)
|
|
1006
|
+
"""
|
|
1007
|
+
if client is None:
|
|
1008
|
+
raise IntegrationError("SIEM client not provided")
|
|
1009
|
+
|
|
1010
|
+
if not hasattr(client, "list_rule_errors"):
|
|
1011
|
+
raise IntegrationError("SIEM client does not support list_rule_errors")
|
|
1012
|
+
|
|
1013
|
+
try:
|
|
1014
|
+
errors = client.list_rule_errors(
|
|
1015
|
+
rule_id=rule_id,
|
|
1016
|
+
hours_back=hours_back,
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
"success": True,
|
|
1021
|
+
"rule_id": rule_id,
|
|
1022
|
+
"error_count": len(errors),
|
|
1023
|
+
"errors": errors,
|
|
1024
|
+
}
|
|
1025
|
+
except Exception as e:
|
|
1026
|
+
raise IntegrationError(f"Failed to list rule errors: {str(e)}") from e
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
def close_alert(
|
|
1030
|
+
alert_id: str,
|
|
1031
|
+
reason: Optional[str] = None,
|
|
1032
|
+
comment: Optional[str] = None,
|
|
1033
|
+
client: SIEMClient = None, # type: ignore
|
|
1034
|
+
) -> Dict[str, Any]:
|
|
1035
|
+
"""
|
|
1036
|
+
Close an alert in the SIEM, typically used for false positives.
|
|
1037
|
+
|
|
1038
|
+
Tool schema:
|
|
1039
|
+
- name: close_alert
|
|
1040
|
+
- description: Close a security alert in the SIEM platform. Use this when an alert
|
|
1041
|
+
has been determined to be a false positive or benign true positive during triage.
|
|
1042
|
+
- parameters:
|
|
1043
|
+
- alert_id (str, required): The ID of the alert to close
|
|
1044
|
+
- reason (str, optional): Reason for closing (e.g., "false_positive", "benign_true_positive")
|
|
1045
|
+
- comment (str, optional): Comment explaining why the alert is being closed
|
|
1046
|
+
|
|
1047
|
+
Args:
|
|
1048
|
+
alert_id: The ID of the alert to close.
|
|
1049
|
+
reason: Optional reason for closing.
|
|
1050
|
+
comment: Optional comment explaining why the alert is being closed.
|
|
1051
|
+
client: The SIEM client.
|
|
1052
|
+
|
|
1053
|
+
Returns:
|
|
1054
|
+
Dictionary containing success status and alert details.
|
|
1055
|
+
|
|
1056
|
+
Raises:
|
|
1057
|
+
IntegrationError: If closing the alert fails.
|
|
1058
|
+
"""
|
|
1059
|
+
if client is None:
|
|
1060
|
+
raise IntegrationError("SIEM client not provided")
|
|
1061
|
+
|
|
1062
|
+
if not hasattr(client, "close_alert"):
|
|
1063
|
+
raise IntegrationError("SIEM client does not support close_alert")
|
|
1064
|
+
|
|
1065
|
+
try:
|
|
1066
|
+
result = client.close_alert(
|
|
1067
|
+
alert_id=alert_id,
|
|
1068
|
+
reason=reason,
|
|
1069
|
+
comment=comment,
|
|
1070
|
+
)
|
|
1071
|
+
|
|
1072
|
+
return {
|
|
1073
|
+
"success": True,
|
|
1074
|
+
"alert_id": result.get("alert_id"),
|
|
1075
|
+
"status": result.get("status"),
|
|
1076
|
+
"reason": result.get("reason"),
|
|
1077
|
+
"comment": result.get("comment"),
|
|
1078
|
+
"alert": result.get("alert"),
|
|
1079
|
+
}
|
|
1080
|
+
except Exception as e:
|
|
1081
|
+
raise IntegrationError(f"Failed to close alert {alert_id}: {str(e)}") from e
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def update_alert_verdict(
|
|
1085
|
+
alert_id: str,
|
|
1086
|
+
verdict: str,
|
|
1087
|
+
comment: Optional[str] = None,
|
|
1088
|
+
client: SIEMClient = None, # type: ignore
|
|
1089
|
+
) -> Dict[str, Any]:
|
|
1090
|
+
"""
|
|
1091
|
+
Update the verdict for an alert in the SIEM.
|
|
1092
|
+
|
|
1093
|
+
Tool schema:
|
|
1094
|
+
- name: update_alert_verdict
|
|
1095
|
+
- description: Update the verdict for a security alert. Use this to set or update the verdict
|
|
1096
|
+
field (e.g., "in-progress", "false_positive", "benign_true_positive", "true_positive", "uncertain").
|
|
1097
|
+
This is the preferred method for setting verdicts as it clearly indicates the intent to
|
|
1098
|
+
update the verdict rather than close the alert.
|
|
1099
|
+
- parameters:
|
|
1100
|
+
- alert_id (str, required): The ID of the alert to update
|
|
1101
|
+
- verdict (str, required): The verdict value. Valid values: "in-progress", "false_positive",
|
|
1102
|
+
"benign_true_positive", "true_positive", "uncertain"
|
|
1103
|
+
- comment (str, optional): Optional comment explaining the verdict
|
|
1104
|
+
|
|
1105
|
+
Args:
|
|
1106
|
+
alert_id: The ID of the alert to update.
|
|
1107
|
+
verdict: The verdict value to set. Valid values: "in-progress", "false_positive",
|
|
1108
|
+
"benign_true_positive", "true_positive", "uncertain".
|
|
1109
|
+
comment: Optional comment explaining the verdict.
|
|
1110
|
+
client: The SIEM client.
|
|
1111
|
+
|
|
1112
|
+
Returns:
|
|
1113
|
+
Dictionary containing success status, alert_id, verdict, and updated alert details.
|
|
1114
|
+
|
|
1115
|
+
Raises:
|
|
1116
|
+
IntegrationError: If updating the verdict fails.
|
|
1117
|
+
"""
|
|
1118
|
+
if client is None:
|
|
1119
|
+
raise IntegrationError("SIEM client not provided")
|
|
1120
|
+
|
|
1121
|
+
if not hasattr(client, "update_alert_verdict"):
|
|
1122
|
+
raise IntegrationError("SIEM client does not support update_alert_verdict")
|
|
1123
|
+
|
|
1124
|
+
try:
|
|
1125
|
+
result = client.update_alert_verdict(
|
|
1126
|
+
alert_id=alert_id,
|
|
1127
|
+
verdict=verdict,
|
|
1128
|
+
comment=comment,
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
return {
|
|
1132
|
+
"success": True,
|
|
1133
|
+
"alert_id": result.get("alert_id"),
|
|
1134
|
+
"verdict": result.get("verdict"),
|
|
1135
|
+
"comment": result.get("comment"),
|
|
1136
|
+
"alert": result.get("alert"),
|
|
1137
|
+
}
|
|
1138
|
+
except Exception as e:
|
|
1139
|
+
raise IntegrationError(f"Failed to update alert verdict for {alert_id}: {str(e)}") from e
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
def tag_alert(
|
|
1143
|
+
alert_id: str,
|
|
1144
|
+
tag: str,
|
|
1145
|
+
client: SIEMClient = None, # type: ignore
|
|
1146
|
+
) -> Dict[str, Any]:
|
|
1147
|
+
"""
|
|
1148
|
+
Tag an alert with a classification tag (FP, TP, or NMI).
|
|
1149
|
+
|
|
1150
|
+
Tool schema:
|
|
1151
|
+
- name: tag_alert
|
|
1152
|
+
- description: Tag a security alert in the SIEM platform with a classification.
|
|
1153
|
+
Use this to mark alerts as FP (False Positive), TP (True Positive), or NMI (Need More Investigation).
|
|
1154
|
+
- parameters:
|
|
1155
|
+
- alert_id (str, required): The ID of the alert to tag
|
|
1156
|
+
- tag (str, required): The tag to apply. Must be one of: "FP" (False Positive),
|
|
1157
|
+
"TP" (True Positive), or "NMI" (Need More Investigation)
|
|
1158
|
+
|
|
1159
|
+
Args:
|
|
1160
|
+
alert_id: The ID of the alert to tag.
|
|
1161
|
+
tag: The tag to apply. Must be one of: "FP", "TP", or "NMI".
|
|
1162
|
+
client: The SIEM client.
|
|
1163
|
+
|
|
1164
|
+
Returns:
|
|
1165
|
+
Dictionary containing success status and alert details with updated tags.
|
|
1166
|
+
|
|
1167
|
+
Raises:
|
|
1168
|
+
IntegrationError: If tagging the alert fails.
|
|
1169
|
+
"""
|
|
1170
|
+
# Validate tag value first before checking client
|
|
1171
|
+
valid_tags = {"FP", "TP", "NMI"}
|
|
1172
|
+
tag_upper = tag.upper()
|
|
1173
|
+
if tag_upper not in valid_tags:
|
|
1174
|
+
raise IntegrationError(
|
|
1175
|
+
f"Invalid tag '{tag}'. Must be one of: FP (False Positive), "
|
|
1176
|
+
f"TP (True Positive), or NMI (Need More Investigation)"
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
if client is None:
|
|
1180
|
+
raise IntegrationError("SIEM client not provided")
|
|
1181
|
+
|
|
1182
|
+
if not hasattr(client, "tag_alert"):
|
|
1183
|
+
raise IntegrationError("SIEM client does not support tag_alert")
|
|
1184
|
+
|
|
1185
|
+
try:
|
|
1186
|
+
result = client.tag_alert(
|
|
1187
|
+
alert_id=alert_id,
|
|
1188
|
+
tag=tag_upper,
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
return {
|
|
1192
|
+
"success": True,
|
|
1193
|
+
"alert_id": result.get("alert_id"),
|
|
1194
|
+
"tag": result.get("tag"),
|
|
1195
|
+
"tags": result.get("tags", []),
|
|
1196
|
+
"alert": result.get("alert"),
|
|
1197
|
+
}
|
|
1198
|
+
except Exception as e:
|
|
1199
|
+
raise IntegrationError(f"Failed to tag alert {alert_id}: {str(e)}") from e
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
def add_alert_note(
|
|
1203
|
+
alert_id: str,
|
|
1204
|
+
note: str,
|
|
1205
|
+
client: SIEMClient = None, # type: ignore
|
|
1206
|
+
) -> Dict[str, Any]:
|
|
1207
|
+
"""
|
|
1208
|
+
Add a note/comment to an alert in the SIEM.
|
|
1209
|
+
|
|
1210
|
+
Tool schema:
|
|
1211
|
+
- name: add_alert_note
|
|
1212
|
+
- description: Add a note or comment to a security alert in the SIEM platform.
|
|
1213
|
+
Use this to document investigation findings, recommendations for detection rule improvements,
|
|
1214
|
+
case numbers, or other relevant information about the alert.
|
|
1215
|
+
- parameters:
|
|
1216
|
+
- alert_id (str, required): The ID of the alert to add a note to
|
|
1217
|
+
- note (str, required): The note/comment text to add
|
|
1218
|
+
|
|
1219
|
+
Args:
|
|
1220
|
+
alert_id: The ID of the alert to add a note to.
|
|
1221
|
+
note: The note/comment text to add.
|
|
1222
|
+
client: The SIEM client.
|
|
1223
|
+
|
|
1224
|
+
Returns:
|
|
1225
|
+
Dictionary containing success status and alert details with the note.
|
|
1226
|
+
|
|
1227
|
+
Raises:
|
|
1228
|
+
IntegrationError: If adding the note fails.
|
|
1229
|
+
"""
|
|
1230
|
+
if client is None:
|
|
1231
|
+
raise IntegrationError("SIEM client not provided")
|
|
1232
|
+
|
|
1233
|
+
if not hasattr(client, "add_alert_note"):
|
|
1234
|
+
raise IntegrationError("SIEM client does not support add_alert_note")
|
|
1235
|
+
|
|
1236
|
+
try:
|
|
1237
|
+
result = client.add_alert_note(
|
|
1238
|
+
alert_id=alert_id,
|
|
1239
|
+
note=note,
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
return {
|
|
1243
|
+
"success": True,
|
|
1244
|
+
"alert_id": result.get("alert_id"),
|
|
1245
|
+
"note": result.get("note"),
|
|
1246
|
+
"alert": result.get("alert"),
|
|
1247
|
+
}
|
|
1248
|
+
except Exception as e:
|
|
1249
|
+
raise IntegrationError(f"Failed to add note to alert {alert_id}: {str(e)}") from e
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def search_kql_query(
|
|
1253
|
+
kql_query: str,
|
|
1254
|
+
limit: int = 500,
|
|
1255
|
+
hours_back: Optional[int] = None,
|
|
1256
|
+
client: SIEMClient = None, # type: ignore
|
|
1257
|
+
) -> Dict[str, Any]:
|
|
1258
|
+
"""
|
|
1259
|
+
Execute a KQL (Kusto Query Language) or advanced query for deeper investigations.
|
|
1260
|
+
|
|
1261
|
+
Tool schema:
|
|
1262
|
+
- name: search_kql_query
|
|
1263
|
+
- description: Execute a KQL (Kusto Query Language) or advanced query for deeper investigations.
|
|
1264
|
+
This tool allows for complex queries including advanced filtering, aggregations, time-based
|
|
1265
|
+
analysis, cross-index searches, and complex joins. Supports both KQL syntax and vendor-specific
|
|
1266
|
+
query DSL (e.g., Elasticsearch Query DSL).
|
|
1267
|
+
- parameters:
|
|
1268
|
+
- kql_query (str, required): KQL query string or advanced query DSL (JSON for Elasticsearch)
|
|
1269
|
+
- limit (int, optional): Maximum number of events to return (default: 500)
|
|
1270
|
+
- hours_back (int, optional): Optional time window in hours to limit the search
|
|
1271
|
+
|
|
1272
|
+
Args:
|
|
1273
|
+
kql_query: KQL query string or advanced query DSL.
|
|
1274
|
+
limit: Maximum number of events to return.
|
|
1275
|
+
hours_back: Optional time window in hours.
|
|
1276
|
+
client: The SIEM client.
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
Dictionary containing search results with events.
|
|
1280
|
+
|
|
1281
|
+
Raises:
|
|
1282
|
+
IntegrationError: If search fails.
|
|
1283
|
+
"""
|
|
1284
|
+
if client is None:
|
|
1285
|
+
raise IntegrationError("SIEM client not provided")
|
|
1286
|
+
|
|
1287
|
+
if not hasattr(client, "search_kql_query"):
|
|
1288
|
+
raise IntegrationError("SIEM client does not support search_kql_query")
|
|
1289
|
+
|
|
1290
|
+
try:
|
|
1291
|
+
result = client.search_kql_query(
|
|
1292
|
+
kql_query=kql_query,
|
|
1293
|
+
limit=limit,
|
|
1294
|
+
hours_back=hours_back,
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
return {
|
|
1298
|
+
"success": True,
|
|
1299
|
+
"query": result.query,
|
|
1300
|
+
"total_count": result.total_count,
|
|
1301
|
+
"returned_count": len(result.events),
|
|
1302
|
+
"events": [
|
|
1303
|
+
{
|
|
1304
|
+
"id": event.id,
|
|
1305
|
+
"timestamp": event.timestamp.isoformat(),
|
|
1306
|
+
"source_type": event.source_type.value,
|
|
1307
|
+
"message": event.message,
|
|
1308
|
+
"host": event.host,
|
|
1309
|
+
"username": event.username,
|
|
1310
|
+
"ip": event.ip,
|
|
1311
|
+
"process_name": event.process_name,
|
|
1312
|
+
"file_hash": event.file_hash,
|
|
1313
|
+
}
|
|
1314
|
+
for event in result.events
|
|
1315
|
+
],
|
|
1316
|
+
}
|
|
1317
|
+
except Exception as e:
|
|
1318
|
+
raise IntegrationError(f"Failed to execute KQL query: {str(e)}") from e
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def get_network_events(
|
|
1322
|
+
source_ip: Optional[str] = None,
|
|
1323
|
+
destination_ip: Optional[str] = None,
|
|
1324
|
+
port: Optional[int] = None,
|
|
1325
|
+
protocol: Optional[str] = None,
|
|
1326
|
+
hours_back: int = 24,
|
|
1327
|
+
limit: int = 100,
|
|
1328
|
+
event_type: Optional[str] = None,
|
|
1329
|
+
client: SIEMClient = None, # type: ignore
|
|
1330
|
+
) -> Dict[str, Any]:
|
|
1331
|
+
"""
|
|
1332
|
+
Retrieve network traffic events (firewall, netflow, proxy logs) with structured fields.
|
|
1333
|
+
|
|
1334
|
+
Tool schema:
|
|
1335
|
+
- name: get_network_events
|
|
1336
|
+
- description: Retrieve network traffic events (firewall, netflow, proxy logs) with structured
|
|
1337
|
+
fields for analysis. Returns network events with source/destination IPs, ports, protocols,
|
|
1338
|
+
bytes, packets, and connection duration.
|
|
1339
|
+
- parameters:
|
|
1340
|
+
- source_ip (str, optional): Source IP address
|
|
1341
|
+
- destination_ip (str, optional): Destination IP address
|
|
1342
|
+
- port (int, optional): Port number
|
|
1343
|
+
- protocol (str, optional): Protocol (tcp, udp, icmp, etc.)
|
|
1344
|
+
- hours_back (int, optional): Time window (default: 24)
|
|
1345
|
+
- limit (int, optional): Max results (default: 100)
|
|
1346
|
+
- event_type (str, optional): Filter by event type ("firewall", "netflow", "proxy", "all")
|
|
1347
|
+
|
|
1348
|
+
Args:
|
|
1349
|
+
source_ip: Source IP address.
|
|
1350
|
+
destination_ip: Destination IP address.
|
|
1351
|
+
port: Port number.
|
|
1352
|
+
protocol: Protocol (tcp, udp, icmp, etc.).
|
|
1353
|
+
hours_back: Time window in hours.
|
|
1354
|
+
limit: Maximum number of events to return.
|
|
1355
|
+
event_type: Filter by event type.
|
|
1356
|
+
client: The SIEM client.
|
|
1357
|
+
|
|
1358
|
+
Returns:
|
|
1359
|
+
Dictionary containing network events with structured fields.
|
|
1360
|
+
|
|
1361
|
+
Raises:
|
|
1362
|
+
IntegrationError: If retrieval fails.
|
|
1363
|
+
"""
|
|
1364
|
+
if client is None:
|
|
1365
|
+
raise IntegrationError("SIEM client not provided")
|
|
1366
|
+
|
|
1367
|
+
if not hasattr(client, "get_network_events"):
|
|
1368
|
+
raise IntegrationError("SIEM client does not support get_network_events")
|
|
1369
|
+
|
|
1370
|
+
try:
|
|
1371
|
+
result = client.get_network_events(
|
|
1372
|
+
source_ip=source_ip,
|
|
1373
|
+
destination_ip=destination_ip,
|
|
1374
|
+
port=port,
|
|
1375
|
+
protocol=protocol,
|
|
1376
|
+
hours_back=hours_back,
|
|
1377
|
+
limit=limit,
|
|
1378
|
+
event_type=event_type,
|
|
1379
|
+
)
|
|
1380
|
+
|
|
1381
|
+
# Result is a dictionary with events array
|
|
1382
|
+
events = result.get("events", [])
|
|
1383
|
+
return {
|
|
1384
|
+
"success": True,
|
|
1385
|
+
"total_count": result.get("total_count", len(events)),
|
|
1386
|
+
"returned_count": len(events),
|
|
1387
|
+
"events": events,
|
|
1388
|
+
}
|
|
1389
|
+
except Exception as e:
|
|
1390
|
+
raise IntegrationError(f"Failed to get network events: {str(e)}") from e
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
def get_dns_events(
|
|
1394
|
+
domain: Optional[str] = None,
|
|
1395
|
+
ip_address: Optional[str] = None,
|
|
1396
|
+
resolved_ip: Optional[str] = None,
|
|
1397
|
+
query_type: Optional[str] = None,
|
|
1398
|
+
hours_back: int = 24,
|
|
1399
|
+
limit: int = 100,
|
|
1400
|
+
client: SIEMClient = None, # type: ignore
|
|
1401
|
+
) -> Dict[str, Any]:
|
|
1402
|
+
"""
|
|
1403
|
+
Retrieve DNS query and response events with structured fields.
|
|
1404
|
+
|
|
1405
|
+
Tool schema:
|
|
1406
|
+
- name: get_dns_events
|
|
1407
|
+
- description: Retrieve DNS query and response events with structured fields for analysis.
|
|
1408
|
+
Returns DNS events with domain, query type, resolved IP, source IP, and response codes.
|
|
1409
|
+
- parameters:
|
|
1410
|
+
- domain (str, optional): Domain name queried
|
|
1411
|
+
- ip_address (str, optional): IP that made the query
|
|
1412
|
+
- resolved_ip (str, optional): Resolved IP address
|
|
1413
|
+
- query_type (str, optional): DNS query type (A, AAAA, MX, TXT, etc.)
|
|
1414
|
+
- hours_back (int, optional): Time window (default: 24)
|
|
1415
|
+
- limit (int, optional): Max results (default: 100)
|
|
1416
|
+
|
|
1417
|
+
Args:
|
|
1418
|
+
domain: Domain name queried.
|
|
1419
|
+
ip_address: IP that made the query.
|
|
1420
|
+
resolved_ip: Resolved IP address.
|
|
1421
|
+
query_type: DNS query type (A, AAAA, MX, TXT, etc.).
|
|
1422
|
+
hours_back: Time window in hours.
|
|
1423
|
+
limit: Maximum number of events to return.
|
|
1424
|
+
client: The SIEM client.
|
|
1425
|
+
|
|
1426
|
+
Returns:
|
|
1427
|
+
Dictionary containing DNS events with structured fields.
|
|
1428
|
+
|
|
1429
|
+
Raises:
|
|
1430
|
+
IntegrationError: If retrieval fails.
|
|
1431
|
+
"""
|
|
1432
|
+
if client is None:
|
|
1433
|
+
raise IntegrationError("SIEM client not provided")
|
|
1434
|
+
|
|
1435
|
+
if not hasattr(client, "get_dns_events"):
|
|
1436
|
+
raise IntegrationError("SIEM client does not support get_dns_events")
|
|
1437
|
+
|
|
1438
|
+
try:
|
|
1439
|
+
result = client.get_dns_events(
|
|
1440
|
+
domain=domain,
|
|
1441
|
+
ip_address=ip_address,
|
|
1442
|
+
resolved_ip=resolved_ip,
|
|
1443
|
+
query_type=query_type,
|
|
1444
|
+
hours_back=hours_back,
|
|
1445
|
+
limit=limit,
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1448
|
+
# Result is a dictionary with events array
|
|
1449
|
+
events = result.get("events", [])
|
|
1450
|
+
return {
|
|
1451
|
+
"success": True,
|
|
1452
|
+
"total_count": result.get("total_count", len(events)),
|
|
1453
|
+
"returned_count": len(events),
|
|
1454
|
+
"events": events,
|
|
1455
|
+
}
|
|
1456
|
+
except Exception as e:
|
|
1457
|
+
raise IntegrationError(f"Failed to get DNS events: {str(e)}") from e
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
def get_alerts_by_entity(
|
|
1461
|
+
entity_value: str,
|
|
1462
|
+
entity_type: Optional[str] = None,
|
|
1463
|
+
hours_back: int = 24,
|
|
1464
|
+
limit: int = 50,
|
|
1465
|
+
severity: Optional[str] = None,
|
|
1466
|
+
client: SIEMClient = None, # type: ignore
|
|
1467
|
+
) -> Dict[str, Any]:
|
|
1468
|
+
"""
|
|
1469
|
+
Retrieve alerts filtered by specific entity (IP, user, host, domain, hash) for correlation analysis.
|
|
1470
|
+
|
|
1471
|
+
Tool schema:
|
|
1472
|
+
- name: get_alerts_by_entity
|
|
1473
|
+
- description: Retrieve alerts filtered by specific entity (IP, user, host, domain, hash) for
|
|
1474
|
+
correlation analysis. Returns alerts that contain the specified entity.
|
|
1475
|
+
- parameters:
|
|
1476
|
+
- entity_value (str, required): Entity value (IP, user, hostname, domain, hash)
|
|
1477
|
+
- entity_type (str, optional): Entity type (auto-detected if not provided: "ip", "user", "host", "domain", "hash")
|
|
1478
|
+
- hours_back (int, optional): Lookback period (default: 24)
|
|
1479
|
+
- limit (int, optional): Max results (default: 50)
|
|
1480
|
+
- severity (str, optional): Filter by severity ("low", "medium", "high", "critical")
|
|
1481
|
+
|
|
1482
|
+
Args:
|
|
1483
|
+
entity_value: Entity value (IP, user, hostname, domain, hash).
|
|
1484
|
+
entity_type: Entity type (auto-detected if not provided).
|
|
1485
|
+
hours_back: Lookback period in hours.
|
|
1486
|
+
limit: Maximum number of alerts to return.
|
|
1487
|
+
severity: Filter by severity level.
|
|
1488
|
+
client: The SIEM client.
|
|
1489
|
+
|
|
1490
|
+
Returns:
|
|
1491
|
+
Dictionary containing alerts related to the entity.
|
|
1492
|
+
|
|
1493
|
+
Raises:
|
|
1494
|
+
IntegrationError: If retrieval fails.
|
|
1495
|
+
"""
|
|
1496
|
+
if client is None:
|
|
1497
|
+
raise IntegrationError("SIEM client not provided")
|
|
1498
|
+
|
|
1499
|
+
if not hasattr(client, "get_alerts_by_entity"):
|
|
1500
|
+
raise IntegrationError("SIEM client does not support get_alerts_by_entity")
|
|
1501
|
+
|
|
1502
|
+
try:
|
|
1503
|
+
result = client.get_alerts_by_entity(
|
|
1504
|
+
entity_value=entity_value,
|
|
1505
|
+
entity_type=entity_type,
|
|
1506
|
+
hours_back=hours_back,
|
|
1507
|
+
limit=limit,
|
|
1508
|
+
severity=severity,
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1511
|
+
return {
|
|
1512
|
+
"success": True,
|
|
1513
|
+
"entity_value": result.get("entity_value"),
|
|
1514
|
+
"entity_type": result.get("entity_type"),
|
|
1515
|
+
"total_count": result.get("total_count", 0),
|
|
1516
|
+
"returned_count": result.get("returned_count", 0),
|
|
1517
|
+
"alerts": result.get("alerts", []),
|
|
1518
|
+
}
|
|
1519
|
+
except Exception as e:
|
|
1520
|
+
raise IntegrationError(f"Failed to get alerts by entity: {str(e)}") from e
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
def get_all_uncertain_alerts_for_host(
|
|
1524
|
+
hostname: str,
|
|
1525
|
+
hours_back: int = 7 * 24, # Default 7 days
|
|
1526
|
+
limit: int = 100,
|
|
1527
|
+
client: SIEMClient = None, # type: ignore
|
|
1528
|
+
) -> Dict[str, Any]:
|
|
1529
|
+
"""
|
|
1530
|
+
Retrieve all alerts with verdict="uncertain" for a specific host.
|
|
1531
|
+
|
|
1532
|
+
Tool schema:
|
|
1533
|
+
- name: get_all_uncertain_alerts_for_host
|
|
1534
|
+
- description: Retrieve all alerts with verdict="uncertain" for a specific host.
|
|
1535
|
+
This is useful for pattern analysis when investigating uncertain alerts to determine
|
|
1536
|
+
if multiple uncertain alerts on the same host indicate a broader issue requiring
|
|
1537
|
+
case creation and escalation.
|
|
1538
|
+
- parameters:
|
|
1539
|
+
- hostname (str, required): The hostname to search for
|
|
1540
|
+
- hours_back (int, optional): How many hours to look back (default: 168 = 7 days)
|
|
1541
|
+
- limit (int, optional): Maximum number of alerts to return (default: 100)
|
|
1542
|
+
|
|
1543
|
+
Args:
|
|
1544
|
+
hostname: The hostname to search for.
|
|
1545
|
+
hours_back: How many hours to look back (default: 168 = 7 days).
|
|
1546
|
+
limit: Maximum number of alerts to return.
|
|
1547
|
+
client: The SIEM client.
|
|
1548
|
+
|
|
1549
|
+
Returns:
|
|
1550
|
+
Dictionary containing uncertain alerts for the host.
|
|
1551
|
+
|
|
1552
|
+
Raises:
|
|
1553
|
+
IntegrationError: If retrieval fails.
|
|
1554
|
+
"""
|
|
1555
|
+
if client is None:
|
|
1556
|
+
raise IntegrationError("SIEM client not provided")
|
|
1557
|
+
|
|
1558
|
+
if not hasattr(client, "get_all_uncertain_alerts_for_host"):
|
|
1559
|
+
raise IntegrationError("SIEM client does not support get_all_uncertain_alerts_for_host")
|
|
1560
|
+
|
|
1561
|
+
try:
|
|
1562
|
+
result = client.get_all_uncertain_alerts_for_host(
|
|
1563
|
+
hostname=hostname,
|
|
1564
|
+
hours_back=hours_back,
|
|
1565
|
+
limit=limit,
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
return {
|
|
1569
|
+
"success": True,
|
|
1570
|
+
"hostname": result.get("hostname"),
|
|
1571
|
+
"hours_back": result.get("hours_back"),
|
|
1572
|
+
"total_count": result.get("total_count", 0),
|
|
1573
|
+
"returned_count": result.get("returned_count", 0),
|
|
1574
|
+
"alerts": result.get("alerts", []),
|
|
1575
|
+
}
|
|
1576
|
+
except Exception as e:
|
|
1577
|
+
raise IntegrationError(f"Failed to get uncertain alerts for host: {str(e)}") from e
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def get_alerts_by_time_window(
|
|
1581
|
+
start_time: str,
|
|
1582
|
+
end_time: str,
|
|
1583
|
+
limit: int = 100,
|
|
1584
|
+
severity: Optional[str] = None,
|
|
1585
|
+
alert_type: Optional[str] = None,
|
|
1586
|
+
client: SIEMClient = None, # type: ignore
|
|
1587
|
+
) -> Dict[str, Any]:
|
|
1588
|
+
"""
|
|
1589
|
+
Retrieve alerts within a specific time window for temporal correlation.
|
|
1590
|
+
|
|
1591
|
+
Tool schema:
|
|
1592
|
+
- name: get_alerts_by_time_window
|
|
1593
|
+
- description: Retrieve alerts within a specific time window for temporal correlation.
|
|
1594
|
+
Returns alerts that occurred between start_time and end_time.
|
|
1595
|
+
- parameters:
|
|
1596
|
+
- start_time (str, required): Start time (ISO format)
|
|
1597
|
+
- end_time (str, required): End time (ISO format)
|
|
1598
|
+
- limit (int, optional): Max results (default: 100)
|
|
1599
|
+
- severity (str, optional): Filter by severity
|
|
1600
|
+
- alert_type (str, optional): Filter by alert type
|
|
1601
|
+
|
|
1602
|
+
Args:
|
|
1603
|
+
start_time: Start time in ISO format.
|
|
1604
|
+
end_time: End time in ISO format.
|
|
1605
|
+
limit: Maximum number of alerts to return.
|
|
1606
|
+
severity: Filter by severity level.
|
|
1607
|
+
alert_type: Filter by alert type.
|
|
1608
|
+
client: The SIEM client.
|
|
1609
|
+
|
|
1610
|
+
Returns:
|
|
1611
|
+
Dictionary containing alerts in the time window.
|
|
1612
|
+
|
|
1613
|
+
Raises:
|
|
1614
|
+
IntegrationError: If retrieval fails.
|
|
1615
|
+
"""
|
|
1616
|
+
if client is None:
|
|
1617
|
+
raise IntegrationError("SIEM client not provided")
|
|
1618
|
+
|
|
1619
|
+
if not hasattr(client, "get_alerts_by_time_window"):
|
|
1620
|
+
raise IntegrationError("SIEM client does not support get_alerts_by_time_window")
|
|
1621
|
+
|
|
1622
|
+
try:
|
|
1623
|
+
result = client.get_alerts_by_time_window(
|
|
1624
|
+
start_time=start_time,
|
|
1625
|
+
end_time=end_time,
|
|
1626
|
+
limit=limit,
|
|
1627
|
+
severity=severity,
|
|
1628
|
+
alert_type=alert_type,
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
return {
|
|
1632
|
+
"success": True,
|
|
1633
|
+
"total_count": result.get("total_count", 0),
|
|
1634
|
+
"returned_count": result.get("returned_count", 0),
|
|
1635
|
+
"alerts": result.get("alerts", []),
|
|
1636
|
+
}
|
|
1637
|
+
except Exception as e:
|
|
1638
|
+
raise IntegrationError(f"Failed to get alerts by time window: {str(e)}") from e
|
|
1639
|
+
|
|
1640
|
+
|
|
1641
|
+
def get_email_events(
|
|
1642
|
+
sender_email: Optional[str] = None,
|
|
1643
|
+
recipient_email: Optional[str] = None,
|
|
1644
|
+
subject: Optional[str] = None,
|
|
1645
|
+
email_id: Optional[str] = None,
|
|
1646
|
+
hours_back: int = 24,
|
|
1647
|
+
limit: int = 100,
|
|
1648
|
+
event_type: Optional[str] = None,
|
|
1649
|
+
client: SIEMClient = None, # type: ignore
|
|
1650
|
+
) -> Dict[str, Any]:
|
|
1651
|
+
"""
|
|
1652
|
+
Retrieve email security events with structured fields for phishing analysis.
|
|
1653
|
+
|
|
1654
|
+
Tool schema:
|
|
1655
|
+
- name: get_email_events
|
|
1656
|
+
- description: Retrieve email security events with structured fields for phishing analysis.
|
|
1657
|
+
Returns email events with sender, recipient, subject, headers, authentication, URLs, and attachments.
|
|
1658
|
+
- parameters:
|
|
1659
|
+
- sender_email (str, optional): Sender email address
|
|
1660
|
+
- recipient_email (str, optional): Recipient email address
|
|
1661
|
+
- subject (str, optional): Email subject (partial match)
|
|
1662
|
+
- email_id (str, optional): Email message ID
|
|
1663
|
+
- hours_back (int, optional): Time window (default: 24)
|
|
1664
|
+
- limit (int, optional): Max results (default: 100)
|
|
1665
|
+
- event_type (str, optional): Filter by event type ("delivered", "blocked", "quarantined", "all")
|
|
1666
|
+
|
|
1667
|
+
Args:
|
|
1668
|
+
sender_email: Sender email address.
|
|
1669
|
+
recipient_email: Recipient email address.
|
|
1670
|
+
subject: Email subject (partial match).
|
|
1671
|
+
email_id: Email message ID.
|
|
1672
|
+
hours_back: Time window in hours.
|
|
1673
|
+
limit: Maximum number of events to return.
|
|
1674
|
+
event_type: Filter by event type.
|
|
1675
|
+
client: The SIEM client.
|
|
1676
|
+
|
|
1677
|
+
Returns:
|
|
1678
|
+
Dictionary containing email events with structured fields.
|
|
1679
|
+
|
|
1680
|
+
Raises:
|
|
1681
|
+
IntegrationError: If retrieval fails.
|
|
1682
|
+
"""
|
|
1683
|
+
if client is None:
|
|
1684
|
+
raise IntegrationError("SIEM client not provided")
|
|
1685
|
+
|
|
1686
|
+
if not hasattr(client, "get_email_events"):
|
|
1687
|
+
raise IntegrationError("SIEM client does not support get_email_events")
|
|
1688
|
+
|
|
1689
|
+
try:
|
|
1690
|
+
result = client.get_email_events(
|
|
1691
|
+
sender_email=sender_email,
|
|
1692
|
+
recipient_email=recipient_email,
|
|
1693
|
+
subject=subject,
|
|
1694
|
+
email_id=email_id,
|
|
1695
|
+
hours_back=hours_back,
|
|
1696
|
+
limit=limit,
|
|
1697
|
+
event_type=event_type,
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1700
|
+
return {
|
|
1701
|
+
"success": True,
|
|
1702
|
+
"total_count": result.get("total_count", 0),
|
|
1703
|
+
"returned_count": result.get("returned_count", 0),
|
|
1704
|
+
"events": result.get("events", []),
|
|
1705
|
+
}
|
|
1706
|
+
except Exception as e:
|
|
1707
|
+
raise IntegrationError(f"Failed to get email events: {str(e)}") from e
|
|
1708
|
+
|
|
1709
|
+
|