1+ from fastapi import FastAPI , Request
2+ import json
3+ import subprocess
4+ import os
5+ import re
6+
7+
8+ SEVERITIES_TO_FILTER = ["High" , "Critical" ] # List of severities to filter
9+ EXCLUDED_IPS = ["1.1.1.3" ] # List of IPs to exclude from blocking
10+ EXCLUDED_PROCESSES = ["sh" , "bash" , "python3" ] # Processes to exclude from termination
11+
12+ app = FastAPI ()
13+
14+ def process_high_severity_alert (alert ):
15+ """Process and filter MAIAlert messages with specified severity levels."""
16+ if alert .get ("message_type" ) == "MAIAlert" and alert .get ("severity" ) in SEVERITIES_TO_FILTER :
17+ print (f"\n #High/Critical Alert Detected!" )
18+ print (f"Time: { alert ['edge_datetime' ]} " )
19+ print (f"Device: { alert ['device_id' ]} " )
20+ print (f"Alert: { alert ['sensor_name' ]} " )
21+ print (f"Severity: { alert ['severity' ]} " )
22+ print (f"Details: { alert ['text_value' ]} \n " )
23+
24+ def extract_ip (text_value ):
25+ """ Extracts IP addresses that come after the words 'to' or 'from' in the text_value field """
26+ # Regular expression to find IPs after "to" or "from"
27+ match = re .findall (r"\b(?:to|from)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" , text_value , re .IGNORECASE )
28+
29+ if match :
30+ # Return the first IP address found after 'to' or 'from'
31+ print (f"[DEBUG] Extracted IP: { match [0 ]} " )
32+ return match [0 ]
33+
34+ print ("[DEBUG] No IP found after 'to' or 'from' in message!" )
35+ return None
36+
37+ def block_ip (ip ):
38+ """ Block the given IP using iptables """
39+ if ip in EXCLUDED_IPS :
40+ print (f"[INFO] Skipping blocking for excluded IP { ip } " )
41+ return
42+
43+ print (f"[INFO] Attempting to block IP { ip } ..." )
44+
45+ # Block the IP using iptables
46+ os .system (f"sudo iptables -A INPUT -s { ip } -j DROP" )
47+ os .system (f"sudo iptables -A OUTPUT -d { ip } -j DROP" )
48+ print (f"[INFO] IP { ip } blocked permanently." )
49+
50+ def extract_port (text_value ):
51+ """ Extracts port number from 'text_value' field """
52+ match = re .search (r"\b(\d{2,5})\b" , text_value )
53+ if match :
54+ port = int (match .group (1 ))
55+ print (f"[DEBUG] Extracted port: { port } " )
56+ return port
57+
58+ print ("[DEBUG] No port found in message!" )
59+ return None
60+
61+ def block_port (port ):
62+ """ Block the port using iptables and kill any process using it """
63+
64+ print (f"[INFO] Attempting to block port { port } ..." )
65+
66+ # Find process using the port
67+ result = subprocess .run (f"sudo lsof -i :{ port } -t" , shell = True , capture_output = True , text = True )
68+ pids = result .stdout .strip ().split ("\n " )
69+
70+ # Kill all processes using the port
71+ for pid in pids :
72+ if pid .isdigit ():
73+ print (f"[INFO] Killing process { pid } using port { port } ..." )
74+ os .system (f"sudo kill -9 { pid } " )
75+
76+ # Block the port using iptables
77+ os .system (f"sudo iptables -A INPUT -p tcp --dport { port } -j DROP" )
78+ os .system (f"sudo iptables -A OUTPUT -p tcp --sport { port } -j DROP" )
79+ print (f"[INFO] Port { port } blocked." )
80+
81+ def extract_process_name (text_value ):
82+ """ Extracts the process name from the alert """
83+ if "|" in text_value :
84+ process_name = text_value .split ("|" )[- 1 ].strip ()
85+ print (f"[DEBUG] Extracted process name: { process_name } " )
86+ return process_name
87+ return text_value # Return full string if no '|' is found
88+
89+ def terminate_process (process_name ):
90+ """ Terminate the process by its name unless it's excluded """
91+ if process_name in EXCLUDED_PROCESSES :
92+ print (f"[INFO] Skipping termination for excluded process: { process_name } " )
93+ return
94+
95+ print (f"[INFO] Attempting to terminate process: { process_name } ..." )
96+ result = subprocess .run (f"pgrep -f { process_name } " , shell = True , capture_output = True , text = True )
97+ pids = result .stdout .strip ().split ("\n " )
98+
99+ for pid in pids :
100+ if pid .isdigit ():
101+ print (f"[INFO] Terminating process { pid } ({ process_name } )..." )
102+ os .system (f"sudo kill -9 { pid } " )
103+
104+ @app .post ("/" )
105+ async def receive_alerts (request : Request ):
106+ """Unified API endpoint to receive and process all types of alerts."""
107+ try :
108+ data = await request .json () # Parse the JSON data
109+
110+ if data .get ("message_type" ) == "ASYNC" :
111+ print ("[INFO] Processing ASYNC message..." )
112+
113+ for alert in data .get ("feed" , []):
114+ # Process high severity alerts
115+ process_high_severity_alert (alert )
116+
117+ msg_type = alert .get ("message_type" , "" )
118+ text_value = alert .get ("text_value" , "" )
119+
120+ # Process IP blocking alerts
121+ if "MAIAlert" in msg_type :
122+ # Process for IP blocking
123+ print (f"[DEBUG] Received Alert: { text_value } " )
124+ ip = extract_ip (text_value )
125+ if ip :
126+ block_ip (ip )
127+
128+ # Process port blocking alerts
129+ if "A new listening port" in text_value :
130+ print (text_value )
131+ port = extract_port (text_value )
132+ if port :
133+ block_port (port )
134+
135+ # Process suspicious process alerts
136+ if "Abnormal process detected" in text_value :
137+ print (text_value )
138+ process_name = extract_process_name (text_value )
139+ if process_name and process_name not in EXCLUDED_PROCESSES :
140+ terminate_process (process_name )
141+ elif process_name in EXCLUDED_PROCESSES :
142+ print (f"[INFO] Skipping remediation for excluded process: { process_name } " )
143+
144+ return {"status" : "success" , "message" : "Alert processed" }
145+
146+ except json .JSONDecodeError :
147+ print ("[ERROR] Failed to decode JSON message!" )
148+ return {"status" : "error" , "message" : "Invalid JSON" }
149+
150+ if __name__ == "__main__" :
151+ import uvicorn
152+ uvicorn .run (app , host = "0.0.0.0" , port = 8000 )
0 commit comments