This commit is contained in:
larssand
2026-06-18 22:38:47 +02:00
parent b4ad932c91
commit 5650973e50
9 changed files with 700 additions and 28 deletions

View File

@@ -5,11 +5,14 @@ import json
import sys
from pathlib import Path
from .anomaly import anomaly_summary, detect_source_anomalies
from .dashboard import serve_dashboard
from .llm import ollama_summary
from .logs import local_in_failures, read_events, summarize_events, top_field_values
from .mitigation import FortiGateClient, parse_allowlist, suggest_block_candidates
from .policies import audit_policies, read_policies
from .syslog_server import listen_udp_syslog
from .monitor import monitor_loop
def _print_json(data: object) -> None:
@@ -24,8 +27,10 @@ def analyze_logs(args: argparse.Namespace) -> int:
min_score=args.min_score,
allowlist=parse_allowlist(args.allowlist),
)
anomalies = detect_source_anomalies(events, limit=args.anomaly_limit)
analysis = {
"summary": summarize_events(events),
"anomaly_summary": anomaly_summary(anomalies),
"diagnostics": {
"top_source_ips": top_field_values(events, "srcip", limit=10),
"top_services": top_field_values(events, "service", limit=10),
@@ -37,6 +42,17 @@ def analyze_logs(args: argparse.Namespace) -> int:
{"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
for candidate in candidates
],
"anomalies": [
{
"subject": finding.subject,
"score": finding.score,
"severity": finding.severity,
"confidence": finding.confidence,
"reasons": finding.reasons,
"evidence": finding.evidence,
}
for finding in anomalies
],
}
_print_json(analysis)
if args.llm:
@@ -78,6 +94,31 @@ def suggest_blocks(args: argparse.Namespace) -> int:
return 0
def detect_anomalies(args: argparse.Namespace) -> int:
events = read_events(args.logs)
anomalies = detect_source_anomalies(events, limit=args.limit)
output = {
"summary": anomaly_summary(anomalies),
"anomalies": [
{
"subject": finding.subject,
"score": finding.score,
"severity": finding.severity,
"confidence": finding.confidence,
"reasons": finding.reasons,
"evidence": finding.evidence,
}
for finding in anomalies
if finding.score >= args.min_score
],
}
_print_json(output)
if args.llm:
print("\nLLM summary:")
print(ollama_summary([], [], args.model, analysis=output, timeout=args.llm_timeout))
return 0
def test_connection(args: argparse.Namespace) -> int:
client = FortiGateClient.from_env()
status = client.system_status()
@@ -105,6 +146,22 @@ def listen_syslog(args: argparse.Namespace) -> int:
return 0
def run_monitor(args: argparse.Namespace) -> int:
monitor_loop(
args.logs,
args.output,
policy_path=args.policies,
interval=args.interval,
anomaly_limit=args.anomaly_limit,
)
return 0
def run_dashboard(args: argparse.Namespace) -> int:
serve_dashboard(args.host, args.port, args.status_file, image_dir=args.image_dir)
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Local FortiGate AI/ML inspection tool")
subparsers = parser.add_subparsers(required=True)
@@ -117,8 +174,18 @@ def build_parser() -> argparse.ArgumentParser:
logs.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
logs.add_argument("--model", default=None, help="Ollama model name")
logs.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
logs.add_argument("--anomaly-limit", type=int, default=20, help="Maximum anomaly findings to include")
logs.set_defaults(func=analyze_logs)
anomalies = subparsers.add_parser("detect-anomalies", help="Score likely traffic anomalies by source IP")
anomalies.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
anomalies.add_argument("--limit", type=int, default=20, help="Maximum anomaly findings to include")
anomalies.add_argument("--min-score", type=int, default=1, help="Minimum anomaly score to output")
anomalies.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
anomalies.add_argument("--model", default=None, help="Ollama model name")
anomalies.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
anomalies.set_defaults(func=detect_anomalies)
policies = subparsers.add_parser("audit-policies", help="Audit FortiOS firewall policy config")
policies.add_argument("--config", required=True, help="Path to FortiOS config backup")
policies.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
@@ -149,6 +216,21 @@ def build_parser() -> argparse.ArgumentParser:
listener.add_argument("--quiet", action="store_true", help="Do not print each received syslog message")
listener.set_defaults(func=listen_syslog)
monitor = subparsers.add_parser("monitor", help="Continuously analyze logs and write dashboard status JSON")
monitor.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
monitor.add_argument("--output", default="state/fgai-status.json", help="Status JSON written for dashboard")
monitor.add_argument("--policies", default=None, help="Optional FortiGate policy JSON/config file to audit continuously")
monitor.add_argument("--interval", type=int, default=10, help="Seconds between analysis runs")
monitor.add_argument("--anomaly-limit", type=int, default=20, help="Maximum anomaly findings to include")
monitor.set_defaults(func=run_monitor)
dashboard = subparsers.add_parser("dashboard", help="Serve local fgAI dashboard")
dashboard.add_argument("--host", default="127.0.0.1", help="Dashboard bind address")
dashboard.add_argument("--port", type=int, default=8088, help="Dashboard TCP port")
dashboard.add_argument("--status-file", default="state/fgai-status.json", help="Status JSON produced by monitor")
dashboard.add_argument("--image-dir", default="images", help="Directory containing dashboard images")
dashboard.set_defaults(func=run_dashboard)
return parser