use crate::error::{FusekiError, FusekiResult};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
pub struct SecurityAuditManager {
config: SecurityAuditConfig,
audit_log: Arc<RwLock<Vec<AuditLogEntry>>>,
vulnerabilities: Arc<RwLock<Vec<Vulnerability>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAuditConfig {
pub enabled: bool,
pub vulnerability_scanning: bool,
pub scan_interval_hours: u64,
pub owasp_checks: bool,
pub compliance_checks: bool,
pub max_log_entries: usize,
}
impl Default for SecurityAuditConfig {
fn default() -> Self {
Self {
enabled: true,
vulnerability_scanning: true,
scan_interval_hours: 24,
owasp_checks: true,
compliance_checks: false,
max_log_entries: 10000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogEntry {
pub timestamp: DateTime<Utc>,
pub event_type: AuditEventType,
pub severity: Severity,
pub user: Option<String>,
pub ip_address: Option<String>,
pub resource: String,
pub action: String,
pub result: AuditResult,
pub details: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuditEventType {
Authentication,
Authorization,
DataAccess,
DataModification,
ConfigurationChange,
SecurityEvent,
ComplianceEvent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuditResult {
Success,
Failure,
Denied,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
Info,
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vulnerability {
pub id: String,
pub severity: Severity,
pub category: VulnerabilityCategory,
pub title: String,
pub description: String,
pub affected_component: String,
pub cve_id: Option<String>,
pub remediation: String,
pub discovered_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VulnerabilityCategory {
InjectionFlaws,
BrokenAuthentication,
SensitiveDataExposure,
XmlExternalEntities,
BrokenAccessControl,
SecurityMisconfiguration,
CrossSiteScripting,
InsecureDeserialization,
UsingComponentsWithKnownVulnerabilities,
InsufficientLogging,
}
impl SecurityAuditManager {
pub fn new(config: SecurityAuditConfig) -> Self {
Self {
config,
audit_log: Arc::new(RwLock::new(Vec::new())),
vulnerabilities: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn log_event(&self, entry: AuditLogEntry) -> FusekiResult<()> {
if !self.config.enabled {
return Ok(());
}
let mut log = self.audit_log.write().await;
if log.len() >= self.config.max_log_entries {
log.remove(0); }
log.push(entry.clone());
match entry.severity {
Severity::Critical | Severity::High => {
warn!(
event_type = ?entry.event_type,
severity = ?entry.severity,
user = ?entry.user,
resource = %entry.resource,
action = %entry.action,
result = ?entry.result,
"Security audit event"
);
}
_ => {
debug!(
event_type = ?entry.event_type,
severity = ?entry.severity,
resource = %entry.resource,
"Security audit event"
);
}
}
Ok(())
}
pub async fn log_authentication(
&self,
user: &str,
ip: &str,
success: bool,
) -> FusekiResult<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
event_type: AuditEventType::Authentication,
severity: if success {
Severity::Info
} else {
Severity::Medium
},
user: Some(user.to_string()),
ip_address: Some(ip.to_string()),
resource: "authentication".to_string(),
action: "login".to_string(),
result: if success {
AuditResult::Success
} else {
AuditResult::Failure
},
details: None,
};
self.log_event(entry).await
}
pub async fn log_authorization(
&self,
user: &str,
resource: &str,
action: &str,
granted: bool,
) -> FusekiResult<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
event_type: AuditEventType::Authorization,
severity: if granted {
Severity::Info
} else {
Severity::Medium
},
user: Some(user.to_string()),
ip_address: None,
resource: resource.to_string(),
action: action.to_string(),
result: if granted {
AuditResult::Success
} else {
AuditResult::Denied
},
details: None,
};
self.log_event(entry).await
}
pub async fn log_data_access(
&self,
user: &str,
dataset: &str,
query: &str,
) -> FusekiResult<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
event_type: AuditEventType::DataAccess,
severity: Severity::Info,
user: Some(user.to_string()),
ip_address: None,
resource: dataset.to_string(),
action: "query".to_string(),
result: AuditResult::Success,
details: Some(query.to_string()),
};
self.log_event(entry).await
}
pub async fn perform_security_scan(&self) -> FusekiResult<SecurityScanReport> {
info!("Starting security vulnerability scan");
let mut vulnerabilities = Vec::new();
if self.config.owasp_checks {
vulnerabilities.extend(self.check_owasp_top_10().await?);
}
vulnerabilities.extend(self.check_configuration_security().await?);
vulnerabilities.extend(self.check_tls_security().await?);
let mut vuln_store = self.vulnerabilities.write().await;
vuln_store.clear();
vuln_store.extend(vulnerabilities.clone());
let report = SecurityScanReport {
scan_time: Utc::now(),
total_vulnerabilities: vulnerabilities.len(),
critical_count: vulnerabilities
.iter()
.filter(|v| v.severity == Severity::Critical)
.count(),
high_count: vulnerabilities
.iter()
.filter(|v| v.severity == Severity::High)
.count(),
medium_count: vulnerabilities
.iter()
.filter(|v| v.severity == Severity::Medium)
.count(),
low_count: vulnerabilities
.iter()
.filter(|v| v.severity == Severity::Low)
.count(),
vulnerabilities,
};
info!(
"Security scan complete: {} vulnerabilities found ({} critical, {} high)",
report.total_vulnerabilities, report.critical_count, report.high_count
);
Ok(report)
}
async fn check_owasp_top_10(&self) -> FusekiResult<Vec<Vulnerability>> {
let mut vulnerabilities = Vec::new();
if !self.config.enabled {
vulnerabilities.push(Vulnerability {
id: "AUDIT-001".to_string(),
severity: Severity::Medium,
category: VulnerabilityCategory::InsufficientLogging,
title: "Audit logging disabled".to_string(),
description: "Security audit logging is disabled".to_string(),
affected_component: "SecurityAuditManager".to_string(),
cve_id: None,
remediation: "Enable audit logging in configuration".to_string(),
discovered_at: Utc::now(),
});
}
Ok(vulnerabilities)
}
async fn check_configuration_security(&self) -> FusekiResult<Vec<Vulnerability>> {
let vulnerabilities = Vec::new();
Ok(vulnerabilities)
}
async fn check_tls_security(&self) -> FusekiResult<Vec<Vulnerability>> {
let vulnerabilities = Vec::new();
Ok(vulnerabilities)
}
pub async fn get_audit_log(&self, filter: Option<AuditLogFilter>) -> Vec<AuditLogEntry> {
let log = self.audit_log.read().await;
if let Some(f) = filter {
log.iter()
.filter(|entry| {
if let Some(ref event_type) = f.event_type {
if entry.event_type != *event_type {
return false;
}
}
if let Some(ref severity) = f.min_severity {
if entry.severity < *severity {
return false;
}
}
if let Some(ref user) = f.user {
if entry.user.as_ref() != Some(user) {
return false;
}
}
true
})
.cloned()
.collect()
} else {
log.clone()
}
}
pub async fn get_vulnerabilities(&self) -> Vec<Vulnerability> {
self.vulnerabilities.read().await.clone()
}
}
#[derive(Debug, Clone)]
pub struct AuditLogFilter {
pub event_type: Option<AuditEventType>,
pub min_severity: Option<Severity>,
pub user: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityScanReport {
pub scan_time: DateTime<Utc>,
pub total_vulnerabilities: usize,
pub critical_count: usize,
pub high_count: usize,
pub medium_count: usize,
pub low_count: usize,
pub vulnerabilities: Vec<Vulnerability>,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_audit_logging() {
let config = SecurityAuditConfig::default();
let manager = SecurityAuditManager::new(config);
manager
.log_authentication("testuser", "127.0.0.1", true)
.await
.unwrap();
let log = manager.get_audit_log(None).await;
assert_eq!(log.len(), 1);
assert_eq!(log[0].event_type, AuditEventType::Authentication);
}
#[tokio::test]
async fn test_audit_log_filter() {
let config = SecurityAuditConfig::default();
let manager = SecurityAuditManager::new(config);
manager
.log_authentication("user1", "127.0.0.1", true)
.await
.unwrap();
manager
.log_authorization("user2", "/dataset", "read", true)
.await
.unwrap();
let filter = AuditLogFilter {
event_type: Some(AuditEventType::Authentication),
min_severity: None,
user: None,
};
let filtered = manager.get_audit_log(Some(filter)).await;
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].event_type, AuditEventType::Authentication);
}
#[tokio::test]
async fn test_security_scan() {
let config = SecurityAuditConfig::default();
let manager = SecurityAuditManager::new(config);
let report = manager.perform_security_scan().await.unwrap();
assert_eq!(
report.total_vulnerabilities,
report.critical_count + report.high_count + report.medium_count + report.low_count
);
}
}