@@ -39,6 +39,7 @@ def __init__(self, run_id: str):
3939 self .pairs_analyzed = 0
4040 self .beacons_detected = 0
4141 self .alerts_generated = 0
42+ self .pairs_skipped = 0
4243 self .errors = 0
4344 self .results : List [DetectionResult ] = []
4445
@@ -61,6 +62,7 @@ def to_dict(self):
6162 "pairs_analyzed" : int (self .pairs_analyzed ),
6263 "beacons_detected" : int (self .beacons_detected ),
6364 "alerts_generated" : int (self .alerts_generated ),
65+ "pairs_skipped" : int (self .pairs_skipped ),
6466 "errors" : int (self .errors ),
6567 }
6668
@@ -73,6 +75,8 @@ def __init__(
7375 detector : BeaconDetector ,
7476 alert_manager : AlertManager ,
7577 config = None ,
78+ whitelist : dict = None ,
79+ persistence = None ,
7680 ):
7781 """
7882 Initialize the analyzer.
@@ -82,11 +86,15 @@ def __init__(
8286 detector: BeaconDetector instance
8387 alert_manager: AlertManager instance
8488 config: Analyzer configuration
89+ whitelist: Whitelist configuration for filtering pairs
90+ persistence: SQLiteStore instance for persisting beacons
8591 """
8692 self .storage = storage
8793 self .detector = detector
8894 self .alert_manager = alert_manager
8995 self .config = config or AnalyzerConfig ()
96+ self ._whitelist = whitelist or {}
97+ self ._persistence = persistence
9098
9199 # Thread safety lock for shared state
92100 self ._lock = threading .RLock ()
@@ -118,6 +126,29 @@ def __init__(
118126 f"ConnectionAnalyzer initialized: interval={ self .config .analysis_interval } s"
119127 )
120128
129+ def _is_whitelisted (self , pair ) -> bool :
130+ """Check if a connection pair matches any whitelist rule."""
131+ if pair .src_ip in self ._whitelist .get ("source_ips" , []):
132+ return True
133+
134+ if pair .dst_ip in self ._whitelist .get ("destination_ips" , []):
135+ return True
136+
137+ if pair .dst_port in self ._whitelist .get ("ports" , []):
138+ return True
139+
140+ pair_str = f"{ pair .src_ip } :{ pair .dst_ip } :{ pair .dst_port } "
141+ if pair_str in self ._whitelist .get ("pairs" , []):
142+ return True
143+
144+ return False
145+
146+ def update_whitelist (self , whitelist : dict ):
147+ """Update whitelist configuration (thread-safe)."""
148+ with self ._lock :
149+ self ._whitelist = whitelist or {}
150+ logger .info ("Whitelist updated" )
151+
121152 def start (self ):
122153
123154 if self ._running :
@@ -165,13 +196,23 @@ def run_analysis(self):
165196 logger .info (f"Starting analysis run: { run_id } " )
166197
167198 try :
168- # logger.info(f"self.config.min_connections {self.config.min_connections} and self.config.min_duration {self.config.min_duration}")
169199 # Get analyzable pairs from storage
170200 pairs = self .storage .get_analyzable_pairs (
171201 min_connections = self .config .min_connections ,
172202 min_duration = self .config .min_duration ,
173203 )
174- # logger.info(f"pairs{pairs}")
204+
205+ # Filter out whitelisted pairs
206+ if self ._whitelist :
207+ original_count = len (pairs )
208+ pairs = [p for p in pairs if not self ._is_whitelisted (p )]
209+ run .pairs_skipped = original_count - len (pairs )
210+ if run .pairs_skipped > 0 :
211+ logger .info (
212+ f"Whitelist filtered { run .pairs_skipped } pairs "
213+ f"({ original_count } -> { len (pairs )} )"
214+ )
215+
175216 # Limit pairs for performance
176217 if len (pairs ) > self .config .max_pairs_per_run :
177218 logger .warning (
@@ -208,6 +249,17 @@ def run_analysis(self):
208249 with self ._lock :
209250 self ._known_beacons [result .pair_key ] = result
210251
252+ # Persist beacon to database
253+ if self ._persistence :
254+ try :
255+ self ._persistence .save_beacon (
256+ result .pair_key , result .to_dict ()
257+ )
258+ except Exception as e :
259+ logger .error (
260+ f"Failed to persist beacon { result .pair_key } : { e } "
261+ )
262+
211263 except Exception as e :
212264 logger .error (f"Error generating alert for { result .pair_key } : { e } " )
213265 run .errors += 1
@@ -220,6 +272,13 @@ def run_analysis(self):
220272 ]
221273 for key in stale_keys :
222274 del self ._known_beacons [key ]
275+ if self ._persistence :
276+ try :
277+ self ._persistence .remove_beacon (key )
278+ except Exception as e :
279+ logger .error (
280+ f"Failed to remove beacon { key } from database: { e } "
281+ )
223282
224283 except Exception as e :
225284 logger .error (f"Analysis run error: { e } " , exc_info = True )
@@ -320,6 +379,26 @@ def get_run_history(self, limit: int = 10):
320379 runs = self ._run_history [- limit :]
321380 return [r .to_dict () for r in reversed (runs )]
322381
382+ def load_historical_beacons (self ):
383+ """Load known beacons from persistence on startup."""
384+ if not self ._persistence :
385+ return
386+
387+ try :
388+ beacon_dicts = self ._persistence .load_beacons ()
389+ for pair_key , detection_dict in beacon_dicts .items ():
390+ try :
391+ result = DetectionResult .from_dict (detection_dict )
392+ self ._known_beacons [pair_key ] = result
393+ except Exception as e :
394+ logger .warning (f"Failed to restore beacon { pair_key } : { e } " )
395+
396+ logger .info (
397+ f"Loaded { len (self ._known_beacons )} historical beacons from database"
398+ )
399+ except Exception as e :
400+ logger .error (f"Failed to load historical beacons: { e } " )
401+
323402 @property
324403 def statistics (self ):
325404 """Get analyzer statistics"""
@@ -331,4 +410,10 @@ def statistics(self):
331410 "total_alerts_generated" : self ._total_alerts_generated ,
332411 "current_known_beacons" : len (self ._known_beacons ),
333412 "active_cooldowns" : len (self ._alert_cooldowns ),
413+ "whitelist_rules" : {
414+ "source_ips" : len (self ._whitelist .get ("source_ips" , [])),
415+ "destination_ips" : len (self ._whitelist .get ("destination_ips" , [])),
416+ "ports" : len (self ._whitelist .get ("ports" , [])),
417+ "pairs" : len (self ._whitelist .get ("pairs" , [])),
418+ },
334419 }
0 commit comments