Skip to content

Commit

Permalink
Merge pull request #110 from Achiefs/109-include-allowlist
Browse files Browse the repository at this point in the history
Include allow list to filter monitoring files
  • Loading branch information
okynos authored May 3, 2023
2 parents 9cb1420 + ce8a2ea commit 722a30a
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 47 deletions.
28 changes: 28 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@ impl Config {

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

pub fn match_allowed(&self, index: usize, filename: &str, array: Array) -> bool {
match array[index]["allowed"].as_vec() {
Some(allowed) => allowed.to_vec().iter().any(|allw| filename.contains(allw.as_str().unwrap())),
None => true
}
}

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

// Returns if a given path and filename is in the configuration paths
pub fn path_in(&self, raw_path: &str, cwd: &str, vector: Vec<Yaml>) -> bool {
// Iterate over monitoring paths to match ignore string and ignore event or not
Expand Down Expand Up @@ -1060,6 +1069,25 @@ mod tests {

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

#[test]
fn test_match_allowed() {
if utils::get_os() == "windows" {
let config = Config::new(&utils::get_os(), Some("test/unit/config/windows/monitor_allowed.yml"));
assert!(!config.match_allowed(1, "file.swp", config.monitor.clone()));
assert!(config.match_allowed(1, "file.txt", config.monitor.clone()));
} else if utils::get_os() == "linux" {
let config = Config::new(&utils::get_os(), Some("test/unit/config/linux/monitor_allowed.yml"));
assert!(!config.match_allowed(2, "file.swp", config.monitor.clone()));
assert!(config.match_allowed(2, "file.txt", config.monitor.clone()));

let config_audit = Config::new(&utils::get_os(), Some("test/unit/config/linux/audit_allowed.yml"));
assert!(!config_audit.match_allowed(0, "file.swp", config_audit.audit.clone()));
assert!(config_audit.match_allowed(0, "file.txt", config_audit.audit.clone()));
}
}

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

#[test]
fn test_get_integrations() {
let os = utils::get_os();
Expand Down
21 changes: 21 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,27 @@ mod tests {

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

#[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.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();
Expand Down
144 changes: 103 additions & 41 deletions src/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ pub fn get_event_integration(event: Event, integrations: Vec<Integration>) -> Op
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
}
Expand Down Expand Up @@ -124,6 +120,48 @@ mod tests {

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

#[cfg(target_os = "windows")]
pub fn create_dummy_event_windows(path: &str, operation: &str) -> 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(format!("C:\\{}\\test.txt", path)),
labels: Vec::new(),
operation: operation.to_string(),
detailed_operation: "CREATE_FILE".to_string(),
checksum: "UNKNOWN".to_string(),
fpid: 0,
system: "test".to_string()
}
}

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

#[cfg(any(target_os = "linux", target_os = "darwin"))]
pub fn create_dummy_event_unix(path: &str, operation: &str) -> 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(format!("/{}/test.txt", path)),
labels: Vec::new(),
operation: operation.to_string(),
detailed_operation: "CREATE_FILE".to_string(),
checksum: "UNKNOWN".to_string(),
fpid: 0,
system: "test".to_string()
}
}

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

#[test]
fn test_clone() {
let integration = create_test_integration();
Expand Down Expand Up @@ -161,26 +199,10 @@ mod tests {
#[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();
let integrations = config.get_integrations(2, config.monitor.clone());
let event = create_dummy_event_windows("tmp", "CREATE");
let integration = get_event_integration(event, integrations.clone()).unwrap();

assert_eq!(integration.name, "rmfile");
assert_eq!(integration.condition[0], "operation");
Expand All @@ -189,6 +211,18 @@ mod tests {
assert_eq!(integration.binary, "powershell.exe");
assert_eq!(integration.script, "C:\\tmp\\remover.ps1");
assert_eq!(integration.parameters, "");

let integrations2 = config.get_integrations(3, config.monitor.clone());
let event2 = create_dummy_event_windows("tmp2", "MODIFY");
let integration2 = get_event_integration(event2, integrations2.clone()).unwrap();

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

// ------------------------------------------------------------------------
Expand All @@ -198,25 +232,9 @@ mod tests {
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 event = create_dummy_event_unix("etc", "CREATE");
let integrations = config.get_integrations(2, config.monitor.clone());
let integration = get_event_integration(event, integrations).unwrap();

assert_eq!(integration.name, "rmfile");
Expand All @@ -226,6 +244,18 @@ mod tests {
assert_eq!(integration.binary, "bash");
assert_eq!(integration.script, "/tmp/remover.sh");
assert_eq!(integration.parameters, "");

let event2 = create_dummy_event_unix("etc2", "MODIFY");
let integrations2 = config.get_integrations(3, config.monitor.clone());
let integration2 = get_event_integration(event2, integrations2).unwrap();

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

// ------------------------------------------------------------------------
Expand All @@ -237,4 +267,36 @@ mod tests {
"(\"Name\", [\"A\", \"B\", \"C\"], \"Binary\", \"Script\", \"Parameters\")");
}

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

#[cfg(target_os = "windows")]
#[test]
fn test_launch_windows(){
let integration = Integration {
name: String::from("Name"),
condition: [String::from("A"), String::from("B"), String::from("C")].to_vec(),
binary: String::from("powershell.exe"),
script: String::from("ls"),
parameters: String::from("")
};

integration.launch(create_dummy_event_windows("tmp", "C").format_json());
}

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

#[cfg(any(target_os = "linux", target_os = "darwin"))]
#[test]
fn test_launch_unix(){
let integration = Integration {
name: String::from("Name"),
condition: [String::from("A"), String::from("B"), String::from("C")].to_vec(),
binary: String::from("bash"),
script: String::from("ls"),
parameters: String::from("")
};

integration.launch(create_dummy_event_unix("etc", "C").format_json());
}

}
37 changes: 31 additions & 6 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,26 @@ pub async fn monitor(tx: mpsc::Sender<Result<notify::Event, notify::Error>>,
if ! config.monitor.is_empty() {
for element in config.monitor.clone() {
let path = element["path"].as_str().unwrap();
info!("Monitoring path: {}", path);
info!("Checking path: {}", path);

match element["ignore"].as_vec() {
Some(ig) => {
let ignore_list_vec = ig.iter().map(|e| { e.as_str().unwrap() });
let ignore_list : String = Itertools::intersperse(ignore_list_vec, ", ").collect();
let ignore_vec = ig.iter().map(|e| e.as_str().unwrap() );
let ignore_list : String = Itertools::intersperse(ignore_vec, ", ").collect();
info!("Ignoring files with: {} inside {}", ignore_list, path);
},
None => info!("Ignore for '{}' not set", path)
};

match element["allowed"].as_vec(){
Some(allowed) => {
let allowed_vec = allowed.iter().map(|e| e.as_str().unwrap());
let allowed_list : String = Itertools::intersperse(allowed_vec, ", ").collect();
info!("Only files with '{}' will trigger event inside '{}'", allowed_list, path)
},
None => debug!("Monitoring all files under this path '{}'", path)
}

match watcher.watch(Path::new(path), RecursiveMode::Recursive) {
Ok(_d) => debug!("Monitoring path: {}", path),
Err(e) => warn!("Could not monitor given path '{}', description: {}", path, e)
Expand All @@ -110,10 +121,20 @@ pub async fn monitor(tx: mpsc::Sender<Result<notify::Event, notify::Error>>,
Ok(d) => debug!("Auditctl command info: {:?}", d),
Err(e) => error!("Auditctl command error: {}", e)
};
info!("Monitoring audit path: {}", path);
info!("Checking audit path: {}", path);

match element["allowed"].as_vec() {
Some(allowed) => {
let allowed_vec = allowed.iter().map(|e| e.as_str().unwrap() );
let allowed_list : String = Itertools::intersperse(allowed_vec, ", ").collect();
info!("Only files with '{}' will trigger event inside '{}'", allowed_list, path)
},
None => debug!("Monitoring all files under this path '{}'", path)
};

match element["ignore"].as_vec() {
Some(ig) => {
let ignore_list_vec = ig.iter().map(|e| { e.as_str().unwrap() });
let ignore_list_vec = ig.iter().map(|e| e.as_str().unwrap() );
let ignore_list : String = Itertools::intersperse(ignore_list_vec, ", ").collect();
info!("Ignoring files with: {} inside {}", ignore_list, path);
},
Expand Down Expand Up @@ -200,6 +221,9 @@ pub async fn monitor(tx: mpsc::Sender<Result<notify::Event, notify::Error>>,
if index != usize::MAX {
// If event contains ignored string ignore event
if ! config.match_ignore(index,
audit_event.clone().file.as_str(),
config.audit.clone()) &&
config.match_allowed(index,
audit_event.clone().file.as_str(),
config.audit.clone()) {
audit_event.process(destination.clone().as_str(), index_name.clone(), config.clone()).await;
Expand All @@ -217,7 +241,8 @@ pub async fn monitor(tx: mpsc::Sender<Result<notify::Event, notify::Error>>,
if index != usize::MAX {
let labels = config.get_labels(index, config.monitor.clone());
if ! config.match_ignore(index,
event_filename.to_str().unwrap(), config.monitor.clone()){
event_filename.to_str().unwrap(), config.monitor.clone()) &&
config.match_allowed(index, event_filename.to_str().unwrap(), config.monitor.clone()) {
let event = event::Event {
id: utils::get_uuid(),
timestamp: current_timestamp,
Expand Down
27 changes: 27 additions & 0 deletions test/unit/config/linux/audit_allowed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
node: "FIM"

# Events configuration, where to store produced events
events:
destination: file
file: /var/lib/fim/events.json

# Audit extended files and folders information
audit:
- path: /tmp
labels: ["tmp", "linux"]
ignore: [".swp"]
allowed: [".txt", ".rs"]

# Simple files and folders information
monitor:
- path: /bin/
- path: /usr/bin/
labels: ["usr/bin", "linux"]
- path: /etc
labels: ["etc", "linux"]

# App procedure and errors logging
log:
file: /var/log/fim/fim.log
# Available levels [debug, info, error, warning]
level: info
27 changes: 27 additions & 0 deletions test/unit/config/linux/monitor_allowed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
node: "FIM"

# Events configuration, where to store produced events
events:
destination: file
file: /var/lib/fim/events.json

# Audit extended files and folders information
audit:
- path: /tmp
labels: ["tmp", "linux"]
ignore: [".swp"]

# Simple files and folders information
monitor:
- path: /bin/
- path: /usr/bin/
labels: ["usr/bin", "linux"]
- path: /etc
labels: ["etc", "linux"]
allowed: [".txt", ".rs"]

# App procedure and errors logging
log:
file: /var/log/fim/fim.log
# Available levels [debug, info, error, warning]
level: info
Loading

0 comments on commit 722a30a

Please sign in to comment.