From e45603eed7458d44fddba2283857418b800426cc36b5968add37e623e91447b7 Mon Sep 17 00:00:00 2001 From: larssand Date: Thu, 18 Jun 2026 21:45:41 +0200 Subject: [PATCH] add syslog --- README.md | 14 +++++++++++++- src/fgai/cli.py | 12 ++++++++++++ src/fgai/syslog_server.py | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/fgai/syslog_server.py diff --git a/README.md b/README.md index c75cf46..fa67e34 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,18 @@ Analyze local logs: fgai analyze-logs --logs logs/fg_syslog.jsonl ``` +Listen for FortiGate syslog locally: + +```bash +fgai listen-syslog --port 5514 --output logs/fg_syslog.jsonl +``` + +UDP port `514` normally needs root privileges on Linux: + +```bash +sudo .venv/bin/fgai listen-syslog --port 514 --output logs/fg_syslog.jsonl +``` + Test FortiGate API access: ```bash @@ -67,7 +79,7 @@ Example FortiGate syslog target, run on the FortiGate CLI and replace the server config log syslogd setting set status enable set server "192.0.2.50" - set port 514 + set port 5514 set mode udp set format default end diff --git a/src/fgai/cli.py b/src/fgai/cli.py index 1be3611..d25fa3b 100644 --- a/src/fgai/cli.py +++ b/src/fgai/cli.py @@ -9,6 +9,7 @@ from .llm import ollama_summary from .logs import read_events, summarize_events from .mitigation import FortiGateClient, parse_allowlist, suggest_block_candidates from .policies import audit_policies, read_policies +from .syslog_server import listen_udp_syslog def _print_json(data: object) -> None: @@ -93,6 +94,11 @@ def fetch_policies(args: argparse.Namespace) -> int: return 0 +def listen_syslog(args: argparse.Namespace) -> int: + listen_udp_syslog(args.host, args.port, args.output) + return 0 + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Local FortiGate AI/ML inspection tool") subparsers = parser.add_subparsers(required=True) @@ -128,6 +134,12 @@ def build_parser() -> argparse.ArgumentParser: fetch.add_argument("--output", help="Write JSON response to this file") fetch.set_defaults(func=fetch_policies) + listener = subparsers.add_parser("listen-syslog", help="Listen for UDP syslog and append to a local log file") + listener.add_argument("--host", default="0.0.0.0", help="Bind address") + listener.add_argument("--port", type=int, default=5514, help="UDP port. Use 514 only with sudo/capability.") + listener.add_argument("--output", default="logs/fg_syslog.jsonl", help="File to append received logs to") + listener.set_defaults(func=listen_syslog) + return parser diff --git a/src/fgai/syslog_server.py b/src/fgai/syslog_server.py new file mode 100644 index 0000000..fe407cf --- /dev/null +++ b/src/fgai/syslog_server.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import socket +from pathlib import Path + + +def listen_udp_syslog(host: str, port: int, output: str, *, max_bytes: int = 65535) -> None: + output_path = Path(output) + output_path.parent.mkdir(parents=True, exist_ok=True) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind((host, port)) + + print(f"Listening for UDP syslog on {host}:{port}") + print(f"Writing logs to {output_path}") + with output_path.open("a", encoding="utf-8", buffering=1) as handle: + while True: + data, address = sock.recvfrom(max_bytes) + message = data.decode("utf-8", errors="replace").strip() + if not message: + continue + handle.write(f"{message}\n") + print(f"{address[0]}:{address[1]} {message}")