Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Integration launcher #108

Merged
merged 7 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use std::path::Path;
use simplelog::LevelFilter;
// To manage common functions
use crate::utils;
// Integrate FIM with external code
use crate::integration::Integration;

// ----------------------------------------------------------------------------

Expand All @@ -36,6 +38,7 @@ pub struct Config {
pub endpoint_pass: String,
pub events_file: String,
pub monitor: Array,
//pub monitor_integrations: Array,
pub audit: Array,
pub node: String,
pub log_file: String,
Expand Down Expand Up @@ -320,6 +323,27 @@ impl Config {
}
}

// ------------------------------------------------------------------------

pub fn get_integrations(&self, index: usize, array: Array) -> Vec<Integration> {
let data = match array[index]["integrations"].clone().into_vec() {
Some(integrations) => integrations,
None => Vec::new()
};
let mut integrations: Vec<Integration> = Vec::new();
data.iter().for_each(|info|
integrations.push(Integration::new(
String::from(info["name"].as_str().unwrap()),
info["condition"]
.clone().into_vec().unwrap().iter().map(|element|
String::from(element.as_str().unwrap()) ).collect(),
String::from(info["binary"].as_str().unwrap()),
String::from(info["script"].as_str().unwrap()),
String::from(info["parameters"].as_str().unwrap()) ))
);
integrations
}

}


Expand Down Expand Up @@ -1034,4 +1058,29 @@ mod tests {
}
}

// ------------------------------------------------------------------------

