use crate::{Result, QsshError};
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use tokio::sync::{RwLock, mpsc};
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
Debug,
Info,
Warning,
Error,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventType {
AuthSuccess { username: String, method: String },
AuthFailure { username: String, method: String, reason: String },
AuthRateLimited { ip: IpAddr },
ConnectionEstablished { ip: IpAddr, version: String },
ConnectionClosed { ip: IpAddr, reason: String },
ConnectionRejected { ip: IpAddr, reason: String },
KeyExchange { algorithm: String, success: bool },
KeyRotation { generation: u64, method: String },
QkdKeyUsed { size: usize },
ChannelOpened { channel_id: u32, channel_type: String },
ChannelClosed { channel_id: u32 },
PortForward { local: u16, remote: String },
InvalidProtocol { description: String },
ReplayAttack { sequence: u64 },
CryptoError { description: String },
ConfigChange { setting: String, old_value: String, new_value: String },
AuditLogRotated { old_file: String, new_file: String },
AnomalyDetected { anomaly_type: String, details: String },
BruteForceAttempt { ip: IpAddr, attempts: u32 },
SuspiciousPattern { pattern: String, confidence: f32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityEvent {
pub timestamp: u64,
pub severity: Severity,
pub event_type: EventType,
pub session_id: Option<String>,
pub source_ip: Option<IpAddr>,
pub additional_data: HashMap<String, String>,
}
impl SecurityEvent {
pub fn new(severity: Severity, event_type: EventType) -> Self {
Self {
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
severity,
event_type,
session_id: None,
source_ip: None,
additional_data: HashMap::new(),
}
}
pub fn with_session(mut self, session_id: String) -> Self {
self.session_id = Some(session_id);
self
}
pub fn with_ip(mut self, ip: IpAddr) -> Self {
self.source_ip = Some(ip);
self
}
pub fn with_data(mut self, key: String, value: String) -> Self {
self.additional_data.insert(key, value);
self
}
}
#[derive(Clone)]
pub struct SecurityConfig {
pub log_file: String,
pub max_log_size: u64,
pub rotation_count: u32,
pub enable_anomaly_detection: bool,
pub alert_threshold: Severity,
pub retention_days: u32,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
log_file: "/var/log/qssh/security.log".to_string(),
max_log_size: 100 * 1024 * 1024, rotation_count: 10,
enable_anomaly_detection: true,
alert_threshold: Severity::Warning,
retention_days: 90,
}
}
}
pub struct SecurityMonitor {
config: SecurityConfig,
events: Arc<RwLock<Vec<SecurityEvent>>>,
alert_handlers: Arc<RwLock<Vec<Box<dyn AlertHandler>>>>,
anomaly_detector: Arc<AnomalyDetector>,
event_sender: mpsc::Sender<SecurityEvent>,
stats: Arc<RwLock<SecurityStats>>,
}
impl SecurityMonitor {
pub fn new(config: SecurityConfig) -> Self {
let (event_sender, event_receiver) = mpsc::channel(1000);
let monitor = Self {
config: config.clone(),
events: Arc::new(RwLock::new(Vec::new())),
alert_handlers: Arc::new(RwLock::new(Vec::new())),
anomaly_detector: Arc::new(AnomalyDetector::new()),
event_sender,
stats: Arc::new(RwLock::new(SecurityStats::default())),
};
let monitor_clone = monitor.clone();
tokio::spawn(async move {
monitor_clone.event_processor(event_receiver).await;
});
if config.enable_anomaly_detection {
let monitor_clone = monitor.clone();
tokio::spawn(async move {
monitor_clone.anomaly_detection_task().await;
});
}
monitor
}
pub async fn log_event(&self, event: SecurityEvent) -> Result<()> {
{
let mut stats = self.stats.write().await;
stats.total_events += 1;
match event.severity {
Severity::Debug => stats.debug_count += 1,
Severity::Info => stats.info_count += 1,
Severity::Warning => stats.warning_count += 1,
Severity::Error => stats.error_count += 1,
Severity::Critical => stats.critical_count += 1,
}
}
self.event_sender.send(event.clone()).await
.map_err(|_| QsshError::Protocol("Failed to send event".into()))?;
if event.severity >= self.config.alert_threshold {
self.trigger_alerts(&event).await;
}
Ok(())
}
async fn event_processor(&self, mut receiver: mpsc::Receiver<SecurityEvent>) {
while let Some(event) = receiver.recv().await {
{
let mut events = self.events.write().await;
events.push(event.clone());
if events.len() > 10000 {
events.drain(0..5000);
}
}
if let Err(e) = self.write_to_log(&event).await {
log::error!("Failed to write security event to log: {}", e);
}
if self.config.enable_anomaly_detection {
self.anomaly_detector.analyze(&event).await;
}
}
}
async fn write_to_log(&self, event: &SecurityEvent) -> Result<()> {
let json = serde_json::to_string(event)
.map_err(|e| QsshError::Protocol(format!("Failed to serialize event: {}", e)))?;
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.config.log_file)
.await
.map_err(|e| QsshError::Io(e))?;
file.write_all(format!("{}\n", json).as_bytes()).await
.map_err(|e| QsshError::Io(e))?;
file.flush().await
.map_err(|e| QsshError::Io(e))?;
let metadata = file.metadata().await
.map_err(|e| QsshError::Io(e))?;
if metadata.len() > self.config.max_log_size {
self.rotate_log().await?;
}
Ok(())
}
async fn rotate_log(&self) -> Result<()> {
for i in (1..self.config.rotation_count).rev() {
let old_name = format!("{}.{}", self.config.log_file, i);
let new_name = format!("{}.{}", self.config.log_file, i + 1);
if tokio::fs::metadata(&old_name).await.is_ok() {
tokio::fs::rename(&old_name, &new_name).await
.map_err(|e| QsshError::Io(e))?;
}
}
tokio::fs::rename(&self.config.log_file, format!("{}.1", self.config.log_file)).await
.map_err(|e| QsshError::Io(e))?;
let event = SecurityEvent::new(
Severity::Info,
EventType::AuditLogRotated {
old_file: self.config.log_file.clone(),
new_file: format!("{}.1", self.config.log_file),
},
);
self.log_event(event).await?;
Ok(())
}
async fn trigger_alerts(&self, event: &SecurityEvent) {
let handlers = self.alert_handlers.read().await;
for handler in handlers.iter() {
handler.handle_alert(event).await;
}
}
pub async fn register_alert_handler(&self, handler: Box<dyn AlertHandler>) {
let mut handlers = self.alert_handlers.write().await;
handlers.push(handler);
}
async fn anomaly_detection_task(&self) {
let mut interval = tokio::time::interval(Duration::from_secs(60));
loop {
interval.tick().await;
let events = self.events.read().await;
let recent_events: Vec<_> = events.iter()
.rev()
.take(1000)
.cloned()
.collect();
drop(events);
for anomaly in self.anomaly_detector.detect_patterns(&recent_events).await {
let event = SecurityEvent::new(
Severity::Warning,
EventType::AnomalyDetected {
anomaly_type: anomaly.anomaly_type,
details: anomaly.details,
},
);
if let Err(e) = self.log_event(event).await {
log::error!("Failed to log anomaly: {}", e);
}
}
}
}
pub async fn get_stats(&self) -> SecurityStats {
self.stats.read().await.clone()
}
pub async fn query_events(&self, filter: EventFilter) -> Vec<SecurityEvent> {
let events = self.events.read().await;
events.iter()
.filter(|e| filter.matches(e))
.cloned()
.collect()
}
}
impl Clone for SecurityMonitor {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
events: self.events.clone(),
alert_handlers: self.alert_handlers.clone(),
anomaly_detector: self.anomaly_detector.clone(),
event_sender: self.event_sender.clone(),
stats: self.stats.clone(),
}
}
}
#[async_trait::async_trait]
pub trait AlertHandler: Send + Sync {
async fn handle_alert(&self, event: &SecurityEvent);
}
pub struct EmailAlertHandler {
recipient: String,
}
#[async_trait::async_trait]
impl AlertHandler for EmailAlertHandler {
async fn handle_alert(&self, event: &SecurityEvent) {
log::error!("SECURITY ALERT to {}: {:?}", self.recipient, event);
}
}
struct AnomalyDetector {
patterns: Arc<RwLock<HashMap<String, PatternTracker>>>,
}
impl AnomalyDetector {
fn new() -> Self {
Self {
patterns: Arc::new(RwLock::new(HashMap::new())),
}
}
async fn analyze(&self, event: &SecurityEvent) {
let mut patterns = self.patterns.write().await;
if let EventType::AuthFailure { .. } = &event.event_type {
if let Some(ip) = event.source_ip {
let key = format!("auth_fail_{}", ip);
let tracker = patterns.entry(key).or_insert(PatternTracker::new());
tracker.increment();
}
}
}
async fn detect_patterns(&self, events: &[SecurityEvent]) -> Vec<Anomaly> {
let mut anomalies = Vec::new();
let patterns = self.patterns.read().await;
for (key, tracker) in patterns.iter() {
if key.starts_with("auth_fail_") && tracker.count > 5 {
let ip_str = key.strip_prefix("auth_fail_").unwrap_or("");
if let Ok(ip) = ip_str.parse::<IpAddr>() {
anomalies.push(Anomaly {
anomaly_type: "BruteForce".to_string(),
details: format!("Multiple auth failures from {}", ip),
confidence: 0.9,
});
}
}
}
anomalies
}
}
struct PatternTracker {
count: u32,
first_seen: u64,
last_seen: u64,
}
impl PatternTracker {
fn new() -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
count: 0,
first_seen: now,
last_seen: now,
}
}
fn increment(&mut self) {
self.count += 1;
self.last_seen = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
}
}
struct Anomaly {
anomaly_type: String,
details: String,
confidence: f32,
}
pub struct EventFilter {
pub severity_min: Option<Severity>,
pub time_range: Option<(u64, u64)>,
pub event_types: Option<Vec<String>>,
pub session_id: Option<String>,
pub source_ip: Option<IpAddr>,
}
impl EventFilter {
pub fn new() -> Self {
Self {
severity_min: None,
time_range: None,
event_types: None,
session_id: None,
source_ip: None,
}
}
fn matches(&self, event: &SecurityEvent) -> bool {
if let Some(min_severity) = self.severity_min {
if event.severity < min_severity {
return false;
}
}
if let Some((start, end)) = self.time_range {
if event.timestamp < start || event.timestamp > end {
return false;
}
}
if let Some(session_id) = &self.session_id {
if event.session_id.as_ref() != Some(session_id) {
return false;
}
}
if let Some(ip) = self.source_ip {
if event.source_ip != Some(ip) {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SecurityStats {
pub total_events: u64,
pub debug_count: u64,
pub info_count: u64,
pub warning_count: u64,
pub error_count: u64,
pub critical_count: u64,
pub anomalies_detected: u64,
pub alerts_triggered: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_security_event_creation() {
let event = SecurityEvent::new(
Severity::Warning,
EventType::AuthFailure {
username: "alice".to_string(),
method: "password".to_string(),
reason: "Invalid password".to_string(),
},
);
assert_eq!(event.severity, Severity::Warning);
assert!(event.timestamp > 0);
}
#[tokio::test]
async fn test_event_filter() {
let filter = EventFilter {
severity_min: Some(Severity::Warning),
time_range: None,
event_types: None,
session_id: None,
source_ip: None,
};
let event1 = SecurityEvent::new(Severity::Info, EventType::AuthSuccess {
username: "alice".to_string(),
method: "key".to_string(),
});
let event2 = SecurityEvent::new(Severity::Error, EventType::CryptoError {
description: "Invalid signature".to_string(),
});
assert!(!filter.matches(&event1)); assert!(filter.matches(&event2)); }
}