iflow-mcp-m507_ai-soc-agent 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
- src/__init__.py +8 -0
- src/ai_controller/README.md +139 -0
- src/ai_controller/__init__.py +12 -0
- src/ai_controller/agent_executor.py +596 -0
- src/ai_controller/cli/__init__.py +2 -0
- src/ai_controller/cli/main.py +243 -0
- src/ai_controller/session_manager.py +409 -0
- src/ai_controller/web/__init__.py +2 -0
- src/ai_controller/web/server.py +1181 -0
- src/ai_controller/web/static/css/README.md +102 -0
- src/api/__init__.py +13 -0
- src/api/case_management.py +271 -0
- src/api/edr.py +187 -0
- src/api/kb.py +136 -0
- src/api/siem.py +308 -0
- src/core/__init__.py +10 -0
- src/core/config.py +242 -0
- src/core/config_storage.py +684 -0
- src/core/dto.py +50 -0
- src/core/errors.py +36 -0
- src/core/logging.py +128 -0
- src/integrations/__init__.py +8 -0
- src/integrations/case_management/__init__.py +5 -0
- src/integrations/case_management/iris/__init__.py +11 -0
- src/integrations/case_management/iris/iris_client.py +885 -0
- src/integrations/case_management/iris/iris_http.py +274 -0
- src/integrations/case_management/iris/iris_mapper.py +263 -0
- src/integrations/case_management/iris/iris_models.py +128 -0
- src/integrations/case_management/thehive/__init__.py +8 -0
- src/integrations/case_management/thehive/thehive_client.py +193 -0
- src/integrations/case_management/thehive/thehive_http.py +147 -0
- src/integrations/case_management/thehive/thehive_mapper.py +190 -0
- src/integrations/case_management/thehive/thehive_models.py +125 -0
- src/integrations/cti/__init__.py +6 -0
- src/integrations/cti/local_tip/__init__.py +10 -0
- src/integrations/cti/local_tip/local_tip_client.py +90 -0
- src/integrations/cti/local_tip/local_tip_http.py +110 -0
- src/integrations/cti/opencti/__init__.py +10 -0
- src/integrations/cti/opencti/opencti_client.py +101 -0
- src/integrations/cti/opencti/opencti_http.py +418 -0
- src/integrations/edr/__init__.py +6 -0
- src/integrations/edr/elastic_defend/__init__.py +6 -0
- src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
- src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
- src/integrations/eng/__init__.py +10 -0
- src/integrations/eng/clickup/__init__.py +8 -0
- src/integrations/eng/clickup/clickup_client.py +513 -0
- src/integrations/eng/clickup/clickup_http.py +156 -0
- src/integrations/eng/github/__init__.py +8 -0
- src/integrations/eng/github/github_client.py +169 -0
- src/integrations/eng/github/github_http.py +158 -0
- src/integrations/eng/trello/__init__.py +8 -0
- src/integrations/eng/trello/trello_client.py +207 -0
- src/integrations/eng/trello/trello_http.py +162 -0
- src/integrations/kb/__init__.py +12 -0
- src/integrations/kb/fs_kb_client.py +313 -0
- src/integrations/siem/__init__.py +6 -0
- src/integrations/siem/elastic/__init__.py +6 -0
- src/integrations/siem/elastic/elastic_client.py +3319 -0
- src/integrations/siem/elastic/elastic_http.py +165 -0
- src/mcp/README.md +183 -0
- src/mcp/TOOLS.md +2827 -0
- src/mcp/__init__.py +13 -0
- src/mcp/__main__.py +18 -0
- src/mcp/agent_profiles.py +408 -0
- src/mcp/flow_agent_profiles.py +424 -0
- src/mcp/mcp_server.py +4086 -0
- src/mcp/rules_engine.py +487 -0
- src/mcp/runbook_manager.py +264 -0
- src/orchestrator/__init__.py +11 -0
- src/orchestrator/incident_workflow.py +244 -0
- src/orchestrator/tools_case.py +1085 -0
- src/orchestrator/tools_cti.py +359 -0
- src/orchestrator/tools_edr.py +315 -0
- src/orchestrator/tools_eng.py +378 -0
- src/orchestrator/tools_kb.py +156 -0
- src/orchestrator/tools_siem.py +1709 -0
- src/web/__init__.py +8 -0
- src/web/config_server.py +511 -0
src/mcp/rules_engine.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rules engine for executing investigation workflows.
|
|
3
|
+
|
|
4
|
+
This module provides a rules engine that can chain together multiple
|
|
5
|
+
investigation skills to perform automated investigations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from ..api.case_management import CaseManagementClient
|
|
15
|
+
from ..api.edr import EDRClient
|
|
16
|
+
from ..api.siem import SIEMClient
|
|
17
|
+
from ..core.errors import IntegrationError
|
|
18
|
+
from ..orchestrator import incident_workflow
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Rule:
|
|
23
|
+
"""
|
|
24
|
+
Represents an investigation rule/workflow.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
description: Optional[str] = None
|
|
29
|
+
trigger: Optional[str] = None # Condition that triggers the rule
|
|
30
|
+
actions: List[str] = None # type: ignore
|
|
31
|
+
enabled: bool = True
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if self.actions is None:
|
|
35
|
+
self.actions = []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class RuleExecutionContext:
|
|
40
|
+
"""
|
|
41
|
+
Execution context for a rule, containing variables and state.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
rule_name: str
|
|
45
|
+
variables: Dict[str, Any]
|
|
46
|
+
results: Dict[str, Any]
|
|
47
|
+
|
|
48
|
+
def __init__(self, rule_name: str):
|
|
49
|
+
self.rule_name = rule_name
|
|
50
|
+
self.variables = {}
|
|
51
|
+
self.results = {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RulesEngine:
|
|
55
|
+
"""
|
|
56
|
+
Engine for executing investigation rules/workflows.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
case_client: Optional[CaseManagementClient] = None,
|
|
62
|
+
siem_client: Optional[SIEMClient] = None,
|
|
63
|
+
edr_client: Optional[EDRClient] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the rules engine with clients.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
case_client: Case management client.
|
|
70
|
+
siem_client: SIEM client.
|
|
71
|
+
edr_client: EDR client.
|
|
72
|
+
"""
|
|
73
|
+
self.case_client = case_client
|
|
74
|
+
self.siem_client = siem_client
|
|
75
|
+
self.edr_client = edr_client
|
|
76
|
+
self.rules: Dict[str, Rule] = {}
|
|
77
|
+
|
|
78
|
+
def load_rule(self, rule_dict: Dict[str, Any]) -> Rule:
|
|
79
|
+
"""
|
|
80
|
+
Load a rule from a dictionary.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
rule_dict: Dictionary containing rule definition.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Rule object.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
IntegrationError: If rule definition is invalid.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
rule = Rule(
|
|
93
|
+
name=rule_dict["name"],
|
|
94
|
+
description=rule_dict.get("description"),
|
|
95
|
+
trigger=rule_dict.get("trigger"),
|
|
96
|
+
actions=rule_dict.get("actions", []),
|
|
97
|
+
enabled=rule_dict.get("enabled", True),
|
|
98
|
+
)
|
|
99
|
+
return rule
|
|
100
|
+
except KeyError as e:
|
|
101
|
+
raise IntegrationError(f"Invalid rule definition: missing {e}") from e
|
|
102
|
+
|
|
103
|
+
def add_rule(self, rule: Rule) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Add a rule to the engine.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
rule: Rule to add.
|
|
109
|
+
"""
|
|
110
|
+
self.rules[rule.name] = rule
|
|
111
|
+
|
|
112
|
+
def list_rules(self) -> List[Dict[str, Any]]:
|
|
113
|
+
"""
|
|
114
|
+
List all registered rules.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of rule metadata dictionaries.
|
|
118
|
+
"""
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
"name": rule.name,
|
|
122
|
+
"description": rule.description,
|
|
123
|
+
"enabled": rule.enabled,
|
|
124
|
+
"action_count": len(rule.actions),
|
|
125
|
+
}
|
|
126
|
+
for rule in self.rules.values()
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
def execute_rule(
|
|
130
|
+
self,
|
|
131
|
+
rule_name: str,
|
|
132
|
+
context: Optional[Dict[str, Any]] = None,
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""
|
|
135
|
+
Execute a rule by name.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
rule_name: Name of the rule to execute.
|
|
139
|
+
context: Optional context variables to pass to the rule.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dictionary containing execution results.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
IntegrationError: If rule not found or execution fails.
|
|
146
|
+
"""
|
|
147
|
+
if rule_name not in self.rules:
|
|
148
|
+
raise IntegrationError(f"Rule not found: {rule_name}")
|
|
149
|
+
|
|
150
|
+
rule = self.rules[rule_name]
|
|
151
|
+
if not rule.enabled:
|
|
152
|
+
raise IntegrationError(f"Rule is disabled: {rule_name}")
|
|
153
|
+
|
|
154
|
+
# Create execution context
|
|
155
|
+
exec_context = RuleExecutionContext(rule_name)
|
|
156
|
+
if context:
|
|
157
|
+
exec_context.variables.update(context)
|
|
158
|
+
|
|
159
|
+
# Check trigger condition if present
|
|
160
|
+
if rule.trigger:
|
|
161
|
+
if not self._evaluate_trigger(rule.trigger, exec_context):
|
|
162
|
+
# Return a human-readable explanation that the rule was not executed
|
|
163
|
+
message = (
|
|
164
|
+
f"Automated rule '{rule_name}' was **not executed** because the "
|
|
165
|
+
f"trigger condition was not met.\n\n"
|
|
166
|
+
f"- Trigger condition: `{rule.trigger}`\n"
|
|
167
|
+
f"- Context variables at evaluation time: "
|
|
168
|
+
f"{json.dumps(exec_context.variables, default=str)}\n\n"
|
|
169
|
+
"You can adjust the context variables or the trigger condition and "
|
|
170
|
+
"call `execute_rule` again if you want this workflow to run."
|
|
171
|
+
)
|
|
172
|
+
return {
|
|
173
|
+
"success": False,
|
|
174
|
+
"rule_name": rule_name,
|
|
175
|
+
"summary": message,
|
|
176
|
+
"details": {
|
|
177
|
+
"reason": "trigger_not_met",
|
|
178
|
+
"trigger": rule.trigger,
|
|
179
|
+
"context": exec_context.variables,
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Execute actions and capture low-level results
|
|
184
|
+
results: List[Dict[str, Any]] = []
|
|
185
|
+
for action in rule.actions:
|
|
186
|
+
try:
|
|
187
|
+
result = self._execute_action(action, exec_context)
|
|
188
|
+
results.append({"action": action, "success": True, "result": result})
|
|
189
|
+
except Exception as e:
|
|
190
|
+
results.append(
|
|
191
|
+
{
|
|
192
|
+
"action": action,
|
|
193
|
+
"success": False,
|
|
194
|
+
"error": str(e),
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
# Optionally continue or stop on error
|
|
198
|
+
# For now, we'll continue but log the error
|
|
199
|
+
|
|
200
|
+
# Build an AI-friendly, playbook-style narrative around the execution
|
|
201
|
+
narrative = self._format_execution_narrative(
|
|
202
|
+
rule=rule,
|
|
203
|
+
exec_context=exec_context,
|
|
204
|
+
results=results,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Return both the human-readable narrative and the structured data
|
|
208
|
+
return {
|
|
209
|
+
"success": True,
|
|
210
|
+
"rule_name": rule_name,
|
|
211
|
+
"playbook": narrative,
|
|
212
|
+
"summary": narrative.split("\n\n")[0] if "\n\n" in narrative else narrative,
|
|
213
|
+
"actions": results,
|
|
214
|
+
"final_context": exec_context.variables,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
def _evaluate_trigger(self, trigger: str, context: RuleExecutionContext) -> bool:
|
|
218
|
+
"""
|
|
219
|
+
Evaluate a trigger condition.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
trigger: Trigger condition string.
|
|
223
|
+
context: Execution context.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if trigger condition is met, False otherwise.
|
|
227
|
+
"""
|
|
228
|
+
# Simple trigger evaluation - can be extended with more sophisticated logic
|
|
229
|
+
# For now, support simple variable comparisons
|
|
230
|
+
try:
|
|
231
|
+
# Replace variables in trigger with values from context
|
|
232
|
+
evaluated = trigger
|
|
233
|
+
for key, value in context.variables.items():
|
|
234
|
+
evaluated = evaluated.replace(f"${key}", str(value))
|
|
235
|
+
evaluated = evaluated.replace(f"{{key}}", str(value))
|
|
236
|
+
|
|
237
|
+
# Simple evaluation (can be made more sophisticated)
|
|
238
|
+
# For now, just check if it's a boolean expression
|
|
239
|
+
return eval(evaluated, {"__builtins__": {}}, {})
|
|
240
|
+
except Exception:
|
|
241
|
+
# If evaluation fails, assume trigger is not met
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
def _execute_action(self, action: str, context: RuleExecutionContext) -> Any:
|
|
245
|
+
"""
|
|
246
|
+
Execute a single action.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
action: Action name or action definition.
|
|
250
|
+
context: Execution context.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Action result.
|
|
254
|
+
"""
|
|
255
|
+
# Parse action (can be a string name or a dict with parameters)
|
|
256
|
+
if isinstance(action, dict):
|
|
257
|
+
action_name = action.get("name") or action.get("action")
|
|
258
|
+
action_params = action.get("params", {})
|
|
259
|
+
else:
|
|
260
|
+
action_name = action
|
|
261
|
+
action_params = {}
|
|
262
|
+
|
|
263
|
+
# Map action names to functions
|
|
264
|
+
action_map = {
|
|
265
|
+
"create_case_from_alert": self._create_case_from_alert,
|
|
266
|
+
"search_siem_for_related_events": self._search_siem_for_related_events,
|
|
267
|
+
"enrich_case_from_siem": self._enrich_case_from_siem,
|
|
268
|
+
"enrich_case_from_edr": self._enrich_case_from_edr,
|
|
269
|
+
"close_incident": self._close_incident,
|
|
270
|
+
"assign_case": self._assign_case,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if action_name not in action_map:
|
|
274
|
+
raise IntegrationError(f"Unknown action: {action_name}")
|
|
275
|
+
|
|
276
|
+
# Execute the action
|
|
277
|
+
return action_map[action_name](action_params, context)
|
|
278
|
+
|
|
279
|
+
def _format_execution_narrative(
|
|
280
|
+
self,
|
|
281
|
+
rule: Rule,
|
|
282
|
+
exec_context: RuleExecutionContext,
|
|
283
|
+
results: List[Dict[str, Any]],
|
|
284
|
+
) -> str:
|
|
285
|
+
"""
|
|
286
|
+
Build a human-readable, playbook-style narrative of a rule execution.
|
|
287
|
+
|
|
288
|
+
This is optimized for MCP/LLM usage: it reads like a mini runbook the
|
|
289
|
+
AI agent can follow, while still reflecting the concrete actions taken
|
|
290
|
+
and their outcomes.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
lines: List[str] = []
|
|
294
|
+
|
|
295
|
+
# Header / objective
|
|
296
|
+
lines.append(f"Automated investigation rule executed: **{rule.name}**")
|
|
297
|
+
if rule.description:
|
|
298
|
+
lines.append(f"Description: {rule.description}")
|
|
299
|
+
|
|
300
|
+
# Context snapshot
|
|
301
|
+
if exec_context.variables:
|
|
302
|
+
lines.append("")
|
|
303
|
+
lines.append("Initial context provided to this workflow:")
|
|
304
|
+
for key, value in exec_context.variables.items():
|
|
305
|
+
try:
|
|
306
|
+
encoded = json.dumps(value, default=str)
|
|
307
|
+
except TypeError:
|
|
308
|
+
encoded = str(value)
|
|
309
|
+
# Keep very long values readable by truncating
|
|
310
|
+
if len(encoded) > 300:
|
|
311
|
+
encoded = encoded[:297] + "..."
|
|
312
|
+
lines.append(f"- **{key}**: {encoded}")
|
|
313
|
+
|
|
314
|
+
# Executed steps
|
|
315
|
+
lines.append("")
|
|
316
|
+
lines.append("Execution steps and outcomes:")
|
|
317
|
+
if not results:
|
|
318
|
+
lines.append("- No actions were defined for this rule.")
|
|
319
|
+
else:
|
|
320
|
+
for idx, step in enumerate(results, start=1):
|
|
321
|
+
action = step.get("action")
|
|
322
|
+
success = step.get("success", False)
|
|
323
|
+
status = "SUCCESS" if success else "FAILED"
|
|
324
|
+
|
|
325
|
+
# Map internal action names to friendly labels
|
|
326
|
+
friendly_names = {
|
|
327
|
+
"create_case_from_alert": "Create case from alert",
|
|
328
|
+
"search_siem_for_related_events": "Search SIEM for related events",
|
|
329
|
+
"enrich_case_from_siem": "Enrich case with SIEM data",
|
|
330
|
+
"enrich_case_from_edr": "Enrich case with EDR data",
|
|
331
|
+
"close_incident": "Close incident in case management",
|
|
332
|
+
"assign_case": "Assign case to analyst",
|
|
333
|
+
}
|
|
334
|
+
friendly_action = friendly_names.get(str(action), str(action))
|
|
335
|
+
|
|
336
|
+
lines.append(f"{idx}. **{friendly_action}** — {status}")
|
|
337
|
+
|
|
338
|
+
if success and "result" in step:
|
|
339
|
+
result = step["result"]
|
|
340
|
+
if isinstance(result, dict):
|
|
341
|
+
for k, v in result.items():
|
|
342
|
+
try:
|
|
343
|
+
v_str = json.dumps(v, default=str)
|
|
344
|
+
except TypeError:
|
|
345
|
+
v_str = str(v)
|
|
346
|
+
if len(v_str) > 200:
|
|
347
|
+
v_str = v_str[:197] + "..."
|
|
348
|
+
lines.append(f" - {k}: {v_str}")
|
|
349
|
+
else:
|
|
350
|
+
lines.append(f" - Result: {result}")
|
|
351
|
+
elif not success:
|
|
352
|
+
error = step.get("error", "Unknown error")
|
|
353
|
+
lines.append(f" - Error: {error}")
|
|
354
|
+
|
|
355
|
+
# Final state / follow-up guidance
|
|
356
|
+
lines.append("")
|
|
357
|
+
lines.append("Final investigation state and guidance for the AI agent:")
|
|
358
|
+
|
|
359
|
+
# Highlight a few commonly important context variables if present
|
|
360
|
+
key_hints = []
|
|
361
|
+
if "case_id" in exec_context.variables:
|
|
362
|
+
key_hints.append(
|
|
363
|
+
f"- Review and continue work on case **{exec_context.variables['case_id']}** "
|
|
364
|
+
"in the case management system."
|
|
365
|
+
)
|
|
366
|
+
if "siem_search_results" in exec_context.variables:
|
|
367
|
+
key_hints.append(
|
|
368
|
+
"- Use the stored `siem_search_results` (in context) to reason about "
|
|
369
|
+
"related events, time ranges, and entities."
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if key_hints:
|
|
373
|
+
lines.extend(key_hints)
|
|
374
|
+
else:
|
|
375
|
+
lines.append(
|
|
376
|
+
"- Use the context variables and step outcomes above to decide on the "
|
|
377
|
+
"next manual investigation or response actions."
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
lines.append(
|
|
381
|
+
"- Treat this output as a high-level playbook: you can reference the "
|
|
382
|
+
"steps and their results when planning further tool calls."
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return "\n".join(lines)
|
|
386
|
+
|
|
387
|
+
def _create_case_from_alert(
|
|
388
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
389
|
+
) -> Any:
|
|
390
|
+
"""Create a case from an alert."""
|
|
391
|
+
if not self.case_client:
|
|
392
|
+
raise IntegrationError("Case management client not configured")
|
|
393
|
+
if not self.siem_client:
|
|
394
|
+
raise IntegrationError("SIEM client not configured")
|
|
395
|
+
|
|
396
|
+
# This would need the actual alert object - simplified for now
|
|
397
|
+
# In practice, you'd get the alert from context or params
|
|
398
|
+
raise IntegrationError("create_case_from_alert requires alert object")
|
|
399
|
+
|
|
400
|
+
def _search_siem_for_related_events(
|
|
401
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
402
|
+
) -> Any:
|
|
403
|
+
"""Search SIEM for related events."""
|
|
404
|
+
if not self.siem_client:
|
|
405
|
+
raise IntegrationError("SIEM client not configured")
|
|
406
|
+
|
|
407
|
+
query = params.get("query") or context.variables.get("search_query", "")
|
|
408
|
+
limit = params.get("limit", 100)
|
|
409
|
+
|
|
410
|
+
result = self.siem_client.search_security_events(query=query, limit=limit)
|
|
411
|
+
context.variables["siem_search_results"] = result
|
|
412
|
+
return {"event_count": len(result.events), "total_count": result.total_count}
|
|
413
|
+
|
|
414
|
+
def _enrich_case_from_siem(
|
|
415
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
416
|
+
) -> Any:
|
|
417
|
+
"""Enrich a case from SIEM data."""
|
|
418
|
+
if not self.case_client:
|
|
419
|
+
raise IntegrationError("Case management client not configured")
|
|
420
|
+
if not self.siem_client:
|
|
421
|
+
raise IntegrationError("SIEM client not configured")
|
|
422
|
+
|
|
423
|
+
case_id = params.get("case_id") or context.variables.get("case_id")
|
|
424
|
+
if not case_id:
|
|
425
|
+
raise IntegrationError("case_id required for enrich_case_from_siem")
|
|
426
|
+
|
|
427
|
+
observables = incident_workflow.enrich_case_from_siem(
|
|
428
|
+
case_id=case_id,
|
|
429
|
+
case_client=self.case_client,
|
|
430
|
+
siem_client=self.siem_client,
|
|
431
|
+
)
|
|
432
|
+
return {"observables_added": len(observables)}
|
|
433
|
+
|
|
434
|
+
def _enrich_case_from_edr(
|
|
435
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
436
|
+
) -> Any:
|
|
437
|
+
"""Enrich a case from EDR data."""
|
|
438
|
+
if not self.case_client:
|
|
439
|
+
raise IntegrationError("Case management client not configured")
|
|
440
|
+
if not self.edr_client:
|
|
441
|
+
raise IntegrationError("EDR client not configured")
|
|
442
|
+
|
|
443
|
+
case_id = params.get("case_id") or context.variables.get("case_id")
|
|
444
|
+
endpoint_id = params.get("endpoint_id")
|
|
445
|
+
|
|
446
|
+
observables = incident_workflow.enrich_case_from_edr(
|
|
447
|
+
case_id=case_id,
|
|
448
|
+
case_client=self.case_client,
|
|
449
|
+
edr_client=self.edr_client,
|
|
450
|
+
endpoint_id=endpoint_id,
|
|
451
|
+
)
|
|
452
|
+
return {"observables_added": len(observables)}
|
|
453
|
+
|
|
454
|
+
def _close_incident(
|
|
455
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
456
|
+
) -> Any:
|
|
457
|
+
"""Close an incident."""
|
|
458
|
+
if not self.case_client:
|
|
459
|
+
raise IntegrationError("Case management client not configured")
|
|
460
|
+
|
|
461
|
+
case_id = params.get("case_id") or context.variables.get("case_id")
|
|
462
|
+
if not case_id:
|
|
463
|
+
raise IntegrationError("case_id required for close_incident")
|
|
464
|
+
|
|
465
|
+
resolution_notes = params.get("resolution_notes")
|
|
466
|
+
case = incident_workflow.close_incident(
|
|
467
|
+
case_id=case_id,
|
|
468
|
+
case_client=self.case_client,
|
|
469
|
+
resolution_notes=resolution_notes,
|
|
470
|
+
)
|
|
471
|
+
return {"case_id": case.id, "status": case.status.value}
|
|
472
|
+
|
|
473
|
+
def _assign_case(
|
|
474
|
+
self, params: Dict[str, Any], context: RuleExecutionContext
|
|
475
|
+
) -> Any:
|
|
476
|
+
"""Assign a case to an analyst."""
|
|
477
|
+
if not self.case_client:
|
|
478
|
+
raise IntegrationError("Case management client not configured")
|
|
479
|
+
|
|
480
|
+
case_id = params.get("case_id") or context.variables.get("case_id")
|
|
481
|
+
assignee = params.get("assignee")
|
|
482
|
+
if not case_id or not assignee:
|
|
483
|
+
raise IntegrationError("case_id and assignee required for assign_case")
|
|
484
|
+
|
|
485
|
+
assignment = self.case_client.assign_case(case_id, assignee)
|
|
486
|
+
return {"case_id": assignment.case_id, "assignee": assignment.assignee}
|
|
487
|
+
|