From c2c4ed5b237038eb8a9252380e18cc0779b71e2a Mon Sep 17 00:00:00 2001 From: DaLaw2 Date: Sat, 25 Apr 2026 19:18:56 +0800 Subject: [PATCH] fix: CI Failed --- net-guardia/src/adapter/ebpf/drop_monitor.rs | 4 - net-guardia/src/adapter/ebpf/xsk_manager.rs | 3 - net-guardia/src/adapter/http/filter.rs | 32 +++-- net-guardia/src/adapter/http/ml.rs | 2 +- net-guardia/src/adapter/http/model_upload.rs | 44 ++++--- .../src/adapter/persistence/api_key.rs | 2 +- .../src/adapter/persistence/setting.rs | 4 +- net-guardia/src/adapter/persistence/soar.rs | 28 +++-- net-guardia/src/core/inference/engine.rs | 2 +- net-guardia/src/core/inference/mod.rs | 2 +- .../src/core/inference/model_watcher.rs | 2 +- .../inference/{inference.rs => runner.rs} | 49 +++----- net-guardia/src/core/response/engine.rs | 117 +++++++++++------- .../src/core/response/playbook_service.rs | 11 +- net-guardia/src/core/response/scheduler.rs | 3 +- net-guardia/src/domain/common/event.rs | 8 -- .../src/domain/common/system/suricata.rs | 6 - net-guardia/src/domain/detection/botnet.rs | 12 +- .../src/domain/detection/flow_tracker.rs | 5 - net-guardia/src/domain/detection/lateral.rs | 12 +- net-guardia/src/domain/detection/scan.rs | 12 +- .../src/infrastructure/app_services.rs | 2 +- .../src/infrastructure/secret_store.rs | 7 +- .../src/infrastructure/service_factory.rs | 47 ++++--- .../src/interface/communication/event.rs | 1 - .../interface/communication/event_types.rs | 1 - .../src/interface/communication/mod.rs | 2 - net-guardia/src/interface/mod.rs | 1 - net-guardia/src/interface/port/soar.rs | 9 +- 29 files changed, 221 insertions(+), 209 deletions(-) rename net-guardia/src/core/inference/{inference.rs => runner.rs} (96%) delete mode 100644 net-guardia/src/interface/communication/event.rs delete mode 100644 net-guardia/src/interface/communication/event_types.rs delete mode 100644 net-guardia/src/interface/communication/mod.rs diff --git a/net-guardia/src/adapter/ebpf/drop_monitor.rs b/net-guardia/src/adapter/ebpf/drop_monitor.rs index df4e07d..2fc76e9 100644 --- a/net-guardia/src/adapter/ebpf/drop_monitor.rs +++ b/net-guardia/src/adapter/ebpf/drop_monitor.rs @@ -31,10 +31,6 @@ impl DropMonitor { self.broadcast_tx.subscribe() } - pub fn get_counters(&self) -> DropCounters { - self.counters.snapshot() - } - /// Record a userspace drop decision (XSK worker's DNS filter) by the /// per-reason counter. Callers at this layer haven't parsed src/dst yet, /// so no broadcast event is emitted — `/api/stats/drops` stays correct, diff --git a/net-guardia/src/adapter/ebpf/xsk_manager.rs b/net-guardia/src/adapter/ebpf/xsk_manager.rs index 37ae864..f0884a7 100644 --- a/net-guardia/src/adapter/ebpf/xsk_manager.rs +++ b/net-guardia/src/adapter/ebpf/xsk_manager.rs @@ -123,7 +123,6 @@ impl XskManager { network.clone(), queue_id, &network.ingress_ifname, - &network.egress_ifname, Direction::Ingress, sink.clone(), dns_filter.clone(), @@ -134,7 +133,6 @@ impl XskManager { network.clone(), queue_id, &network.egress_ifname, - &network.ingress_ifname, Direction::Egress, sink, None, @@ -194,7 +192,6 @@ impl XskPair { config: EbpfConfig, queue_id: u32, rx_ifname: &str, - _tx_ifname: &str, direction: Direction, sink: Option>, dns_filter: Option>, diff --git a/net-guardia/src/adapter/http/filter.rs b/net-guardia/src/adapter/http/filter.rs index 0ad27b5..08ff7f5 100644 --- a/net-guardia/src/adapter/http/filter.rs +++ b/net-guardia/src/adapter/http/filter.rs @@ -5,8 +5,8 @@ use common::model::http_method::HttpMethod; use serde::Deserialize; use crate::adapter::http::response::ok_or_error; -use crate::interface::port::protocol_filter::ProtocolFilterPort; use crate::core::data_plane::dns_filter_service::DnsFilterService; +use crate::interface::port::protocol_filter::ProtocolFilterPort; pub fn initialize() -> Scope { web::scope("/filter") @@ -152,11 +152,17 @@ async fn get_ipv6_ssh_service(service: web::Data) -> imp HttpResponse::Ok().json(service.get_ipv6_ssh_service()) } -async fn add_ipv4_ssh_service(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv4_ssh_service( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv4_ssh_service(ip_addr.into_inner())) } -async fn add_ipv6_ssh_service(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv6_ssh_service( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv6_ssh_service(ip_addr.into_inner())) } @@ -196,11 +202,17 @@ async fn get_ipv6_ssh_white_list(service: web::Data) -> HttpResponse::Ok().json(service.get_ipv6_ssh_white_list()) } -async fn add_ipv4_ssh_white_list(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv4_ssh_white_list( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv4_ssh_white_list(ip_addr.into_inner())) } -async fn add_ipv6_ssh_white_list(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv6_ssh_white_list( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv6_ssh_white_list(ip_addr.into_inner())) } @@ -228,11 +240,17 @@ async fn get_ipv6_ssh_black_list(service: web::Data) -> HttpResponse::Ok().json(service.get_ipv6_ssh_black_list()) } -async fn add_ipv4_ssh_black_list(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv4_ssh_black_list( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv4_ssh_black_list(ip_addr.into_inner())) } -async fn add_ipv6_ssh_black_list(ip_addr: web::Json, service: web::Data) -> impl Responder { +async fn add_ipv6_ssh_black_list( + ip_addr: web::Json, + service: web::Data, +) -> impl Responder { ok_or_error(service.add_ipv6_ssh_black_list(ip_addr.into_inner())) } diff --git a/net-guardia/src/adapter/http/ml.rs b/net-guardia/src/adapter/http/ml.rs index 2ae1334..ca5c1a5 100644 --- a/net-guardia/src/adapter/http/ml.rs +++ b/net-guardia/src/adapter/http/ml.rs @@ -3,7 +3,7 @@ use tokio::sync::broadcast; use crate::core::identity::extractor::AuthClaims; use crate::core::inference::engine::Engine; -use crate::core::inference::inference::Inference; +use crate::core::inference::runner::Inference; use crate::domain::common::config::constants::AUDIT_ACTOR_SECURITY_ADMIN_PREFIX; use crate::domain::common::event::AuditEvent; use crate::domain::detection::model_adapter::ModelSourceState; diff --git a/net-guardia/src/adapter/http/model_upload.rs b/net-guardia/src/adapter/http/model_upload.rs index 6053697..33bec23 100644 --- a/net-guardia/src/adapter/http/model_upload.rs +++ b/net-guardia/src/adapter/http/model_upload.rs @@ -36,8 +36,8 @@ use tokio::task; use uuid::Uuid; use crate::core::identity::extractor::AuthClaims; -use crate::core::inference::inference::Inference; use crate::core::inference::model_loader::build_adapter; +use crate::core::inference::runner::Inference; use crate::domain::common::config::AppConfig; use crate::domain::common::config::constants::{ AUDIT_ACTOR_SECURITY_ADMIN_PREFIX, MANIFEST_FILENAME, MODELS_DIR, STAGING_SUBDIR, @@ -164,16 +164,16 @@ async fn upload( return e.into_response(); } }; - let outcome = validate_and_promote( - &staging_dir, - &summary, - inference.get_ref(), - audit_tx.get_ref(), - promote_lock.get_ref(), - &claims.username, + let outcome = validate_and_promote(&PromoteContext { + staging_dir: &staging_dir, + summary: &summary, + inference: inference.get_ref(), + audit_tx: audit_tx.get_ref(), + promote_lock: promote_lock.get_ref(), + actor_username: &claims.username, batch_size, onnx_load_timeout, - ) + }) .await; // Always sweep staging — successful promote renames the files out, @@ -512,16 +512,26 @@ pub fn looks_like_onnx(first_chunk: &[u8]) -> bool { /// logged but does not roll back the rename; the chain prefers a /// missing audit entry to a rolled-back promote that a downstream /// subscriber may already have reacted to. -async fn validate_and_promote( - staging_dir: &Path, - summary: &UploadSummary, - inference: &Inference, - audit_tx: &broadcast::Sender, - promote_lock: &PromoteGate, - actor_username: &str, +struct PromoteContext<'a> { + staging_dir: &'a Path, + summary: &'a UploadSummary, + inference: &'a Inference, + audit_tx: &'a broadcast::Sender, + promote_lock: &'a PromoteGate, + actor_username: &'a str, batch_size: usize, onnx_load_timeout: Duration, -) -> Result { +} + +async fn validate_and_promote(ctx: &PromoteContext<'_>) -> Result { + let staging_dir = ctx.staging_dir; + let summary = ctx.summary; + let inference = ctx.inference; + let audit_tx = ctx.audit_tx; + let promote_lock = ctx.promote_lock; + let actor_username = ctx.actor_username; + let batch_size = ctx.batch_size; + let onnx_load_timeout = ctx.onnx_load_timeout; let staging_manifest = staging_dir.join(MANIFEST_FILENAME); // Structural manifest validation. The full `build_adapter` pipeline diff --git a/net-guardia/src/adapter/persistence/api_key.rs b/net-guardia/src/adapter/persistence/api_key.rs index 17dd659..0097d5c 100644 --- a/net-guardia/src/adapter/persistence/api_key.rs +++ b/net-guardia/src/adapter/persistence/api_key.rs @@ -106,7 +106,7 @@ impl Database { Ok(conn.last_insert_rowid()) } - pub fn list_api_keys(&self) -> Result)>, Error> { + pub fn list_api_keys(&self) -> Result, Error> { let conn = self.conn()?; let mut stmt = conn.prepare("SELECT id, name, permission_level, created_at, last_used_at FROM api_keys")?; let rows = stmt.query_map([], |row| { diff --git a/net-guardia/src/adapter/persistence/setting.rs b/net-guardia/src/adapter/persistence/setting.rs index 1c95906..fe235a8 100644 --- a/net-guardia/src/adapter/persistence/setting.rs +++ b/net-guardia/src/adapter/persistence/setting.rs @@ -26,7 +26,7 @@ impl Database { Ok(()) } - pub fn get_app_secret(&self, key: &str) -> Result, Error> { + fn get_app_secret(&self, key: &str) -> Result, Error> { let conn = self.conn()?; let result = conn.query_row("SELECT value FROM app_secrets WHERE key = ?1", params![key], |row| { row.get(0) @@ -38,7 +38,7 @@ impl Database { } } - pub fn set_app_secret(&self, key: &str, value: &str) -> Result<(), Error> { + fn set_app_secret(&self, key: &str, value: &str) -> Result<(), Error> { let conn = self.conn()?; conn.execute( "INSERT OR REPLACE INTO app_secrets (key, value) VALUES (?1, ?2)", diff --git a/net-guardia/src/adapter/persistence/soar.rs b/net-guardia/src/adapter/persistence/soar.rs index d553583..6686f80 100644 --- a/net-guardia/src/adapter/persistence/soar.rs +++ b/net-guardia/src/adapter/persistence/soar.rs @@ -6,7 +6,8 @@ use serde_json::Value; use super::Database; use crate::domain::common::error::Error; use crate::domain::response::playbook_data::{ - ActionView, ActiveBlockView, ConditionView, ExecutionView, PendingUnblock, PlaybookView, UpdatePlaybookInput, + ActionView, ActiveBlockView, ConditionView, CreatePlaybookInput, ExecutionView, PendingUnblock, PlaybookView, + UpdatePlaybookInput, }; use crate::interface::port::soar::SoarRepo; @@ -369,12 +370,7 @@ impl SoarRepo for Database { fn insert_playbook_atomic( &self, - name: &str, - trigger_event: &str, - threshold: Option, - count: Option, - window: Option, - cooldown: i64, + input: &CreatePlaybookInput, actions: &[(i64, String, String)], conditions: &[(String, String, String, Option)], ) -> Result { @@ -382,7 +378,7 @@ impl SoarRepo for Database { let tx = conn.transaction()?; tx.execute( "INSERT INTO playbooks (name, trigger_event, condition_threshold, condition_count, condition_window_secs, cooldown_secs) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - params![name, trigger_event, threshold, count, window, cooldown], + params![input.name, input.trigger_event, input.condition_threshold, input.condition_count, input.condition_window_secs, input.cooldown_secs], )?; let playbook_id = tx.last_insert_rowid(); for (action_order, action_type, params_json) in actions { @@ -457,11 +453,21 @@ mod tests { use crate::interface::port::soar::SoarRepo; let db = test_db(); + use crate::domain::response::playbook_data::CreatePlaybookInput; + + let input = CreatePlaybookInput { + name: "atom_pb".to_string(), + trigger_event: "threat".to_string(), + condition_threshold: Some(0.8), + condition_count: None, + condition_window_secs: None, + cooldown_secs: 300, + actions: vec![("block_ip".to_string(), "{}".to_string())], + conditions: vec![], + }; let actions = vec![(1i64, "block_ip".to_string(), "{}".to_string())]; let conditions = vec![("threshold".to_string(), ">=".to_string(), "0.8".to_string(), None)]; - let id = db - .insert_playbook_atomic("atom_pb", "threat", Some(0.8), None, None, 300, &actions, &conditions) - .unwrap(); + let id = db.insert_playbook_atomic(&input, &actions, &conditions).unwrap(); assert!(id > 0); let loaded = db.list_playbooks().unwrap(); assert!(!loaded.is_empty()); diff --git a/net-guardia/src/core/inference/engine.rs b/net-guardia/src/core/inference/engine.rs index 1d5d5a4..1c09926 100644 --- a/net-guardia/src/core/inference/engine.rs +++ b/net-guardia/src/core/inference/engine.rs @@ -14,7 +14,7 @@ use tokio::time::interval; use super::alert::MLAlert; use super::drift_detector::DriftDetectorHandle; -use super::inference::Inference; +use super::runner::Inference; use super::traffic_logger::TrafficLogger; use crate::domain::data_plane::user_packet::UserPacket; use crate::domain::detection::aggregator::AttackAggregator; diff --git a/net-guardia/src/core/inference/mod.rs b/net-guardia/src/core/inference/mod.rs index ae19ac0..38efbf3 100644 --- a/net-guardia/src/core/inference/mod.rs +++ b/net-guardia/src/core/inference/mod.rs @@ -2,8 +2,8 @@ pub mod alert; pub mod config_loader; pub mod drift_detector; pub mod engine; -pub mod inference; pub mod manifest; pub mod model_loader; pub mod model_watcher; +pub mod runner; pub mod traffic_logger; diff --git a/net-guardia/src/core/inference/model_watcher.rs b/net-guardia/src/core/inference/model_watcher.rs index fa50bdc..1f0a0b1 100644 --- a/net-guardia/src/core/inference/model_watcher.rs +++ b/net-guardia/src/core/inference/model_watcher.rs @@ -14,8 +14,8 @@ use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use tokio::sync::mpsc; use tokio::time::sleep; -use super::inference::Inference; use super::model_loader::build_adapter; +use super::runner::Inference; use crate::domain::common::config::AppConfig; use crate::domain::common::config::constants::{MANIFEST_FILENAME, MODELS_DIR, STAGING_SUBDIR}; use crate::domain::detection::error::MLError; diff --git a/net-guardia/src/core/inference/inference.rs b/net-guardia/src/core/inference/runner.rs similarity index 96% rename from net-guardia/src/core/inference/inference.rs rename to net-guardia/src/core/inference/runner.rs index 7b1da00..dd5be65 100644 --- a/net-guardia/src/core/inference/inference.rs +++ b/net-guardia/src/core/inference/runner.rs @@ -139,26 +139,7 @@ impl Inference { /// Dispatch on adapter variant. fn infer_batch_inner(&self, adapter: &MLModelAdapter, flows: &[FlowData]) -> Vec { match adapter { - MLModelAdapter::MultiTask { - ae, - classifier, - batch_size, - n_ae, - n_cls, - labels, - normal_idx, - c2_idx, - } => self.infer_multitask( - ae, - classifier, - *batch_size, - *n_ae, - *n_cls, - labels, - *normal_idx, - *c2_idx, - flows, - ), + MLModelAdapter::MultiTask { .. } => self.infer_multitask(adapter, flows), MLModelAdapter::AutoencoderOnly { model, batch_size, @@ -177,18 +158,22 @@ impl Inference { /// MultiTask path. Runs the AE batch → computes per-flow MSE → feeds the /// classifier over (ae_features ++ ae_score) → fires on anomaly OR /// non-Normal classifier agreement OR elevated C2 head. - fn infer_multitask( - &self, - ae: &RunnableModel, - classifier: &RunnableModel, - batch_size: usize, - n_ae: usize, - n_cls: usize, - labels: &BTreeMap, - normal_idx: Option, - c2_idx: Option, - flows: &[FlowData], - ) -> Vec { + fn infer_multitask(&self, adapter: &MLModelAdapter, flows: &[FlowData]) -> Vec { + let MLModelAdapter::MultiTask { + ae, + classifier, + batch_size, + n_ae, + n_cls, + labels, + normal_idx, + c2_idx, + } = adapter + else { + unreachable!() + }; + let (batch_size, n_ae, n_cls) = (*batch_size, *n_ae, *n_cls); + let (normal_idx, c2_idx) = (*normal_idx, *c2_idx); let n = flows.len(); let all_ae_features: Vec> = flows.iter().map(|f| self.preprocess_ae_features(f)).collect(); diff --git a/net-guardia/src/core/response/engine.rs b/net-guardia/src/core/response/engine.rs index b8da271..a213c5d 100644 --- a/net-guardia/src/core/response/engine.rs +++ b/net-guardia/src/core/response/engine.rs @@ -43,17 +43,29 @@ pub struct SoarEngine { pub(super) secrets: Option>, } +pub struct SoarEngineDeps { + pub db: Arc, + pub config: Arc>, + pub access_control: Arc, + pub alert_notifier: Option>, + pub geoip: Option>, + pub rate_limit: Option>, + pub enforce_level_cache: Arc, + pub secrets: Option>, +} + impl SoarEngine { - pub fn new( - db: Arc, - config: Arc>, - access_control: Arc, - alert_notifier: Option>, - geoip: Option>, - rate_limit: Option>, - enforce_level_cache: Arc, - secrets: Option>, - ) -> Result { + pub fn new(deps: SoarEngineDeps) -> Result { + let SoarEngineDeps { + db, + config, + access_control, + alert_notifier, + geoip, + rate_limit, + enforce_level_cache, + secrets, + } = deps; let soar_cfg = config.load(); let rate_limit_channel = soar_cfg.soar.rate_limit_cmd_channel_capacity; let freq_max_keys = soar_cfg.soar.frequency_max_tracked_keys; @@ -398,8 +410,17 @@ mod tests { AppConfig::seed_defaults(&*db).expect("seed config defaults"); let cfg = AppConfig::from_settings(&*db).expect("load config"); let config = Arc::new(ArcSwap::from_pointee(cfg)); - SoarEngine::new(db as Arc, config, ac, None, None, None, cache, None) - .expect("Failed to create SOAR engine") + SoarEngine::new(SoarEngineDeps { + db: db as Arc, + config, + access_control: ac, + alert_notifier: None, + geoip: None, + rate_limit: None, + enforce_level_cache: cache, + secrets: None, + }) + .expect("Failed to create SOAR engine") } #[tokio::test] @@ -492,16 +513,16 @@ mod tests { AppConfig::seed_defaults(&*db).expect("seed config defaults"); let cfg = AppConfig::from_settings(&*db).expect("load config"); let config = Arc::new(ArcSwap::from_pointee(cfg)); - let engine = SoarEngine::new( - db as Arc, + let engine = SoarEngine::new(SoarEngineDeps { + db: db as Arc, config, - mock.clone(), - None, - None, - None, - cache, - None, - ) + access_control: mock.clone(), + alert_notifier: None, + geoip: None, + rate_limit: None, + enforce_level_cache: cache, + secrets: None, + }) .expect("Failed to create engine"); engine.recover_active_blocks().await.expect("Recovery should succeed"); @@ -526,16 +547,16 @@ mod tests { AppConfig::seed_defaults(&*db).expect("seed config defaults"); let cfg = AppConfig::from_settings(&*db).expect("load config"); let config = Arc::new(ArcSwap::from_pointee(cfg)); - let engine = SoarEngine::new( - db as Arc, + let engine = SoarEngine::new(SoarEngineDeps { + db: db as Arc, config, - mock.clone(), - None, - None, - None, - cache, - None, - ) + access_control: mock.clone(), + alert_notifier: None, + geoip: None, + rate_limit: None, + enforce_level_cache: cache, + secrets: None, + }) .expect("Failed to create engine"); // Should not panic — errors are logged, not propagated @@ -640,16 +661,16 @@ mod tests { AppConfig::seed_defaults(&*db).expect("seed config defaults"); let cfg = AppConfig::from_settings(&*db).expect("load config"); let config = Arc::new(ArcSwap::from_pointee(cfg)); - let engine = SoarEngine::new( - db as Arc, + let engine = SoarEngine::new(SoarEngineDeps { + db: db as Arc, config, - mock.clone(), - None, - None, - None, - cache, - None, - ) + access_control: mock.clone(), + alert_notifier: None, + geoip: None, + rate_limit: None, + enforce_level_cache: cache, + secrets: None, + }) .expect("Failed to create engine"); let event = ThreatDetectedEvent { @@ -715,16 +736,16 @@ mod tests { AppConfig::seed_defaults(&*db).expect("seed config defaults"); let cfg = AppConfig::from_settings(&*db).expect("load config"); let config = Arc::new(ArcSwap::from_pointee(cfg)); - let engine = SoarEngine::new( - db.clone() as Arc, + let engine = SoarEngine::new(SoarEngineDeps { + db: db.clone() as Arc, config, - mock, - None, - None, - None, - cache, - None, - ) + access_control: mock, + alert_notifier: None, + geoip: None, + rate_limit: None, + enforce_level_cache: cache, + secrets: None, + }) .expect("Failed to create engine"); // Should have loaded default playbooks diff --git a/net-guardia/src/core/response/playbook_service.rs b/net-guardia/src/core/response/playbook_service.rs index ac6acfd..2e48006 100644 --- a/net-guardia/src/core/response/playbook_service.rs +++ b/net-guardia/src/core/response/playbook_service.rs @@ -51,16 +51,7 @@ impl PlaybookService { ) }) .collect(); - let playbook_id = self.db.insert_playbook_atomic( - &input.name, - &input.trigger_event, - input.condition_threshold, - input.condition_count, - input.condition_window_secs, - input.cooldown_secs, - &actions, - &conditions, - )?; + let playbook_id = self.db.insert_playbook_atomic(input, &actions, &conditions)?; self.soar_engine.reload_cache()?; Ok(playbook_id) } diff --git a/net-guardia/src/core/response/scheduler.rs b/net-guardia/src/core/response/scheduler.rs index 54f78ee..71cb6a2 100644 --- a/net-guardia/src/core/response/scheduler.rs +++ b/net-guardia/src/core/response/scheduler.rs @@ -92,7 +92,8 @@ impl TtlScheduler { // Atomically drop acl_rules entry AND mark soar_block_rules // unblocked in one transaction. let ip_version = ip_version_from_str(&block.source_ip); - self.db.commit_soar_unblock_to_db(block.id, ip_version, &block.source_ip)?; + self.db + .commit_soar_unblock_to_db(block.id, ip_version, &block.source_ip)?; self.soar_engine.decrement_block_count(); removed += 1; } diff --git a/net-guardia/src/domain/common/event.rs b/net-guardia/src/domain/common/event.rs index cb9cb1c..e3367bd 100644 --- a/net-guardia/src/domain/common/event.rs +++ b/net-guardia/src/domain/common/event.rs @@ -3,8 +3,6 @@ use std::str::FromStr; use serde::Serialize; -use crate::interface::communication::event::Event; - // -- Detection Source --------------------------------------------------------- /// Identifies which detection subsystem produced a detection. @@ -113,8 +111,6 @@ pub struct ThreatDetectedEvent { pub c2_score: f32, } -impl Event for ThreatDetectedEvent {} - /// Fired when the ML drift detector finds feature drift beyond 3 sigma. #[derive(Debug, Clone)] pub struct DriftDetectedEvent { @@ -122,8 +118,6 @@ pub struct DriftDetectedEvent { pub max_deviation: f64, } -impl Event for DriftDetectedEvent {} - // -- Audit Events ------------------------------------------------------------- /// Fired for auditable actions (enforce mode changes, playbook CRUD, etc.). @@ -137,5 +131,3 @@ pub struct AuditEvent { /// JSON string with action-specific details pub detail: String, } - -impl Event for AuditEvent {} diff --git a/net-guardia/src/domain/common/system/suricata.rs b/net-guardia/src/domain/common/system/suricata.rs index 2ae9f54..48c1af8 100644 --- a/net-guardia/src/domain/common/system/suricata.rs +++ b/net-guardia/src/domain/common/system/suricata.rs @@ -18,9 +18,3 @@ pub enum SuricataHealth { reason: String, }, } - -impl SuricataHealth { - pub fn is_running(&self) -> bool { - matches!(self, SuricataHealth::Running { .. }) - } -} diff --git a/net-guardia/src/domain/detection/botnet.rs b/net-guardia/src/domain/detection/botnet.rs index 7c4d0d2..c784d12 100644 --- a/net-guardia/src/domain/detection/botnet.rs +++ b/net-guardia/src/domain/detection/botnet.rs @@ -64,12 +64,12 @@ impl BotnetDetector { set.last_alert = alert.clone(); if set.sources.len() >= self.threshold { - if let Some(last) = set.last_alerted { - if now.duration_since(last) < self.window { - set.sources.clear(); - set.window_start = now; - return None; - } + if let Some(last) = set.last_alerted + && now.duration_since(last) < self.window + { + set.sources.clear(); + set.window_start = now; + return None; } Some(set.sources.len()) } else { diff --git a/net-guardia/src/domain/detection/flow_tracker.rs b/net-guardia/src/domain/detection/flow_tracker.rs index b815a8c..65409f3 100644 --- a/net-guardia/src/domain/detection/flow_tracker.rs +++ b/net-guardia/src/domain/detection/flow_tracker.rs @@ -302,11 +302,6 @@ impl FlowTracker { entry.lock().add_packet(&packet, &self.limits); } - /// Get all active flows (clone, no drain). Used by WebSocket. - pub fn get_flows(&self) -> Vec { - self.active.iter().map(|(_, entry)| entry.lock().clone()).collect() - } - /// Extract scalar stats from all active flows without cloning packet vectors. pub fn get_flow_stats(&self, convert: impl Fn(&FlowData) -> T) -> Vec { self.active.iter().map(|(_, entry)| convert(&entry.lock())).collect() diff --git a/net-guardia/src/domain/detection/lateral.rs b/net-guardia/src/domain/detection/lateral.rs index 5d82c93..d66c595 100644 --- a/net-guardia/src/domain/detection/lateral.rs +++ b/net-guardia/src/domain/detection/lateral.rs @@ -64,12 +64,12 @@ impl LateralMovementDetector { set.dests.insert(alert.dst_ip.clone()); if set.dests.len() >= self.threshold { - if let Some(last) = set.last_alerted { - if now.duration_since(last) < self.window { - set.dests.clear(); - set.window_start = now; - return None; - } + if let Some(last) = set.last_alerted + && now.duration_since(last) < self.window + { + set.dests.clear(); + set.window_start = now; + return None; } Some(set.dests.len()) } else { diff --git a/net-guardia/src/domain/detection/scan.rs b/net-guardia/src/domain/detection/scan.rs index 1367129..6b1be14 100644 --- a/net-guardia/src/domain/detection/scan.rs +++ b/net-guardia/src/domain/detection/scan.rs @@ -62,12 +62,12 @@ impl ScanDetector { set.last_dst_ip = alert.dst_ip.clone(); if set.ports.len() >= self.threshold { - if let Some(last) = set.last_alerted { - if now.duration_since(last) < self.window { - set.ports.clear(); - set.window_start = now; - return None; - } + if let Some(last) = set.last_alerted + && now.duration_since(last) < self.window + { + set.ports.clear(); + set.window_start = now; + return None; } Some((set.ports.len(), set.last_dst_ip.clone())) } else { diff --git a/net-guardia/src/infrastructure/app_services.rs b/net-guardia/src/infrastructure/app_services.rs index 9604f46..13c59b6 100644 --- a/net-guardia/src/infrastructure/app_services.rs +++ b/net-guardia/src/infrastructure/app_services.rs @@ -11,8 +11,8 @@ use tokio::sync::oneshot; use crate::core::inference::alert::MLAlert; use crate::core::inference::drift_detector::DriftDetectorHandle; use crate::core::inference::engine::Engine; -use crate::core::inference::inference::Inference; use crate::core::inference::model_loader::build_adapter; +use crate::core::inference::runner::Inference; use crate::core::inference::traffic_logger::{RotationPolicy, TrafficLogger}; use crate::domain::common::config::AppConfig; use crate::domain::common::config::constants::{MANIFEST_FILENAME, MODELS_DIR}; diff --git a/net-guardia/src/infrastructure/secret_store.rs b/net-guardia/src/infrastructure/secret_store.rs index b738ad9..586215f 100644 --- a/net-guardia/src/infrastructure/secret_store.rs +++ b/net-guardia/src/infrastructure/secret_store.rs @@ -14,6 +14,7 @@ use crate::domain::common::error::Error; use crate::domain::common::error::crypto::CryptoError; use crate::domain::common::log::crypto::CryptoLog; use crate::interface::port::secret_store::SecretStorePort; +use crate::interface::port::setting::SettingRepo; /// AES-256-GCM envelope encryption for sensitive values stored in `app_secrets`. pub struct SecretStore { @@ -126,7 +127,8 @@ impl SecretStore { impl SecretStorePort for SecretStore { fn get_secret(&self, key: &str) -> Result, Error> { - match self.db.get_app_secret(key)? { + let repo: &dyn SettingRepo = self.db.as_ref(); + match repo.get_app_secret(key)? { Some(envelope_json) => Ok(Some(self.decrypt(&envelope_json)?)), None => Ok(None), } @@ -134,7 +136,8 @@ impl SecretStorePort for SecretStore { fn set_secret(&self, key: &str, plaintext: &str) -> Result<(), Error> { let envelope = self.encrypt(plaintext)?; - self.db.set_app_secret(key, &envelope) + let repo: &dyn SettingRepo = self.db.as_ref(); + repo.set_app_secret(key, &envelope) } fn encrypt_envelope(&self, plaintext: &str) -> Result { diff --git a/net-guardia/src/infrastructure/service_factory.rs b/net-guardia/src/infrastructure/service_factory.rs index 714c8bc..03fcdd0 100644 --- a/net-guardia/src/infrastructure/service_factory.rs +++ b/net-guardia/src/infrastructure/service_factory.rs @@ -26,7 +26,7 @@ use crate::core::data_plane::rate_limit_service::RateLimitService; use crate::core::identity::jwt::JwtService; use crate::core::inference::drift_detector::DriftDetectorHandle; use crate::core::reporting::email_scheduler::ReportScheduler; -use crate::core::response::engine::SoarEngine; +use crate::core::response::engine::{SoarEngine, SoarEngineDeps}; use crate::core::response::playbook_service::PlaybookService; use crate::core::response::scheduler::TtlScheduler; use crate::domain::common::config::AppConfig; @@ -109,6 +109,13 @@ fn stage_registry() -> HashMap<&'static str, (&'static str, u32)> { ]) } +struct EbpfBuild { + ingress: Ebpf, + egress: Ebpf, + program_array: ProgramArray, + services: EbpfServices, +} + /// Factory responsible for creating and wiring all application services. pub struct ServiceFactory; @@ -153,7 +160,12 @@ impl ServiceFactory { // the reason. let (ingress_ebpf, egress_ebpf, ingress_program_array, ebpf_services) = match Self::try_build_ebpf(&app_config) { - Ok((ingress, egress, pa, services)) => (Some(ingress), Some(egress), Some(pa), Arc::new(services)), + Ok(build) => ( + Some(build.ingress), + Some(build.egress), + Some(build.program_array), + Arc::new(build.services), + ), Err((stage, err)) => { let health = ebpf_preflight::classify(stage, &err, None); log!(SystemLog::EbpfBringupFailed(format!("{:?}", health))); @@ -264,16 +276,16 @@ impl ServiceFactory { // Create SOAR engine let rate_limit_port: Arc = ebpf_services.rate_limit.clone(); - let soar_engine = Arc::new(SoarEngine::new( - db.clone(), - app_config.clone(), - access_control_port.clone(), - alert_notifier.clone(), - geoip.clone(), - Some(rate_limit_port.clone()), + let soar_engine = Arc::new(SoarEngine::new(SoarEngineDeps { + db: db.clone(), + config: app_config.clone(), + access_control: access_control_port.clone(), + alert_notifier: alert_notifier.clone(), + geoip: geoip.clone(), + rate_limit: Some(rate_limit_port.clone()), enforce_level_cache, - Some(secret_store_port.clone()), - )?); + secrets: Some(secret_store_port.clone()), + })?); // Create TTL scheduler let ttl_scheduler = TtlScheduler::new(db.clone(), access_control_port.clone(), soar_engine.clone()); @@ -361,11 +373,7 @@ impl ServiceFactory { /// ingress pipeline, write queue counts, and hand out map handles to the /// services. Returns the original stage on the first failure so the /// classifier can render targeted diagnostics. - fn try_build_ebpf( - app_config: &Arc>, - ) -> Result<(Ebpf, Ebpf, ProgramArray, EbpfServices), (EbpfFailStage, Error)> { - use crate::domain::common::system::health::EbpfFailStage; - + fn try_build_ebpf(app_config: &Arc>) -> Result { let mut ingress = Self::load_ebpf("ingress").map_err(|e| (EbpfFailStage::Load, e))?; let mut egress = Self::load_ebpf("egress").map_err(|e| (EbpfFailStage::Load, e))?; @@ -381,7 +389,12 @@ impl ServiceFactory { let services = EbpfServices::new(app_config.clone(), &mut ingress, &mut egress) .map_err(|e| (EbpfFailStage::MapsBind, e))?; - Ok((ingress, egress, pipeline, services)) + Ok(EbpfBuild { + ingress, + egress, + program_array: pipeline, + services, + }) } fn load_ebpf(name: &str) -> Result { diff --git a/net-guardia/src/interface/communication/event.rs b/net-guardia/src/interface/communication/event.rs deleted file mode 100644 index bb40d91..0000000 --- a/net-guardia/src/interface/communication/event.rs +++ /dev/null @@ -1 +0,0 @@ -pub trait Event: Send + Clone + 'static {} diff --git a/net-guardia/src/interface/communication/event_types.rs b/net-guardia/src/interface/communication/event_types.rs deleted file mode 100644 index c78f522..0000000 --- a/net-guardia/src/interface/communication/event_types.rs +++ /dev/null @@ -1 +0,0 @@ -// Types are available via crate::domain::event diff --git a/net-guardia/src/interface/communication/mod.rs b/net-guardia/src/interface/communication/mod.rs deleted file mode 100644 index d5c9bc1..0000000 --- a/net-guardia/src/interface/communication/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod event; -pub mod event_types; diff --git a/net-guardia/src/interface/mod.rs b/net-guardia/src/interface/mod.rs index 50b89bc..f678336 100644 --- a/net-guardia/src/interface/mod.rs +++ b/net-guardia/src/interface/mod.rs @@ -1,3 +1,2 @@ -pub mod communication; pub mod port; pub mod utils; diff --git a/net-guardia/src/interface/port/soar.rs b/net-guardia/src/interface/port/soar.rs index 4e68935..851079d 100644 --- a/net-guardia/src/interface/port/soar.rs +++ b/net-guardia/src/interface/port/soar.rs @@ -1,6 +1,6 @@ use crate::domain::common::error::Error; use crate::domain::response::playbook_data::{ - ActiveBlockView, ExecutionView, PendingUnblock, PlaybookView, UpdatePlaybookInput, + ActiveBlockView, CreatePlaybookInput, ExecutionView, PendingUnblock, PlaybookView, UpdatePlaybookInput, }; /// Threat Response BC — SOAR aggregate repository. @@ -50,12 +50,7 @@ pub trait SoarRepo: Send + Sync { /// `conditions` tuples: `(condition_type, operator, value, value2)`. fn insert_playbook_atomic( &self, - name: &str, - trigger_event: &str, - threshold: Option, - count: Option, - window: Option, - cooldown: i64, + input: &CreatePlaybookInput, actions: &[(i64, String, String)], conditions: &[(String, String, String, Option)], ) -> Result;