Feature/#158 add rulefilepath column (#168)
* add level csv column * update * Feature/output detect count151 (#167) * add output process count of detects events #151 * add output process count of detects event when output stdio #151 * add format enter * update Co-authored-by: DustInDark <nextsasasa@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ use std::process;
|
|||||||
pub struct CsvFormat<'a> {
|
pub struct CsvFormat<'a> {
|
||||||
time: &'a str,
|
time: &'a str,
|
||||||
filepath: &'a str,
|
filepath: &'a str,
|
||||||
|
rulepath: &'a str,
|
||||||
level: &'a str,
|
level: &'a str,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
message: &'a str,
|
message: &'a str,
|
||||||
@@ -79,6 +80,7 @@ fn emit_csv<W: std::io::Write>(writer: &mut W) -> Result<(), Box<dyn Error>> {
|
|||||||
wtr.serialize(CsvFormat {
|
wtr.serialize(CsvFormat {
|
||||||
time: &format_time(time),
|
time: &format_time(time),
|
||||||
filepath: &detect_info.filepath,
|
filepath: &detect_info.filepath,
|
||||||
|
rulepath: &detect_info.rulepath,
|
||||||
level: &detect_info.level,
|
level: &detect_info.level,
|
||||||
title: &detect_info.title,
|
title: &detect_info.title,
|
||||||
message: &detect_info.detail,
|
message: &detect_info.detail,
|
||||||
@@ -118,6 +120,7 @@ fn test_emit_csv() {
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fs::{read_to_string, remove_file};
|
use std::fs::{read_to_string, remove_file};
|
||||||
let testfilepath: &str = "test.evtx";
|
let testfilepath: &str = "test.evtx";
|
||||||
|
let testrulepath: &str = "test-rule.yml";
|
||||||
let test_title = "test_title";
|
let test_title = "test_title";
|
||||||
let test_level = "high";
|
let test_level = "high";
|
||||||
let output = "pokepoke";
|
let output = "pokepoke";
|
||||||
@@ -141,6 +144,7 @@ fn test_emit_csv() {
|
|||||||
let event: Value = serde_json::from_str(val).unwrap();
|
let event: Value = serde_json::from_str(val).unwrap();
|
||||||
messages.insert(
|
messages.insert(
|
||||||
testfilepath.to_string(),
|
testfilepath.to_string(),
|
||||||
|
testrulepath.to_string(),
|
||||||
&event,
|
&event,
|
||||||
test_level.to_string(),
|
test_level.to_string(),
|
||||||
test_title.to_string(),
|
test_title.to_string(),
|
||||||
@@ -152,11 +156,13 @@ fn test_emit_csv() {
|
|||||||
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
|
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let expect_tz = expect_time.with_timezone(&Local);
|
let expect_tz = expect_time.with_timezone(&Local);
|
||||||
let expect = "Time,Filepath,Level,Title,Message\n".to_string()
|
let expect = "Time,Filepath,Rulepath,Level,Title,Message\n".to_string()
|
||||||
+ &expect_tz.clone().format("%Y-%m-%dT%H:%M:%S%:z").to_string()
|
+ &expect_tz.clone().format("%Y-%m-%dT%H:%M:%S%:z").to_string()
|
||||||
+ ","
|
+ ","
|
||||||
+ testfilepath
|
+ testfilepath
|
||||||
+ ","
|
+ ","
|
||||||
|
+ testrulepath
|
||||||
|
+ ","
|
||||||
+ test_level
|
+ test_level
|
||||||
+ ","
|
+ ","
|
||||||
+ test_title
|
+ test_title
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ impl Detection {
|
|||||||
return rulefile_loader
|
return rulefile_loader
|
||||||
.files
|
.files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|rule_file| rule::create_rule(rule_file))
|
.map(|rule_file_tuple| rule::create_rule(rule_file_tuple.0, rule_file_tuple.1))
|
||||||
.filter_map(return_if_success)
|
.filter_map(return_if_success)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
@@ -156,6 +156,7 @@ impl Detection {
|
|||||||
fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) {
|
fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) {
|
||||||
MESSAGES.lock().unwrap().insert(
|
MESSAGES.lock().unwrap().insert(
|
||||||
record_info.evtx_filepath.to_string(),
|
record_info.evtx_filepath.to_string(),
|
||||||
|
rule.rulepath.to_string(),
|
||||||
&record_info.record,
|
&record_info.record,
|
||||||
rule.yaml["level"].as_str().unwrap_or("").to_string(),
|
rule.yaml["level"].as_str().unwrap_or("").to_string(),
|
||||||
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
||||||
@@ -168,6 +169,7 @@ impl Detection {
|
|||||||
let output = Detection::create_count_output(rule, &agg_result);
|
let output = Detection::create_count_output(rule, &agg_result);
|
||||||
MESSAGES.lock().unwrap().insert_message(
|
MESSAGES.lock().unwrap().insert_message(
|
||||||
agg_result.filepath,
|
agg_result.filepath,
|
||||||
|
rule.rulepath.to_string(),
|
||||||
agg_result.start_timedate,
|
agg_result.start_timedate,
|
||||||
rule.yaml["level"].as_str().unwrap_or("").to_string(),
|
rule.yaml["level"].as_str().unwrap_or("").to_string(),
|
||||||
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct Message {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DetectInfo {
|
pub struct DetectInfo {
|
||||||
pub filepath: String,
|
pub filepath: String,
|
||||||
|
pub rulepath: String,
|
||||||
pub level: String,
|
pub level: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub detail: String,
|
pub detail: String,
|
||||||
@@ -38,6 +39,7 @@ impl Message {
|
|||||||
pub fn insert_message(
|
pub fn insert_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
target_file: String,
|
target_file: String,
|
||||||
|
rule_path: String,
|
||||||
event_time: DateTime<Utc>,
|
event_time: DateTime<Utc>,
|
||||||
level: String,
|
level: String,
|
||||||
event_title: String,
|
event_title: String,
|
||||||
@@ -45,6 +47,7 @@ impl Message {
|
|||||||
) {
|
) {
|
||||||
let detect_info = DetectInfo {
|
let detect_info = DetectInfo {
|
||||||
filepath: target_file,
|
filepath: target_file,
|
||||||
|
rulepath: rule_path,
|
||||||
level: level,
|
level: level,
|
||||||
title: event_title,
|
title: event_title,
|
||||||
detail: event_detail,
|
detail: event_detail,
|
||||||
@@ -65,6 +68,7 @@ impl Message {
|
|||||||
pub fn insert(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
target_file: String,
|
target_file: String,
|
||||||
|
rule_path: String,
|
||||||
event_record: &Value,
|
event_record: &Value,
|
||||||
level: String,
|
level: String,
|
||||||
event_title: String,
|
event_title: String,
|
||||||
@@ -73,7 +77,14 @@ impl Message {
|
|||||||
let message = &self.parse_message(event_record, output);
|
let message = &self.parse_message(event_record, output);
|
||||||
let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
|
let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
|
||||||
let time = Message::get_event_time(event_record).unwrap_or(default_time);
|
let time = Message::get_event_time(event_record).unwrap_or(default_time);
|
||||||
self.insert_message(target_file, time, level, event_title, message.to_string())
|
self.insert_message(
|
||||||
|
target_file,
|
||||||
|
rule_path,
|
||||||
|
time,
|
||||||
|
level,
|
||||||
|
event_title,
|
||||||
|
message.to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_message(&mut self, event_record: &Value, output: String) -> String {
|
fn parse_message(&mut self, event_record: &Value, output: String) -> String {
|
||||||
@@ -200,6 +211,7 @@ mod tests {
|
|||||||
let event_record_1: Value = serde_json::from_str(json_str_1).unwrap();
|
let event_record_1: Value = serde_json::from_str(json_str_1).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
"a".to_string(),
|
"a".to_string(),
|
||||||
|
"test_rule".to_string(),
|
||||||
&event_record_1,
|
&event_record_1,
|
||||||
"high".to_string(),
|
"high".to_string(),
|
||||||
"test1".to_string(),
|
"test1".to_string(),
|
||||||
@@ -223,6 +235,7 @@ mod tests {
|
|||||||
let event_record_2: Value = serde_json::from_str(json_str_2).unwrap();
|
let event_record_2: Value = serde_json::from_str(json_str_2).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
"a".to_string(),
|
"a".to_string(),
|
||||||
|
"test_rule2".to_string(),
|
||||||
&event_record_2,
|
&event_record_2,
|
||||||
"high".to_string(),
|
"high".to_string(),
|
||||||
"test2".to_string(),
|
"test2".to_string(),
|
||||||
@@ -246,6 +259,7 @@ mod tests {
|
|||||||
let event_record_3: Value = serde_json::from_str(json_str_3).unwrap();
|
let event_record_3: Value = serde_json::from_str(json_str_3).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
"a".to_string(),
|
"a".to_string(),
|
||||||
|
"test_rule3".to_string(),
|
||||||
&event_record_3,
|
&event_record_3,
|
||||||
"high".to_string(),
|
"high".to_string(),
|
||||||
"test3".to_string(),
|
"test3".to_string(),
|
||||||
@@ -264,6 +278,7 @@ mod tests {
|
|||||||
let event_record_4: Value = serde_json::from_str(json_str_4).unwrap();
|
let event_record_4: Value = serde_json::from_str(json_str_4).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
"a".to_string(),
|
"a".to_string(),
|
||||||
|
"test_rule4".to_string(),
|
||||||
&event_record_4,
|
&event_record_4,
|
||||||
"medium".to_string(),
|
"medium".to_string(),
|
||||||
"test4".to_string(),
|
"test4".to_string(),
|
||||||
@@ -272,7 +287,7 @@ mod tests {
|
|||||||
|
|
||||||
let display = format!("{}", format_args!("{:?}", message));
|
let display = format!("{}", format_args!("{:?}", message));
|
||||||
println!("display::::{}", display);
|
println!("display::::{}", display);
|
||||||
let expect = "Message { map: {1970-01-01T00:00:00Z: [DetectInfo { filepath: \"a\", level: \"medium\", title: \"test4\", detail: \"CommandLine4: hoge\" }], 1996-02-27T01:05:01Z: [DetectInfo { filepath: \"a\", level: \"high\", title: \"test1\", detail: \"CommandLine1: hoge\" }, DetectInfo { filepath: \"a\", level: \"high\", title: \"test2\", detail: \"CommandLine2: hoge\" }], 2000-01-21T09:06:01Z: [DetectInfo { filepath: \"a\", level: \"high\", title: \"test3\", detail: \"CommandLine3: hoge\" }]} }";
|
let expect = "Message { map: {1970-01-01T00:00:00Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule4\", level: \"medium\", title: \"test4\", detail: \"CommandLine4: hoge\" }], 1996-02-27T01:05:01Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule\", level: \"high\", title: \"test1\", detail: \"CommandLine1: hoge\" }, DetectInfo { filepath: \"a\", rulepath: \"test_rule2\", level: \"high\", title: \"test2\", detail: \"CommandLine2: hoge\" }], 2000-01-21T09:06:01Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule3\", level: \"high\", title: \"test3\", detail: \"CommandLine3: hoge\" }]} }";
|
||||||
assert_eq!(display, expect);
|
assert_eq!(display, expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ mod tests {
|
|||||||
|
|
||||||
fn check_rule_parse_error(rule_str: &str, errmsgs: Vec<String>) {
|
fn check_rule_parse_error(rule_str: &str, errmsgs: Vec<String>) {
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let mut rule_node = create_rule(rule_yaml.next().unwrap());
|
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
|
||||||
|
|
||||||
assert_eq!(rule_node.init(), Err(errmsgs));
|
assert_eq!(rule_node.init(), Err(errmsgs));
|
||||||
}
|
}
|
||||||
@@ -1183,7 +1183,7 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let mut rule_node = create_rule(rule_yaml.next().unwrap());
|
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rule_node.init(),
|
rule_node.init(),
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let test = rule_yaml.next().unwrap();
|
let test = rule_yaml.next().unwrap();
|
||||||
let mut rule_node = create_rule(test);
|
let mut rule_node = create_rule("testpath".to_string(), test);
|
||||||
let init_result = rule_node.init();
|
let init_result = rule_node.init();
|
||||||
assert_eq!(init_result.is_ok(), true);
|
assert_eq!(init_result.is_ok(), true);
|
||||||
let target = vec![SIMPLE_RECORD_STR, record_str];
|
let target = vec![SIMPLE_RECORD_STR, record_str];
|
||||||
@@ -754,7 +754,7 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let test = rule_yaml.next().unwrap();
|
let test = rule_yaml.next().unwrap();
|
||||||
let mut rule_node = create_rule(test);
|
let mut rule_node = create_rule("testpath".to_string(), test);
|
||||||
let error_checker = rule_node.init();
|
let error_checker = rule_node.init();
|
||||||
if error_checker.is_err() {
|
if error_checker.is_err() {
|
||||||
assert!(false, "failed to init rulenode");
|
assert!(false, "failed to init rulenode");
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ mod condition_parser;
|
|||||||
mod count;
|
mod count;
|
||||||
use self::count::TimeFrameInfo;
|
use self::count::TimeFrameInfo;
|
||||||
|
|
||||||
pub fn create_rule(yaml: Yaml) -> RuleNode {
|
pub fn create_rule(rulepath: String, yaml: Yaml) -> RuleNode {
|
||||||
return RuleNode::new(yaml);
|
return RuleNode::new(rulepath, yaml);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ruleファイルを表すノード
|
/// Ruleファイルを表すノード
|
||||||
pub struct RuleNode {
|
pub struct RuleNode {
|
||||||
|
pub rulepath: String,
|
||||||
pub yaml: Yaml,
|
pub yaml: Yaml,
|
||||||
detection: Option<DetectionNode>,
|
detection: Option<DetectionNode>,
|
||||||
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
|
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
|
||||||
@@ -38,8 +39,9 @@ impl Debug for RuleNode {
|
|||||||
unsafe impl Sync for RuleNode {}
|
unsafe impl Sync for RuleNode {}
|
||||||
|
|
||||||
impl RuleNode {
|
impl RuleNode {
|
||||||
pub fn new(yaml: Yaml) -> RuleNode {
|
pub fn new(rulepath: String, yaml: Yaml) -> RuleNode {
|
||||||
return RuleNode {
|
return RuleNode {
|
||||||
|
rulepath: rulepath,
|
||||||
yaml: yaml,
|
yaml: yaml,
|
||||||
detection: Option::None,
|
detection: Option::None,
|
||||||
countdata: HashMap::new(),
|
countdata: HashMap::new(),
|
||||||
@@ -321,7 +323,7 @@ mod tests {
|
|||||||
assert_eq!(rule_yaml.is_ok(), true);
|
assert_eq!(rule_yaml.is_ok(), true);
|
||||||
let rule_yamls = rule_yaml.unwrap();
|
let rule_yamls = rule_yaml.unwrap();
|
||||||
let mut rule_yaml = rule_yamls.into_iter();
|
let mut rule_yaml = rule_yamls.into_iter();
|
||||||
let mut rule_node = create_rule(rule_yaml.next().unwrap());
|
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
|
||||||
assert_eq!(rule_node.init().is_ok(), true);
|
assert_eq!(rule_node.init().is_ok(), true);
|
||||||
return rule_node;
|
return rule_node;
|
||||||
}
|
}
|
||||||
@@ -877,7 +879,7 @@ mod tests {
|
|||||||
output: 'Rule parse test'
|
output: 'Rule parse test'
|
||||||
"#;
|
"#;
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let mut rule_node = create_rule(rule_yaml.next().unwrap());
|
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rule_node.init(),
|
rule_node.init(),
|
||||||
@@ -897,7 +899,7 @@ mod tests {
|
|||||||
output: 'Rule parse test'
|
output: 'Rule parse test'
|
||||||
"#;
|
"#;
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let mut rule_node = create_rule(rule_yaml.next().unwrap());
|
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rule_node.init(),
|
rule_node.init(),
|
||||||
@@ -909,7 +911,7 @@ mod tests {
|
|||||||
fn _check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
|
fn _check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
|
||||||
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
|
||||||
let test = rule_yaml.next().unwrap();
|
let test = rule_yaml.next().unwrap();
|
||||||
let mut rule_node = create_rule(test);
|
let mut rule_node = create_rule("testpath".to_string(), test);
|
||||||
let _init = rule_node.init();
|
let _init = rule_node.init();
|
||||||
match serde_json::from_str(record_str) {
|
match serde_json::from_str(record_str) {
|
||||||
Ok(record) => {
|
Ok(record) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use yaml_rust::YamlLoader;
|
use yaml_rust::YamlLoader;
|
||||||
|
|
||||||
pub struct ParseYaml {
|
pub struct ParseYaml {
|
||||||
pub files: Vec<yaml_rust::Yaml>,
|
pub files: Vec<(String, yaml_rust::Yaml)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseYaml {
|
impl ParseYaml {
|
||||||
@@ -46,7 +46,9 @@ impl ParseYaml {
|
|||||||
if i["ignore"].as_bool().unwrap_or(false) {
|
if i["ignore"].as_bool().unwrap_or(false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
&self.files.push(i);
|
&self
|
||||||
|
.files
|
||||||
|
.push((format!("{}", entry.path().display()), i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user