add dynamic
This commit is contained in:
@@ -6,7 +6,7 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .llm import ollama_summary
|
from .llm import ollama_summary
|
||||||
from .logs import read_events, summarize_events
|
from .logs import local_in_failures, read_events, summarize_events, top_field_values
|
||||||
from .mitigation import FortiGateClient, parse_allowlist, suggest_block_candidates
|
from .mitigation import FortiGateClient, parse_allowlist, suggest_block_candidates
|
||||||
from .policies import audit_policies, read_policies
|
from .policies import audit_policies, read_policies
|
||||||
from .syslog_server import listen_udp_syslog
|
from .syslog_server import listen_udp_syslog
|
||||||
@@ -27,6 +27,13 @@ def analyze_logs(args: argparse.Namespace) -> int:
|
|||||||
_print_json(
|
_print_json(
|
||||||
{
|
{
|
||||||
"summary": summarize_events(events),
|
"summary": summarize_events(events),
|
||||||
|
"diagnostics": {
|
||||||
|
"top_source_ips": top_field_values(events, "srcip", limit=10),
|
||||||
|
"top_services": top_field_values(events, "service", limit=10),
|
||||||
|
"top_actions": top_field_values(events, "action", limit=10),
|
||||||
|
"top_subtypes": top_field_values(events, "subtype", limit=10),
|
||||||
|
"local_in_failures": local_in_failures(events, limit=10),
|
||||||
|
},
|
||||||
"block_candidates": [
|
"block_candidates": [
|
||||||
{"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
|
{"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
|
||||||
for candidate in candidates
|
for candidate in candidates
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import shlex
|
import shlex
|
||||||
|
from collections import Counter
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -117,3 +118,29 @@ def summarize_events(events: Iterable[LogEvent]) -> dict[str, int]:
|
|||||||
if event.severity in {"critical", "high", "alert", "emergency"}:
|
if event.severity in {"critical", "high", "alert", "emergency"}:
|
||||||
summary["critical_or_high"] += 1
|
summary["critical_or_high"] += 1
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def top_field_values(events: Iterable[LogEvent], field: str, *, limit: int = 10) -> list[dict[str, int | str]]:
|
||||||
|
counter: Counter[str] = Counter()
|
||||||
|
for event in events:
|
||||||
|
value = event.fields.get(field)
|
||||||
|
if value:
|
||||||
|
counter[value] += 1
|
||||||
|
return [{"value": value, "count": count} for value, count in counter.most_common(limit)]
|
||||||
|
|
||||||
|
|
||||||
|
def local_in_failures(events: Iterable[LogEvent], *, limit: int = 10) -> list[dict[str, int | str]]:
|
||||||
|
counter: Counter[tuple[str, str, str]] = Counter()
|
||||||
|
for event in events:
|
||||||
|
policy_type = event.fields.get("policytype", event.fields.get("type", "")).lower()
|
||||||
|
msg = event.fields.get("msg", "").lower()
|
||||||
|
if not (policy_type.startswith("local-in") or "local-in" in policy_type or msg == "connection failed"):
|
||||||
|
continue
|
||||||
|
src_ip = event.src_ip or "unknown"
|
||||||
|
service = event.fields.get("service", event.fields.get("app", "unknown"))
|
||||||
|
policy_id = event.fields.get("policyid", event.fields.get("poluuid", "unknown"))
|
||||||
|
counter[(src_ip, service, policy_id)] += 1
|
||||||
|
return [
|
||||||
|
{"src_ip": src_ip, "service": service, "policy": policy_id, "count": count}
|
||||||
|
for (src_ip, service, policy_id), count in counter.most_common(limit)
|
||||||
|
]
|
||||||
|
|||||||
35
tests/test_diagnostics.py
Normal file
35
tests/test_diagnostics.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from fgai.logs import local_in_failures, parse_log_line, top_field_values
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticTests(unittest.TestCase):
|
||||||
|
def test_local_in_failures_group_by_source_service_policy(self):
|
||||||
|
events = [
|
||||||
|
parse_log_line(
|
||||||
|
'type=traffic policytype="local-in-policy6" policyid=2 srcip=192.168.1.20 '
|
||||||
|
'service="udp/5353" msg="Connection Failed"'
|
||||||
|
),
|
||||||
|
parse_log_line(
|
||||||
|
'type=traffic policytype="local-in-policy6" policyid=2 srcip=192.168.1.20 '
|
||||||
|
'service="udp/5353" msg="Connection Failed"'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
local_in_failures(events),
|
||||||
|
[{"src_ip": "192.168.1.20", "service": "udp/5353", "policy": "2", "count": 2}],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_top_field_values_counts_values(self):
|
||||||
|
events = [
|
||||||
|
parse_log_line("srcip=1.1.1.1 service=https"),
|
||||||
|
parse_log_line("srcip=1.1.1.1 service=http"),
|
||||||
|
parse_log_line("srcip=8.8.8.8 service=http"),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(top_field_values(events, "srcip", limit=1), [{"value": "1.1.1.1", "count": 2}])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user