rust_network_scanner/
compliance.rs

1//! Compliance scanning module for network security v2.0
2//!
3//! Provides PCI-DSS and CIS benchmark compliance checking.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Compliance frameworks supported
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11pub enum ComplianceFramework {
12    PCIDSS,
13    CISBenchmark,
14    NIST80053,
15    HIPAA,
16    SOC2,
17}
18
19impl ComplianceFramework {
20    /// Get framework name
21    pub fn name(&self) -> &'static str {
22        match self {
23            ComplianceFramework::PCIDSS => "PCI-DSS v4.0",
24            ComplianceFramework::CISBenchmark => "CIS Controls v8",
25            ComplianceFramework::NIST80053 => "NIST SP 800-53",
26            ComplianceFramework::HIPAA => "HIPAA Security Rule",
27            ComplianceFramework::SOC2 => "SOC 2 Type II",
28        }
29    }
30}
31
32/// Compliance check status
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub enum ComplianceStatus {
35    Pass,
36    Fail,
37    Warning,
38    NotApplicable,
39    NotTested,
40}
41
42/// Individual compliance check
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ComplianceCheck {
45    pub id: String,
46    pub framework: ComplianceFramework,
47    pub requirement: String,
48    pub description: String,
49    pub status: ComplianceStatus,
50    pub evidence: String,
51    pub remediation: Option<String>,
52}
53
54/// Compliance scan result
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ComplianceResult {
57    pub target: String,
58    pub framework: ComplianceFramework,
59    pub scan_time: DateTime<Utc>,
60    pub checks: Vec<ComplianceCheck>,
61    pub passed: usize,
62    pub failed: usize,
63    pub warnings: usize,
64    pub compliance_score: f32,
65}
66
67impl ComplianceResult {
68    /// Create a new result
69    pub fn new(target: &str, framework: ComplianceFramework) -> Self {
70        Self {
71            target: target.to_string(),
72            framework,
73            scan_time: Utc::now(),
74            checks: Vec::new(),
75            passed: 0,
76            failed: 0,
77            warnings: 0,
78            compliance_score: 0.0,
79        }
80    }
81
82    /// Add a check result
83    pub fn add_check(&mut self, check: ComplianceCheck) {
84        match check.status {
85            ComplianceStatus::Pass => self.passed += 1,
86            ComplianceStatus::Fail => self.failed += 1,
87            ComplianceStatus::Warning => self.warnings += 1,
88            _ => {}
89        }
90        self.checks.push(check);
91        self.calculate_score();
92    }
93
94    /// Calculate compliance score
95    fn calculate_score(&mut self) {
96        let total = self.passed + self.failed;
97        if total > 0 {
98            self.compliance_score = (self.passed as f32 / total as f32) * 100.0;
99        }
100    }
101
102    /// Check if compliant
103    pub fn is_compliant(&self) -> bool {
104        self.failed == 0
105    }
106
107    /// Get summary
108    pub fn summary(&self) -> String {
109        format!(
110            "{} Compliance: {:.1}% | Passed: {} | Failed: {} | Warnings: {}",
111            self.framework.name(),
112            self.compliance_score,
113            self.passed,
114            self.failed,
115            self.warnings
116        )
117    }
118
119    /// Export as JSON
120    pub fn to_json(&self) -> Result<String, serde_json::Error> {
121        serde_json::to_string_pretty(self)
122    }
123
124    /// Get failed checks
125    pub fn get_failed_checks(&self) -> Vec<&ComplianceCheck> {
126        self.checks
127            .iter()
128            .filter(|c| c.status == ComplianceStatus::Fail)
129            .collect()
130    }
131}
132
133/// Port security configuration for compliance checking
134#[derive(Debug, Clone)]
135pub struct PortSecurityConfig {
136    /// Ports that should be closed
137    pub prohibited_ports: Vec<u16>,
138    /// Ports that require encryption
139    pub encryption_required_ports: Vec<u16>,
140    /// Allowed ports
141    pub allowed_ports: Vec<u16>,
142}
143
144impl Default for PortSecurityConfig {
145    fn default() -> Self {
146        Self {
147            // PCI-DSS prohibited ports
148            prohibited_ports: vec![23, 21, 69, 512, 513, 514, 137, 138, 139],
149            // Ports requiring encryption
150            encryption_required_ports: vec![80, 8080, 25, 110, 143],
151            // Allowed secure ports
152            allowed_ports: vec![22, 443, 8443, 993, 995, 465, 587],
153        }
154    }
155}
156
157/// Compliance scanner
158pub struct ComplianceScanner {
159    port_config: PortSecurityConfig,
160}
161
162impl ComplianceScanner {
163    /// Create a new compliance scanner
164    pub fn new() -> Self {
165        Self {
166            port_config: PortSecurityConfig::default(),
167        }
168    }
169
170    /// Scan for PCI-DSS compliance
171    pub fn scan_pci_dss(&self, target: &str, open_ports: &[u16], services: &HashMap<u16, String>) -> ComplianceResult {
172        let mut result = ComplianceResult::new(target, ComplianceFramework::PCIDSS);
173
174        // PCI-DSS 1.3.1 - Restrict inbound traffic
175        self.check_prohibited_ports(&mut result, open_ports);
176
177        // PCI-DSS 2.2.7 - Use strong cryptography
178        self.check_encryption_required(&mut result, open_ports, services);
179
180        // PCI-DSS 2.3 - Encrypt administrative access
181        self.check_secure_admin(&mut result, open_ports, services);
182
183        // PCI-DSS 4.1 - Encrypt cardholder data transmission
184        self.check_data_encryption(&mut result, open_ports);
185
186        // PCI-DSS 6.5.4 - No insecure protocols
187        self.check_insecure_protocols(&mut result, services);
188
189        // PCI-DSS 11.2 - Vulnerability scanning
190        self.check_vulnerability_scanning(&mut result, open_ports);
191
192        result
193    }
194
195    /// Scan for CIS Benchmark compliance
196    pub fn scan_cis_benchmark(&self, target: &str, open_ports: &[u16], services: &HashMap<u16, String>) -> ComplianceResult {
197        let mut result = ComplianceResult::new(target, ComplianceFramework::CISBenchmark);
198
199        // CIS Control 4.1 - Secure configuration
200        self.check_secure_configuration(&mut result, open_ports);
201
202        // CIS Control 4.8 - Disable unnecessary services
203        self.check_unnecessary_services(&mut result, open_ports, services);
204
205        // CIS Control 9.2 - Limit network ports
206        self.check_open_port_count(&mut result, open_ports);
207
208        // CIS Control 13.1 - Encrypt sensitive data
209        self.check_data_encryption(&mut result, open_ports);
210
211        result
212    }
213
214    /// Check for prohibited ports (PCI-DSS 1.3.1)
215    fn check_prohibited_ports(&self, result: &mut ComplianceResult, open_ports: &[u16]) {
216        let prohibited_open: Vec<u16> = open_ports
217            .iter()
218            .filter(|p| self.port_config.prohibited_ports.contains(p))
219            .copied()
220            .collect();
221
222        let status = if prohibited_open.is_empty() {
223            ComplianceStatus::Pass
224        } else {
225            ComplianceStatus::Fail
226        };
227
228        let evidence = if prohibited_open.is_empty() {
229            "No prohibited ports found open".to_string()
230        } else {
231            format!("Prohibited ports open: {:?}", prohibited_open)
232        };
233
234        result.add_check(ComplianceCheck {
235            id: "PCI-DSS-1.3.1".to_string(),
236            framework: ComplianceFramework::PCIDSS,
237            requirement: "Restrict inbound traffic to necessary ports".to_string(),
238            description: "No insecure or unnecessary ports should be accessible".to_string(),
239            status,
240            evidence,
241            remediation: Some("Close or firewall prohibited ports: Telnet (23), FTP (21), TFTP (69)".to_string()),
242        });
243    }
244
245    /// Check encryption requirements (PCI-DSS 2.2.7)
246    fn check_encryption_required(&self, result: &mut ComplianceResult, open_ports: &[u16], services: &HashMap<u16, String>) {
247        let unencrypted: Vec<u16> = open_ports
248            .iter()
249            .filter(|p| self.port_config.encryption_required_ports.contains(p))
250            .copied()
251            .collect();
252
253        let status = if unencrypted.is_empty() {
254            ComplianceStatus::Pass
255        } else {
256            ComplianceStatus::Fail
257        };
258
259        let evidence = if unencrypted.is_empty() {
260            "All services use encrypted protocols".to_string()
261        } else {
262            format!("Unencrypted services on ports: {:?}", unencrypted)
263        };
264
265        result.add_check(ComplianceCheck {
266            id: "PCI-DSS-2.2.7".to_string(),
267            framework: ComplianceFramework::PCIDSS,
268            requirement: "Use strong cryptography for non-console administrative access".to_string(),
269            description: "All administrative access must be encrypted".to_string(),
270            status,
271            evidence,
272            remediation: Some("Replace HTTP with HTTPS, use IMAPS/POP3S instead of IMAP/POP3".to_string()),
273        });
274    }
275
276    /// Check secure administrative access (PCI-DSS 2.3)
277    fn check_secure_admin(&self, result: &mut ComplianceResult, open_ports: &[u16], _services: &HashMap<u16, String>) {
278        // Check for SSH (secure) vs Telnet (insecure)
279        let has_telnet = open_ports.contains(&23);
280        let has_ssh = open_ports.contains(&22);
281
282        let status = if has_telnet {
283            ComplianceStatus::Fail
284        } else if has_ssh {
285            ComplianceStatus::Pass
286        } else {
287            ComplianceStatus::NotApplicable
288        };
289
290        let evidence = match (has_telnet, has_ssh) {
291            (true, _) => "Telnet (insecure) is enabled".to_string(),
292            (false, true) => "SSH (secure) is used for remote access".to_string(),
293            (false, false) => "No remote administration ports detected".to_string(),
294        };
295
296        result.add_check(ComplianceCheck {
297            id: "PCI-DSS-2.3".to_string(),
298            framework: ComplianceFramework::PCIDSS,
299            requirement: "Encrypt all non-console administrative access".to_string(),
300            description: "Use SSH instead of Telnet for remote administration".to_string(),
301            status,
302            evidence,
303            remediation: Some("Disable Telnet and use SSH with key-based authentication".to_string()),
304        });
305    }
306
307    /// Check data transmission encryption (PCI-DSS 4.1)
308    fn check_data_encryption(&self, result: &mut ComplianceResult, open_ports: &[u16]) {
309        let has_https = open_ports.contains(&443) || open_ports.contains(&8443);
310        let has_http_only = open_ports.contains(&80) && !has_https;
311
312        let status = if has_http_only {
313            ComplianceStatus::Fail
314        } else if has_https {
315            ComplianceStatus::Pass
316        } else {
317            ComplianceStatus::NotApplicable
318        };
319
320        let evidence = if has_https {
321            "HTTPS is available for secure data transmission".to_string()
322        } else if has_http_only {
323            "Only HTTP (unencrypted) is available".to_string()
324        } else {
325            "No web services detected".to_string()
326        };
327
328        result.add_check(ComplianceCheck {
329            id: "PCI-DSS-4.1".to_string(),
330            framework: ComplianceFramework::PCIDSS,
331            requirement: "Use strong cryptography to protect cardholder data during transmission".to_string(),
332            description: "All data transmission must be encrypted with TLS 1.2+".to_string(),
333            status,
334            evidence,
335            remediation: Some("Enable HTTPS with TLS 1.2 or higher, disable HTTP".to_string()),
336        });
337    }
338
339    /// Check for insecure protocols (PCI-DSS 6.5.4)
340    fn check_insecure_protocols(&self, result: &mut ComplianceResult, services: &HashMap<u16, String>) {
341        let insecure_services: Vec<String> = services
342            .values()
343            .filter(|s| {
344                let s_lower = s.to_lowercase();
345                s_lower.contains("telnet")
346                    || s_lower.contains("ftp")
347                    || (s_lower.contains("ssl") && s_lower.contains("2"))
348                    || s_lower.contains("sslv3")
349            })
350            .cloned()
351            .collect();
352
353        let status = if insecure_services.is_empty() {
354            ComplianceStatus::Pass
355        } else {
356            ComplianceStatus::Fail
357        };
358
359        let evidence = if insecure_services.is_empty() {
360            "No insecure protocols detected".to_string()
361        } else {
362            format!("Insecure protocols found: {:?}", insecure_services)
363        };
364
365        result.add_check(ComplianceCheck {
366            id: "PCI-DSS-6.5.4".to_string(),
367            framework: ComplianceFramework::PCIDSS,
368            requirement: "Do not use insecure protocols".to_string(),
369            description: "Telnet, FTP, SSL 2.0/3.0, and early TLS must be disabled".to_string(),
370            status,
371            evidence,
372            remediation: Some("Disable Telnet, FTP, SSL 2.0/3.0, TLS 1.0/1.1".to_string()),
373        });
374    }
375
376    /// Check vulnerability scanning status (PCI-DSS 11.2)
377    fn check_vulnerability_scanning(&self, result: &mut ComplianceResult, _open_ports: &[u16]) {
378        // This is a procedural check - we can only verify scanning is being performed
379        result.add_check(ComplianceCheck {
380            id: "PCI-DSS-11.2".to_string(),
381            framework: ComplianceFramework::PCIDSS,
382            requirement: "Run internal and external network vulnerability scans".to_string(),
383            description: "Quarterly vulnerability scans required".to_string(),
384            status: ComplianceStatus::Pass, // We're performing the scan right now
385            evidence: "Vulnerability scan is being performed".to_string(),
386            remediation: None,
387        });
388    }
389
390    /// Check secure configuration (CIS Control 4.1)
391    fn check_secure_configuration(&self, result: &mut ComplianceResult, open_ports: &[u16]) {
392        let high_risk_ports: Vec<u16> = open_ports
393            .iter()
394            .filter(|p| [23, 21, 25, 110, 143, 445, 3389].contains(p))
395            .copied()
396            .collect();
397
398        let status = if high_risk_ports.is_empty() {
399            ComplianceStatus::Pass
400        } else if high_risk_ports.len() <= 2 {
401            ComplianceStatus::Warning
402        } else {
403            ComplianceStatus::Fail
404        };
405
406        result.add_check(ComplianceCheck {
407            id: "CIS-4.1".to_string(),
408            framework: ComplianceFramework::CISBenchmark,
409            requirement: "Establish secure configurations".to_string(),
410            description: "Minimize attack surface by closing unnecessary ports".to_string(),
411            status,
412            evidence: format!("High-risk ports open: {:?}", high_risk_ports),
413            remediation: Some("Close or restrict high-risk ports, use encrypted alternatives".to_string()),
414        });
415    }
416
417    /// Check for unnecessary services (CIS Control 4.8)
418    fn check_unnecessary_services(&self, result: &mut ComplianceResult, open_ports: &[u16], _services: &HashMap<u16, String>) {
419        let common_unnecessary: Vec<u16> = open_ports
420            .iter()
421            .filter(|p| [7, 9, 13, 17, 19, 37, 79].contains(p))
422            .copied()
423            .collect();
424
425        let status = if common_unnecessary.is_empty() {
426            ComplianceStatus::Pass
427        } else {
428            ComplianceStatus::Fail
429        };
430
431        result.add_check(ComplianceCheck {
432            id: "CIS-4.8".to_string(),
433            framework: ComplianceFramework::CISBenchmark,
434            requirement: "Uninstall or disable unnecessary services".to_string(),
435            description: "Legacy and unnecessary services should be disabled".to_string(),
436            status,
437            evidence: format!("Unnecessary service ports: {:?}", common_unnecessary),
438            remediation: Some("Disable echo, discard, daytime, chargen, finger services".to_string()),
439        });
440    }
441
442    /// Check open port count (CIS Control 9.2)
443    fn check_open_port_count(&self, result: &mut ComplianceResult, open_ports: &[u16]) {
444        let port_count = open_ports.len();
445
446        let status = if port_count <= 5 {
447            ComplianceStatus::Pass
448        } else if port_count <= 10 {
449            ComplianceStatus::Warning
450        } else {
451            ComplianceStatus::Fail
452        };
453
454        result.add_check(ComplianceCheck {
455            id: "CIS-9.2".to_string(),
456            framework: ComplianceFramework::CISBenchmark,
457            requirement: "Ensure only approved ports are open".to_string(),
458            description: "Limit network exposure to minimum necessary ports".to_string(),
459            status,
460            evidence: format!("{} ports open: {:?}", port_count, open_ports),
461            remediation: Some("Review and close unnecessary ports, implement firewall rules".to_string()),
462        });
463    }
464}
465
466impl Default for ComplianceScanner {
467    fn default() -> Self {
468        Self::new()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[test]
477    fn test_pci_dss_scan_pass() {
478        let scanner = ComplianceScanner::new();
479        let open_ports = vec![22, 443];
480        let services = HashMap::new();
481
482        let result = scanner.scan_pci_dss("192.168.1.1", &open_ports, &services);
483
484        assert!(result.compliance_score > 50.0);
485        assert!(result.failed < result.passed);
486    }
487
488    #[test]
489    fn test_pci_dss_scan_fail() {
490        let scanner = ComplianceScanner::new();
491        let open_ports = vec![23, 21, 80]; // Telnet, FTP, HTTP - all bad
492        let services = HashMap::new();
493
494        let result = scanner.scan_pci_dss("192.168.1.1", &open_ports, &services);
495
496        assert!(result.failed > 0);
497    }
498
499    #[test]
500    fn test_cis_benchmark_scan() {
501        let scanner = ComplianceScanner::new();
502        let open_ports = vec![22, 443, 8443];
503        let services = HashMap::new();
504
505        let result = scanner.scan_cis_benchmark("192.168.1.1", &open_ports, &services);
506
507        assert!(!result.checks.is_empty());
508    }
509
510    #[test]
511    fn test_compliance_result_summary() {
512        let mut result = ComplianceResult::new("test", ComplianceFramework::PCIDSS);
513
514        result.add_check(ComplianceCheck {
515            id: "TEST-1".to_string(),
516            framework: ComplianceFramework::PCIDSS,
517            requirement: "Test".to_string(),
518            description: "Test check".to_string(),
519            status: ComplianceStatus::Pass,
520            evidence: "Passed".to_string(),
521            remediation: None,
522        });
523
524        assert_eq!(result.passed, 1);
525        assert!(result.summary().contains("100.0%"));
526    }
527
528    #[test]
529    fn test_failed_checks() {
530        let mut result = ComplianceResult::new("test", ComplianceFramework::PCIDSS);
531
532        result.add_check(ComplianceCheck {
533            id: "TEST-1".to_string(),
534            framework: ComplianceFramework::PCIDSS,
535            requirement: "Test".to_string(),
536            description: "Test".to_string(),
537            status: ComplianceStatus::Pass,
538            evidence: "OK".to_string(),
539            remediation: None,
540        });
541
542        result.add_check(ComplianceCheck {
543            id: "TEST-2".to_string(),
544            framework: ComplianceFramework::PCIDSS,
545            requirement: "Test".to_string(),
546            description: "Test".to_string(),
547            status: ComplianceStatus::Fail,
548            evidence: "Failed".to_string(),
549            remediation: Some("Fix it".to_string()),
550        });
551
552        let failed = result.get_failed_checks();
553        assert_eq!(failed.len(), 1);
554        assert_eq!(failed[0].id, "TEST-2");
555    }
556}