mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
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) <noreply@anthropic.com>
This commit is contained in:
parent
bdd40bcf3a
commit
84d3f52cd9
@ -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")
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<String>,
|
||||
pub means: Vec<f64>,
|
||||
pub stds: Vec<f64>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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<String>,
|
||||
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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<f64>,
|
||||
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<String, ClipParams>, 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<String> {
|
||||
Self::all_feature_names().iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
pub fn to_csv_record(&self) -> Vec<String> {
|
||||
let mut record: Vec<String> = 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.
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<dyn AccessControlPort>,
|
||||
}
|
||||
|
||||
/// Input for creating a new playbook.
|
||||
pub struct CreatePlaybookInput {
|
||||
pub name: String,
|
||||
pub trigger_event: String,
|
||||
pub condition_threshold: Option<f64>,
|
||||
pub condition_count: Option<i64>,
|
||||
pub condition_window_secs: Option<i64>,
|
||||
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<f64>,
|
||||
pub condition_count: Option<i64>,
|
||||
pub condition_window_secs: Option<i64>,
|
||||
pub cooldown_secs: i64,
|
||||
pub actions: Vec<ActionData>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
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<Database>, soar_engine: Arc<SoarEngine>, access_control: Arc<dyn AccessControlPort>) -> Self {
|
||||
Self {
|
||||
|
||||
@ -44,13 +44,4 @@ impl RateLimitService {
|
||||
}
|
||||
}
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RateLimitSettings {
|
||||
pub packet_rate: Option<u64>,
|
||||
pub syn_rate: Option<u64>,
|
||||
pub udp_rate: Option<u64>,
|
||||
pub dns_rate: Option<u64>,
|
||||
pub window_ns: Option<u64>,
|
||||
}
|
||||
use crate::model::system::rate_limit_settings::RateLimitSettings;
|
||||
|
||||
@ -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<ThreatBreakdownItem>,
|
||||
pub top_blocked_ips: Vec<BlockedIpItem>,
|
||||
pub geo_distribution: Vec<GeoItem>,
|
||||
pub soar_activity: SoarActivity,
|
||||
pub system_health: SystemHealthSummary,
|
||||
pub recommendations: Vec<String>,
|
||||
}
|
||||
|
||||
#[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<Self, Error> {
|
||||
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<BlockedIpItem> = 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<ThreatBreakdownItem> = 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<GeoItem> = 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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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()`.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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<dyn RepositoryPort>;
|
||||
let cache = Arc::new(AtomicU8::new(0));
|
||||
let comm = Arc::new(CommunicationManager::new());
|
||||
comm.register_event_type::<crate::interface::communication::event_types::AuditEvent>();
|
||||
comm.register_event_type::<crate::model::event::AuditEvent>();
|
||||
let handler = Arc::new(EnforceModeHandler::new(db, comm.clone(), cache));
|
||||
let _ = comm
|
||||
.clone()
|
||||
|
||||
@ -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<String>,
|
||||
pub country_code: Option<String>,
|
||||
pub city: Option<String>,
|
||||
pub latitude: Option<f64>,
|
||||
pub longitude: Option<f64>,
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
|
||||
pub struct GeoIpService {
|
||||
reader: Arc<Reader<Vec<u8>>>,
|
||||
cache: Arc<RwLock<LruCache<IpAddr, Option<GeoLocation>>>>,
|
||||
|
||||
@ -31,26 +31,7 @@ use macros::log;
|
||||
/// Shared flag: true when all services (eBPF, ML, SOAR) are fully initialized.
|
||||
pub type ReadyFlag = Arc<std::sync::atomic::AtomicBool>;
|
||||
|
||||
/// 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 {
|
||||
|
||||
@ -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::<crate::interface::communication::event_types::ThreatDetectedEvent>();
|
||||
comm.register_event_type::<crate::interface::communication::event_types::DriftDetectedEvent>();
|
||||
comm.register_event_type::<crate::interface::communication::event_types::AuditEvent>();
|
||||
comm.register_event_type::<crate::model::event::ThreatDetectedEvent>();
|
||||
comm.register_event_type::<crate::model::event::DriftDetectedEvent>();
|
||||
comm.register_event_type::<crate::model::event::AuditEvent>();
|
||||
|
||||
// Seed default SOAR playbooks if empty
|
||||
db.seed_default_playbooks()?;
|
||||
|
||||
@ -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<String>,
|
||||
/// 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<String>,
|
||||
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
|
||||
|
||||
31
net-guardia/src/model/detection/drift.rs
Normal file
31
net-guardia/src/model/detection/drift.rs
Normal file
@ -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<String>,
|
||||
pub means: Vec<f64>,
|
||||
pub stds: Vec<f64>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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<String>,
|
||||
pub max_deviation: f64,
|
||||
}
|
||||
138
net-guardia/src/model/detection/flow_features.rs
Normal file
138
net-guardia/src/model/detection/flow_features.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::model::ml_detection::ClipParams;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlowFeatures {
|
||||
pub features: Vec<f64>,
|
||||
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<String, ClipParams>, 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<String> {
|
||||
Self::all_feature_names().iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
pub fn to_csv_record(&self) -> Vec<String> {
|
||||
let mut record: Vec<String> = self.features.iter().map(|f| f.to_string()).collect();
|
||||
record.push("BENIGN".to_string());
|
||||
record
|
||||
}
|
||||
}
|
||||
@ -1 +1,3 @@
|
||||
pub mod drift;
|
||||
pub mod flow_features;
|
||||
pub mod ml_detection;
|
||||
|
||||
52
net-guardia/src/model/event.rs
Normal file
52
net-guardia/src/model/event.rs
Normal file
@ -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<String>,
|
||||
/// 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<String>,
|
||||
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 {}
|
||||
@ -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;
|
||||
|
||||
|
||||
9
net-guardia/src/model/monitoring/geolocation.rs
Normal file
9
net-guardia/src/model/monitoring/geolocation.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GeoLocation {
|
||||
pub country: Option<String>,
|
||||
pub country_code: Option<String>,
|
||||
pub city: Option<String>,
|
||||
pub latitude: Option<f64>,
|
||||
pub longitude: Option<f64>,
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod direction;
|
||||
pub mod drop_event;
|
||||
pub mod flow_stats;
|
||||
pub mod geolocation;
|
||||
pub mod user_packet;
|
||||
|
||||
182
net-guardia/src/model/report/data.rs
Normal file
182
net-guardia/src/model/report/data.rs
Normal file
@ -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<ThreatBreakdownItem>,
|
||||
pub top_blocked_ips: Vec<BlockedIpItem>,
|
||||
pub geo_distribution: Vec<GeoItem>,
|
||||
pub soar_activity: SoarActivity,
|
||||
pub system_health: SystemHealthSummary,
|
||||
pub recommendations: Vec<String>,
|
||||
}
|
||||
|
||||
#[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<Self, Error> {
|
||||
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<BlockedIpItem> = 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<ThreatBreakdownItem> = 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<GeoItem> = 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
1
net-guardia/src/model/report/mod.rs
Normal file
1
net-guardia/src/model/report/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod data;
|
||||
@ -1 +1,2 @@
|
||||
pub mod playbook;
|
||||
pub mod playbook_data;
|
||||
|
||||
48
net-guardia/src/model/soar/playbook_data.rs
Normal file
48
net-guardia/src/model/soar/playbook_data.rs
Normal file
@ -0,0 +1,48 @@
|
||||
/// Input for creating a new playbook.
|
||||
pub struct CreatePlaybookInput {
|
||||
pub name: String,
|
||||
pub trigger_event: String,
|
||||
pub condition_threshold: Option<f64>,
|
||||
pub condition_count: Option<i64>,
|
||||
pub condition_window_secs: Option<i64>,
|
||||
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<f64>,
|
||||
pub condition_count: Option<i64>,
|
||||
pub condition_window_secs: Option<i64>,
|
||||
pub cooldown_secs: i64,
|
||||
pub actions: Vec<ActionData>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
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,
|
||||
}
|
||||
@ -1,2 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod health;
|
||||
pub mod rate_limit_settings;
|
||||
pub mod readiness;
|
||||
|
||||
10
net-guardia/src/model/system/rate_limit_settings.rs
Normal file
10
net-guardia/src/model/system/rate_limit_settings.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RateLimitSettings {
|
||||
pub packet_rate: Option<u64>,
|
||||
pub syn_rate: Option<u64>,
|
||||
pub udp_rate: Option<u64>,
|
||||
pub dns_rate: Option<u64>,
|
||||
pub window_ns: Option<u64>,
|
||||
}
|
||||
20
net-guardia/src/model/system/readiness.rs
Normal file
20
net-guardia/src/model/system/readiness.rs
Normal file
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user