1use crate::{Event, EventualiError, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::{HashMap, HashSet};
4use chrono::{DateTime, Utc};
5
6pub struct VulnerabilityScanner {
8 scan_rules: Vec<ScanRule>,
9 severity_thresholds: HashMap<VulnerabilitySeverity, u32>,
10 whitelist: HashSet<String>,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ScanRule {
16 pub id: String,
17 pub name: String,
18 pub description: String,
19 pub category: VulnerabilityCategory,
20 pub severity: VulnerabilitySeverity,
21 pub pattern: ScanPattern,
22 pub enabled: bool,
23 pub created_at: DateTime<Utc>,
24 pub updated_at: DateTime<Utc>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq)]
29pub enum VulnerabilityCategory {
30 DataLeakage,
31 InjectionAttack,
32 AccessControl,
33 Cryptographic,
34 Authentication,
35 Authorization,
36 InputValidation,
37 OutputEncoding,
38 SessionManagement,
39 ErrorHandling,
40 Logging,
41 Configuration,
42 BusinessLogic,
43 ApiSecurity,
44 NetworkSecurity,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
49pub enum VulnerabilitySeverity {
50 Critical,
51 High,
52 Medium,
53 Low,
54 Info,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub enum ScanPattern {
60 RegexPattern(String),
61 JsonPathPattern(String),
62 KeywordPattern(Vec<String>),
63 StructuralPattern(StructuralCheck),
64 CustomRule(String), }
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct StructuralCheck {
70 pub check_type: StructuralCheckType,
71 pub parameters: HashMap<String, String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub enum StructuralCheckType {
77 EncryptionPresence,
78 AccessControlValidation,
79 DataSanitization,
80 AuthenticationRequired,
81 AuthorizationRequired,
82 AuditTrailPresence,
83 RateLimitCompliance,
84 InputSizeValidation,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct VulnerabilityScanResult {
90 pub scan_id: String,
91 pub scan_timestamp: DateTime<Utc>,
92 pub events_scanned: usize,
93 pub vulnerabilities_found: Vec<VulnerabilityFinding>,
94 pub scan_duration_ms: u64,
95 pub severity_counts: HashMap<VulnerabilitySeverity, usize>,
96 pub category_counts: HashMap<VulnerabilityCategory, usize>,
97 pub compliance_score: f64, }
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct VulnerabilityFinding {
103 pub id: String,
104 pub rule_id: String,
105 pub event_id: String,
106 pub aggregate_id: String,
107 pub category: VulnerabilityCategory,
108 pub severity: VulnerabilitySeverity,
109 pub title: String,
110 pub description: String,
111 pub evidence: String,
112 pub remediation: String,
113 pub cve_references: Vec<String>,
114 pub owasp_references: Vec<String>,
115 pub found_at: DateTime<Utc>,
116 pub status: VulnerabilityStatus,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub enum VulnerabilityStatus {
122 Open,
123 InProgress,
124 Resolved,
125 FalsePositive,
126 Accepted, Suppressed, }
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct SecurityBaseline {
133 pub name: String,
134 pub version: String,
135 pub description: String,
136 pub requirements: Vec<SecurityRequirement>,
137 pub compliance_frameworks: Vec<ComplianceFramework>,
138 pub created_at: DateTime<Utc>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SecurityRequirement {
144 pub id: String,
145 pub title: String,
146 pub description: String,
147 pub category: VulnerabilityCategory,
148 pub required_controls: Vec<String>,
149 pub validation_rules: Vec<String>,
150 pub compliance_mappings: HashMap<String, String>, }
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub enum ComplianceFramework {
156 OWASP,
157 NIST,
158 ISO27001,
159 SOC2,
160 GDPR,
161 HIPAA,
162 PCI,
163 Custom(String),
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct PenetrationTest {
169 pub test_id: String,
170 pub test_name: String,
171 pub target_scope: Vec<String>, pub attack_scenarios: Vec<AttackScenario>,
173 pub started_at: DateTime<Utc>,
174 pub completed_at: Option<DateTime<Utc>>,
175 pub status: TestStatus,
176 pub findings: Vec<PenetrationFinding>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct AttackScenario {
182 pub scenario_id: String,
183 pub name: String,
184 pub description: String,
185 pub attack_type: AttackType,
186 pub steps: Vec<AttackStep>,
187 pub expected_outcome: String,
188 pub actual_outcome: Option<String>,
189 pub success: Option<bool>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub enum AttackType {
195 SqlInjection,
196 NoSqlInjection,
197 CommandInjection,
198 CrossSiteScripting,
199 CrossSiteRequestForgery,
200 AuthenticationBypass,
201 AuthorizationBypass,
202 PrivilegeEscalation,
203 DataExfiltration,
204 DenialOfService,
205 BusinessLogicFlaws,
206 CryptographicAttacks,
207 SessionManipulation,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct AttackStep {
213 pub step_number: u32,
214 pub action: String,
215 pub payload: Option<String>,
216 pub expected_response: String,
217 pub actual_response: Option<String>,
218 pub success: Option<bool>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct PenetrationFinding {
224 pub finding_id: String,
225 pub scenario_id: String,
226 pub severity: VulnerabilitySeverity,
227 pub title: String,
228 pub description: String,
229 pub attack_vector: String,
230 pub impact: String,
231 pub evidence: String,
232 pub remediation: String,
233 pub exploitability: ExploitabilityLevel,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub enum ExploitabilityLevel {
239 Trivial,
240 Easy,
241 Moderate,
242 Difficult,
243 VeryDifficult,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub enum TestStatus {
249 Pending,
250 Running,
251 Completed,
252 Failed,
253 Cancelled,
254}
255
256impl VulnerabilityScanner {
257 pub fn new() -> Self {
259 let mut scanner = Self {
260 scan_rules: Vec::new(),
261 severity_thresholds: HashMap::new(),
262 whitelist: HashSet::new(),
263 };
264
265 scanner.severity_thresholds.insert(VulnerabilitySeverity::Critical, 0);
267 scanner.severity_thresholds.insert(VulnerabilitySeverity::High, 5);
268 scanner.severity_thresholds.insert(VulnerabilitySeverity::Medium, 20);
269 scanner.severity_thresholds.insert(VulnerabilitySeverity::Low, 50);
270 scanner.severity_thresholds.insert(VulnerabilitySeverity::Info, 100);
271
272 scanner.load_default_rules();
274
275 scanner
276 }
277
278 fn load_default_rules(&mut self) {
280 self.add_rule(ScanRule {
282 id: "sql-injection-001".to_string(),
283 name: "SQL Injection Pattern Detection".to_string(),
284 description: "Detects potential SQL injection patterns in event data".to_string(),
285 category: VulnerabilityCategory::InjectionAttack,
286 severity: VulnerabilitySeverity::High,
287 pattern: ScanPattern::RegexPattern(
288 r"(?i)(union\s+select|select\s+.*\s+from|insert\s+into|update\s+.*\s+set|delete\s+from|drop\s+table|\'\s*or\s*\'\w*\'\s*=\s*\'\w*|admin\'\s*--|\'\s*;\s*drop)".to_string()
289 ),
290 enabled: true,
291 created_at: Utc::now(),
292 updated_at: Utc::now(),
293 }).unwrap();
294
295 self.add_rule(ScanRule {
297 id: "pii-exposure-001".to_string(),
298 name: "PII Data Exposure".to_string(),
299 description: "Detects potential exposure of personally identifiable information".to_string(),
300 category: VulnerabilityCategory::DataLeakage,
301 severity: VulnerabilitySeverity::Critical,
302 pattern: ScanPattern::KeywordPattern(vec![
303 "ssn".to_string(),
304 "social_security".to_string(),
305 "credit_card".to_string(),
306 "passport".to_string(),
307 "driver_license".to_string(),
308 ]),
309 enabled: true,
310 created_at: Utc::now(),
311 updated_at: Utc::now(),
312 }).unwrap();
313
314 self.add_rule(ScanRule {
316 id: "auth-weakness-001".to_string(),
317 name: "Weak Authentication Patterns".to_string(),
318 description: "Detects weak authentication patterns".to_string(),
319 category: VulnerabilityCategory::Authentication,
320 severity: VulnerabilitySeverity::Medium,
321 pattern: ScanPattern::KeywordPattern(vec![
322 "password=123456".to_string(),
323 "password=admin".to_string(),
324 "password=password".to_string(),
325 "admin:admin".to_string(),
326 "guest:guest".to_string(),
327 ]),
328 enabled: true,
329 created_at: Utc::now(),
330 updated_at: Utc::now(),
331 }).unwrap();
332
333 self.add_rule(ScanRule {
335 id: "xss-001".to_string(),
336 name: "Cross-Site Scripting (XSS)".to_string(),
337 description: "Detects potential XSS payloads in event data".to_string(),
338 category: VulnerabilityCategory::InputValidation,
339 severity: VulnerabilitySeverity::High,
340 pattern: ScanPattern::RegexPattern(
341 r"(?i)(<script|javascript:|onclick|onload|onerror|onmouseover|alert\(|confirm\(|prompt\()".to_string()
342 ),
343 enabled: true,
344 created_at: Utc::now(),
345 updated_at: Utc::now(),
346 }).unwrap();
347 }
348
349 pub fn add_rule(&mut self, rule: ScanRule) -> Result<()> {
351 if rule.id.is_empty() {
352 return Err(EventualiError::Configuration(
353 "Rule ID cannot be empty".to_string()
354 ));
355 }
356
357 if self.scan_rules.iter().any(|r| r.id == rule.id) {
359 return Err(EventualiError::Configuration(
360 format!("Rule ID already exists: {}", rule.id)
361 ));
362 }
363
364 self.scan_rules.push(rule);
365 Ok(())
366 }
367
368 pub async fn scan_events(&self, events: Vec<Event>) -> Result<VulnerabilityScanResult> {
370 let scan_start = std::time::Instant::now();
371 let scan_id = uuid::Uuid::new_v4().to_string();
372
373 let mut vulnerabilities = Vec::new();
374 let mut severity_counts: HashMap<VulnerabilitySeverity, usize> = HashMap::new();
375 let mut category_counts: HashMap<VulnerabilityCategory, usize> = HashMap::new();
376
377 for event in &events {
378 if self.whitelist.contains(&event.aggregate_id) {
380 continue;
381 }
382
383 for rule in &self.scan_rules {
384 if !rule.enabled {
385 continue;
386 }
387
388 if let Some(finding) = self.apply_scan_rule(event, rule).await? {
389 *severity_counts.entry(finding.severity.clone()).or_insert(0) += 1;
390 *category_counts.entry(finding.category.clone()).or_insert(0) += 1;
391 vulnerabilities.push(finding);
392 }
393 }
394 }
395
396 let scan_duration = scan_start.elapsed().as_millis() as u64;
397 let compliance_score = self.calculate_compliance_score(&severity_counts, events.len());
398
399 Ok(VulnerabilityScanResult {
400 scan_id,
401 scan_timestamp: Utc::now(),
402 events_scanned: events.len(),
403 vulnerabilities_found: vulnerabilities,
404 scan_duration_ms: scan_duration,
405 severity_counts,
406 category_counts,
407 compliance_score,
408 })
409 }
410
411 async fn apply_scan_rule(&self, event: &Event, rule: &ScanRule) -> Result<Option<VulnerabilityFinding>> {
413 let event_data_str = match &event.data {
414 crate::EventData::Json(data) => data.to_string(),
415 crate::EventData::Protobuf(data) => String::from_utf8_lossy(data).to_string(),
416 };
417
418 let matches = match &rule.pattern {
419 ScanPattern::RegexPattern(pattern) => {
420 regex::Regex::new(pattern)
421 .map_err(|e| EventualiError::Configuration(format!("Invalid regex: {e}")))?
422 .is_match(&event_data_str)
423 },
424 ScanPattern::JsonPathPattern(_path) => {
425 false
427 },
428 ScanPattern::KeywordPattern(keywords) => {
429 let lower_data = event_data_str.to_lowercase();
430 keywords.iter().any(|keyword| lower_data.contains(&keyword.to_lowercase()))
431 },
432 ScanPattern::StructuralPattern(check) => {
433 self.apply_structural_check(event, check).await?
434 },
435 ScanPattern::CustomRule(_rule) => {
436 false
438 },
439 };
440
441 if matches {
442 let finding = VulnerabilityFinding {
443 id: uuid::Uuid::new_v4().to_string(),
444 rule_id: rule.id.clone(),
445 event_id: event.id.to_string(),
446 aggregate_id: event.aggregate_id.clone(),
447 category: rule.category.clone(),
448 severity: rule.severity.clone(),
449 title: rule.name.clone(),
450 description: rule.description.clone(),
451 evidence: self.extract_evidence(&event_data_str, &rule.pattern),
452 remediation: self.get_remediation_advice(&rule.category),
453 cve_references: Vec::new(),
454 owasp_references: self.get_owasp_references(&rule.category),
455 found_at: Utc::now(),
456 status: VulnerabilityStatus::Open,
457 };
458
459 Ok(Some(finding))
460 } else {
461 Ok(None)
462 }
463 }
464
465 async fn apply_structural_check(&self, event: &Event, check: &StructuralCheck) -> Result<bool> {
467 match check.check_type {
468 StructuralCheckType::EncryptionPresence => {
469 let data_str = match &event.data {
471 crate::EventData::Json(data) => data.to_string(),
472 crate::EventData::Protobuf(_) => return Ok(true), };
474
475 Ok(!data_str.chars().any(|c| c.is_ascii_alphabetic()) ||
477 data_str.contains("encrypted") ||
478 data_str.contains("cipher"))
479 },
480 StructuralCheckType::AccessControlValidation => {
481 Ok(event.metadata.user_id.is_some() ||
483 event.metadata.headers.contains_key("permissions"))
484 },
485 StructuralCheckType::AuditTrailPresence => {
486 Ok(event.metadata.headers.contains_key("audit_trail") ||
488 event.metadata.correlation_id.is_some())
489 },
490 _ => Ok(false), }
492 }
493
494 fn extract_evidence(&self, data: &str, pattern: &ScanPattern) -> String {
496 match pattern {
497 ScanPattern::RegexPattern(regex) => {
498 if let Ok(re) = regex::Regex::new(regex) {
499 if let Some(matched) = re.find(data) {
500 return format!("Matched pattern: {}", matched.as_str());
501 }
502 }
503 "Pattern match found".to_string()
504 },
505 ScanPattern::KeywordPattern(keywords) => {
506 let lower_data = data.to_lowercase();
507 for keyword in keywords {
508 if lower_data.contains(&keyword.to_lowercase()) {
509 return format!("Found keyword: {keyword}");
510 }
511 }
512 "Keyword match found".to_string()
513 },
514 _ => "Vulnerability detected".to_string(),
515 }
516 }
517
518 fn get_remediation_advice(&self, category: &VulnerabilityCategory) -> String {
520 match category {
521 VulnerabilityCategory::DataLeakage => {
522 "Remove or encrypt sensitive data. Implement data classification and handling policies."
523 },
524 VulnerabilityCategory::InjectionAttack => {
525 "Use parameterized queries and input validation. Sanitize all user input."
526 },
527 VulnerabilityCategory::Authentication => {
528 "Implement strong authentication mechanisms. Use multi-factor authentication."
529 },
530 VulnerabilityCategory::InputValidation => {
531 "Implement comprehensive input validation and output encoding."
532 },
533 VulnerabilityCategory::AccessControl => {
534 "Implement proper access control mechanisms and least privilege principle."
535 },
536 _ => "Review security controls and implement appropriate countermeasures."
537 }.to_string()
538 }
539
540 fn get_owasp_references(&self, category: &VulnerabilityCategory) -> Vec<String> {
542 match category {
543 VulnerabilityCategory::InjectionAttack => vec!["OWASP-A03".to_string()],
544 VulnerabilityCategory::Authentication => vec!["OWASP-A07".to_string()],
545 VulnerabilityCategory::AccessControl => vec!["OWASP-A01".to_string()],
546 VulnerabilityCategory::InputValidation => vec!["OWASP-A03".to_string()],
547 VulnerabilityCategory::DataLeakage => vec!["OWASP-A02".to_string()],
548 _ => Vec::new(),
549 }
550 }
551
552 fn calculate_compliance_score(&self, severity_counts: &HashMap<VulnerabilitySeverity, usize>, total_events: usize) -> f64 {
554 if total_events == 0 {
555 return 100.0;
556 }
557
558 let mut penalty = 0.0;
559
560 for (severity, count) in severity_counts {
561 let weight = match severity {
562 VulnerabilitySeverity::Critical => 10.0,
563 VulnerabilitySeverity::High => 5.0,
564 VulnerabilitySeverity::Medium => 2.0,
565 VulnerabilitySeverity::Low => 1.0,
566 VulnerabilitySeverity::Info => 0.1,
567 };
568
569 penalty += (*count as f64) * weight;
570 }
571
572 let score = 100.0 - (penalty / total_events as f64 * 10.0);
574 score.clamp(0.0, 100.0)
575 }
576
577 pub fn add_to_whitelist(&mut self, aggregate_id: String) {
579 self.whitelist.insert(aggregate_id);
580 }
581
582 pub fn remove_from_whitelist(&mut self, aggregate_id: &str) -> bool {
584 self.whitelist.remove(aggregate_id)
585 }
586
587 pub fn get_scan_statistics(&self) -> HashMap<String, usize> {
589 let mut stats = HashMap::new();
590 stats.insert("total_rules".to_string(), self.scan_rules.len());
591 stats.insert("enabled_rules".to_string(),
592 self.scan_rules.iter().filter(|r| r.enabled).count());
593 stats.insert("whitelisted_aggregates".to_string(), self.whitelist.len());
594
595 for rule in &self.scan_rules {
597 let category_key = format!("rules_{:?}", rule.category).to_lowercase();
598 *stats.entry(category_key).or_insert(0) += 1;
599 }
600
601 stats
602 }
603}
604
605impl Default for VulnerabilityScanner {
606 fn default() -> Self {
607 Self::new()
608 }
609}
610
611pub struct PenetrationTestFramework {
613 active_tests: HashMap<String, PenetrationTest>,
614 test_scenarios: Vec<AttackScenario>,
615}
616
617impl PenetrationTestFramework {
618 pub fn new() -> Self {
620 let mut framework = Self {
621 active_tests: HashMap::new(),
622 test_scenarios: Vec::new(),
623 };
624
625 framework.load_default_scenarios();
626 framework
627 }
628
629 fn load_default_scenarios(&mut self) {
631 self.test_scenarios.push(AttackScenario {
633 scenario_id: "sql-injection-001".to_string(),
634 name: "Basic SQL Injection".to_string(),
635 description: "Test for basic SQL injection vulnerabilities".to_string(),
636 attack_type: AttackType::SqlInjection,
637 steps: vec![
638 AttackStep {
639 step_number: 1,
640 action: "Submit SQL injection payload".to_string(),
641 payload: Some("' OR '1'='1".to_string()),
642 expected_response: "Error or unexpected data access".to_string(),
643 actual_response: None,
644 success: None,
645 },
646 ],
647 expected_outcome: "System should reject malicious input".to_string(),
648 actual_outcome: None,
649 success: None,
650 });
651
652 self.test_scenarios.push(AttackScenario {
654 scenario_id: "auth-bypass-001".to_string(),
655 name: "Authentication Bypass".to_string(),
656 description: "Test for authentication bypass vulnerabilities".to_string(),
657 attack_type: AttackType::AuthenticationBypass,
658 steps: vec![
659 AttackStep {
660 step_number: 1,
661 action: "Attempt to access protected resources without authentication".to_string(),
662 payload: None,
663 expected_response: "Access denied".to_string(),
664 actual_response: None,
665 success: None,
666 },
667 ],
668 expected_outcome: "System should deny access".to_string(),
669 actual_outcome: None,
670 success: None,
671 });
672 }
673
674 pub fn start_test(&mut self, test_name: String, target_scope: Vec<String>) -> Result<String> {
676 let test_id = uuid::Uuid::new_v4().to_string();
677
678 let test = PenetrationTest {
679 test_id: test_id.clone(),
680 test_name,
681 target_scope,
682 attack_scenarios: self.test_scenarios.clone(),
683 started_at: Utc::now(),
684 completed_at: None,
685 status: TestStatus::Running,
686 findings: Vec::new(),
687 };
688
689 self.active_tests.insert(test_id.clone(), test);
690 Ok(test_id)
691 }
692
693 pub async fn execute_test(&mut self, test_id: &str, events: Vec<Event>) -> Result<()> {
695 let scenarios = if let Some(test) = self.active_tests.get(test_id) {
697 test.attack_scenarios.clone()
698 } else {
699 return Err(EventualiError::Configuration(format!("Test not found: {test_id}")));
700 };
701
702 if let Some(test) = self.active_tests.get_mut(test_id) {
704 test.status = TestStatus::Running;
705 }
706
707 let mut findings = Vec::new();
709 for (i, scenario) in scenarios.iter().enumerate() {
710 let success = self.execute_scenario(scenario, &events).await?;
712
713 if let Some(test) = self.active_tests.get_mut(test_id) {
715 if i < test.attack_scenarios.len() {
716 test.attack_scenarios[i].success = Some(success);
717 }
718 }
719
720 if success {
721 let finding = PenetrationFinding {
723 finding_id: uuid::Uuid::new_v4().to_string(),
724 scenario_id: scenario.scenario_id.clone(),
725 severity: match scenario.attack_type {
726 AttackType::SqlInjection => VulnerabilitySeverity::High,
727 AttackType::AuthenticationBypass => VulnerabilitySeverity::Critical,
728 AttackType::PrivilegeEscalation => VulnerabilitySeverity::Critical,
729 _ => VulnerabilitySeverity::Medium,
730 },
731 title: format!("Successful {}", scenario.name),
732 description: scenario.description.clone(),
733 attack_vector: format!("{:?}", scenario.attack_type),
734 impact: "System security compromised".to_string(),
735 evidence: scenario.actual_outcome.clone().unwrap_or_default(),
736 remediation: "Implement appropriate security controls".to_string(),
737 exploitability: ExploitabilityLevel::Easy,
738 };
739
740 findings.push(finding);
741 }
742 }
743
744 if let Some(test) = self.active_tests.get_mut(test_id) {
746 test.findings.extend(findings);
747 test.status = TestStatus::Completed;
748 test.completed_at = Some(Utc::now());
749 }
750
751 Ok(())
752 }
753
754 async fn execute_scenario(&self, scenario: &AttackScenario, _events: &[Event]) -> Result<bool> {
756 match scenario.attack_type {
764 AttackType::SqlInjection => {
765 Ok(false) },
769 AttackType::AuthenticationBypass => {
770 Ok(false) },
773 _ => Ok(false), }
775 }
776
777 pub fn get_test_results(&self, test_id: &str) -> Result<&PenetrationTest> {
779 self.active_tests.get(test_id).ok_or_else(|| {
780 EventualiError::Configuration(format!("Test not found: {test_id}"))
781 })
782 }
783
784 pub fn list_tests(&self) -> Vec<&PenetrationTest> {
786 self.active_tests.values().collect()
787 }
788}
789
790impl Default for PenetrationTestFramework {
791 fn default() -> Self {
792 Self::new()
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799 use crate::{EventData, EventMetadata};
800 use uuid::Uuid;
801
802 fn create_test_event_with_data(data: serde_json::Value) -> Event {
803 Event {
804 id: Uuid::new_v4(),
805 aggregate_id: "test-aggregate".to_string(),
806 aggregate_type: "TestAggregate".to_string(),
807 event_type: "TestEvent".to_string(),
808 event_version: 1,
809 aggregate_version: 1,
810 data: EventData::Json(data),
811 metadata: EventMetadata::default(),
812 timestamp: Utc::now(),
813 }
814 }
815
816 #[tokio::test]
817 async fn test_vulnerability_scanner_creation() {
818 let scanner = VulnerabilityScanner::new();
819 assert!(!scanner.scan_rules.is_empty());
820 assert_eq!(scanner.severity_thresholds.len(), 5);
821 }
822
823 #[tokio::test]
824 async fn test_sql_injection_detection() {
825 let scanner = VulnerabilityScanner::new();
826
827 let malicious_data = serde_json::json!({
828 "query": "SELECT * FROM users WHERE id = 1 OR '1'='1"
829 });
830 let event = create_test_event_with_data(malicious_data);
831
832 let result = scanner.scan_events(vec![event]).await.unwrap();
833 assert!(!result.vulnerabilities_found.is_empty());
834
835 let finding = &result.vulnerabilities_found[0];
836 assert_eq!(finding.category, VulnerabilityCategory::InjectionAttack);
837 assert_eq!(finding.severity, VulnerabilitySeverity::High);
838 }
839
840 #[tokio::test]
841 async fn test_pii_detection() {
842 let scanner = VulnerabilityScanner::new();
843
844 let pii_data = serde_json::json!({
845 "user_ssn": "123-45-6789",
846 "credit_card": "4111-1111-1111-1111"
847 });
848 let event = create_test_event_with_data(pii_data);
849
850 let result = scanner.scan_events(vec![event]).await.unwrap();
851 assert!(!result.vulnerabilities_found.is_empty());
852
853 let finding = &result.vulnerabilities_found[0];
854 assert_eq!(finding.category, VulnerabilityCategory::DataLeakage);
855 assert_eq!(finding.severity, VulnerabilitySeverity::Critical);
856 }
857
858 #[tokio::test]
859 async fn test_clean_event_no_vulnerabilities() {
860 let scanner = VulnerabilityScanner::new();
861
862 let clean_data = serde_json::json!({
863 "user_action": "login",
864 "timestamp": "2023-01-01T00:00:00Z"
865 });
866 let event = create_test_event_with_data(clean_data);
867
868 let result = scanner.scan_events(vec![event]).await.unwrap();
869 assert!(result.vulnerabilities_found.is_empty());
870 assert_eq!(result.compliance_score, 100.0);
871 }
872
873 #[tokio::test]
874 async fn test_whitelist_functionality() {
875 let mut scanner = VulnerabilityScanner::new();
876 scanner.add_to_whitelist("whitelisted-aggregate".to_string());
877
878 let malicious_data = serde_json::json!({
879 "query": "SELECT * FROM users WHERE id = 1 OR '1'='1"
880 });
881 let mut event = create_test_event_with_data(malicious_data);
882 event.aggregate_id = "whitelisted-aggregate".to_string();
883
884 let result = scanner.scan_events(vec![event]).await.unwrap();
885 assert!(result.vulnerabilities_found.is_empty());
886 }
887
888 #[test]
889 fn test_penetration_test_framework() {
890 let mut framework = PenetrationTestFramework::new();
891 assert!(!framework.test_scenarios.is_empty());
892
893 let test_id = framework.start_test(
894 "Test Security Assessment".to_string(),
895 vec!["test-*".to_string()]
896 ).unwrap();
897
898 let test = framework.get_test_results(&test_id).unwrap();
899 assert_eq!(test.status, TestStatus::Running);
900 }
901
902 #[test]
903 fn test_compliance_score_calculation() {
904 let scanner = VulnerabilityScanner::new();
905
906 let mut severity_counts = HashMap::new();
907 severity_counts.insert(VulnerabilitySeverity::Critical, 1);
908 severity_counts.insert(VulnerabilitySeverity::High, 2);
909
910 let score = scanner.calculate_compliance_score(&severity_counts, 100);
911 assert!(score < 100.0); assert!(score >= 0.0); }
914}