Add host correlation and assurance coverage
This commit is contained in:
parent
8194d13734
commit
b40ac4252c
36 changed files with 944 additions and 23 deletions
66
enodia_sentinel/correlation.py
Normal file
66
enodia_sentinel/correlation.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Read-only incident correlation rules.
|
||||
|
||||
Correlation does not hide the raw alerts or mutate the host. It adds a small
|
||||
``correlations`` list to an incident when the incident's existing evidence
|
||||
matches a higher-confidence story.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .alert import Severity
|
||||
|
||||
SID_MULTI_STAGE_INTRUSION = 100080
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CorrelationRule:
|
||||
sid: int
|
||||
signature: str
|
||||
classtype: str
|
||||
severity: Severity
|
||||
summary: str
|
||||
required_any: tuple[set[str], ...]
|
||||
max_window: int
|
||||
|
||||
|
||||
DEFAULT_RULES: tuple[CorrelationRule, ...] = (
|
||||
CorrelationRule(
|
||||
sid=SID_MULTI_STAGE_INTRUSION,
|
||||
signature="correlation.multi_stage_intrusion",
|
||||
classtype="multi-stage-intrusion",
|
||||
severity=Severity.CRITICAL,
|
||||
summary=(
|
||||
"Web/database service spawned a shell and the same incident showed "
|
||||
"suspicious egress or an unusual listener within the correlation window"
|
||||
),
|
||||
required_any=(
|
||||
{"exec_rule.web-rce"},
|
||||
{"host_rule.suspicious-egress", "host_rule.suspicious-listener"},
|
||||
),
|
||||
max_window=600,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def correlate(incident: dict, rules: tuple[CorrelationRule, ...] = DEFAULT_RULES) -> list[dict]:
|
||||
"""Return correlation records matched by an incident index entry."""
|
||||
signatures = set(incident.get("signatures") or [])
|
||||
duration = float(incident.get("last_ts", 0.0)) - float(incident.get("first_ts", 0.0))
|
||||
out: list[dict] = []
|
||||
for rule in rules:
|
||||
if duration > rule.max_window:
|
||||
continue
|
||||
if not all(signatures & choices for choices in rule.required_any):
|
||||
continue
|
||||
out.append({
|
||||
"sid": rule.sid,
|
||||
"signature": rule.signature,
|
||||
"classtype": rule.classtype,
|
||||
"severity": str(rule.severity),
|
||||
"summary": rule.summary,
|
||||
"window_seconds": rule.max_window,
|
||||
"matched_signatures": sorted(signatures),
|
||||
})
|
||||
return out
|
||||
Loading…
Add table
Add a link
Reference in a new issue