Cybersecurity-Projects/PROJECTS/intermediate/siem-dashboard/backend/app/controllers/rule_ctrl.py

119 lines
3.2 KiB
Python

"""
©AngelaMos | 2026
rule_ctrl.py
"""
from typing import Any
from datetime import datetime, timedelta, UTC
from flask import g
from app.engine.correlation import (
CorrelationState,
evaluate_rule,
)
from app.models.CorrelationRule import CorrelationRule
from app.models.LogEvent import LogEvent
def list_rules() -> list[Any]:
"""
Return all correlation rules
"""
return list(CorrelationRule.objects.order_by("-created_at")) # type: ignore[no-untyped-call]
def get_rule(rule_id: str) -> CorrelationRule:
"""
Return a single correlation rule by ID
"""
return CorrelationRule.get_by_id(rule_id)
def create_rule() -> CorrelationRule:
"""
Create a new correlation rule from validated request data
"""
data = g.validated
rule = CorrelationRule(
name=data.name,
description=data.description,
rule_type=data.rule_type,
conditions=data.conditions,
severity=data.severity,
enabled=data.enabled,
mitre_tactic=data.mitre_tactic,
mitre_technique=data.mitre_technique,
)
rule.save() # type: ignore[no-untyped-call]
return rule
def update_rule(rule_id: str) -> CorrelationRule:
"""
Partially update an existing correlation rule
"""
data = g.validated
rule = CorrelationRule.get_by_id(rule_id)
updates = data.model_dump(exclude_none=True)
for field_name, value in updates.items():
setattr(rule, field_name, value)
rule.save() # type: ignore[no-untyped-call]
return rule
def delete_rule(rule_id: str) -> dict[str, Any]:
"""
Delete a correlation rule by ID
"""
rule = CorrelationRule.get_by_id(rule_id)
rule.delete() # type: ignore[no-untyped-call]
return {"deleted": True}
def test_rule(rule_id: str) -> dict[str, Any]:
"""
Simulate a rule against historical log events and return matches
"""
data = g.validated
rule = CorrelationRule.get_by_id(rule_id)
cutoff = datetime.now(UTC).replace(
second=0, microsecond=0,
)
since = cutoff - timedelta(hours=data.hours)
events = LogEvent.objects(timestamp__gte=since).order_by("timestamp") # type: ignore[no-untyped-call]
state = CorrelationState()
alerts_fired: list[dict[str, Any]] = []
for event in events:
event_data = {
"id": str(event.id),
"timestamp": str(event.timestamp),
"source_type": event.source_type,
"severity": event.severity,
"event_type": event.event_type,
"source_ip": event.source_ip,
"dest_ip": event.dest_ip,
"hostname": event.hostname,
"username": event.username,
}
result = evaluate_rule(rule, event_data, state)
if result:
alerts_fired.append({
"group_value": result.group_value,
"matched_event_count": len(result.matched_event_ids),
"matched_event_ids": result.matched_event_ids,
})
return {
"rule_id": str(rule.id),
"rule_name": rule.name,
"events_evaluated": events.count(), # type: ignore[no-untyped-call]
"alerts_would_fire": len(alerts_fired),
"alerts": alerts_fired,
}