#[test]
fn test_get_integrations() {
let os = utils::get_os();
let config = Config::new(&os,
Some(format!("test/unit/config/{}/monitor_integration.yml", os)
.as_str())
);
if os.clone() == "windows" {
let integrations = config.get_integrations(2, config.monitor.clone());
assert_eq!(integrations.len(), 1);
}else if os.clone() == "macos"{
let integrations = config.get_integrations(2, config.monitor.clone());
assert_eq!(integrations.len(), 1);
}else{
let integrations_monitor = config.get_integrations(2, config.monitor.clone());
assert_eq!(integrations_monitor.len(), 1);

// Not implemented yet
//let integrations_audit = config.get_integrations(2, config.audit.clone());
//assert_eq!(integrations_audit.len(), 1);
}
}

}
37 changes: 36 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub struct Event {

impl Event {
// Get formatted string with all required data
fn format_json(&self) -> String {
pub fn format_json(&self) -> String {
let obj = json!({
"id": self.id.clone(),
"timestamp": self.timestamp.clone(),
Expand All @@ -59,6 +59,26 @@ impl Event {

// ------------------------------------------------------------------------

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(),
kind: self.kind.clone(),
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()
Expand Down Expand Up @@ -124,6 +144,21 @@ impl Event {
}
}

// ------------------------------------------------------------------------

pub fn get_string(&self, field: String) -> String {
match field.as_str() {
"path" => String::from(self.path.to_str().unwrap()),
"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()
}
}
}

// ----------------------------------------------------------------------------
Expand Down
240 changes: 240 additions & 0 deletions src/integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright (C) 2023, Achiefs.

// To implement Debug and fmt method
use std::fmt;
// To log the program process
use log::{debug, warn};
// To manage script execution
use std::process::Command;

// Single event data management
use crate::event::Event;
use crate::utils;

// ----------------------------------------------------------------------------

#[derive(Clone)]
pub struct Integration {
pub name: String,
pub condition: Vec<String>,
pub binary: String,
pub script: String,
pub parameters: String
}

// ----------------------------------------------------------------------------

impl Integration {

pub fn clone(&self) -> Self {
Integration {
name: self.name.clone(),
condition: self.condition.clone(),
binary: self.binary.clone(),
script: self.script.clone(),
parameters: self.parameters.clone()
}
}

// ------------------------------------------------------------------------

pub fn new(name: String, condition: Vec<String>, binary: String, script: String, parameters: String) -> Self {
Integration {
name,
condition,
binary,
script,
parameters
}
}

// ------------------------------------------------------------------------

pub fn launch(&self, event: String) {
let formatted_event = match utils::get_os().as_str() {
"windows" => format!("'{}'", event),
_ => format!("{}", event)
Fixed Show fixed Hide fixed
};
let output = Command::new(self.binary.clone())
.arg(self.script.clone())
.arg(formatted_event)
.arg(self.parameters.clone())
.output()
.expect("Failed to execute integration script");
debug!("Integration output: [{}]", String::from_utf8(output.stdout).unwrap());
let stderr = String::from_utf8(output.stderr).unwrap();
if !stderr.is_empty() { warn!("Integration error: '{}'", stderr) }
}

}

// ----------------------------------------------------------------------------

pub fn get_event_integration(event: Event, integrations: Vec<Integration>) -> Option<Integration> {
let option = integrations.iter().find(|integration|
match integration.condition[1].as_str() {
"==" => event.get_string(integration.condition[0].clone()) == integration.condition[2],
">" => event.get_string(integration.condition[0].clone()) > integration.condition[2],
"<" => event.get_string(integration.condition[0].clone()) < integration.condition[2],
">=" => event.get_string(integration.condition[0].clone()) >= integration.condition[2],
"<=" => event.get_string(integration.condition[0].clone()) <= integration.condition[2],
"!=" => event.get_string(integration.condition[0].clone()) != integration.condition[2],
_ => false
}
);
option.map(|int| int.clone())
}

// ----------------------------------------------------------------------------

impl fmt::Debug for Integration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{
f.debug_tuple("")
.field(&self.name)
.field(&self.condition)
.field(&self.binary)
.field(&self.script)
.field(&self.parameters)
.finish()
}
}

// ----------------------------------------------------------------------------


#[cfg(test)]
mod tests {
use super::*;
use notify::event::*;
use crate::config::*;
use std::path::PathBuf;
use crate::event::Event;

// ------------------------------------------------------------------------

pub fn create_test_integration() -> Integration {
Integration {
name: String::from("Name"),
condition: [String::from("A"), String::from("B"), String::from("C")].to_vec(),
binary: String::from("Binary"),
script: String::from("Script"),
parameters: String::from("Parameters")
}
}

// ------------------------------------------------------------------------

#[test]
fn test_clone() {
let integration = create_test_integration();
let cloned = integration.clone();
assert_eq!(integration.name, cloned.name);
assert_eq!(integration.condition, cloned.condition);
assert_eq!(integration.binary, cloned.binary);
assert_eq!(integration.script, cloned.script);
assert_eq!(integration.parameters, cloned.parameters);
}

// ------------------------------------------------------------------------

#[test]
fn test_new() {
let integration = Integration::new(
String::from("Name"),
[String::from("A"), String::from("B"), String::from("C")].to_vec(),
String::from("Binary"),
String::from("Script"),
String::from("Parameters")
);
assert_eq!(integration.name, "Name");
assert_eq!(integration.condition[0], "A");
assert_eq!(integration.condition[1], "B");
assert_eq!(integration.condition[2], "C");
assert_eq!(integration.binary, "Binary");
assert_eq!(integration.script, "Script");
assert_eq!(integration.parameters, "Parameters");
}

// ------------------------------------------------------------------------

#[cfg(target_os = "windows")]
#[test]
fn test_get_event_integration_windows() {
let config = Config::new("windows", Some("test/unit/config/windows/monitor_integration.yml"));
let 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::from("C:\\tmp\\test.txt"),
labels: Vec::new(),
operation: "CREATE".to_string(),
detailed_operation: "CREATE_FILE".to_string(),
checksum: "UNKNOWN".to_string(),
fpid: 0,
system: "test".to_string()
};

let index = config.get_index(event.path.to_str().unwrap(), "", config.monitor.clone());
let integrations = config.get_integrations(index, config.monitor.clone());

let integration = get_event_integration(event, integrations).unwrap();

assert_eq!(integration.name, "rmfile");
assert_eq!(integration.condition[0], "operation");
assert_eq!(integration.condition[1], "==");
assert_eq!(integration.condition[2], "CREATE");
assert_eq!(integration.binary, "powershell.exe");
assert_eq!(integration.script, "C:\\tmp\\remover.ps1");
assert_eq!(integration.parameters, "");
}

// ------------------------------------------------------------------------

#[cfg(any(target_os = "linux", target_os = "darwin"))]
#[test]
fn test_get_event_integration_unix() {
let os = utils::get_os();
let config = Config::new(&os, Some(format!("test/unit/config/{}/monitor_integration.yml", os).as_str()));
let 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::from("/etc/test.txt"),
labels: Vec::new(),
operation: "CREATE".to_string(),
detailed_operation: "CREATE_FILE".to_string(),
checksum: "UNKNOWN".to_string(),
fpid: 0,
system: "test".to_string()
};

let index = config.get_index(event.path.to_str().unwrap(), "", config.monitor.clone());
let integrations = config.get_integrations(index, config.monitor.clone());

let integration = get_event_integration(event, integrations).unwrap();

assert_eq!(integration.name, "rmfile");
assert_eq!(integration.condition[0], "operation");
assert_eq!(integration.condition[1], "==");
assert_eq!(integration.condition[2], "CREATE");
assert_eq!(integration.binary, "bash");
assert_eq!(integration.script, "/tmp/remover.sh");
assert_eq!(integration.parameters, "");
}

// ------------------------------------------------------------------------

#[test]
fn test_integration_fmt(){
let out = format!("{:?}", create_test_integration());
assert_eq!(out,
"(\"Name\", [\"A\", \"B\", \"C\"], \"Binary\", \"Script\", \"Parameters\")");
}

}
Loading