From 84d3f52cd9bb9d548db9a2dc3fc3393e2d7e63ed Mon Sep 17 00:00:00 2001 From: DaLaw2 Date: Tue, 31 Mar 2026 23:51:28 +0800 Subject: [PATCH] refactor: move domain model types from core/infra/interface to model/ directory Move SOAR playbook data types, report data types, ML drift/flow feature types, event types, GeoLocation, RateLimitSettings, and ReadinessState into their proper model/ subdirectories. Original locations re-export from model to preserve backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- net-guardia/src/adapter/http/rate_limit.rs | 3 +- net-guardia/src/adapter/http/soar.rs | 3 +- net-guardia/src/core/ml/config_loader.rs | 2 +- net-guardia/src/core/ml/drift_detector.rs | 32 +-- net-guardia/src/core/ml/engine.rs | 2 +- net-guardia/src/core/ml/feature_extractor.rs | 137 +------------ net-guardia/src/core/ml/inference.rs | 2 +- net-guardia/src/core/playbook_service.rs | 50 +---- net-guardia/src/core/rate_limit_service.rs | 11 +- net-guardia/src/core/report/data.rs | 183 +----------------- net-guardia/src/core/report/engine.rs | 2 +- net-guardia/src/core/soar/engine.rs | 2 +- net-guardia/src/core/system.rs | 5 +- .../src/infrastructure/app_services.rs | 2 +- .../src/infrastructure/audit_logger.rs | 2 +- .../infrastructure/enforce_mode_handler.rs | 4 +- net-guardia/src/infrastructure/geoip.rs | 11 +- net-guardia/src/infrastructure/http_server.rs | 21 +- .../src/infrastructure/service_factory.rs | 9 +- .../interface/communication/event_types.rs | 53 +---- net-guardia/src/model/detection/drift.rs | 31 +++ .../src/model/detection/flow_features.rs | 138 +++++++++++++ net-guardia/src/model/detection/mod.rs | 2 + net-guardia/src/model/event.rs | 52 +++++ net-guardia/src/model/mod.rs | 2 + .../src/model/monitoring/geolocation.rs | 9 + net-guardia/src/model/monitoring/mod.rs | 1 + net-guardia/src/model/report/data.rs | 182 +++++++++++++++++ net-guardia/src/model/report/mod.rs | 1 + net-guardia/src/model/soar/mod.rs | 1 + net-guardia/src/model/soar/playbook_data.rs | 48 +++++ net-guardia/src/model/system/mod.rs | 2 + .../src/model/system/rate_limit_settings.rs | 10 + net-guardia/src/model/system/readiness.rs | 20 ++ 34 files changed, 529 insertions(+), 506 deletions(-) create mode 100644 net-guardia/src/model/detection/drift.rs create mode 100644 net-guardia/src/model/detection/flow_features.rs create mode 100644 net-guardia/src/model/event.rs create mode 100644 net-guardia/src/model/monitoring/geolocation.rs create mode 100644 net-guardia/src/model/report/data.rs create mode 100644 net-guardia/src/model/report/mod.rs create mode 100644 net-guardia/src/model/soar/playbook_data.rs create mode 100644 net-guardia/src/model/system/rate_limit_settings.rs create mode 100644 net-guardia/src/model/system/readiness.rs diff --git a/net-guardia/src/adapter/http/rate_limit.rs b/net-guardia/src/adapter/http/rate_limit.rs index 1348d37..708416f 100644 --- a/net-guardia/src/adapter/http/rate_limit.rs +++ b/net-guardia/src/adapter/http/rate_limit.rs @@ -1,7 +1,8 @@ use actix_web::{HttpResponse, Responder, Scope, web}; use common::define::setting::*; -use crate::core::rate_limit_service::{RateLimitService, RateLimitSettings}; +use crate::core::rate_limit_service::RateLimitService; +use crate::model::system::rate_limit_settings::RateLimitSettings; pub fn initialize() -> Scope { web::scope("/rate-limit") diff --git a/net-guardia/src/adapter/http/soar.rs b/net-guardia/src/adapter/http/soar.rs index d7f6a10..417ce1b 100644 --- a/net-guardia/src/adapter/http/soar.rs +++ b/net-guardia/src/adapter/http/soar.rs @@ -2,7 +2,8 @@ use actix_web::{HttpResponse, Scope, web}; use serde::Deserialize; use crate::core::auth::extractor::AuthClaims; -use crate::core::playbook_service::{CreatePlaybookInput, PlaybookService}; +use crate::core::playbook_service::PlaybookService; +use crate::model::soar::playbook_data::CreatePlaybookInput; #[derive(Deserialize)] struct CreatePlaybookRequest { diff --git a/net-guardia/src/core/ml/config_loader.rs b/net-guardia/src/core/ml/config_loader.rs index b82af92..040ca3a 100644 --- a/net-guardia/src/core/ml/config_loader.rs +++ b/net-guardia/src/core/ml/config_loader.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use crate::model::error::ml::MLError; -pub use crate::model::config::MLInferenceConfig; +use crate::model::config::MLInferenceConfig; /// Backward-compatible alias so existing `use config_loader::InferenceConfig` paths still compile. pub type InferenceConfig = MLInferenceConfig; diff --git a/net-guardia/src/core/ml/drift_detector.rs b/net-guardia/src/core/ml/drift_detector.rs index 238fdf3..66c23bf 100644 --- a/net-guardia/src/core/ml/drift_detector.rs +++ b/net-guardia/src/core/ml/drift_detector.rs @@ -1,40 +1,10 @@ use std::collections::VecDeque; use std::time::{Duration, Instant}; -use crate::model::system::config::MLInferenceConfig; +use crate::model::detection::drift::{DriftReport, FeatureBaselines}; const WINDOW_DURATION: Duration = Duration::from_secs(3600); // 1 hour -/// Baselines loaded from the inference config (scaler mean / std). -/// If inference_config has no scaler data, drift detection is disabled. -pub struct FeatureBaselines { - pub names: Vec, - pub means: Vec, - pub stds: Vec, -} - -impl FeatureBaselines { - /// Build baselines from the ML inference config. - /// Returns `None` if the config has no features (drift detection disabled). - pub fn from_inference_config(config: &MLInferenceConfig) -> Option { - if config.ae_feature_names.is_empty() { - return None; - } - Some(Self { - names: config.ae_feature_names.clone(), - means: config.ae_scaler_mean.clone(), - stds: config.ae_scaler_std.clone(), - }) - } -} - -/// Report emitted when feature drift is detected. -#[derive(Debug, Clone)] -pub struct DriftReport { - pub drifted_features: Vec, - pub max_deviation: f64, -} - /// Tracks rolling mean/stddev of normalized input features over a 1-hour window. /// Compares against training-time baselines to detect data drift. pub struct DriftDetector { diff --git a/net-guardia/src/core/ml/engine.rs b/net-guardia/src/core/ml/engine.rs index 9c03580..b2e5d74 100644 --- a/net-guardia/src/core/ml/engine.rs +++ b/net-guardia/src/core/ml/engine.rs @@ -9,11 +9,11 @@ use tokio::time::interval; use super::aggregator::AttackAggregator; use super::config_loader::InferenceConfig; use super::drift_detector::DriftDetector; -use super::feature_extractor::FlowFeatures; use super::flow_tracker::{FlowData, FlowTracker}; use super::inference::Inference; use super::model_loader::MLModels; use super::traffic_logger::TrafficLogger; +use crate::model::detection::flow_features::FlowFeatures; use super::alert::MLAlert; use crate::model::log::ml::MLLog; diff --git a/net-guardia/src/core/ml/feature_extractor.rs b/net-guardia/src/core/ml/feature_extractor.rs index 8387853..5063a4d 100644 --- a/net-guardia/src/core/ml/feature_extractor.rs +++ b/net-guardia/src/core/ml/feature_extractor.rs @@ -1,15 +1,9 @@ -use std::collections::HashMap; - use common::define::tcp_flags::*; use super::flow_tracker::FlowData; -use crate::model::ml_detection::{ClipParams, PacketData}; +use crate::model::ml_detection::PacketData; -#[derive(Debug, Clone)] -pub struct FlowFeatures { - pub features: Vec, - pub feature_num: usize, -} +use crate::model::detection::flow_features::FlowFeatures; impl FlowFeatures { pub fn extract(flow: &FlowData, feature_names: &[String]) -> Self { @@ -24,133 +18,6 @@ impl FlowFeatures { Self { features, feature_num } } - - pub fn normalize(&mut self, means: &[f64], stds: &[f64]) { - for i in 0..self.feature_num { - if stds[i] > 0.0 { - self.features[i] = (self.features[i] - means[i]) / stds[i]; - } else { - self.features[i] = 0.0; - } - } - } - - pub fn clip(&mut self, clip_min: f64, clip_max: f64) { - for i in 0..self.feature_num { - self.features[i] = self.features[i].max(clip_min).min(clip_max); - } - } - - pub fn winsorize(&mut self, clip_params: &HashMap, feature_names: &[String]) { - for (i, feature_name) in feature_names.iter().enumerate() { - if i < self.feature_num - && let Some(params) = clip_params.get(feature_name) - { - self.features[i] = self.features[i].clamp(params.lower, params.upper); - } - } - } - - pub fn all_feature_names() -> Vec<&'static str> { - vec![ - "Destination Port", - "Protocol", - "Flow Duration", - "Total Fwd Packets", - "Total Backward Packets", - "Total Length of Fwd Packets", - "Total Length of Bwd Packets", - "Fwd Packet Length Max", - "Fwd Packet Length Min", - "Fwd Packet Length Mean", - "Fwd Packet Length Std", - "Bwd Packet Length Max", - "Bwd Packet Length Min", - "Bwd Packet Length Mean", - "Bwd Packet Length Std", - "Flow Bytes/s", - "Flow Packets/s", - "Flow IAT Mean", - "Flow IAT Std", - "Flow IAT Max", - "Flow IAT Min", - "Fwd IAT Total", - "Fwd IAT Mean", - "Fwd IAT Std", - "Fwd IAT Max", - "Fwd IAT Min", - "Bwd IAT Total", - "Bwd IAT Mean", - "Bwd IAT Std", - "Bwd IAT Max", - "Bwd IAT Min", - "Fwd PSH Flags", - "Bwd PSH Flags", - "Fwd URG Flags", - "Bwd URG Flags", - "Fwd Header Length", - "Bwd Header Length", - "Fwd Packets/s", - "Bwd Packets/s", - "Min Packet Length", - "Max Packet Length", - "Packet Length Mean", - "Packet Length Std", - "Packet Length Variance", - "FIN Flag Count", - "SYN Flag Count", - "RST Flag Count", - "PSH Flag Count", - "ACK Flag Count", - "URG Flag Count", - "CWE Flag Count", - "ECE Flag Count", - "Down/Up Ratio", - "Average Packet Size", - "Avg Fwd Segment Size", - "Avg Bwd Segment Size", - "Fwd Header Length.1", - "Fwd Avg Bytes/Bulk", - "Fwd Avg Packets/Bulk", - "Fwd Avg Bulk Rate", - "Bwd Avg Bytes/Bulk", - "Bwd Avg Packets/Bulk", - "Bwd Avg Bulk Rate", - "Subflow Fwd Packets", - "Subflow Fwd Bytes", - "Subflow Bwd Packets", - "Subflow Bwd Bytes", - "Init_Win_bytes_forward", - "Init_Win_bytes_backward", - "act_data_pkt_fwd", - "min_seg_size_forward", - "Active Mean", - "Active Std", - "Active Max", - "Active Min", - "Idle Mean", - "Idle Std", - "Idle Max", - "Idle Min", - // Phase 2: new features - "fwd_iat_std", - "bwd_iat_std", - "flow_iat_std", - "fwd_bwd_bytes_ratio", - "pkt_len_variance", - "fwd_iat_skewness", - ] - } - - pub fn all_feature_names_owned() -> Vec { - Self::all_feature_names().iter().map(|s| s.to_string()).collect() - } - - pub fn to_csv_record(&self) -> Vec { - let mut record: Vec = self.features.iter().map(|f| f.to_string()).collect(); - record.push("BENIGN".to_string()); - record - } } /// All statistics pre-computed once from a FlowData, then looked up by feature name. diff --git a/net-guardia/src/core/ml/inference.rs b/net-guardia/src/core/ml/inference.rs index e0d79fa..3c90702 100644 --- a/net-guardia/src/core/ml/inference.rs +++ b/net-guardia/src/core/ml/inference.rs @@ -4,9 +4,9 @@ use macros::log; use tract_onnx::prelude::*; use super::config_loader::InferenceConfig; -use super::feature_extractor::FlowFeatures; use super::flow_tracker::FlowData; use super::model_loader::MLModels; +use crate::model::detection::flow_features::FlowFeatures; use crate::model::log::ml::MLLog; use crate::model::ml_detection::DetectionResult; diff --git a/net-guardia/src/core/playbook_service.rs b/net-guardia/src/core/playbook_service.rs index 66b3a77..7e74398 100644 --- a/net-guardia/src/core/playbook_service.rs +++ b/net-guardia/src/core/playbook_service.rs @@ -7,6 +7,7 @@ use macros::log; use crate::model::error::Error; use crate::model::error::soar::SoarError; +use crate::model::soar::playbook_data::*; /// Domain service for SOAR playbook CRUD operations. /// Coordinates DB reads/writes, SOAR engine cache refresh, and eBPF unblock. @@ -16,55 +17,6 @@ pub struct PlaybookService { access_control: Arc, } -/// Input for creating a new playbook. -pub struct CreatePlaybookInput { - pub name: String, - pub trigger_event: String, - pub condition_threshold: Option, - pub condition_count: Option, - pub condition_window_secs: Option, - pub cooldown_secs: i64, - pub actions: Vec<(String, String)>, // (action_type, params_json) -} - -/// Flattened playbook representation for API responses. -pub struct PlaybookData { - pub id: i64, - pub name: String, - pub enabled: bool, - pub trigger_event: String, - pub condition_threshold: Option, - pub condition_count: Option, - pub condition_window_secs: Option, - pub cooldown_secs: i64, - pub actions: Vec, -} - -pub struct ActionData { - pub id: i64, - pub action_order: i64, - pub action_type: String, - pub params: serde_json::Value, -} - -/// Execution record from soar_executions table. -pub struct ExecutionData { - pub id: i64, - pub playbook_id: i64, - pub source_ip: Option, - pub trigger_event: String, - pub actions_executed: serde_json::Value, - pub created_at: String, -} - -/// Active block record from soar_block_rules table. -pub struct ActiveBlockData { - pub id: i64, - pub source_ip: String, - pub playbook_id: i64, - pub expires_at: String, -} - impl PlaybookService { pub fn new(db: Arc, soar_engine: Arc, access_control: Arc) -> Self { Self { diff --git a/net-guardia/src/core/rate_limit_service.rs b/net-guardia/src/core/rate_limit_service.rs index efc1955..31927b0 100644 --- a/net-guardia/src/core/rate_limit_service.rs +++ b/net-guardia/src/core/rate_limit_service.rs @@ -44,13 +44,4 @@ impl RateLimitService { } } -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct RateLimitSettings { - pub packet_rate: Option, - pub syn_rate: Option, - pub udp_rate: Option, - pub dns_rate: Option, - pub window_ns: Option, -} +use crate::model::system::rate_limit_settings::RateLimitSettings; diff --git a/net-guardia/src/core/report/data.rs b/net-guardia/src/core/report/data.rs index 10198ae..a01d2e3 100644 --- a/net-guardia/src/core/report/data.rs +++ b/net-guardia/src/core/report/data.rs @@ -1,182 +1 @@ -use serde::{Deserialize, Serialize}; - -use crate::interface::port::repository::RepositoryPort; -use crate::model::error::Error; - -/// Shared report data structure used by both HTML email and PDF report. -#[derive(Debug, Clone, Serialize)] -pub struct ReportData { - pub period: String, - pub generated_at: String, - pub executive_summary: ExecutiveSummary, - pub threat_breakdown: Vec, - pub top_blocked_ips: Vec, - pub geo_distribution: Vec, - pub soar_activity: SoarActivity, - pub system_health: SystemHealthSummary, - pub recommendations: Vec, -} - -#[derive(Debug, Clone, Serialize)] -pub struct ExecutiveSummary { - pub total_threats: u64, - pub total_blocked: u64, - pub uptime_percent: f64, - pub active_rules: u64, -} - -#[derive(Debug, Clone, Serialize)] -pub struct ThreatBreakdownItem { - pub threat_type: String, - pub count: u64, - pub trend: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BlockedIpItem { - pub ip: String, - pub count: u64, - pub country: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GeoItem { - pub country: String, - pub threat_count: u64, -} - -#[derive(Debug, Clone, Serialize)] -pub struct SoarActivity { - pub auto_blocks_executed: u64, - pub playbooks_triggered: u64, - pub auto_unblocks: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SystemHealthSummary { - pub avg_cpu_percent: f64, - pub avg_memory_percent: f64, - pub disk_usage_percent: f64, - pub ebpf_status: String, -} - -impl ReportData { - /// Build report data from database settings (aggregated by the ML pipeline). - pub fn from_database(db: &dyn RepositoryPort) -> Result { - let now = chrono::Local::now(); - let period = format!( - "{} — {}", - (now - chrono::Duration::days(7)).format("%Y-%m-%d"), - now.format("%Y-%m-%d") - ); - - let threats_count: u64 = db - .get_setting("weekly_threats_count")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - - let top_ips: Vec = db - .get_setting("weekly_top_ips")? - .and_then(|v| serde_json::from_str(&v).ok()) - .unwrap_or_else(|| { - vec![BlockedIpItem { - ip: "—".into(), - count: 0, - country: "N/A".into(), - }] - }); - - let breakdown: Vec = db - .get_setting("weekly_threat_breakdown")? - .and_then(|v| { - let obj: serde_json::Value = serde_json::from_str(&v).ok()?; - let items = obj - .as_object()? - .iter() - .map(|(k, v)| ThreatBreakdownItem { - threat_type: k.clone(), - count: v.as_u64().unwrap_or(0), - trend: "—".into(), - }) - .collect(); - Some(items) - }) - .unwrap_or_default(); - - let health: SystemHealthSummary = db - .get_setting("weekly_system_health")? - .and_then(|v| serde_json::from_str(&v).ok()) - .unwrap_or(SystemHealthSummary { - avg_cpu_percent: 0.0, - avg_memory_percent: 0.0, - disk_usage_percent: 0.0, - ebpf_status: "running".into(), - }); - - // Generate recommendations based on data - let mut recommendations = Vec::new(); - if threats_count > 10 { - recommendations.push("Consider enabling geo-blocking for high-risk regions".into()); - } - if breakdown.iter().any(|b| b.threat_type == "port_scan" && b.count > 50) { - recommendations.push("Review exposed ports and consider tightening protocol filter rules".into()); - } - if recommendations.is_empty() { - recommendations.push("No action needed — your network security posture is healthy".into()); - } - - let uptime_percent: f64 = db - .get_setting("system_uptime_percent")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0.0); - - let active_rules: u64 = db - .get_setting("active_rules_count")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - - let geo_distribution: Vec = db - .get_setting("weekly_geo_distribution")? - .and_then(|v| serde_json::from_str(&v).ok()) - .unwrap_or_default(); - - let auto_blocks: u64 = db - .get_setting("weekly_soar_blocks")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - let playbooks_triggered: u64 = db - .get_setting("weekly_soar_triggers")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - let auto_unblocks: u64 = db - .get_setting("weekly_soar_unblocks")? - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - - let blocked_count: u64 = db - .get_setting("weekly_blocked_count")? - .and_then(|v| v.parse().ok()) - .unwrap_or(auto_blocks); - - Ok(ReportData { - period, - generated_at: now.format("%Y-%m-%d %H:%M:%S").to_string(), - executive_summary: ExecutiveSummary { - total_threats: threats_count, - total_blocked: blocked_count, - uptime_percent, - active_rules, - }, - threat_breakdown: breakdown, - top_blocked_ips: top_ips, - geo_distribution, - soar_activity: SoarActivity { - auto_blocks_executed: auto_blocks, - playbooks_triggered, - auto_unblocks, - }, - system_health: health, - recommendations, - }) - } -} +// Types are available via crate::model::report::data diff --git a/net-guardia/src/core/report/engine.rs b/net-guardia/src/core/report/engine.rs index 38a7e76..7a5d771 100644 --- a/net-guardia/src/core/report/engine.rs +++ b/net-guardia/src/core/report/engine.rs @@ -1,10 +1,10 @@ use std::path::PathBuf; use tracing::info; -use crate::core::report::data::ReportData; use crate::interface::port::repository::RepositoryPort; use crate::model::error::Error; use crate::model::error::notification::NotificationError; +use crate::model::report::data::ReportData; /// Generate a self-contained HTML security report and write to disk. /// Returns the path to the generated HTML file. diff --git a/net-guardia/src/core/soar/engine.rs b/net-guardia/src/core/soar/engine.rs index 33e3e4e..86aa6de 100644 --- a/net-guardia/src/core/soar/engine.rs +++ b/net-guardia/src/core/soar/engine.rs @@ -11,11 +11,11 @@ use crate::adapter::persistence::Database; use crate::core::ebpf::rate_limit::RateLimitConfig; use crate::infrastructure::communication_manager::CommunicationManager; use crate::infrastructure::geoip::GeoIpService; -use crate::interface::communication::event_types::ThreatDetectedEvent; use crate::interface::port::access_control::AccessControlPort; use crate::interface::port::notification::{AlertNotifier, AlertPayload}; use crate::model::error::Error; use crate::model::error::soar::SoarError; +use crate::model::event::ThreatDetectedEvent; use crate::model::log::soar::SoarLog; use crate::model::soar::playbook::{Playbook, PlaybookAction}; diff --git a/net-guardia/src/core/system.rs b/net-guardia/src/core/system.rs index c5d2100..e960696 100644 --- a/net-guardia/src/core/system.rs +++ b/net-guardia/src/core/system.rs @@ -23,14 +23,15 @@ use crate::infrastructure::app_services::AppServices; use crate::infrastructure::audit_logger::AuditLogger; use crate::infrastructure::communication_manager::CommunicationManager; use crate::infrastructure::geoip::GeoIpService; -use crate::infrastructure::http_server::{HttpServerParams, ReadinessState}; +use crate::infrastructure::http_server::HttpServerParams; use crate::infrastructure::service_factory::ServiceFactory; -use crate::interface::communication::event_types::{DriftDetectedEvent, ThreatDetectedEvent}; use crate::model::error::Error; use crate::model::error::system::SystemError; +use crate::model::event::{DriftDetectedEvent, ThreatDetectedEvent}; use crate::model::log::ml::MLLog; use crate::model::log::system::SystemLog; use crate::model::ml_detection::AlertMessage; +use crate::model::system::readiness::ReadinessState; /// Orchestrates system lifecycle: startup ordering and shutdown. /// Construction is delegated to `ServiceFactory::build()`. diff --git a/net-guardia/src/infrastructure/app_services.rs b/net-guardia/src/infrastructure/app_services.rs index bdc0817..55a6dff 100644 --- a/net-guardia/src/infrastructure/app_services.rs +++ b/net-guardia/src/infrastructure/app_services.rs @@ -9,12 +9,12 @@ use crate::core::ml::alert::MLAlert; use crate::core::ml::config_loader::InferenceConfig; use crate::core::ml::drift_detector::DriftDetector; use crate::core::ml::engine::Engine; -use crate::core::ml::feature_extractor::FlowFeatures; use crate::core::ml::model_loader::MLModels; use crate::core::ml::traffic_logger::TrafficLogger; use crate::infrastructure::app_config::AppConfig; use crate::infrastructure::health::SystemHealth; use crate::infrastructure::statistics::FlowStatistics; +use crate::model::detection::flow_features::FlowFeatures; use crate::model::error::Error; use crate::model::error::misc::MiscError; use crate::model::error::system::SystemError; diff --git a/net-guardia/src/infrastructure/audit_logger.rs b/net-guardia/src/infrastructure/audit_logger.rs index b941d30..88fe989 100644 --- a/net-guardia/src/infrastructure/audit_logger.rs +++ b/net-guardia/src/infrastructure/audit_logger.rs @@ -4,7 +4,7 @@ use macros::log; use crate::adapter::persistence::Database; use crate::infrastructure::communication_manager::CommunicationManager; -use crate::interface::communication::event_types::{AuditEvent, DriftDetectedEvent}; +use crate::model::event::{AuditEvent, DriftDetectedEvent}; use crate::model::log::system::SystemLog; /// Subscribes to `AuditEvent` and persists each entry to the `audit_log` table. diff --git a/net-guardia/src/infrastructure/enforce_mode_handler.rs b/net-guardia/src/infrastructure/enforce_mode_handler.rs index dafb840..d8dd9fa 100644 --- a/net-guardia/src/infrastructure/enforce_mode_handler.rs +++ b/net-guardia/src/infrastructure/enforce_mode_handler.rs @@ -7,11 +7,11 @@ use macros::log; use crate::infrastructure::communication_manager::CommunicationManager; use crate::interface::communication::command::CommandHandler; use crate::interface::communication::command_types::ChangeEnforceModeCommand; -use crate::interface::communication::event_types::AuditEvent; use crate::interface::communication::query::QueryHandler; use crate::interface::communication::query_types::GetEnforceModeQuery; use crate::interface::port::repository::RepositoryPort; use crate::model::error::Error; +use crate::model::event::AuditEvent; use crate::model::log::system::SystemLog; /// Map enforce-mode string to u8: monitor=0, ml_only=1, enforce=2. @@ -85,7 +85,7 @@ mod tests { let db = Arc::new(Database::new(":memory:").unwrap()) as Arc; let cache = Arc::new(AtomicU8::new(0)); let comm = Arc::new(CommunicationManager::new()); - comm.register_event_type::(); + comm.register_event_type::(); let handler = Arc::new(EnforceModeHandler::new(db, comm.clone(), cache)); let _ = comm .clone() diff --git a/net-guardia/src/infrastructure/geoip.rs b/net-guardia/src/infrastructure/geoip.rs index b87d74f..3e5a679 100644 --- a/net-guardia/src/infrastructure/geoip.rs +++ b/net-guardia/src/infrastructure/geoip.rs @@ -8,18 +8,9 @@ use std::num::NonZeroUsize; use tokio::sync::RwLock; use tokio::task; +use crate::model::monitoring::geolocation::GeoLocation; use crate::utils::ip_address; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct GeoLocation { - pub country: Option, - pub country_code: Option, - pub city: Option, - pub latitude: Option, - pub longitude: Option, - pub timezone: Option, -} - pub struct GeoIpService { reader: Arc>>, cache: Arc>>>, diff --git a/net-guardia/src/infrastructure/http_server.rs b/net-guardia/src/infrastructure/http_server.rs index bcb39a9..8ab95e4 100644 --- a/net-guardia/src/infrastructure/http_server.rs +++ b/net-guardia/src/infrastructure/http_server.rs @@ -31,26 +31,7 @@ use macros::log; /// Shared flag: true when all services (eBPF, ML, SOAR) are fully initialized. pub type ReadyFlag = Arc; -/// Per-subsystem readiness state exposed by `/health/ready`. -pub struct ReadinessState { - pub db_connected: std::sync::atomic::AtomicBool, - pub ml_model_loaded: std::sync::atomic::AtomicBool, - pub soar_engine_running: std::sync::atomic::AtomicBool, - pub ebpf_attached: std::sync::atomic::AtomicBool, - pub started_at: std::time::Instant, -} - -impl ReadinessState { - pub fn new() -> Self { - Self { - db_connected: std::sync::atomic::AtomicBool::new(false), - ml_model_loaded: std::sync::atomic::AtomicBool::new(false), - soar_engine_running: std::sync::atomic::AtomicBool::new(false), - ebpf_attached: std::sync::atomic::AtomicBool::new(false), - started_at: std::time::Instant::now(), - } - } -} +use crate::model::system::readiness::ReadinessState; /// Parameters for starting the HTTP server, avoiding `#[cfg]` on function params. pub struct HttpServerParams { diff --git a/net-guardia/src/infrastructure/service_factory.rs b/net-guardia/src/infrastructure/service_factory.rs index e4c6d8c..26442e7 100644 --- a/net-guardia/src/infrastructure/service_factory.rs +++ b/net-guardia/src/infrastructure/service_factory.rs @@ -20,7 +20,7 @@ use crate::core::dns_filter_service::DnsFilterService; use crate::core::ebpf::EbpfServices; use crate::core::email::scheduler::ReportScheduler; use crate::core::ml::config_loader::InferenceConfig; -use crate::core::ml::drift_detector::{DriftDetector, FeatureBaselines}; +use crate::core::ml::drift_detector::DriftDetector; use crate::core::notification_service::NotificationService; use crate::core::playbook_service::PlaybookService; use crate::core::rate_limit_service::RateLimitService; @@ -36,6 +36,7 @@ use crate::interface::communication::query_types::GetEnforceModeQuery; use crate::interface::port::access_control::AccessControlPort; use crate::interface::port::notification::AlertNotifier; use crate::interface::port::repository::RepositoryPort; +use crate::model::detection::drift::FeatureBaselines; use crate::model::direction::FlowDirection; use crate::model::error::Error; use crate::model::error::ebpf::EbpfError; @@ -148,9 +149,9 @@ impl ServiceFactory { .build(); // Register event type channels - comm.register_event_type::(); - comm.register_event_type::(); - comm.register_event_type::(); + comm.register_event_type::(); + comm.register_event_type::(); + comm.register_event_type::(); // Seed default SOAR playbooks if empty db.seed_default_playbooks()?; diff --git a/net-guardia/src/interface/communication/event_types.rs b/net-guardia/src/interface/communication/event_types.rs index 947eb3b..9668ee6 100644 --- a/net-guardia/src/interface/communication/event_types.rs +++ b/net-guardia/src/interface/communication/event_types.rs @@ -1,52 +1 @@ -use crate::interface::communication::event::Event; - -// ── ML Events ──────────────────────────────────────────────────────── - -/// Fired when the ML engine detects a potential threat. -/// Consumed by the SOAR engine to trigger automated responses. -#[derive(Debug, Clone)] -pub struct ThreatDetectedEvent { - pub attack_type: String, - pub confidence: f32, - /// Source IP address (e.g. "192.168.1.100") - pub source_ip: String, - /// Destination IP address (e.g. "10.0.0.1") - pub dest_ip: String, - /// Number of alert flows from this src_ip in recent window - pub flow_count: u32, - /// Packets per second of the triggering flow - pub packet_rate: f64, - /// IP protocol number (6=TCP, 17=UDP) - pub protocol: u8, - /// Source country ISO 3166-1 alpha-2 code, None if GeoIP unavailable - pub geoip_country: Option, - /// Whether this src_ip had a block action in the past 24h - pub is_repeat_offender: bool, -} - -impl Event for ThreatDetectedEvent {} - -/// Fired when the ML drift detector finds feature drift beyond 3σ. -#[derive(Debug, Clone)] -pub struct DriftDetectedEvent { - pub drifted_features: Vec, - pub max_deviation: f64, -} - -impl Event for DriftDetectedEvent {} - -// ── Audit Events ──────────────────────────────────────────────────── - -/// Fired for auditable actions (enforce mode changes, playbook CRUD, etc.). -/// Consumed by AuditLogger to persist to DB and structured logs. -#[derive(Debug, Clone)] -pub struct AuditEvent { - /// Who performed the action: "admin", "system", "soar" - pub actor: String, - /// What action was performed: "enforce_mode_changed", "playbook_created", etc. - pub action: String, - /// JSON string with action-specific details - pub detail: String, -} - -impl Event for AuditEvent {} +// Types are available via crate::model::event diff --git a/net-guardia/src/model/detection/drift.rs b/net-guardia/src/model/detection/drift.rs new file mode 100644 index 0000000..8a8ff3a --- /dev/null +++ b/net-guardia/src/model/detection/drift.rs @@ -0,0 +1,31 @@ +use crate::model::system::config::MLInferenceConfig; + +/// Baselines loaded from the inference config (scaler mean / std). +/// If inference_config has no scaler data, drift detection is disabled. +pub struct FeatureBaselines { + pub names: Vec, + pub means: Vec, + pub stds: Vec, +} + +impl FeatureBaselines { + /// Build baselines from the ML inference config. + /// Returns `None` if the config has no features (drift detection disabled). + pub fn from_inference_config(config: &MLInferenceConfig) -> Option { + if config.ae_feature_names.is_empty() { + return None; + } + Some(Self { + names: config.ae_feature_names.clone(), + means: config.ae_scaler_mean.clone(), + stds: config.ae_scaler_std.clone(), + }) + } +} + +/// Report emitted when feature drift is detected. +#[derive(Debug, Clone)] +pub struct DriftReport { + pub drifted_features: Vec, + pub max_deviation: f64, +} diff --git a/net-guardia/src/model/detection/flow_features.rs b/net-guardia/src/model/detection/flow_features.rs new file mode 100644 index 0000000..7b6d39c --- /dev/null +++ b/net-guardia/src/model/detection/flow_features.rs @@ -0,0 +1,138 @@ +use std::collections::HashMap; + +use crate::model::ml_detection::ClipParams; + +#[derive(Debug, Clone)] +pub struct FlowFeatures { + pub features: Vec, + pub feature_num: usize, +} + +impl FlowFeatures { + pub fn normalize(&mut self, means: &[f64], stds: &[f64]) { + for i in 0..self.feature_num { + if stds[i] > 0.0 { + self.features[i] = (self.features[i] - means[i]) / stds[i]; + } else { + self.features[i] = 0.0; + } + } + } + + pub fn clip(&mut self, clip_min: f64, clip_max: f64) { + for i in 0..self.feature_num { + self.features[i] = self.features[i].max(clip_min).min(clip_max); + } + } + + pub fn winsorize(&mut self, clip_params: &HashMap, feature_names: &[String]) { + for (i, feature_name) in feature_names.iter().enumerate() { + if i < self.feature_num + && let Some(params) = clip_params.get(feature_name) + { + self.features[i] = self.features[i].clamp(params.lower, params.upper); + } + } + } + + pub fn all_feature_names() -> Vec<&'static str> { + vec![ + "Destination Port", + "Protocol", + "Flow Duration", + "Total Fwd Packets", + "Total Backward Packets", + "Total Length of Fwd Packets", + "Total Length of Bwd Packets", + "Fwd Packet Length Max", + "Fwd Packet Length Min", + "Fwd Packet Length Mean", + "Fwd Packet Length Std", + "Bwd Packet Length Max", + "Bwd Packet Length Min", + "Bwd Packet Length Mean", + "Bwd Packet Length Std", + "Flow Bytes/s", + "Flow Packets/s", + "Flow IAT Mean", + "Flow IAT Std", + "Flow IAT Max", + "Flow IAT Min", + "Fwd IAT Total", + "Fwd IAT Mean", + "Fwd IAT Std", + "Fwd IAT Max", + "Fwd IAT Min", + "Bwd IAT Total", + "Bwd IAT Mean", + "Bwd IAT Std", + "Bwd IAT Max", + "Bwd IAT Min", + "Fwd PSH Flags", + "Bwd PSH Flags", + "Fwd URG Flags", + "Bwd URG Flags", + "Fwd Header Length", + "Bwd Header Length", + "Fwd Packets/s", + "Bwd Packets/s", + "Min Packet Length", + "Max Packet Length", + "Packet Length Mean", + "Packet Length Std", + "Packet Length Variance", + "FIN Flag Count", + "SYN Flag Count", + "RST Flag Count", + "PSH Flag Count", + "ACK Flag Count", + "URG Flag Count", + "CWE Flag Count", + "ECE Flag Count", + "Down/Up Ratio", + "Average Packet Size", + "Avg Fwd Segment Size", + "Avg Bwd Segment Size", + "Fwd Header Length.1", + "Fwd Avg Bytes/Bulk", + "Fwd Avg Packets/Bulk", + "Fwd Avg Bulk Rate", + "Bwd Avg Bytes/Bulk", + "Bwd Avg Packets/Bulk", + "Bwd Avg Bulk Rate", + "Subflow Fwd Packets", + "Subflow Fwd Bytes", + "Subflow Bwd Packets", + "Subflow Bwd Bytes", + "Init_Win_bytes_forward", + "Init_Win_bytes_backward", + "act_data_pkt_fwd", + "min_seg_size_forward", + "Active Mean", + "Active Std", + "Active Max", + "Active Min", + "Idle Mean", + "Idle Std", + "Idle Max", + "Idle Min", + // Phase 2: new features + "fwd_iat_std", + "bwd_iat_std", + "flow_iat_std", + "fwd_bwd_bytes_ratio", + "pkt_len_variance", + "fwd_iat_skewness", + ] + } + + pub fn all_feature_names_owned() -> Vec { + Self::all_feature_names().iter().map(|s| s.to_string()).collect() + } + + pub fn to_csv_record(&self) -> Vec { + let mut record: Vec = self.features.iter().map(|f| f.to_string()).collect(); + record.push("BENIGN".to_string()); + record + } +} diff --git a/net-guardia/src/model/detection/mod.rs b/net-guardia/src/model/detection/mod.rs index 18a86f4..f960169 100644 --- a/net-guardia/src/model/detection/mod.rs +++ b/net-guardia/src/model/detection/mod.rs @@ -1 +1,3 @@ +pub mod drift; +pub mod flow_features; pub mod ml_detection; diff --git a/net-guardia/src/model/event.rs b/net-guardia/src/model/event.rs new file mode 100644 index 0000000..125247a --- /dev/null +++ b/net-guardia/src/model/event.rs @@ -0,0 +1,52 @@ +use crate::interface::communication::event::Event; + +// -- ML Events ---------------------------------------------------------------- + +/// Fired when the ML engine detects a potential threat. +/// Consumed by the SOAR engine to trigger automated responses. +#[derive(Debug, Clone)] +pub struct ThreatDetectedEvent { + pub attack_type: String, + pub confidence: f32, + /// Source IP address (e.g. "192.168.1.100") + pub source_ip: String, + /// Destination IP address (e.g. "10.0.0.1") + pub dest_ip: String, + /// Number of alert flows from this src_ip in recent window + pub flow_count: u32, + /// Packets per second of the triggering flow + pub packet_rate: f64, + /// IP protocol number (6=TCP, 17=UDP) + pub protocol: u8, + /// Source country ISO 3166-1 alpha-2 code, None if GeoIP unavailable + pub geoip_country: Option, + /// Whether this src_ip had a block action in the past 24h + pub is_repeat_offender: bool, +} + +impl Event for ThreatDetectedEvent {} + +/// Fired when the ML drift detector finds feature drift beyond 3 sigma. +#[derive(Debug, Clone)] +pub struct DriftDetectedEvent { + pub drifted_features: Vec, + pub max_deviation: f64, +} + +impl Event for DriftDetectedEvent {} + +// -- Audit Events ------------------------------------------------------------- + +/// Fired for auditable actions (enforce mode changes, playbook CRUD, etc.). +/// Consumed by AuditLogger to persist to DB and structured logs. +#[derive(Debug, Clone)] +pub struct AuditEvent { + /// Who performed the action: "admin", "system", "soar" + pub actor: String, + /// What action was performed: "enforce_mode_changed", "playbook_created", etc. + pub action: String, + /// JSON string with action-specific details + pub detail: String, +} + +impl Event for AuditEvent {} diff --git a/net-guardia/src/model/mod.rs b/net-guardia/src/model/mod.rs index b75a013..693177a 100644 --- a/net-guardia/src/model/mod.rs +++ b/net-guardia/src/model/mod.rs @@ -2,9 +2,11 @@ pub mod access_control; pub mod detection; pub mod error; +pub mod event; pub mod identity; pub mod log; pub mod monitoring; +pub mod report; pub mod soar; pub mod system; diff --git a/net-guardia/src/model/monitoring/geolocation.rs b/net-guardia/src/model/monitoring/geolocation.rs new file mode 100644 index 0000000..a38636d --- /dev/null +++ b/net-guardia/src/model/monitoring/geolocation.rs @@ -0,0 +1,9 @@ +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GeoLocation { + pub country: Option, + pub country_code: Option, + pub city: Option, + pub latitude: Option, + pub longitude: Option, + pub timezone: Option, +} diff --git a/net-guardia/src/model/monitoring/mod.rs b/net-guardia/src/model/monitoring/mod.rs index 80413ea..4a6ad00 100644 --- a/net-guardia/src/model/monitoring/mod.rs +++ b/net-guardia/src/model/monitoring/mod.rs @@ -1,4 +1,5 @@ pub mod direction; pub mod drop_event; pub mod flow_stats; +pub mod geolocation; pub mod user_packet; diff --git a/net-guardia/src/model/report/data.rs b/net-guardia/src/model/report/data.rs new file mode 100644 index 0000000..10198ae --- /dev/null +++ b/net-guardia/src/model/report/data.rs @@ -0,0 +1,182 @@ +use serde::{Deserialize, Serialize}; + +use crate::interface::port::repository::RepositoryPort; +use crate::model::error::Error; + +/// Shared report data structure used by both HTML email and PDF report. +#[derive(Debug, Clone, Serialize)] +pub struct ReportData { + pub period: String, + pub generated_at: String, + pub executive_summary: ExecutiveSummary, + pub threat_breakdown: Vec, + pub top_blocked_ips: Vec, + pub geo_distribution: Vec, + pub soar_activity: SoarActivity, + pub system_health: SystemHealthSummary, + pub recommendations: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ExecutiveSummary { + pub total_threats: u64, + pub total_blocked: u64, + pub uptime_percent: f64, + pub active_rules: u64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ThreatBreakdownItem { + pub threat_type: String, + pub count: u64, + pub trend: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlockedIpItem { + pub ip: String, + pub count: u64, + pub country: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GeoItem { + pub country: String, + pub threat_count: u64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SoarActivity { + pub auto_blocks_executed: u64, + pub playbooks_triggered: u64, + pub auto_unblocks: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SystemHealthSummary { + pub avg_cpu_percent: f64, + pub avg_memory_percent: f64, + pub disk_usage_percent: f64, + pub ebpf_status: String, +} + +impl ReportData { + /// Build report data from database settings (aggregated by the ML pipeline). + pub fn from_database(db: &dyn RepositoryPort) -> Result { + let now = chrono::Local::now(); + let period = format!( + "{} — {}", + (now - chrono::Duration::days(7)).format("%Y-%m-%d"), + now.format("%Y-%m-%d") + ); + + let threats_count: u64 = db + .get_setting("weekly_threats_count")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + + let top_ips: Vec = db + .get_setting("weekly_top_ips")? + .and_then(|v| serde_json::from_str(&v).ok()) + .unwrap_or_else(|| { + vec![BlockedIpItem { + ip: "—".into(), + count: 0, + country: "N/A".into(), + }] + }); + + let breakdown: Vec = db + .get_setting("weekly_threat_breakdown")? + .and_then(|v| { + let obj: serde_json::Value = serde_json::from_str(&v).ok()?; + let items = obj + .as_object()? + .iter() + .map(|(k, v)| ThreatBreakdownItem { + threat_type: k.clone(), + count: v.as_u64().unwrap_or(0), + trend: "—".into(), + }) + .collect(); + Some(items) + }) + .unwrap_or_default(); + + let health: SystemHealthSummary = db + .get_setting("weekly_system_health")? + .and_then(|v| serde_json::from_str(&v).ok()) + .unwrap_or(SystemHealthSummary { + avg_cpu_percent: 0.0, + avg_memory_percent: 0.0, + disk_usage_percent: 0.0, + ebpf_status: "running".into(), + }); + + // Generate recommendations based on data + let mut recommendations = Vec::new(); + if threats_count > 10 { + recommendations.push("Consider enabling geo-blocking for high-risk regions".into()); + } + if breakdown.iter().any(|b| b.threat_type == "port_scan" && b.count > 50) { + recommendations.push("Review exposed ports and consider tightening protocol filter rules".into()); + } + if recommendations.is_empty() { + recommendations.push("No action needed — your network security posture is healthy".into()); + } + + let uptime_percent: f64 = db + .get_setting("system_uptime_percent")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0.0); + + let active_rules: u64 = db + .get_setting("active_rules_count")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + + let geo_distribution: Vec = db + .get_setting("weekly_geo_distribution")? + .and_then(|v| serde_json::from_str(&v).ok()) + .unwrap_or_default(); + + let auto_blocks: u64 = db + .get_setting("weekly_soar_blocks")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + let playbooks_triggered: u64 = db + .get_setting("weekly_soar_triggers")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + let auto_unblocks: u64 = db + .get_setting("weekly_soar_unblocks")? + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + + let blocked_count: u64 = db + .get_setting("weekly_blocked_count")? + .and_then(|v| v.parse().ok()) + .unwrap_or(auto_blocks); + + Ok(ReportData { + period, + generated_at: now.format("%Y-%m-%d %H:%M:%S").to_string(), + executive_summary: ExecutiveSummary { + total_threats: threats_count, + total_blocked: blocked_count, + uptime_percent, + active_rules, + }, + threat_breakdown: breakdown, + top_blocked_ips: top_ips, + geo_distribution, + soar_activity: SoarActivity { + auto_blocks_executed: auto_blocks, + playbooks_triggered, + auto_unblocks, + }, + system_health: health, + recommendations, + }) + } +} diff --git a/net-guardia/src/model/report/mod.rs b/net-guardia/src/model/report/mod.rs new file mode 100644 index 0000000..7a345e4 --- /dev/null +++ b/net-guardia/src/model/report/mod.rs @@ -0,0 +1 @@ +pub mod data; diff --git a/net-guardia/src/model/soar/mod.rs b/net-guardia/src/model/soar/mod.rs index c64db5b..4629a63 100644 --- a/net-guardia/src/model/soar/mod.rs +++ b/net-guardia/src/model/soar/mod.rs @@ -1 +1,2 @@ pub mod playbook; +pub mod playbook_data; diff --git a/net-guardia/src/model/soar/playbook_data.rs b/net-guardia/src/model/soar/playbook_data.rs new file mode 100644 index 0000000..5a4aa0b --- /dev/null +++ b/net-guardia/src/model/soar/playbook_data.rs @@ -0,0 +1,48 @@ +/// Input for creating a new playbook. +pub struct CreatePlaybookInput { + pub name: String, + pub trigger_event: String, + pub condition_threshold: Option, + pub condition_count: Option, + pub condition_window_secs: Option, + pub cooldown_secs: i64, + pub actions: Vec<(String, String)>, // (action_type, params_json) +} + +/// Flattened playbook representation for API responses. +pub struct PlaybookData { + pub id: i64, + pub name: String, + pub enabled: bool, + pub trigger_event: String, + pub condition_threshold: Option, + pub condition_count: Option, + pub condition_window_secs: Option, + pub cooldown_secs: i64, + pub actions: Vec, +} + +pub struct ActionData { + pub id: i64, + pub action_order: i64, + pub action_type: String, + pub params: serde_json::Value, +} + +/// Execution record from soar_executions table. +pub struct ExecutionData { + pub id: i64, + pub playbook_id: i64, + pub source_ip: Option, + pub trigger_event: String, + pub actions_executed: serde_json::Value, + pub created_at: String, +} + +/// Active block record from soar_block_rules table. +pub struct ActiveBlockData { + pub id: i64, + pub source_ip: String, + pub playbook_id: i64, + pub expires_at: String, +} diff --git a/net-guardia/src/model/system/mod.rs b/net-guardia/src/model/system/mod.rs index 9225abc..55dcb9a 100644 --- a/net-guardia/src/model/system/mod.rs +++ b/net-guardia/src/model/system/mod.rs @@ -1,2 +1,4 @@ pub mod config; pub mod health; +pub mod rate_limit_settings; +pub mod readiness; diff --git a/net-guardia/src/model/system/rate_limit_settings.rs b/net-guardia/src/model/system/rate_limit_settings.rs new file mode 100644 index 0000000..c756e45 --- /dev/null +++ b/net-guardia/src/model/system/rate_limit_settings.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct RateLimitSettings { + pub packet_rate: Option, + pub syn_rate: Option, + pub udp_rate: Option, + pub dns_rate: Option, + pub window_ns: Option, +} diff --git a/net-guardia/src/model/system/readiness.rs b/net-guardia/src/model/system/readiness.rs new file mode 100644 index 0000000..7312fcc --- /dev/null +++ b/net-guardia/src/model/system/readiness.rs @@ -0,0 +1,20 @@ +/// Per-subsystem readiness state exposed by `/health/ready`. +pub struct ReadinessState { + pub db_connected: std::sync::atomic::AtomicBool, + pub ml_model_loaded: std::sync::atomic::AtomicBool, + pub soar_engine_running: std::sync::atomic::AtomicBool, + pub ebpf_attached: std::sync::atomic::AtomicBool, + pub started_at: std::time::Instant, +} + +impl ReadinessState { + pub fn new() -> Self { + Self { + db_connected: std::sync::atomic::AtomicBool::new(false), + ml_model_loaded: std::sync::atomic::AtomicBool::new(false), + soar_engine_running: std::sync::atomic::AtomicBool::new(false), + ebpf_attached: std::sync::atomic::AtomicBool::new(false), + started_at: std::time::Instant::now(), + } + } +}