diff --git a/.github/workflows/coverall.yml b/.github/workflows/coverall.yml index 7db6456..822e626 100644 --- a/.github/workflows/coverall.yml +++ b/.github/workflows/coverall.yml @@ -20,7 +20,7 @@ jobs: uses: actions-rs/tarpaulin@v0.1 with: version: 0.22.0 - args: '--all-features --out Lcov --exclude-files src/main.rs src/monitor.rs' + args: '--all-features --out Lcov --exclude-files src/main.rs src/monitor.rs src/init.rs' - name: upload to Coveralls uses: coverallsapp/github-action@master with: diff --git a/src/event.rs b/src/event.rs index bb61c24..be84a48 100644 --- a/src/event.rs +++ b/src/event.rs @@ -2,7 +2,6 @@ use crate::appconfig::*; use crate::ruleset::*; - use notify::event::*; pub trait Event { @@ -16,22 +15,6 @@ pub trait Event { // ---------------------------------------------------------------------------- -/*pub async fn route(event: &dyn Event) { - let cfg = unsafe { super::GCONFIG.clone().unwrap() }; - match cfg.get_events_destination().as_str() { - appconfig::BOTH_MODE => { - event.log(cfg.get_events_file()); - event.send().await; - }, - appconfig::NETWORK_MODE => { - event.send().await; - }, - _ => event.log(cfg.get_events_file()) - } -}*/ - -// ---------------------------------------------------------------------------- - pub fn get_operation(event_kind: EventKind) -> String { let detailed_operation: String = get_detailed_operation(event_kind); if detailed_operation == "ANY" { diff --git a/src/ruleevent.rs b/src/ruleevent.rs index 8f98ad5..764fc89 100644 --- a/src/ruleevent.rs +++ b/src/ruleevent.rs @@ -12,7 +12,6 @@ use std::path::PathBuf; use reqwest::Client; use std::fs::OpenOptions; use std::time::Duration; -//use std::fmt; use std::io::Write; pub struct RuleEvent { @@ -20,7 +19,6 @@ pub struct RuleEvent { pub rule: String, pub timestamp: String, pub hostname: String, - pub node: String, pub version: String, pub path: PathBuf, pub fpid: u32, @@ -28,7 +26,7 @@ pub struct RuleEvent { pub message: String } -// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- impl Event for RuleEvent { // Get formatted string with all required data @@ -38,7 +36,6 @@ impl Event for RuleEvent { "rule": self.rule.clone(), "timestamp": self.timestamp.clone(), "hostname": self.hostname.clone(), - "node": self.node.clone(), "fpid": self.fpid.clone(), "version": self.version.clone(), "system": self.system.clone(), @@ -55,7 +52,6 @@ impl Event for RuleEvent { rule: self.rule.clone(), timestamp: self.timestamp.clone(), hostname: self.hostname.clone(), - node: self.node.clone(), version: self.version.clone(), path: self.path.clone(), fpid: self.fpid, @@ -91,13 +87,12 @@ impl Event for RuleEvent { // Splunk endpoint integration if cfg.endpoint_type == "Splunk" { let data = json!({ - "source": self.node.clone(), + "source": "FIM_RULESET", "sourcetype": "_json", "event": json!({ "rule": self.rule.clone(), "timestamp": self.timestamp.clone(), "hostname": self.hostname.clone(), - "node": self.node.clone(), "fpid": self.fpid.clone(), "version": self.version.clone(), "system": self.system.clone(), @@ -127,7 +122,6 @@ impl Event for RuleEvent { "rule": self.rule.clone(), "timestamp": self.timestamp.clone(), "hostname": self.hostname.clone(), - "node": self.node.clone(), "fpid": self.fpid.clone(), "version": self.version.clone(), "system": self.system.clone(), @@ -175,11 +169,125 @@ impl Event for RuleEvent { "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() } } +} + +// ---------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + 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() -> RuleEvent { + RuleEvent { + id: 0, + rule: "\\.php$".to_string(), + timestamp: "Timestamp".to_string(), + hostname: "Hostname".to_string(), + version: "x.x.x".to_string(), + path: PathBuf::new(), + fpid: 0, + system: "test".to_string(), + message: "This is a message".to_string(), + } + } + + // ------------------------------------------------------------------------ + + #[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.version, cloned.version); + assert_eq!(event.path, cloned.path); + assert_eq!(event.fpid, cloned.fpid); + assert_eq!(event.system, cloned.system); + assert_eq!(event.message, cloned.message); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_new() { + let evt = create_test_event(); + assert_eq!(evt.id, 0); + assert_eq!(evt.timestamp, "Timestamp".to_string()); + assert_eq!(evt.hostname, "Hostname".to_string()); + assert_eq!(evt.version, "x.x.x".to_string()); + assert_eq!(evt.path, PathBuf::new()); + assert_eq!(evt.fpid, 0); + assert_eq!(evt.system, String::from("test")); + assert_eq!(evt.message, String::from("This is a message")); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_send() { + let evt = create_test_event(); + let cfg = AppConfig::new(&utils::get_os(), None); + block_on( evt.send(cfg) ); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_send_splunk() { + let evt = create_test_event(); + let cfg = AppConfig::new(&utils::get_os(), Some("test/unit/config/common/test_send_splunk.yml")); + block_on( evt.send(cfg) ); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_process() { + let event = create_test_event(); + let cfg = AppConfig::new(&utils::get_os(), None); + let ruleset = Ruleset::new(&utils::get_os(), None); + + block_on(event.process(cfg, ruleset)); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_format_json() { + let expected = "{\"fpid\":0,\"hostname\":\"Hostname\",\"id\":0,\"message\":\"This is a message\",\ + \"rule\":\"\\\\.php$\",\"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_ruleevent.json"); + let evt = create_test_event(); + + evt.log(filename.clone()); + let contents = fs::read_to_string(filename.clone()); + let expected = "{\"fpid\":0,\"hostname\":\"Hostname\",\"id\":0,\"message\":\"This is a message\",\ + \"rule\":\"\\\\.php$\",\"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/ruleset.rs b/src/ruleset.rs index 4ecde21..305cf79 100644 --- a/src/ruleset.rs +++ b/src/ruleset.rs @@ -138,7 +138,6 @@ impl Ruleset { rule, timestamp: format!("{:?}", SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_millis()), hostname: utils::get_hostname(), - node: cfg.clone().node, version: String::from(appconfig::VERSION), path: filepath, fpid: utils::get_pid(), @@ -211,58 +210,160 @@ pub fn get_ruleset_path(system: &str) -> String { #[cfg(test)] mod tests { - //use super::*; + use super::*; + use tokio_test::block_on; + + #[cfg(not(target_os = "windows"))] + #[test] + fn test_get_ruleset_path_unix() { + let current_dir = utils::get_current_dir(); + let default_path_linux = format!("{}/config/linux/rules.yml", current_dir); + let default_path_macos = format!("{}/config/macos/rules.yml", current_dir); + assert_eq!(get_ruleset_path("linux"), default_path_linux); + assert_eq!(get_ruleset_path("macos"), default_path_macos); + } // ------------------------------------------------------------------------ - /*pub fn create_test_config(filter: &str, events_destination: &str) -> AppConfig { - AppConfig { - 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(), - log_file: String::from("./test.log"), - log_level: String::from(filter), - log_max_file_size: 64, - system: String::from("test"), - insecure: true - } + #[cfg(target_os = "windows")] + #[test] + fn test_get_ruleset_path_windows() { + let current_dir = utils::get_current_dir(); + let default_path_windows = format!("{}\\config\\windows\\rules.yml", current_dir); + assert_eq!(get_ruleset_path("windows"), default_path_windows); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_read_ruleset_unix() { + let yaml = read_ruleset(String::from("config/linux/rules.yml")); + + assert_eq!(yaml[0]["rules"][0]["id"].as_i64().unwrap(), 1); + assert_eq!(yaml[0]["rules"][0]["path"].as_str().unwrap(), "/etc"); + assert_eq!(yaml[0]["rules"][0]["rule"].as_str().unwrap(), "\\.sh$"); + assert_eq!(yaml[0]["rules"][0]["message"].as_str().unwrap(), "Shell script present in /etc folder."); + } + + // ------------------------------------------------------------------------ + + #[cfg(target_os = "windows")] + #[test] + fn test_read_ruleset_windows() { + let yaml = read_ruleset(String::from("config/windows/rules.yml")); + + assert_eq!(yaml[0]["rules"][0]["id"].as_i64().unwrap(), 1); + assert_eq!(yaml[0]["rules"][0]["path"].as_str().unwrap(), "C:\\"); + assert_eq!(yaml[0]["rules"][0]["rule"].as_str().unwrap(), "\\.ps1$"); + assert_eq!(yaml[0]["rules"][0]["message"].as_str().unwrap(), "Powershell script present in root directory."); + } + + // ------------------------------------------------------------------------ + + #[test] + #[should_panic(expected = "NotFound")] + fn test_read_ruleset_panic() { + read_ruleset(String::from("NotFound")); + } + + // ------------------------------------------------------------------------ + + #[test] + #[should_panic(expected = "ScanError")] + fn test_read_ruleset_panic_not_config() { + read_ruleset(String::from("README.md")); + } + + // ------------------------------------------------------------------------ + + #[test] + fn test_sanitize() { + assert_eq!("test", sanitize("test")); + assert_eq!("test", sanitize("t\"est")); + assert_eq!("C\\test", sanitize("C:\\test")); + assert_eq!("test", sanitize("t\'est")); + assert_eq!("test", sanitize("t/est")); + assert_eq!("test", sanitize("t|est")); + assert_eq!("test", sanitize("t>est")); + assert_eq!("test", sanitize("t