Cybersecurity-Projects/PROJECTS/intermediate/siem-dashboard/backend/app/engine/normalizer.py

150 lines
4.2 KiB
Python

"""
©AngelaMos | 2026
normalizer.py
"""
from datetime import datetime, UTC
from typing import Any, Callable
from app.models.LogEvent import SourceType
NormalizerFn = Callable[[dict[str, Any]], dict[str, Any]]
NORMALIZERS: dict[str, NormalizerFn] = {}
def _register(source_type: SourceType) -> Callable[[NormalizerFn], NormalizerFn]:
"""
Register a normalizer function for a source type
"""
def decorator(fn: NormalizerFn) -> NormalizerFn:
NORMALIZERS[source_type.value] = fn
return fn
return decorator
def normalize(raw: dict[str, Any]) -> dict[str, Any]:
"""
Dispatch to the appropriate format normalizer and return enriched data
"""
source_type = raw.get("source_type", SourceType.GENERIC)
normalizer_fn = NORMALIZERS.get(source_type, _normalize_generic)
base = _extract_common(raw)
specific = normalizer_fn(raw)
base["normalized"] = {**base.get("normalized", {}), **specific}
return base
def _extract_common(raw: dict[str, Any]) -> dict[str, Any]:
"""
Pull fields shared across all log formats into a base dict
"""
return {
"timestamp": raw.get("timestamp",
datetime.now(UTC)),
"source_type": raw.get("source_type",
SourceType.GENERIC),
"source_ip": raw.get("source_ip"),
"dest_ip": raw.get("dest_ip"),
"source_port": raw.get("source_port"),
"dest_port": raw.get("dest_port"),
"event_type": raw.get("event_type"),
"hostname": raw.get("hostname"),
"username": raw.get("username"),
"raw": raw,
"normalized": {},
}
@_register(SourceType.FIREWALL)
def _normalize_firewall(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract firewall-specific fields like action and protocol
"""
return {
"action": raw.get("action"),
"protocol": raw.get("protocol"),
"bytes_sent": raw.get("bytes_sent"),
"bytes_received": raw.get("bytes_received"),
"rule_name": raw.get("rule_name"),
}
@_register(SourceType.IDS)
def _normalize_ids(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract IDS-specific fields like signature and severity
"""
return {
"signature_id": raw.get("signature_id"),
"signature_name": raw.get("signature_name"),
"classification": raw.get("classification"),
"priority": raw.get("priority"),
}
@_register(SourceType.AUTH)
def _normalize_auth(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract authentication-specific fields like method and result
"""
return {
"auth_method": raw.get("auth_method"),
"result": raw.get("result"),
"failure_reason": raw.get("failure_reason"),
"service": raw.get("service"),
}
@_register(SourceType.ENDPOINT)
def _normalize_endpoint(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract endpoint-specific fields like process and command line
"""
return {
"process_name": raw.get("process_name"),
"process_id": raw.get("process_id"),
"parent_process": raw.get("parent_process"),
"command_line": raw.get("command_line"),
"file_path": raw.get("file_path"),
}
@_register(SourceType.DNS)
def _normalize_dns(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract DNS-specific fields like query and record type
"""
return {
"query": raw.get("query"),
"query_type": raw.get("query_type"),
"response": raw.get("response"),
"response_code": raw.get("response_code"),
}
@_register(SourceType.PROXY)
def _normalize_proxy(raw: dict[str, Any]) -> dict[str, Any]:
"""
Extract proxy-specific fields like URL and user agent
"""
return {
"url": raw.get("url"),
"method": raw.get("method"),
"status_code": raw.get("status_code"),
"user_agent": raw.get("user_agent"),
"content_type": raw.get("content_type"),
"bytes_transferred": raw.get("bytes_transferred"),
}
@_register(SourceType.GENERIC)
def _normalize_generic(raw: dict[str, Any]) -> dict[str, Any]:
"""
Fallback normalizer for unrecognized source types
"""
return {
"message": raw.get("message"),
}