158 lines
6.0 KiB
Python
158 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
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:
|
|
print(json.dumps(data, indent=2, sort_keys=True))
|
|
|
|
|
|
def analyze_logs(args: argparse.Namespace) -> int:
|
|
events = read_events(args.logs)
|
|
candidates = suggest_block_candidates(
|
|
events,
|
|
min_events=args.min_events,
|
|
min_score=args.min_score,
|
|
allowlist=parse_allowlist(args.allowlist),
|
|
)
|
|
_print_json(
|
|
{
|
|
"summary": summarize_events(events),
|
|
"block_candidates": [
|
|
{"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
|
|
for candidate in candidates
|
|
],
|
|
}
|
|
)
|
|
if args.llm:
|
|
print("\nLLM summary:")
|
|
print(ollama_summary([], candidates, args.model))
|
|
return 0
|
|
|
|
|
|
def audit_policy_file(args: argparse.Namespace) -> int:
|
|
findings = audit_policies(read_policies(args.config))
|
|
_print_json([finding.__dict__ for finding in findings])
|
|
if args.llm:
|
|
print("\nLLM summary:")
|
|
print(ollama_summary(findings, [], args.model))
|
|
return 0
|
|
|
|
|
|
def suggest_blocks(args: argparse.Namespace) -> int:
|
|
events = read_events(args.logs)
|
|
candidates = suggest_block_candidates(
|
|
events,
|
|
min_events=args.min_events,
|
|
min_score=args.min_score,
|
|
allowlist=parse_allowlist(args.allowlist),
|
|
)
|
|
if not candidates:
|
|
print("No block candidates met the current thresholds.")
|
|
return 0
|
|
|
|
for candidate in candidates:
|
|
print(f"{candidate.src_ip} score={candidate.score} reasons={', '.join(candidate.reasons)}")
|
|
if not args.execute:
|
|
print(" dry-run: not blocked")
|
|
continue
|
|
client = FortiGateClient.from_env()
|
|
reason = f"fgAI UTM mitigation score={candidate.score}"
|
|
response = client.quarantine_ip(candidate.src_ip, args.expiry_minutes, reason)
|
|
print(f" blocked: {response}")
|
|
return 0
|
|
|
|
|
|
def test_connection(args: argparse.Namespace) -> int:
|
|
client = FortiGateClient.from_env()
|
|
status = client.system_status()
|
|
results = status.get("results", status)
|
|
print("FortiGate API connection OK")
|
|
_print_json(results)
|
|
return 0
|
|
|
|
|
|
def fetch_policies(args: argparse.Namespace) -> int:
|
|
client = FortiGateClient.from_env()
|
|
policies = client.firewall_policies()
|
|
if args.output:
|
|
output = Path(args.output)
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(json.dumps(policies, indent=2, sort_keys=True), encoding="utf-8")
|
|
print(f"Wrote {output}")
|
|
else:
|
|
_print_json(policies)
|
|
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)
|
|
|
|
logs = subparsers.add_parser("analyze-logs", help="Analyze local FortiGate logs")
|
|
logs.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
|
|
logs.add_argument("--min-events", type=int, default=3)
|
|
logs.add_argument("--min-score", type=int, default=7)
|
|
logs.add_argument("--allowlist", default=None, help="Comma-separated IPs/CIDRs never to block")
|
|
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.set_defaults(func=analyze_logs)
|
|
|
|
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")
|
|
policies.add_argument("--model", default=None, help="Ollama model name")
|
|
policies.set_defaults(func=audit_policy_file)
|
|
|
|
blocks = subparsers.add_parser("suggest-blocks", help="Suggest or execute guarded source IP blocks")
|
|
blocks.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
|
|
blocks.add_argument("--min-events", type=int, default=3)
|
|
blocks.add_argument("--min-score", type=int, default=7)
|
|
blocks.add_argument("--allowlist", default=None, help="Comma-separated IPs/CIDRs never to block")
|
|
blocks.add_argument("--execute", action="store_true", help="Actually call the FortiGate quarantine API")
|
|
blocks.add_argument("--expiry-minutes", type=int, default=60)
|
|
blocks.set_defaults(func=suggest_blocks)
|
|
|
|
connection = subparsers.add_parser("test-connection", help="Test FortiGate REST API credentials")
|
|
connection.set_defaults(func=test_connection)
|
|
|
|
fetch = subparsers.add_parser("fetch-policies", help="Fetch firewall policies through the FortiGate REST API")
|
|
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
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
try:
|
|
return args.func(args)
|
|
except Exception as exc:
|
|
print(f"fgai: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|