From eb5c68edf58565831889d6d8a43ababf0b30cc9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Fern=C3=A1ndez?= Date: Mon, 22 Apr 2024 12:50:31 +0200 Subject: [PATCH] Initial commit in ruleset declaration --- Cargo.toml | 5 + config/windows/rules.yml | 6 + src/event.rs | 376 ++-------------------------- src/integration.rs | 3 +- src/launcher.rs | 9 +- src/main.rs | 11 +- src/monitor.rs | 6 +- src/monitorevent.rs | 516 +++++++++++++++++++++++++++++++++++++++ src/monitorruleevent.rs | 225 +++++++++++++++++ src/rotator.rs | 1 + src/ruleset.rs | 240 ++++++++++++++++++ 11 files changed, 1034 insertions(+), 364 deletions(-) create mode 100644 config/windows/rules.yml create mode 100644 src/monitorevent.rs create mode 100644 src/monitorruleevent.rs create mode 100644 src/ruleset.rs diff --git a/Cargo.toml b/Cargo.toml index 34bc410..9e02a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,11 @@ time = { version = "0.3.17", default-features = false } ctrlc = { version = "3.3.1", default-features = false, features = ["termination"] } log-panics = { version = "2.1.0", features = ["with-backtrace"]} +[dependencies.regex] +version = "1.3" +default-features = false +features = ["std"] + [target.'cfg(windows)'.dependencies] windows-service = "0.6.0" zip = "0.6.6" diff --git a/config/windows/rules.yml b/config/windows/rules.yml new file mode 100644 index 0000000..81628b1 --- /dev/null +++ b/config/windows/rules.yml @@ -0,0 +1,6 @@ +monitor: + - path: C:\tmp\ + id: 7 + rule: '\.php$' + message: "This is a test" + labels: ["windows", "test"] \ No newline at end of file diff --git a/src/event.rs b/src/event.rs index caeaff4..0c36ae4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,217 +1,34 @@ // Copyright (C) 2021, Achiefs. -use std::fmt; -use std::fs::OpenOptions; -use std::io::Write; -use std::time::Duration; -use notify::event::*; -use log::*; -use serde_json::{json, to_string}; -use std::path::PathBuf; -use reqwest::Client; - use crate::config; -pub struct Event { - pub id: String, - pub timestamp: String, - pub hostname: String, - pub node: String, - pub version: String, - pub path: PathBuf, - pub size: u64, - pub kind: notify::EventKind, - pub labels: Vec, - pub operation: String, - pub detailed_operation: String, - pub checksum: String, - pub fpid: u32, - pub system: String -} - -impl Event { - // Get formatted string with all required data - pub fn format_json(&self) -> String { - let obj = json!({ - "id": self.id.clone(), - "timestamp": self.timestamp.clone(), - "hostname": self.hostname.clone(), - "node": self.node.clone(), - "fpid": self.fpid.clone(), - "version": self.version.clone(), - "labels": self.labels.clone(), - "operation": self.operation.clone(), - "detailed_operation": self.detailed_operation.clone(), - "file": String::from(self.path.clone().to_str().unwrap()), - "file_size": self.size.clone(), - "checksum": self.checksum.clone(), - "system": self.system.clone() - }); - to_string(&obj).unwrap() - } - - // ------------------------------------------------------------------------ - - pub fn clone(&self) -> Self { - Event { - id: self.id.clone(), - timestamp: self.timestamp.clone(), - hostname: self.hostname.clone(), - node: self.node.clone(), - version: self.version.clone(), - path: self.path.clone(), - size: self.size, - kind: self.kind, - labels: self.labels.clone(), - operation: self.operation.clone(), - detailed_operation: self.detailed_operation.clone(), - checksum: self.checksum.clone(), - fpid: self.fpid, - system: self.system.clone() - } - } - - // ------------------------------------------------------------------------ - - // Function to write the received events to file - pub fn log(&self, file: String){ - let mut events_file = OpenOptions::new() - .create(true) - .append(true) - .open(file) - .expect("(log) Unable to open events log file."); - - match writeln!(events_file, "{}", self.format_json() ) { - Ok(_d) => debug!("Event log written"), - Err(e) => error!("Event could not be written, Err: [{}]", e) - } - } - - // ------------------------------------------------------------------------ - - // Function to send events through network - pub async fn send(&self, index: String) { - let config = unsafe { super::GCONFIG.clone().unwrap() }; - - // Splunk endpoint integration - if config.endpoint_type == "Splunk" { - let data = json!({ - "source": self.node.clone(), - "sourcetype": "_json", - "event": json!({ - "timestamp": self.timestamp.clone(), - "hostname": self.hostname.clone(), - "node": self.node.clone(), - "fpid": self.fpid.clone(), - "version": self.version.clone(), - "labels": self.labels.clone(), - "operation": self.operation.clone(), - "detailed_operation": self.detailed_operation.clone(), - "file": String::from(self.path.clone().to_str().unwrap()), - "file_size": self.size.clone(), - "checksum": self.checksum.clone(), - "system": self.system.clone() - }), - "index": "fim_events" - }); - debug!("Sending received event to Splunk integration, event: {}", data); - let request_url = format!("{}/services/collector/event", config.endpoint_address); - let client = Client::builder() - .danger_accept_invalid_certs(config.insecure) - .timeout(Duration::from_secs(30)) - .build().unwrap(); - match client - .post(request_url) - .header("Authorization", format!("Splunk {}", config.endpoint_token)) - .json(&data) - .send() - .await { - Ok(response) => debug!("Response received: {:?}", - response.text().await.unwrap()), - Err(e) => debug!("Error on request: {:?}", e) - } - // Elastic endpoint integration - } else { - let data = json!({ - "timestamp": self.timestamp.clone(), - "hostname": self.hostname.clone(), - "node": self.node.clone(), - "fpid": self.fpid.clone(), - "version": self.version.clone(), - "labels": self.labels.clone(), - "operation": self.operation.clone(), - "detailed_operation": self.detailed_operation.clone(), - "file": String::from(self.path.clone().to_str().unwrap()), - "file_size": self.size.clone(), - "checksum": self.checksum.clone(), - "system": self.system.clone() - }); - let request_url = format!("{}/{}/_doc/{}", config.endpoint_address, index, self.id); - let client = Client::builder() - .danger_accept_invalid_certs(config.insecure) - .timeout(Duration::from_secs(30)) - .build().unwrap(); - match client - .post(request_url) - .basic_auth(config.endpoint_user, Some(config.endpoint_pass)) - .json(&data) - .send() - .await { - Ok(response) => debug!("Response received: {:?}", - response.text().await.unwrap()), - Err(e) => debug!("Error on request: {:?}", e) - } - } - - } - - // ------------------------------------------------------------------------ - - // Function to manage event destination - pub async fn process(&self, destination: &str, index_name: String, config: config::Config){ - match destination { - config::BOTH_MODE => { - self.log(config.get_events_file()); - self.send(index_name).await; - }, - config::NETWORK_MODE => { - self.send(index_name).await; - }, - _ => self.log(config.get_events_file()) - } - } - - // ------------------------------------------------------------------------ +use notify::event::*; - pub fn get_string(&self, field: String) -> String { - match field.as_str() { - "path" => String::from(self.path.to_str().unwrap()), - "file_size" => self.size.clone().to_string(), - "hostname" => self.hostname.clone(), - "node" => self.node.clone(), - "version" => self.version.clone(), - "operation" => self.operation.clone(), - "detailed_operation" => self.detailed_operation.clone(), - "checksum" => self.checksum.clone(), - "system" => self.system.clone(), - _ => "".to_string() - } - } +pub trait Event { + fn format_json(&self) -> String; + fn clone(&self) -> Self; + fn log(&self, file: String); + async fn send(&self); + //async fn route(&self); + async fn process(&self); + fn get_string(&self, field: String) -> String; } // ---------------------------------------------------------------------------- -impl fmt::Debug for Event { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{ - f.debug_tuple("") - .field(&self.id) - .field(&self.path) - .field(&self.size) - .field(&self.operation) - .field(&self.detailed_operation) - .finish() +/*pub async fn route(event: &dyn Event) { + let config = unsafe { super::GCONFIG.clone().unwrap() }; + match config.get_events_destination().as_str() { + config::BOTH_MODE => { + event.log(config.get_events_file()); + event.send().await; + }, + config::NETWORK_MODE => { + event.send().await; + }, + _ => event.log(config.get_events_file()) } -} +}*/ // ---------------------------------------------------------------------------- @@ -290,105 +107,6 @@ pub fn get_detailed_operation(event_kind: EventKind) -> String { #[cfg(test)] mod tests { use super::*; - use crate::event::Event; - use crate::config::Config; - use crate::utils; - use std::path::PathBuf; - use tokio_test::block_on; - use std::fs; - - // ------------------------------------------------------------------------ - - fn remove_test_file(filename: String) { - fs::remove_file(filename).unwrap() - } - - fn create_test_event() -> Event { - Event { - id: "Test_id".to_string(), - timestamp: "Timestamp".to_string(), - hostname: "Hostname".to_string(), - node: "FIM".to_string(), - version: "x.x.x".to_string(), - kind: EventKind::Create(CreateKind::Any), - path: PathBuf::new(), - size: 0, - labels: Vec::new(), - operation: "CREATE".to_string(), - detailed_operation: "CREATE_FILE".to_string(), - checksum: "UNKNOWN".to_string(), - fpid: 0, - system: "test".to_string() - } - } - - fn initialize() { - unsafe{ - super::super::GCONFIG = Some(config::Config::new(&utils::get_os(), None)); - } - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_clone() { - let event = create_test_event(); - let cloned = event.clone(); - assert_eq!(event.id, cloned.id); - assert_eq!(event.timestamp, cloned.timestamp); - assert_eq!(event.hostname, cloned.hostname); - assert_eq!(event.node, cloned.node); - assert_eq!(event.version, cloned.version); - assert_eq!(event.path, cloned.path); - assert_eq!(event.size, cloned.size); - assert_eq!(event.kind, cloned.kind); - assert_eq!(event.labels, cloned.labels); - assert_eq!(event.operation, cloned.operation); - assert_eq!(event.detailed_operation, cloned.detailed_operation); - assert_eq!(event.checksum, cloned.checksum); - assert_eq!(event.fpid, cloned.fpid); - assert_eq!(event.system, cloned.system); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_create_event() { - let evt = create_test_event(); - assert_eq!(evt.id, "Test_id".to_string()); - assert_eq!(evt.timestamp, "Timestamp".to_string()); - assert_eq!(evt.hostname, "Hostname".to_string()); - assert_eq!(evt.node, "FIM".to_string()); - assert_eq!(evt.version, "x.x.x".to_string()); - assert_eq!(evt.kind, EventKind::Create(CreateKind::Any) ); - assert_eq!(evt.path, PathBuf::new()); - assert_eq!(evt.labels, Vec::::new()); - assert_eq!(evt.operation, String::from("CREATE")); - assert_eq!(evt.detailed_operation, String::from("CREATE_FILE")); - assert_eq!(evt.fpid, 0); - assert_eq!(evt.system, String::from("test")); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_send() { - initialize(); - let evt = create_test_event(); - block_on( evt.send(String::from("test")) ); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_send_splunk() { - initialize(); - let evt = create_test_event(); - unsafe { - super::super::GCONFIG = Some(config::Config::new(&utils::get_os(), Some("test/unit/config/common/test_send_splunk.yml"))); - } - block_on( evt.send(String::from("test")) ); - } // ------------------------------------------------------------------------ @@ -497,56 +215,4 @@ mod tests { assert_eq!(get_detailed_operation(EventKind::Other), String::from("OTHER")); } - - // ------------------------------------------------------------------------ - - #[test] - fn test_process() { - initialize(); - let config = Config::new(&utils::get_os(), None); - let event = create_test_event(); - - block_on(event.process(config::NETWORK_MODE, String::from("test"), config.clone())); - block_on(event.process(config::FILE_MODE, String::from("test2"), config.clone())); - block_on(event.process(config::BOTH_MODE, String::from("test3"), config.clone())); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_event_fmt(){ - let out = format!("{:?}", create_test_event()); - assert_eq!(out, "(\"Test_id\", \"\", 0, \"CREATE\", \"CREATE_FILE\")"); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_format_json() { - let expected = "{\"checksum\":\"UNKNOWN\",\"detailed_operation\":\"CREATE_FILE\",\ - \"file\":\"\",\"file_size\":0,\"fpid\":0,\ - \"hostname\":\"Hostname\",\"id\":\"Test_id\",\"labels\":[],\ - \"node\":\"FIM\",\"operation\":\"CREATE\",\"system\":\"test\",\ - \"timestamp\":\"Timestamp\",\"version\":\"x.x.x\"}"; - assert_eq!(create_test_event().format_json(), expected); - } - - // ------------------------------------------------------------------------ - - #[test] - fn test_log() { - let filename = String::from("test_event.json"); - let evt = create_test_event(); - - evt.log(filename.clone()); - let contents = fs::read_to_string(filename.clone()); - let expected = "{\"checksum\":\"UNKNOWN\",\"detailed_operation\":\"CREATE_FILE\",\ - \"file\":\"\",\"file_size\":0,\"fpid\":0,\ - \"hostname\":\"Hostname\",\"id\":\"Test_id\",\"labels\":[],\ - \"node\":\"FIM\",\"operation\":\"CREATE\",\ - \"system\":\"test\",\ - \"timestamp\":\"Timestamp\",\"version\":\"x.x.x\"}\n"; - assert_eq!(contents.unwrap(), expected); - remove_test_file(filename.clone()); - } } \ No newline at end of file diff --git a/src/integration.rs b/src/integration.rs index b9fa85a..3198a3f 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -9,6 +9,7 @@ use std::process::Command; // Single event data management use crate::event::Event; +use crate::monitorevent::MonitorEvent; use crate::utils; // ---------------------------------------------------------------------------- @@ -70,7 +71,7 @@ impl Integration { // ---------------------------------------------------------------------------- -pub fn get_event_integration(event: Event, integrations: Vec) -> Option { +pub fn get_event_integration(event: MonitorEvent, integrations: Vec) -> Option { let option = integrations.iter().find(|integration| match integration.condition[1].as_str() { "==" => event.get_string(integration.condition[0].clone()) == integration.condition[2], diff --git a/src/launcher.rs b/src/launcher.rs index 4a1472c..4f803ef 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -2,12 +2,13 @@ use crate::config; use crate::event::Event; +use crate::monitorevent::MonitorEvent; use crate::integration; use log::debug; // ---------------------------------------------------------------------------- -pub fn check_integrations(event: Event, config: config::Config) { +pub fn check_integrations(event: MonitorEvent, config: config::Config) { let index = config.get_index(event.path.to_str().unwrap(), "", config.monitor.clone()); if index != usize::MAX { let integrations = config.get_integrations(index, config.monitor.clone()); @@ -25,14 +26,14 @@ pub fn check_integrations(event: Event, config: config::Config) { mod tests { use super::*; use std::path::PathBuf; - use crate::event::Event; + use crate::event::MonitorEvent; use crate::config::*; use notify::event::*; // ------------------------------------------------------------------------ - fn create_test_event() -> Event { - Event { + fn create_test_event() -> MonitorEvent { + MonitorEvent { id: "Test_id".to_string(), timestamp: "Timestamp".to_string(), hostname: "Hostname".to_string(), diff --git a/src/main.rs b/src/main.rs index 32ad216..dbfe28c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,10 +13,14 @@ mod utils; mod hash; // Configuration load functions mod config; +// Ruleset load functions +mod ruleset; // Index management functions mod index; // Single event data management mod event; +mod monitorevent; +mod monitorruleevent; // File reading continuously mod logreader; mod auditevent; @@ -33,6 +37,7 @@ mod multiwatcher; mod rotator; static mut GCONFIG: Option = None; +static mut GRULESET: Option = None; // ---------------------------------------------------------------------------- @@ -45,7 +50,8 @@ fn init(){ println!("[INFO] Achiefs File Integrity Monitoring software starting!"); println!("[INFO] Reading config..."); unsafe{ - GCONFIG = Some(config::Config::new(&utils::get_os(), None)); + GCONFIG = Some(config::Config::new(&utils::get_os(), None)); + GRULESET = Some(ruleset::Ruleset::new(&utils::get_os(), None)); // Create folders to store logs based on config.yml fs::create_dir_all( @@ -64,9 +70,10 @@ fn init(){ .expect("Unable to open log file") ).unwrap(); - println!("[INFO] Configuration successfully read, forwarding output to log file"); + println!("[INFO] Configuration successfully read, forwarding output to log file."); println!("[INFO] Log file: '{}'", GCONFIG.clone().unwrap().log_file); println!("[INFO] Log level: '{}'", GCONFIG.clone().unwrap().log_level); + println!("[INFO] Ruleset successfully load."); }; log_panics::init(); } diff --git a/src/monitor.rs b/src/monitor.rs index d95a5bc..722490a 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -28,6 +28,8 @@ use crate::config; use crate::index; // Single event data management use crate::event; +use event::Event; +use crate::monitorevent::MonitorEvent; // File reading continuously use crate::logreader; // integrations checker @@ -248,7 +250,7 @@ pub async fn monitor(tx: mpsc::Sender>, if ! config.match_ignore(index, event_filename.to_str().unwrap(), config.monitor.clone()) && config.match_allowed(index, event_filename.to_str().unwrap(), config.monitor.clone()) { - let event = event::Event { + let event = MonitorEvent { id: utils::get_uuid(), timestamp: current_timestamp, hostname: utils::get_hostname(), @@ -266,7 +268,7 @@ pub async fn monitor(tx: mpsc::Sender>, }; debug!("Event processed: {:?}", event); - event.process(destination.clone().as_str(), index_name.clone(), config.clone()).await; + event.process().await; launcher::check_integrations(event.clone(), config.clone()); }else{ debug!("Event ignored not stored in alerts"); diff --git a/src/monitorevent.rs b/src/monitorevent.rs new file mode 100644 index 0000000..be4b46e --- /dev/null +++ b/src/monitorevent.rs @@ -0,0 +1,516 @@ +// Copyright (C) 2024, Achiefs. + +use crate::event; +use crate::config; + +use event::Event; +use log::*; +use serde_json::{json, to_string}; +use std::path::PathBuf; +use reqwest::Client; +use std::fs::OpenOptions; +use std::time::Duration; +use std::fmt; +use std::io::Write; + + +pub struct MonitorEvent { + pub id: String, + pub timestamp: String, + pub hostname: String, + pub node: String, + pub version: String, + pub path: PathBuf, + pub size: u64, + pub kind: notify::EventKind, + pub labels: Vec, + pub operation: String, + pub detailed_operation: String, + pub checksum: String, + pub fpid: u32, + pub system: String +} + + + +impl Event for MonitorEvent { + // Get formatted string with all required data + fn format_json(&self) -> String { + let obj = json!({ + "id": self.id.clone(), + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "operation": self.operation.clone(), + "detailed_operation": self.detailed_operation.clone(), + "file": String::from(self.path.clone().to_str().unwrap()), + "file_size": self.size.clone(), + "checksum": self.checksum.clone(), + "system": self.system.clone() + }); + to_string(&obj).unwrap() + } + + // ------------------------------------------------------------------------ + + fn clone(&self) -> Self { + MonitorEvent { + id: self.id.clone(), + timestamp: self.timestamp.clone(), + hostname: self.hostname.clone(), + node: self.node.clone(), + version: self.version.clone(), + path: self.path.clone(), + size: self.size, + kind: self.kind, + labels: self.labels.clone(), + operation: self.operation.clone(), + detailed_operation: self.detailed_operation.clone(), + checksum: self.checksum.clone(), + fpid: self.fpid, + system: self.system.clone() + } + } + + // ------------------------------------------------------------------------ + + // Function to write the received events to file + fn log(&self, file: String) { + let mut events_file = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .expect("(log) Unable to open events log file."); + + match writeln!(events_file, "{}", self.format_json() ) { + Ok(_d) => debug!("Event log written"), + Err(e) => error!("Event could not be written, Err: [{}]", e) + } + } + + // ------------------------------------------------------------------------ + + // Function to send events through network + async fn send(&self) { + use time::OffsetDateTime; + let current_date = OffsetDateTime::now_utc(); + let config = unsafe { super::GCONFIG.clone().unwrap() }; + let index = format!("fim-{}-{}-{}", current_date.year(), current_date.month() as u8, current_date.day() ); + + // Splunk endpoint integration + if config.endpoint_type == "Splunk" { + let data = json!({ + "source": self.node.clone(), + "sourcetype": "_json", + "event": json!({ + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "operation": self.operation.clone(), + "detailed_operation": self.detailed_operation.clone(), + "file": String::from(self.path.clone().to_str().unwrap()), + "file_size": self.size.clone(), + "checksum": self.checksum.clone(), + "system": self.system.clone() + }), + "index": "fim_events" + }); + debug!("Sending received event to Splunk integration, event: {}", data); + let request_url = format!("{}/services/collector/event", config.endpoint_address); + let client = Client::builder() + .danger_accept_invalid_certs(config.insecure) + .timeout(Duration::from_secs(30)) + .build().unwrap(); + match client + .post(request_url) + .header("Authorization", format!("Splunk {}", config.endpoint_token)) + .json(&data) + .send() + .await { + Ok(response) => debug!("Response received: {:?}", + response.text().await.unwrap()), + Err(e) => debug!("Error on request: {:?}", e) + } + // Elastic endpoint integration + } else { + let data = json!({ + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "operation": self.operation.clone(), + "detailed_operation": self.detailed_operation.clone(), + "file": String::from(self.path.clone().to_str().unwrap()), + "file_size": self.size.clone(), + "checksum": self.checksum.clone(), + "system": self.system.clone() + }); + let request_url = format!("{}/{}/_doc/{}", config.endpoint_address, index, self.id); + let client = Client::builder() + .danger_accept_invalid_certs(config.insecure) + .timeout(Duration::from_secs(30)) + .build().unwrap(); + match client + .post(request_url) + .basic_auth(config.endpoint_user, Some(config.endpoint_pass)) + .json(&data) + .send() + .await { + Ok(response) => debug!("Response received: {:?}", + response.text().await.unwrap()), + Err(e) => debug!("Error on request: {:?}", e) + } + } + + } + + // ------------------------------------------------------------------------ + + // Function to manage event destination + async fn process(&self) { + use regex::Regex; + + let ruleset = unsafe { super::GRULESET.clone().unwrap() }; + + let rule_index = ruleset.get_index(self.path.clone().to_str().unwrap(), "", ruleset.monitor.clone()); + let rule = ruleset.get_rule(rule_index, ruleset.monitor.clone()); + let expression = match Regex::new(&rule){ + Err(e) => { + error!("Cannot create regex rule: {}, Error: {}", rule, e); + return; + }, + Ok(exp) => exp, + }; + println!("Rule: {}", rule.clone()); + let filename = self.path.file_name().unwrap().to_str().unwrap(); + if expression.is_match(filename){ + //event_match... + println!("Rule {} Match!", rule.clone()); + println!("Filename: {}", filename); + } + + route(self).await; + + } + + // ------------------------------------------------------------------------ + + fn get_string(&self, field: String) -> String { + match field.as_str() { + "path" => String::from(self.path.to_str().unwrap()), + "file_size" => self.size.clone().to_string(), + "hostname" => self.hostname.clone(), + "node" => self.node.clone(), + "version" => self.version.clone(), + "operation" => self.operation.clone(), + "detailed_operation" => self.detailed_operation.clone(), + "checksum" => self.checksum.clone(), + "system" => self.system.clone(), + _ => "".to_string() + } + } +} + +// ---------------------------------------------------------------------------- + +impl fmt::Debug for MonitorEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{ + f.debug_tuple("") + .field(&self.id) + .field(&self.path) + .field(&self.size) + .field(&self.operation) + .field(&self.detailed_operation) + .finish() + } +} + +// ---------------------------------------------------------------------------- + +pub async fn route(event: &MonitorEvent) { + let config = unsafe { super::GCONFIG.clone().unwrap() }; + match config.get_events_destination().as_str() { + config::BOTH_MODE => { + event.log(config.get_events_file()); + event.send().await; + }, + config::NETWORK_MODE => { + event.send().await; + }, + _ => event.log(config.get_events_file()) + } +} + +// ---------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + use crate::event::Event; + use crate::config::Config; + use crate::utils; + use std::path::PathBuf; + use tokio_test::block_on; + use std::fs; + + // ------------------------------------------------------------------------ + + fn remove_test_file(filename: String) { + fs::remove_file(filename).unwrap() + } + + fn create_test_event() -> Event { + Event { + id: "Test_id".to_string(), + timestamp: "Timestamp".to_string(), + hostname: "Hostname".to_string(), + node: "FIM".to_string(), + version: "x.x.x".to_string(), + kind: EventKind::Create(CreateKind::Any), + path: PathBuf::new(), + size: 0, + labels: Vec::new(), + operation: "CREATE".to_string(), + detailed_operation: "CREATE_FILE".to_string(), + checksum: "UNKNOWN".to_string(), + fpid: 0, + system: "test".to_string() + } + } + + fn initialize() { + unsafe{ + super::super::GCONFIG = Some(config::Config::new(&utils::get_os(), None)); + } + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_clone() { + let event = create_test_event(); + let cloned = event.clone(); + assert_eq!(event.id, cloned.id); + assert_eq!(event.timestamp, cloned.timestamp); + assert_eq!(event.hostname, cloned.hostname); + assert_eq!(event.node, cloned.node); + assert_eq!(event.version, cloned.version); + assert_eq!(event.path, cloned.path); + assert_eq!(event.size, cloned.size); + assert_eq!(event.kind, cloned.kind); + assert_eq!(event.labels, cloned.labels); + assert_eq!(event.operation, cloned.operation); + assert_eq!(event.detailed_operation, cloned.detailed_operation); + assert_eq!(event.checksum, cloned.checksum); + assert_eq!(event.fpid, cloned.fpid); + assert_eq!(event.system, cloned.system); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_create_event() { + let evt = create_test_event(); + assert_eq!(evt.id, "Test_id".to_string()); + assert_eq!(evt.timestamp, "Timestamp".to_string()); + assert_eq!(evt.hostname, "Hostname".to_string()); + assert_eq!(evt.node, "FIM".to_string()); + assert_eq!(evt.version, "x.x.x".to_string()); + assert_eq!(evt.kind, EventKind::Create(CreateKind::Any) ); + assert_eq!(evt.path, PathBuf::new()); + assert_eq!(evt.labels, Vec::::new()); + assert_eq!(evt.operation, String::from("CREATE")); + assert_eq!(evt.detailed_operation, String::from("CREATE_FILE")); + assert_eq!(evt.fpid, 0); + assert_eq!(evt.system, String::from("test")); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_send() { + initialize(); + let evt = create_test_event(); + block_on( evt.send(String::from("test")) ); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_send_splunk() { + initialize(); + let evt = create_test_event(); + unsafe { + super::super::GCONFIG = Some(config::Config::new(&utils::get_os(), Some("test/unit/config/common/test_send_splunk.yml"))); + } + block_on( evt.send(String::from("test")) ); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_get_operation(){ + assert_eq!(get_operation(EventKind::Create(CreateKind::Any)), String::from("CREATE")); + assert_eq!(get_operation(EventKind::Modify(ModifyKind::Any)), String::from("WRITE")); + assert_eq!(get_operation(EventKind::Remove(RemoveKind::Any)), String::from("REMOVE")); + assert_eq!(get_operation(EventKind::Access(AccessKind::Any)), String::from("ACCESS")); + assert_eq!(get_operation(EventKind::Other), String::from("OTHER")); + assert_eq!(get_operation(EventKind::Any), String::from("ANY")); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_get_detailed_operation(){ + assert_eq!(get_detailed_operation(EventKind::Any), String::from("ANY")); + assert_eq!(get_detailed_operation(EventKind::Create(CreateKind::Any)), + String::from("CREATE_ANY")); + assert_eq!(get_detailed_operation(EventKind::Create(CreateKind::File)), + String::from("CREATE_FILE")); + assert_eq!(get_detailed_operation(EventKind::Create(CreateKind::Folder)), + String::from("CREATE_FOLDER")); + assert_eq!(get_detailed_operation(EventKind::Create(CreateKind::Other)), + String::from("CREATE_OTHER")); + + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Any)), + String::from("MODIFY_ANY")); + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Data(DataChange::Any))), + String::from("MODIFY_DATA_ANY")); + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Data(DataChange::Size))), + String::from("MODIFY_DATA_SIZE")); + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Data(DataChange::Content))), + String::from("MODIFY_DATA_CONTENT")); + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Data(DataChange::Other))), + String::from("MODIFY_DATA_OTHER")); + assert_eq!(get_detailed_operation(EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any))), + String::from("MODIFY_METADATA_ANY")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::AccessTime))), + String::from("MODIFY_METADATA_ACCESSTIME")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::WriteTime))), + String::from("MODIFY_METADATA_WRITETIME")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::Permissions))), + String::from("MODIFY_METADATA_PERMISSIONS")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::Ownership))), + String::from("MODIFY_METADATA_OWNERSHIP")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::Extended))), + String::from("MODIFY_METADATA_EXTENDED")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Metadata(MetadataKind::Other))), + String::from("MODIFY_METADATA_OTHER")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Name(RenameMode::Any))), String::from("MODIFY_RENAME_ANY")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Name(RenameMode::To))), String::from("MODIFY_RENAME_TO")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Name(RenameMode::From))), String::from("MODIFY_RENAME_FROM")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Name(RenameMode::Both))), String::from("MODIFY_RENAME_BOTH")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Name(RenameMode::Other))), String::from("MODIFY_RENAME_OTHER")); + assert_eq!(get_detailed_operation(EventKind::Modify( + ModifyKind::Other)), String::from("MODIFY_OTHER")); + + assert_eq!(get_detailed_operation(EventKind::Remove( + RemoveKind::Any)), String::from("REMOVE_ANY")); + assert_eq!(get_detailed_operation(EventKind::Remove( + RemoveKind::File)), String::from("REMOVE_FILE")); + assert_eq!(get_detailed_operation(EventKind::Remove( + RemoveKind::Folder)), String::from("REMOVE_FOLDER")); + assert_eq!(get_detailed_operation(EventKind::Remove( + RemoveKind::Other)), String::from("REMOVE_OTHER")); + + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Any)), String::from("ACCESS_ANY")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Read)), String::from("ACCESS_READ")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Open(AccessMode::Any))), String::from("ACCESS_OPEN_ANY")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Open(AccessMode::Execute))), String::from("ACCESS_OPEN_EXECUTE")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Open(AccessMode::Read))), String::from("ACCESS_OPEN_READ")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Open(AccessMode::Write))), String::from("ACCESS_OPEN_WRITE")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Open(AccessMode::Other))), String::from("ACCESS_OPEN_OTHER")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Close(AccessMode::Any))), String::from("ACCESS_CLOSE_ANY")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Close(AccessMode::Execute))), String::from("ACCESS_CLOSE_EXECUTE")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Close(AccessMode::Read))), String::from("ACCESS_CLOSE_READ")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Close(AccessMode::Write))), String::from("ACCESS_CLOSE_WRITE")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Close(AccessMode::Other))), String::from("ACCESS_CLOSE_OTHER")); + assert_eq!(get_detailed_operation(EventKind::Access( + AccessKind::Other)), String::from("ACCESS_OTHER")); + + assert_eq!(get_detailed_operation(EventKind::Other), String::from("OTHER")); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_process() { + initialize(); + let config = Config::new(&utils::get_os(), None); + let event = create_test_event(); + + block_on(event.process(config::NETWORK_MODE, String::from("test"), config.clone())); + block_on(event.process(config::FILE_MODE, String::from("test2"), config.clone())); + block_on(event.process(config::BOTH_MODE, String::from("test3"), config.clone())); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_event_fmt(){ + let out = format!("{:?}", create_test_event()); + assert_eq!(out, "(\"Test_id\", \"\", 0, \"CREATE\", \"CREATE_FILE\")"); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_format_json() { + let expected = "{\"checksum\":\"UNKNOWN\",\"detailed_operation\":\"CREATE_FILE\",\ + \"file\":\"\",\"file_size\":0,\"fpid\":0,\ + \"hostname\":\"Hostname\",\"id\":\"Test_id\",\"labels\":[],\ + \"node\":\"FIM\",\"operation\":\"CREATE\",\"system\":\"test\",\ + \"timestamp\":\"Timestamp\",\"version\":\"x.x.x\"}"; + assert_eq!(create_test_event().format_json(), expected); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_log() { + let filename = String::from("test_event.json"); + let evt = create_test_event(); + + evt.log(filename.clone()); + let contents = fs::read_to_string(filename.clone()); + let expected = "{\"checksum\":\"UNKNOWN\",\"detailed_operation\":\"CREATE_FILE\",\ + \"file\":\"\",\"file_size\":0,\"fpid\":0,\ + \"hostname\":\"Hostname\",\"id\":\"Test_id\",\"labels\":[],\ + \"node\":\"FIM\",\"operation\":\"CREATE\",\ + \"system\":\"test\",\ + \"timestamp\":\"Timestamp\",\"version\":\"x.x.x\"}\n"; + assert_eq!(contents.unwrap(), expected); + remove_test_file(filename.clone()); + } +} \ No newline at end of file diff --git a/src/monitorruleevent.rs b/src/monitorruleevent.rs new file mode 100644 index 0000000..da82e7c --- /dev/null +++ b/src/monitorruleevent.rs @@ -0,0 +1,225 @@ +// Copyright (C) 2024, Achiefs. + +use crate::event; +use crate::config; + +use event::Event; +use log::*; +use serde_json::{json, to_string}; +use std::path::PathBuf; +use reqwest::Client; +use std::fs::OpenOptions; +use std::time::Duration; +//use std::fmt; +use std::io::Write; + +pub struct MonitorRuleEvent { + pub id: String, + pub cid: String, + pub rule: String, + pub timestamp: String, + pub hostname: String, + pub node: String, + pub version: String, + pub path: PathBuf, + pub labels: Vec, + pub fpid: u32, + pub system: String, + pub message: String +} + +// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +impl Event for MonitorRuleEvent { + // Get formatted string with all required data + fn format_json(&self) -> String { + let obj = json!({ + "id": self.id.clone(), + "cid": self.cid.clone(), + "rule": self.rule.clone(), + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "system": self.system.clone(), + "message": self.message.clone() + }); + to_string(&obj).unwrap() + } + + // ------------------------------------------------------------------------ + + fn clone(&self) -> Self { + MonitorRuleEvent { + id: self.id.clone(), + cid: self.cid.clone(), + rule: self.rule.clone(), + timestamp: self.timestamp.clone(), + hostname: self.hostname.clone(), + node: self.node.clone(), + version: self.version.clone(), + path: self.path.clone(), + labels: self.labels.clone(), + fpid: self.fpid, + system: self.system.clone(), + message: self.message.clone() + } + } + + // ------------------------------------------------------------------------ + + // Function to write the received events to file + fn log(&self, file: String) { + let mut events_file = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .expect("(log) Unable to open events log file."); + + match writeln!(events_file, "{}", self.format_json() ) { + Ok(_d) => debug!("Event log written"), + Err(e) => error!("Event could not be written, Err: [{}]", e) + } + } + + // ------------------------------------------------------------------------ + + // Function to send events through network + async fn send(&self) { + use time::OffsetDateTime; + let current_date = OffsetDateTime::now_utc(); + let config = unsafe { super::GCONFIG.clone().unwrap() }; + let index = format!("fim-{}-{}-{}", current_date.year(), current_date.month() as u8, current_date.day() ); + + // Splunk endpoint integration + if config.endpoint_type == "Splunk" { + let data = json!({ + "source": self.node.clone(), + "sourcetype": "_json", + "event": json!({ + "cid": self.cid.clone(), + "rule": self.rule.clone(), + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "system": self.system.clone(), + "message": self.message.clone() + }), + "index": "fim_events" + }); + debug!("Sending received event to Splunk integration, event: {}", data); + let request_url = format!("{}/services/collector/event", config.endpoint_address); + let client = Client::builder() + .danger_accept_invalid_certs(config.insecure) + .timeout(Duration::from_secs(30)) + .build().unwrap(); + match client + .post(request_url) + .header("Authorization", format!("Splunk {}", config.endpoint_token)) + .json(&data) + .send() + .await { + Ok(response) => debug!("Response received: {:?}", + response.text().await.unwrap()), + Err(e) => debug!("Error on request: {:?}", e) + } + // Elastic endpoint integration + } else { + let data = json!({ + "cid": self.cid.clone(), + "rule": self.rule.clone(), + "timestamp": self.timestamp.clone(), + "hostname": self.hostname.clone(), + "node": self.node.clone(), + "fpid": self.fpid.clone(), + "version": self.version.clone(), + "labels": self.labels.clone(), + "system": self.system.clone(), + "message": self.message.clone() + }); + let request_url = format!("{}/{}/_doc/{}", config.endpoint_address, index, self.id); + let client = Client::builder() + .danger_accept_invalid_certs(config.insecure) + .timeout(Duration::from_secs(30)) + .build().unwrap(); + match client + .post(request_url) + .basic_auth(config.endpoint_user, Some(config.endpoint_pass)) + .json(&data) + .send() + .await { + Ok(response) => debug!("Response received: {:?}", + response.text().await.unwrap()), + Err(e) => debug!("Error on request: {:?}", e) + } + } + + } + + // ----------------------------------------------------------------------- + + // ------------------------------------------------------------------------ + + // Function to manage event destination + async fn process(&self) { + use regex::Regex; + + let ruleset = unsafe { super::GRULESET.clone().unwrap() }; + + let rule_index = ruleset.get_index(self.path.clone().to_str().unwrap(), "", ruleset.monitor.clone()); + let mut rule = ruleset.get_rule(rule_index, ruleset.monitor.clone()); + rule.push_str("{1}"); + let expression = Regex::new(&rule).unwrap(); + if expression.is_match(self.path.file_name().unwrap().to_str().unwrap()){ + //event_match... + } + + route(self).await; + + } + + // ------------------------------------------------------------------------ + + fn get_string(&self, field: String) -> String { + match field.as_str() { + "cid" => self.cid.clone(), + "rule" => self.rule.clone(), + "path" => String::from(self.path.to_str().unwrap()), + "hostname" => self.hostname.clone(), + "node" => self.node.clone(), + "version" => self.version.clone(), + "system" => self.system.clone(), + "message" => self.message.clone(), + _ => "".to_string() + } + } +} + + +// ------------------------------------------------------------------------ + +/*pub async fn send_custom_event(message: String, ) { + Event { + + } +}*/ + + +pub async fn route(event: &MonitorRuleEvent) { + let config = unsafe { super::GCONFIG.clone().unwrap() }; + match config.get_events_destination().as_str() { + config::BOTH_MODE => { + event.log(config.get_events_file()); + event.send().await; + }, + config::NETWORK_MODE => { + event.send().await; + }, + _ => event.log(config.get_events_file()) + } +} \ No newline at end of file diff --git a/src/rotator.rs b/src/rotator.rs index 8a99b3e..3ab1869 100644 --- a/src/rotator.rs +++ b/src/rotator.rs @@ -222,6 +222,7 @@ pub fn rotator(){ } start_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + thread::sleep(Duration::from_secs(1800)); } } } diff --git a/src/ruleset.rs b/src/ruleset.rs new file mode 100644 index 0000000..c967047 --- /dev/null +++ b/src/ruleset.rs @@ -0,0 +1,240 @@ +// Copyright (C) 2024, Achiefs. + +// Global definitions +const RULESET_MACOS_PATH: &str = "/Applications/FileMonitor.app/rules.yml"; +const RULESET_LINUX_PATH: &str = "/etc/fim/rules.yml"; +const RULESET_WINDOWS_PATH: &str = "C:\\Program Files\\File Integrity Monitor\\rules.yml"; + +use yaml_rust::yaml::{Yaml, YamlLoader, Array}; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use crate::utils; + +// ---------------------------------------------------------------------------- + +#[derive(Clone)] +pub struct Ruleset { + pub path: String, + pub monitor: Array, + pub audit: Array, + pub node: String, + pub system: String +} + +impl Ruleset { + + pub fn clone(&self) -> Self { + Ruleset { + path: self.path.clone(), + monitor: self.monitor.clone(), + audit: self.audit.clone(), + node: self.node.clone(), + system: self.system.clone() + } + } + + pub fn new(system: &str, ruleset_path: Option<&str>) -> Self { + println!("[INFO] System detected '{}'", system); + let cfg = match ruleset_path { + Some(path) => String::from(path), + None => get_ruleset_path(system) + }; + println!("[INFO] Loading rules from: '{}'", cfg); + let yaml = read_ruleset(cfg.clone()); + println!("{:?}", yaml.clone()); + + // Manage null value on monitor value + let monitor = match yaml[0]["monitor"].as_vec() { + Some(value) => value.to_vec(), + None => Vec::new() + }; + + // Manage null value on audit value + let audit = match yaml[0]["audit"].as_vec() { + Some(value) => { + if utils::get_os() != "linux"{ + panic!("Audit only supported in Linux systems."); + } + value.to_vec() + }, + None => { + if monitor.is_empty() { + panic!("Neither monitor or audit section found in rules.yml."); + }; + Vec::new() + } + }; + + // Manage null value on node value + let node = match yaml[0]["node"].as_str() { + Some(value) => String::from(value), + None => { + match system { + "linux" => match utils::get_machine_id().is_empty() { + true => utils::get_hostname(), + false => utils::get_machine_id() + }, + "macos" => match utils::get_machine_id().is_empty(){ + true => utils::get_hostname(), + false => utils::get_machine_id() + } + _ => { + println!("[WARN] node not found in rules.yml, using hostname."); + utils::get_hostname() + } + } + } + }; + + Ruleset { + path: cfg, + monitor, + audit, + node, + system: String::from(system) + } + } + + // ------------------------------------------------------------------------ + + pub fn get_index(&self, raw_path: &str, cwd: &str, array: Array) -> usize { + // Iterate over monitoring paths to match the given path + match array.iter().position(|it| { + if !cwd.is_empty() && (raw_path.starts_with("./") || raw_path == "." || !raw_path.contains('/')) { + utils::match_path(cwd, it["path"].as_str().unwrap()) + }else{ + utils::match_path(raw_path, it["path"].as_str().unwrap()) + } + }){ + Some(pos) => pos, + None => usize::MAX + } + } + + // ------------------------------------------------------------------------ + + pub fn clean_rule(raw_rule: String) -> String { + let mut rule = raw_rule.clone(); + rule.retain(|x| {!['\"', ':', '\'', '/', '|', '>', '<', '?'].contains(&x)}); + rule + } + + // ------------------------------------------------------------------------ + + pub fn get_rule(&self, index: usize, array: Array) -> String { + match array[index]["rule"].as_str() { + Some(rule) => String::from(Ruleset::clean_rule(String::from(rule))), + None => String::from("") + } + } +} + +// ---------------------------------------------------------------------------- + +pub fn read_ruleset(path: String) -> Vec { + let mut file: File = File::open(path.clone()) + .unwrap_or_else(|_| panic!("(read_ruleset): Unable to open file '{}'", path)); + let mut contents: String = String::new(); + + file.read_to_string(&mut contents) + .expect(&format!("(read_ruleset): Unable to read contents of file '{}'", path)); + YamlLoader::load_from_str(&contents).unwrap() +} + + +// ---------------------------------------------------------------------------- + +pub fn get_ruleset_path(system: &str) -> String { + // Select directory where to load rules.yml it depends on system + let current_dir: String = utils::get_current_dir(); + if system == "windows" { + let default_path: String = format!("{}\\config\\{}\\rules.yml", current_dir, system); + let relative_path: String = format!("{}\\..\\..\\config\\{}\\rules.yml", current_dir, system); + if Path::new(default_path.as_str()).exists() { + default_path + }else if Path::new(&format!("{}\\rules.yml", current_dir)).exists() { + format!("{}\\rules.yml", current_dir) + }else if Path::new(relative_path.as_str()).exists() { + relative_path + }else{ + String::from(RULESET_WINDOWS_PATH) + } + }else{ + let default_path: String = format!("{}/config/{}/rules.yml", current_dir, system); + let relative_path: String = format!("{}/../../config/{}/rules.yml", current_dir, system); + if Path::new(default_path.as_str()).exists() { + default_path + }else if Path::new(&format!("{}/rules.yml", current_dir)).exists() { + format!("{}/rules.yml", current_dir) + }else if Path::new(relative_path.as_str()).exists() { + relative_path + }else if system == "macos" { + String::from(RULESET_MACOS_PATH) + }else{ + String::from(RULESET_LINUX_PATH) + } + } +} + +// ---------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + // ------------------------------------------------------------------------ + + /*pub fn create_test_config(filter: &str, events_destination: &str) -> Config { + Config { + version: String::from(VERSION), + path: String::from("test"), + events_watcher: String::from("Recommended"), + events_destination: String::from(events_destination), + events_max_file_checksum: 64, + events_max_file_size: 128, + endpoint_type: String::from("Elastic"), + endpoint_address: String::from("test"), + endpoint_user: String::from("test"), + endpoint_pass: String::from("test"), + endpoint_token: String::from("test"), + events_file: String::from("test"), + monitor: Array::new(), + audit: Array::new(), + node: String::from("test"), + log_file: String::from("./test.log"), + log_level: String::from(filter), + log_max_file_size: 64, + system: String::from("test"), + insecure: true + } + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_clone() { + let config = create_test_config("info", ""); + let cloned = config.clone(); + assert_eq!(config.version, cloned.version); + assert_eq!(config.path, cloned.path); + assert_eq!(config.events_destination, cloned.events_destination); + assert_eq!(config.events_max_file_checksum, cloned.events_max_file_checksum); + assert_eq!(config.events_max_file_size, cloned.events_max_file_size); + assert_eq!(config.endpoint_type, cloned.endpoint_type); + assert_eq!(config.endpoint_address, cloned.endpoint_address); + assert_eq!(config.endpoint_user, cloned.endpoint_user); + assert_eq!(config.endpoint_pass, cloned.endpoint_pass); + assert_eq!(config.endpoint_token, cloned.endpoint_token); + assert_eq!(config.events_file, cloned.events_file); + assert_eq!(config.monitor, cloned.monitor); + assert_eq!(config.audit, cloned.audit); + assert_eq!(config.node, cloned.node); + assert_eq!(config.log_file, cloned.log_file); + assert_eq!(config.log_level, cloned.log_level); + assert_eq!(config.log_max_file_size, cloned.log_max_file_size); + assert_eq!(config.system, cloned.system); + assert_eq!(config.insecure, cloned.insecure); + }*/ + +}