11//! Event correlation engine
22
3+ use crate :: events:: security:: SecurityEvent ;
34use anyhow:: Result ;
5+ use chrono:: Duration ;
6+ use std:: collections:: HashMap ;
7+
8+ #[ derive( Debug , Clone ) ]
9+ pub struct CorrelatedEventGroup {
10+ pub correlation_key : String ,
11+ pub events : Vec < SecurityEvent > ,
12+ }
413
514/// Event correlation engine
615pub struct CorrelationEngine {
7- // TODO: Implement in TASK-017
16+ window : Duration ,
817}
918
1019impl CorrelationEngine {
1120 pub fn new ( ) -> Result < Self > {
12- Ok ( Self { } )
21+ Ok ( Self {
22+ window : Duration :: minutes ( 5 ) ,
23+ } )
24+ }
25+
26+ pub fn correlate ( & self , events : & [ SecurityEvent ] ) -> Vec < CorrelatedEventGroup > {
27+ let mut grouped: HashMap < String , Vec < SecurityEvent > > = HashMap :: new ( ) ;
28+
29+ for event in events {
30+ if let Some ( key) = self . correlation_key ( event) {
31+ grouped. entry ( key) . or_default ( ) . push ( event. clone ( ) ) ;
32+ }
33+ }
34+
35+ grouped
36+ . into_iter ( )
37+ . filter_map ( |( correlation_key, mut grouped_events) | {
38+ grouped_events. sort_by_key ( SecurityEvent :: timestamp) ;
39+ let first = grouped_events. first ( ) ?. timestamp ( ) ;
40+ let last = grouped_events. last ( ) ?. timestamp ( ) ;
41+ if grouped_events. len ( ) >= 2 && ( last - first) <= self . window {
42+ Some ( CorrelatedEventGroup {
43+ correlation_key,
44+ events : grouped_events,
45+ } )
46+ } else {
47+ None
48+ }
49+ } )
50+ . collect ( )
51+ }
52+
53+ fn correlation_key ( & self , event : & SecurityEvent ) -> Option < String > {
54+ match event {
55+ SecurityEvent :: Syscall ( event) => Some ( format ! ( "pid:{}" , event. pid) ) ,
56+ SecurityEvent :: Container ( event) => Some ( format ! ( "container:{}" , event. container_id) ) ,
57+ SecurityEvent :: Network ( event) => event
58+ . container_id
59+ . as_ref ( )
60+ . map ( |container_id| format ! ( "container:{container_id}" ) ) ,
61+ SecurityEvent :: Alert ( event) => event
62+ . source_event_id
63+ . as_ref ( )
64+ . map ( |source_event_id| format ! ( "source:{source_event_id}" ) ) ,
65+ }
1366 }
1467}
1568
@@ -18,3 +71,63 @@ impl Default for CorrelationEngine {
1871 Self :: new ( ) . unwrap ( )
1972 }
2073}
74+
75+ #[ cfg( test) ]
76+ mod tests {
77+ use super :: * ;
78+ use crate :: events:: security:: { ContainerEvent , ContainerEventType , SecurityEvent } ;
79+ use crate :: events:: syscall:: { SyscallEvent , SyscallType } ;
80+ use chrono:: { Duration , Utc } ;
81+
82+ #[ test]
83+ fn test_correlates_syscall_events_by_pid_within_window ( ) {
84+ let engine = CorrelationEngine :: new ( ) . unwrap ( ) ;
85+ let now = Utc :: now ( ) ;
86+ let events = vec ! [
87+ SecurityEvent :: Syscall ( SyscallEvent :: new( 4242 , 1000 , SyscallType :: Execve , now) ) ,
88+ SecurityEvent :: Syscall ( SyscallEvent :: new(
89+ 4242 ,
90+ 1000 ,
91+ SyscallType :: Open ,
92+ now + Duration :: seconds( 10 ) ,
93+ ) ) ,
94+ SecurityEvent :: Syscall ( SyscallEvent :: new( 7 , 1000 , SyscallType :: Execve , now) ) ,
95+ ] ;
96+
97+ let groups = engine. correlate ( & events) ;
98+ assert_eq ! ( groups. len( ) , 1 ) ;
99+ assert_eq ! ( groups[ 0 ] . correlation_key, "pid:4242" ) ;
100+ assert_eq ! ( groups[ 0 ] . events. len( ) , 2 ) ;
101+ }
102+
103+ #[ test]
104+ fn test_correlates_container_events_by_container_id ( ) {
105+ let engine = CorrelationEngine :: new ( ) . unwrap ( ) ;
106+ let now = Utc :: now ( ) ;
107+ let events = vec ! [
108+ SecurityEvent :: Container ( ContainerEvent {
109+ container_id: "container-1" . into( ) ,
110+ event_type: ContainerEventType :: Start ,
111+ timestamp: now,
112+ details: None ,
113+ } ) ,
114+ SecurityEvent :: Container ( ContainerEvent {
115+ container_id: "container-1" . into( ) ,
116+ event_type: ContainerEventType :: Stop ,
117+ timestamp: now + Duration :: seconds( 30 ) ,
118+ details: Some ( "manual stop" . into( ) ) ,
119+ } ) ,
120+ SecurityEvent :: Container ( ContainerEvent {
121+ container_id: "container-2" . into( ) ,
122+ event_type: ContainerEventType :: Start ,
123+ timestamp: now,
124+ details: None ,
125+ } ) ,
126+ ] ;
127+
128+ let groups = engine. correlate ( & events) ;
129+ assert_eq ! ( groups. len( ) , 1 ) ;
130+ assert_eq ! ( groups[ 0 ] . correlation_key, "container:container-1" ) ;
131+ assert_eq ! ( groups[ 0 ] . events. len( ) , 2 ) ;
132+ }
133+ }
0 commit comments