Skip to main content

kindly_guard_server/
standard_impl.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Standard implementations of security component traits
15//! These provide baseline functionality without enhanced optimizations
16
17use crate::scanner::Threat;
18use crate::storage::StorageProvider;
19use crate::traits::{
20    CorrelationEngine, CorrelationRules, CorrelationStats, EnhancedScanner, EventHandle,
21    ProcessorStats, RateLimitDecision, RateLimitKey, RateLimiter, RateLimiterStats, ScannerMetrics,
22    SecurityComponentFactory, SecurityEvent, SecurityEventProcessor, SecurityInsights,
23    SecurityScannerTrait, ThreatPattern,
24};
25use anyhow::Result;
26use async_trait::async_trait;
27use parking_lot::RwLock;
28use std::collections::HashMap;
29use std::sync::atomic::{AtomicU64, Ordering};
30use std::sync::Arc;
31use std::time::{Duration, Instant};
32
33/// Standard event processor implementation
34pub struct StandardEventProcessor {
35    events_processed: AtomicU64,
36    start_time: Instant,
37    storage: Arc<dyn StorageProvider>,
38    monitored_endpoints: RwLock<HashMap<String, Instant>>,
39}
40
41impl StandardEventProcessor {
42    pub fn new(storage: Arc<dyn StorageProvider>) -> Self {
43        Self {
44            events_processed: AtomicU64::new(0),
45            start_time: Instant::now(),
46            storage,
47            monitored_endpoints: RwLock::new(HashMap::new()),
48        }
49    }
50}
51
52#[async_trait]
53impl SecurityEventProcessor for StandardEventProcessor {
54    async fn process_event(&self, event: SecurityEvent) -> Result<EventHandle> {
55        let event_id = self.events_processed.fetch_add(1, Ordering::SeqCst);
56
57        // Store event persistently
58        self.storage.store_event(&event).await?;
59
60        // Simple monitoring based on event type
61        if event.event_type.contains("failure") || event.event_type.contains("threat") {
62            let mut monitored = self.monitored_endpoints.write();
63            monitored.insert(event.client_id.clone(), Instant::now());
64        }
65
66        Ok(EventHandle {
67            event_id,
68            processed: true,
69        })
70    }
71
72    fn get_stats(&self) -> ProcessorStats {
73        let events_processed = self.events_processed.load(Ordering::Relaxed);
74        let elapsed = self.start_time.elapsed().as_secs_f64();
75
76        ProcessorStats {
77            events_processed,
78            events_per_second: events_processed as f64 / elapsed.max(1.0),
79            buffer_utilization: 0.0, // Storage doesn't expose buffer utilization yet
80            correlation_hits: 0,
81        }
82    }
83
84    fn is_monitored(&self, endpoint: &str) -> bool {
85        let monitored = self.monitored_endpoints.read();
86        monitored
87            .get(endpoint)
88            .is_some_and(|&time| time.elapsed() < Duration::from_secs(300))
89    }
90
91    async fn get_insights(&self, client_id: &str) -> Result<SecurityInsights> {
92        use crate::storage::EventFilter;
93        use chrono::{Duration, Utc};
94
95        // Query recent events for this client
96        let filter = EventFilter {
97            client_id: Some(client_id.to_string()),
98            from_time: Some(Utc::now() - Duration::hours(1)),
99            limit: Some(100),
100            ..Default::default()
101        };
102
103        let events = self.storage.query_events(filter).await?;
104
105        let threat_count = events
106            .iter()
107            .filter(|e| e.event_type.contains("threat"))
108            .count();
109
110        let risk_score = (threat_count as f32 / 10.0).min(1.0);
111
112        Ok(SecurityInsights {
113            risk_score,
114            detected_patterns: vec![],
115            recommendations: if risk_score > 0.5 {
116                vec!["Consider additional authentication".to_string()]
117            } else {
118                vec![]
119            },
120        })
121    }
122
123    async fn cleanup(&self) -> Result<()> {
124        // Clean up old monitored endpoints
125        let mut monitored = self.monitored_endpoints.write();
126        monitored.retain(|_, &mut time| time.elapsed() < Duration::from_secs(3600));
127        Ok(())
128    }
129}
130
131/// Standard scanner implementation
132pub struct StandardScanner {
133    scans_performed: AtomicU64,
134    threats_detected: AtomicU64,
135}
136
137impl Default for StandardScanner {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl StandardScanner {
144    pub const fn new() -> Self {
145        Self {
146            scans_performed: AtomicU64::new(0),
147            threats_detected: AtomicU64::new(0),
148        }
149    }
150}
151
152impl EnhancedScanner for StandardScanner {
153    fn enhanced_scan(&self, data: &[u8]) -> Result<Vec<Threat>> {
154        self.scans_performed.fetch_add(1, Ordering::Relaxed);
155
156        // Basic pattern matching
157        let data_str = String::from_utf8_lossy(data);
158        let mut threats = Vec::new();
159
160        // Check for obvious injection patterns
161        if data_str.contains("'; DROP TABLE")
162            || data_str.contains("1=1")
163            || data_str.contains("'1'='1'")
164            || data_str.contains("' OR '")
165        {
166            threats.push(Threat {
167                threat_type: crate::scanner::ThreatType::SqlInjection,
168                severity: crate::scanner::Severity::High,
169                location: crate::scanner::Location::Text {
170                    offset: 0,
171                    length: data.len(),
172                },
173                description: "SQL injection pattern detected".to_string(),
174                remediation: Some("Sanitize input".to_string()),
175            });
176        }
177
178        if !threats.is_empty() {
179            self.threats_detected
180                .fetch_add(threats.len() as u64, Ordering::Relaxed);
181        }
182
183        Ok(threats)
184    }
185
186    fn get_metrics(&self) -> ScannerMetrics {
187        ScannerMetrics {
188            scans_performed: self.scans_performed.load(Ordering::Relaxed),
189            threats_detected: self.threats_detected.load(Ordering::Relaxed),
190            avg_scan_time_us: 100, // Placeholder
191            pattern_cache_hits: 0,
192        }
193    }
194
195    fn preload_patterns(&self, _patterns: &[String]) -> Result<()> {
196        // No-op for standard implementation
197        Ok(())
198    }
199}
200
201/// Standard correlation engine
202pub struct StandardCorrelationEngine {
203    patterns_detected: AtomicU64,
204    rules: RwLock<CorrelationRules>,
205}
206
207impl Default for StandardCorrelationEngine {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213impl StandardCorrelationEngine {
214    pub const fn new() -> Self {
215        Self {
216            patterns_detected: AtomicU64::new(0),
217            rules: RwLock::new(CorrelationRules {
218                time_window: Duration::from_secs(300),
219                min_events: 5,
220                patterns: vec![],
221            }),
222        }
223    }
224}
225
226#[async_trait]
227impl CorrelationEngine for StandardCorrelationEngine {
228    async fn correlate(&self, events: &[SecurityEvent]) -> Result<Vec<ThreatPattern>> {
229        let rules = self.rules.read();
230        let mut patterns = Vec::new();
231
232        // Simple correlation: look for repeated failures
233        let mut failure_counts: HashMap<String, usize> = HashMap::new();
234
235        for event in events {
236            if event.event_type.contains("failure") {
237                *failure_counts.entry(event.client_id.clone()).or_insert(0) += 1;
238            }
239        }
240
241        for (client_id, count) in failure_counts {
242            if count >= rules.min_events {
243                self.patterns_detected.fetch_add(1, Ordering::Relaxed);
244                patterns.push(ThreatPattern {
245                    pattern_type: "repeated_failures".to_string(),
246                    confidence: 0.8,
247                    events: vec![],
248                    description: format!("{count} failures from {client_id}"),
249                });
250            }
251        }
252
253        Ok(patterns)
254    }
255
256    async fn update_rules(&self, rules: CorrelationRules) -> Result<()> {
257        *self.rules.write() = rules;
258        Ok(())
259    }
260
261    fn get_correlation_stats(&self) -> CorrelationStats {
262        CorrelationStats {
263            patterns_detected: self.patterns_detected.load(Ordering::Relaxed),
264            false_positives: 0,
265            avg_correlation_time_ms: 10,
266        }
267    }
268}
269
270/// Standard rate limiter using token bucket
271pub struct StandardRateLimiter {
272    #[allow(dead_code)] // Storage integration for persistence planned
273    storage: Arc<dyn StorageProvider>,
274    buckets: RwLock<HashMap<RateLimitKey, TokenBucket>>,
275    requests_allowed: AtomicU64,
276    requests_denied: AtomicU64,
277    default_rpm: u32,
278    burst_capacity: u32,
279}
280
281struct TokenBucket {
282    tokens: f64,
283    last_refill: Instant,
284    rpm: u32,
285}
286
287impl StandardRateLimiter {
288    pub fn new(storage: Arc<dyn StorageProvider>, default_rpm: u32, burst_capacity: u32) -> Self {
289        Self {
290            storage,
291            buckets: RwLock::new(HashMap::new()),
292            requests_allowed: AtomicU64::new(0),
293            requests_denied: AtomicU64::new(0),
294            default_rpm,
295            burst_capacity,
296        }
297    }
298}
299
300#[async_trait]
301impl RateLimiter for StandardRateLimiter {
302    async fn check_rate_limit(&self, key: &RateLimitKey) -> Result<RateLimitDecision> {
303        let mut buckets = self.buckets.write();
304        let bucket = buckets.entry(key.clone()).or_insert_with(|| TokenBucket {
305            tokens: f64::from(self.burst_capacity),
306            last_refill: Instant::now(),
307            rpm: self.default_rpm,
308        });
309
310        // Refill tokens
311        let elapsed = bucket.last_refill.elapsed();
312        let tokens_to_add = elapsed.as_secs_f64() * (f64::from(bucket.rpm) / 60.0);
313        bucket.tokens = (bucket.tokens + tokens_to_add).min(f64::from(self.burst_capacity));
314        bucket.last_refill = Instant::now();
315
316        // Check if request allowed
317        let allowed = bucket.tokens >= 1.0;
318        if allowed {
319            bucket.tokens -= 1.0;
320            self.requests_allowed.fetch_add(1, Ordering::Relaxed);
321        } else {
322            self.requests_denied.fetch_add(1, Ordering::Relaxed);
323        }
324
325        Ok(RateLimitDecision {
326            allowed,
327            tokens_remaining: bucket.tokens,
328            reset_after: Duration::from_secs(60),
329        })
330    }
331
332    async fn record_request(&self, _key: &RateLimitKey) -> Result<()> {
333        // Already recorded in check_rate_limit
334        Ok(())
335    }
336
337    async fn apply_penalty(&self, client_id: &str, factor: f32) -> Result<()> {
338        let mut buckets = self.buckets.write();
339        for (key, bucket) in buckets.iter_mut() {
340            if key.client_id == client_id {
341                bucket.tokens = (bucket.tokens / f64::from(factor)).max(0.0);
342            }
343        }
344        Ok(())
345    }
346
347    fn get_stats(&self) -> RateLimiterStats {
348        RateLimiterStats {
349            requests_allowed: self.requests_allowed.load(Ordering::Relaxed),
350            requests_denied: self.requests_denied.load(Ordering::Relaxed),
351            active_buckets: self.buckets.read().len(),
352        }
353    }
354}
355
356/// Standard component factory
357pub struct StandardFactory;
358
359impl SecurityComponentFactory for StandardFactory {
360    fn create_event_processor(
361        &self,
362        _config: &crate::config::Config,
363        storage: Arc<dyn crate::storage::StorageProvider>,
364    ) -> Result<Arc<dyn SecurityEventProcessor>> {
365        Ok(Arc::new(StandardEventProcessor::new(storage)))
366    }
367
368    fn create_scanner(&self, _config: &crate::config::Config) -> Result<Arc<dyn EnhancedScanner>> {
369        Ok(Arc::new(StandardScanner::new()))
370    }
371
372    fn create_correlation_engine(
373        &self,
374        _config: &crate::config::Config,
375        _storage: Arc<dyn crate::storage::StorageProvider>,
376    ) -> Result<Arc<dyn CorrelationEngine>> {
377        Ok(Arc::new(StandardCorrelationEngine::new()))
378    }
379
380    fn create_rate_limiter(
381        &self,
382        config: &crate::config::Config,
383        storage: Arc<dyn crate::storage::StorageProvider>,
384    ) -> Result<Arc<dyn RateLimiter>> {
385        Ok(Arc::new(StandardRateLimiter::new(
386            storage,
387            config.rate_limit.default_rpm,
388            config.rate_limit.burst_capacity,
389        )))
390    }
391
392    fn create_security_scanner(
393        &self,
394        _config: &crate::config::Config,
395    ) -> Result<Arc<dyn SecurityScannerTrait>> {
396        // For now, return a simple wrapper around the existing scanner
397        // TODO: Properly refactor SecurityScanner to implement trait
398        Err(anyhow::anyhow!(
399            "SecurityScanner trait implementation pending"
400        ))
401    }
402}