diff --git a/src/detections/detection.rs b/src/detections/detection.rs index d657c22c..5121d1fd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -89,7 +89,7 @@ impl Detection { return rulefile_loader .files .into_iter() - .map(|rule_file| rule::parse_rule(rule_file)) + .map(|rule_file| rule::create_rule(rule_file)) .filter_map(return_if_success) .collect(); } diff --git a/src/detections/rule.rs b/src/detections/rule.rs index 1342ee34..cf62b496 100644 --- a/src/detections/rule.rs +++ b/src/detections/rule.rs @@ -2,7 +2,7 @@ extern crate regex; use mopa::mopafy; -use std::vec; +use std::{collections::HashMap, sync::Arc, vec}; use crate::detections::utils; @@ -10,25 +10,8 @@ use regex::Regex; use serde_json::Value; use yaml_rust::Yaml; -// TODO テストケースかかなきゃ... -pub fn parse_rule(yaml: Yaml) -> RuleNode { - let detection = parse_detection(&yaml); - - return RuleNode { - yaml: yaml, - detection: detection, - }; -} - -fn parse_detection(yaml: &Yaml) -> Option { - if yaml["detection"].is_badvalue() { - return Option::None; - } else { - let node = DetectionNode { - selection: parse_selection(&yaml), - }; - return Option::Some(node); - } +pub fn create_rule(yaml: Yaml) -> RuleNode { + return RuleNode::new(yaml); } fn concat_selection_key(key_list: &Vec) -> String { @@ -40,48 +23,754 @@ fn concat_selection_key(key_list: &Vec) -> String { }); } -fn parse_selection(yaml: &Yaml) -> Option> { - // TODO detection-selectionが存在しない場合のチェック - let selection_yaml = &yaml["detection"]["selection"]; - if selection_yaml.is_badvalue() { - return Option::None; - } - return Option::Some(parse_selection_recursively(vec![], &selection_yaml)); +#[derive(Debug, Clone)] +/// 字句解析で出てくるトークン +pub enum ConditionToken { + LeftParenthesis, + RightParenthesis, + Space, + Not, + And, + Or, + SelectionReference(String), + + // パースの時に上手く処理するために作った疑似的なトークン + ParenthesisContainer(Vec), // 括弧を表すトークン + AndContainer(Vec), // ANDでつながった条件をまとめるためのトークン + OrContainer(Vec), // ORでつながった条件をまとめるためのトークン + NotContainer(Vec), // 「NOT」と「NOTで否定される式」をまとめるためのトークン この配列には要素が一つしか入らないが、他のContainerと同じように扱えるようにするためにVecにしている。あんまり良くない。 + OperandContainer(Vec), // ANDやORやNOT等の演算子に対して、非演算子を表す } -fn parse_selection_recursively( - key_list: Vec, - yaml: &Yaml, -) -> Box { - if yaml.as_hash().is_some() { - // 連想配列はAND条件と解釈する - let yaml_hash = yaml.as_hash().unwrap(); - let mut and_node = AndSelectionNode::new(); +// ここを参考にしました。https://qiita.com/yasuo-ozu/items/7ce2f8ff846ba00dd244 +impl IntoIterator for ConditionToken { + type Item = ConditionToken; + type IntoIter = std::vec::IntoIter; - yaml_hash.keys().for_each(|hash_key| { - let child_yaml = yaml_hash.get(hash_key).unwrap(); - let mut child_key_list = key_list.clone(); - child_key_list.push(hash_key.as_str().unwrap().to_string()); - let child_node = parse_selection_recursively(child_key_list, child_yaml); - and_node.child_nodes.push(child_node); - }); - return Box::new(and_node); - } else if yaml.as_vec().is_some() { - // 配列はOR条件と解釈する。 - let mut or_node = OrSelectionNode::new(); - yaml.as_vec().unwrap().iter().for_each(|child_yaml| { - let child_node = parse_selection_recursively(key_list.clone(), child_yaml); - or_node.child_nodes.push(child_node); - }); - - return Box::new(or_node); - } else { - // 連想配列と配列以外は末端ノード - return Box::new(LeafSelectionNode::new(key_list, yaml.clone())); + fn into_iter(self) -> Self::IntoIter { + let v = match self { + ConditionToken::ParenthesisContainer(sub_tokens) => sub_tokens, + ConditionToken::AndContainer(sub_tokens) => sub_tokens, + ConditionToken::OrContainer(sub_tokens) => sub_tokens, + ConditionToken::NotContainer(sub_tokens) => sub_tokens, + ConditionToken::OperandContainer(sub_tokens) => sub_tokens, + _ => vec![], + }; + v.into_iter() } } -// Ruleファイルを表すノード +impl ConditionToken { + fn replace_subtoken(&self, sub_tokens: Vec) -> ConditionToken { + return match self { + ConditionToken::ParenthesisContainer(_) => { + ConditionToken::ParenthesisContainer(sub_tokens) + } + ConditionToken::AndContainer(_) => ConditionToken::AndContainer(sub_tokens), + ConditionToken::OrContainer(_) => ConditionToken::OrContainer(sub_tokens), + ConditionToken::NotContainer(_) => ConditionToken::NotContainer(sub_tokens), + ConditionToken::OperandContainer(_) => ConditionToken::OperandContainer(sub_tokens), + ConditionToken::LeftParenthesis => ConditionToken::LeftParenthesis, + ConditionToken::RightParenthesis => ConditionToken::RightParenthesis, + ConditionToken::Space => ConditionToken::Space, + ConditionToken::Not => ConditionToken::Not, + ConditionToken::And => ConditionToken::And, + ConditionToken::Or => ConditionToken::Or, + ConditionToken::SelectionReference(name) => { + ConditionToken::SelectionReference(name.clone()) + } + }; + } + + pub fn sub_tokens<'a>(&'a self) -> Vec { + // TODO ここでcloneを使わずに実装できるようにしたい。 + return match self { + ConditionToken::ParenthesisContainer(sub_tokens) => sub_tokens.clone(), + ConditionToken::AndContainer(sub_tokens) => sub_tokens.clone(), + ConditionToken::OrContainer(sub_tokens) => sub_tokens.clone(), + ConditionToken::NotContainer(sub_tokens) => sub_tokens.clone(), + ConditionToken::OperandContainer(sub_tokens) => sub_tokens.clone(), + ConditionToken::LeftParenthesis => vec![], + ConditionToken::RightParenthesis => vec![], + ConditionToken::Space => vec![], + ConditionToken::Not => vec![], + ConditionToken::And => vec![], + ConditionToken::Or => vec![], + ConditionToken::SelectionReference(_) => vec![], + }; + } + + pub fn sub_tokens_without_parenthesis<'a>(&'a self) -> Vec { + return match self { + ConditionToken::ParenthesisContainer(_) => vec![], + _ => self.sub_tokens(), + }; + } +} + +#[derive(Debug)] +pub struct ConditionCompiler { + regex_patterns: Vec, +} + +// conditionの式を読み取るクラス。 +impl ConditionCompiler { + pub fn new() -> Self { + // ここで字句解析するときに使う正規表現の一覧を定義する。 + let mut regex_patterns = vec![]; + regex_patterns.push(Regex::new(r"^\(").unwrap()); + regex_patterns.push(Regex::new(r"^\)").unwrap()); + regex_patterns.push(Regex::new(r"^ ").unwrap()); + // ^\w+については、sigmaのソースのsigma/tools/sigma/parser/condition.pyのSigmaConditionTokenizerを参考にしている。 + // 上記ソースの(SigmaConditionToken.TOKEN_ID, re.compile("[\\w*]+")),を参考。 + regex_patterns.push(Regex::new(r"^\w+").unwrap()); + + return ConditionCompiler { + regex_patterns: regex_patterns, + }; + } + + fn compile_condition( + &self, + condition_str: String, + name_2_node: &HashMap>>, + ) -> Result, String> { + // パイプはここでは処理しない + let re_pipe = Regex::new(r"\|.*").unwrap(); + let captured = re_pipe.captures(&condition_str); + let condition_str = if captured.is_some() { + let captured = captured.unwrap().get(0).unwrap().as_str().to_string(); + condition_str.replacen(&captured, "", 1) + } else { + condition_str + }; + + let result = self.compile_condition_body(condition_str, name_2_node); + if let Result::Err(msg) = result { + return Result::Err(format!("condition parse error has occured. {}", msg)); + } else { + return result; + } + } + + /// 与えたConditionからSelectionNodeを作る + fn compile_condition_body( + &self, + condition_str: String, + name_2_node: &HashMap>>, + ) -> Result, String> { + let tokens = self.tokenize(&condition_str)?; + + let parsed = self.parse(tokens)?; + + return self.to_selectnode(parsed, name_2_node); + } + + /// 構文解析を実行する。 + fn parse(&self, tokens: Vec) -> Result { + // 括弧で囲まれた部分を解析します。 + // (括弧で囲まれた部分は後で解析するため、ここでは一時的にConditionToken::ParenthesisContainerに変換しておく) + // 括弧の中身を解析するのはparse_rest_parenthesis()で行う。 + let tokens = self.parse_parenthesis(tokens)?; + + // AndとOrをパースする。 + let tokens = self.parse_and_or_operator(tokens)?; + + // OperandContainerトークンの中身をパースする。(現状、Notを解析するためだけにある。将来的に修飾するキーワードが増えたらここを変える。) + let token = self.parse_operand_container(tokens)?; + + // 括弧で囲まれている部分を探して、もしあればその部分を再帰的に構文解析します。 + return self.parse_rest_parenthesis(token); + } + + /// 括弧で囲まれている部分を探して、もしあればその部分を再帰的に構文解析します。 + fn parse_rest_parenthesis(&self, token: ConditionToken) -> Result { + if let ConditionToken::ParenthesisContainer(sub_token) = token { + let new_token = self.parse(sub_token)?; + return Result::Ok(new_token); + } + + let sub_tokens = token.sub_tokens(); + if sub_tokens.len() == 0 { + return Result::Ok(token); + } + + let mut new_sub_tokens = vec![]; + for sub_token in sub_tokens { + let new_token = self.parse_rest_parenthesis(sub_token)?; + new_sub_tokens.push(new_token); + } + return Result::Ok(token.replace_subtoken(new_sub_tokens)); + } + + /// 字句解析を行う + fn tokenize(&self, condition_str: &String) -> Result, String> { + let mut cur_condition_str = condition_str.clone(); + + let mut tokens = Vec::new(); + while cur_condition_str.len() != 0 { + let captured = self.regex_patterns.iter().find_map(|regex| { + return regex.captures(cur_condition_str.as_str()); + }); + if captured.is_none() { + // トークンにマッチしないのはありえないという方針でパースしています。 + return Result::Err("An unusable character was found.".to_string()); + } + + let mached_str = captured.unwrap().get(0).unwrap().as_str(); + let token = self.to_enum(mached_str.to_string()); + if let ConditionToken::Space = token { + // 空白は特に意味ないので、読み飛ばす。 + cur_condition_str = cur_condition_str.replacen(mached_str, "", 1); + continue; + } + + tokens.push(token); + cur_condition_str = cur_condition_str.replacen(mached_str, "", 1); + } + + return Result::Ok(tokens); + } + + /// 文字列をConditionTokenに変換する。 + fn to_enum(&self, token: String) -> ConditionToken { + if token == "(" { + return ConditionToken::LeftParenthesis; + } else if token == ")" { + return ConditionToken::RightParenthesis; + } else if token == " " { + return ConditionToken::Space; + } else if token == "not" { + return ConditionToken::Not; + } else if token == "and" { + return ConditionToken::And; + } else if token == "or" { + return ConditionToken::Or; + } else { + return ConditionToken::SelectionReference(token.clone()); + } + } + + /// 右括弧と左括弧をだけをパースする。戻り値の配列にはLeftParenthesisとRightParenthesisが含まれず、代わりにTokenContainerに変換される。TokenContainerが括弧で囲まれた部分を表現している。 + fn parse_parenthesis( + &self, + tokens: Vec, + ) -> Result, String> { + let mut ret = vec![]; + let mut token_ite = tokens.into_iter(); + while let Some(token) = token_ite.next() { + // まず、左括弧を探す。 + let is_left = match token { + ConditionToken::LeftParenthesis => true, + _ => false, + }; + if !is_left { + ret.push(token); + continue; + } + + // 左括弧が見つかったら、対応する右括弧を見つける。 + let mut left_cnt = 1; + let mut right_cnt = 0; + let mut sub_tokens = vec![]; + while let Some(token) = token_ite.next() { + if let ConditionToken::LeftParenthesis = token { + left_cnt += 1; + } else if let ConditionToken::RightParenthesis = token { + right_cnt += 1; + } + if left_cnt == right_cnt { + break; + } + sub_tokens.push(token); + } + // 最後までついても対応する右括弧が見つからないことを表している + if left_cnt != right_cnt { + return Result::Err("expected ')'. but not found.".to_string()); + } + + // ここで再帰的に呼び出す。 + ret.push(ConditionToken::ParenthesisContainer(sub_tokens)); + } + + // この時点で右括弧が残っている場合は右括弧の数が左括弧よりも多いことを表している。 + let is_right_left = ret.iter().any(|token| { + return match token { + ConditionToken::RightParenthesis => true, + _ => false, + }; + }); + if is_right_left { + return Result::Err("expected '('. but not found.".to_string()); + } + + return Result::Ok(ret); + } + + /// AND, ORをパースする。 + fn parse_and_or_operator(&self, tokens: Vec) -> Result { + if tokens.len() == 0 { + // 長さ0は呼び出してはいけない + return Result::Err("unknown error.".to_string()); + } + + // まず、selection1 and not selection2みたいな式のselection1やnot selection2のように、ANDやORでつながるトークンをまとめる。 + let tokens = self.to_operand_container(tokens)?; + + // 先頭又は末尾がAND/ORなのはだめ + if self.is_logical(&tokens[0]) || self.is_logical(&tokens[tokens.len() - 1]) { + return Result::Err("illegal Logical Operator(and, or) was found.".to_string()); + } + + // OperandContainerとLogicalOperator(AndとOR)が交互に並んでいるので、それぞれリストに投入 + let mut operand_list = vec![]; + let mut operator_list = vec![]; + for (i, token) in tokens.into_iter().enumerate() { + if (i % 2 == 1) != self.is_logical(&token) { + // インデックスが奇数の時はLogicalOperatorで、インデックスが偶数のときはOperandContainerになる + return Result::Err("The use of logical operator(and, or) was wrong.".to_string()); + } + + if i % 2 == 0 { + // ここで再帰的にAND,ORをパースする関数を呼び出す + operand_list.push(token); + } else { + operator_list.push(token); + } + } + + // 先にANDでつながっている部分を全部まとめる + let mut operant_ite = operand_list.into_iter(); + let mut operands = vec![operant_ite.next().unwrap()]; + for token in operator_list.iter() { + if let ConditionToken::Or = token { + // Orの場合はそのままリストに追加 + operands.push(operant_ite.next().unwrap()); + } else { + // Andの場合はANDでつなげる + let and_operands = vec![operands.pop().unwrap(), operant_ite.next().unwrap()]; + let and_container = ConditionToken::AndContainer(and_operands); + operands.push(and_container); + } + } + + // 次にOrでつながっている部分をまとめる + let or_contaienr = ConditionToken::OrContainer(operands); + return Result::Ok(or_contaienr); + } + + /// OperandContainerの中身をパースする。現状はNotをパースするためだけに存在している。 + fn parse_operand_container( + &self, + parent_token: ConditionToken, + ) -> Result { + if let ConditionToken::OperandContainer(sub_tokens) = parent_token { + // 現状ではNOTの場合は、「not」と「notで修飾されるselectionノードの名前」の2つ入っているはず + // NOTが無い場合、「selectionノードの名前」の一つしか入っていないはず。 + + // 上記の通り、3つ以上入っていることはないはず。 + if sub_tokens.len() >= 3 { + return Result::Err( + "unknown error. maybe it's because there are multiple name of selection node." + .to_string(), + ); + } + + // 0はありえないはず + if sub_tokens.len() == 0 { + return Result::Err("unknown error.".to_string()); + } + + // 1つだけ入っている場合、NOTはありえない。 + if sub_tokens.len() == 1 { + let operand_subtoken = sub_tokens.into_iter().next().unwrap(); + if let ConditionToken::Not = operand_subtoken { + return Result::Err("illegal not was found.".to_string()); + } + + return Result::Ok(operand_subtoken); + } + + // 2つ入っている場合、先頭がNotで次はNotじゃない何かのはず + let mut sub_tokens_ite = sub_tokens.into_iter(); + let first_token = sub_tokens_ite.next().unwrap(); + let second_token = sub_tokens_ite.next().unwrap(); + if let ConditionToken::Not = first_token { + if let ConditionToken::Not = second_token { + return Result::Err("not is continuous.".to_string()); + } else { + let not_container = ConditionToken::NotContainer(vec![second_token]); + return Result::Ok(not_container); + } + } else { + return Result::Err( + "unknown error. maybe it's because there are multiple name of selection node." + .to_string(), + ); + } + } else { + let sub_tokens = parent_token.sub_tokens_without_parenthesis(); + if sub_tokens.len() == 0 { + return Result::Ok(parent_token); + } + + let mut new_sub_tokens = vec![]; + for sub_token in sub_tokens { + let new_sub_token = self.parse_operand_container(sub_token)?; + new_sub_tokens.push(new_sub_token); + } + + return Result::Ok(parent_token.replace_subtoken(new_sub_tokens)); + } + } + + /// ConditionTokenからSelectionNodeトレイトを実装した構造体に変換します。 + fn to_selectnode( + &self, + token: ConditionToken, + name_2_node: &HashMap>>, + ) -> Result, String> { + // RefSelectionNodeに変換 + if let ConditionToken::SelectionReference(selection_name) = token { + let selection_node = name_2_node.get(&selection_name); + if selection_node.is_none() { + let err_msg = format!("{} is not defined.", selection_name); + return Result::Err(err_msg); + } else { + let selection_node = selection_node.unwrap(); + let selection_node = Arc::clone(selection_node); + let ref_node = RefSelectionNode::new(selection_node); + return Result::Ok(Box::new(ref_node)); + } + } + + // AndSelectionNodeに変換 + if let ConditionToken::AndContainer(sub_tokens) = token { + let mut select_and_node = AndSelectionNode::new(); + for sub_token in sub_tokens.into_iter() { + let sub_node = self.to_selectnode(sub_token, name_2_node)?; + select_and_node.child_nodes.push(sub_node); + } + return Result::Ok(Box::new(select_and_node)); + } + + // OrSelectionNodeに変換 + if let ConditionToken::OrContainer(sub_tokens) = token { + let mut select_or_node = OrSelectionNode::new(); + for sub_token in sub_tokens.into_iter() { + let sub_node = self.to_selectnode(sub_token, name_2_node)?; + select_or_node.child_nodes.push(sub_node); + } + return Result::Ok(Box::new(select_or_node)); + } + + // NotSelectionNodeに変換 + if let ConditionToken::NotContainer(sub_tokens) = token { + if sub_tokens.len() > 1 { + return Result::Err("unknown error".to_string()); + } + + let select_sub_node = + self.to_selectnode(sub_tokens.into_iter().next().unwrap(), name_2_node)?; + let select_not_node = NotSelectionNode::new(select_sub_node); + return Result::Ok(Box::new(select_not_node)); + } + + return Result::Err("unknown error".to_string()); + } + + /// ConditionTokenがAndまたはOrTokenならばTrue + fn is_logical(&self, token: &ConditionToken) -> bool { + return match token { + ConditionToken::And => true, + ConditionToken::Or => true, + _ => false, + }; + } + + /// ConditionToken::OperandContainerに変換できる部分があれば変換する。 + fn to_operand_container( + &self, + tokens: Vec, + ) -> Result, String> { + let mut ret = vec![]; + let mut grouped_operands = vec![]; // ANDとORの間にあるトークンを表す。ANDとORをOperatorとしたときのOperand + let mut token_ite = tokens.into_iter(); + while let Some(token) = token_ite.next() { + if self.is_logical(&token) { + // ここに来るのはエラーのはずだが、後でエラー出力するので、ここではエラー出さない。 + if grouped_operands.is_empty() { + ret.push(token); + continue; + } + ret.push(ConditionToken::OperandContainer(grouped_operands)); + ret.push(token); + grouped_operands = vec![]; + continue; + } + + grouped_operands.push(token); + } + if !grouped_operands.is_empty() { + ret.push(ConditionToken::OperandContainer(grouped_operands)); + } + + return Result::Ok(ret); + } +} + +#[derive(Debug)] +pub struct AggregationParseInfo { + _field_name: Option, // countの括弧に囲まれた部分の文字 + _by_field_name: Option, // count() by の後に指定される文字列 + _cmp_op: AggregationConditionToken, // (必須)<とか>とか何が指定されたのか + _cmp_num: i32, // (必須)<とか>とかの後にある数値 +} + +#[derive(Debug)] +pub enum AggregationConditionToken { + COUNT(String), // count + SPACE, // 空白 + BY, // by + EQ, // ..と等しい + LE, // ..以下 + LT, // ..未満 + GE, // ..以上 + GT, // .よりおおきい + KEYWORD(String), // BYのフィールド名 +} + +/// SIGMAルールでいうAggregationConditionを解析する。 +/// AggregationConditionはconditionに指定された式のパイプ以降の部分を指してます。 +#[derive(Debug)] +pub struct AggegationConditionCompiler { + regex_patterns: Vec, +} + +impl AggegationConditionCompiler { + pub fn new() -> Self { + // ここで字句解析するときに使う正規表現の一覧を定義する。 + // ここはSigmaのGithubレポジトリにある、toos/sigma/parser/condition.pyのSigmaConditionTokenizerのtokendefsを参考にしています。 + let mut regex_patterns = vec![]; + regex_patterns.push(Regex::new(r"^count\( *\w* *\)").unwrap()); // countの式 + regex_patterns.push(Regex::new(r"^ ").unwrap()); + regex_patterns.push(Regex::new(r"^by").unwrap()); + regex_patterns.push(Regex::new(r"^==").unwrap()); + regex_patterns.push(Regex::new(r"^<=").unwrap()); + regex_patterns.push(Regex::new(r"^>=").unwrap()); + regex_patterns.push(Regex::new(r"^<").unwrap()); + regex_patterns.push(Regex::new(r"^>").unwrap()); + regex_patterns.push(Regex::new(r"^\w+").unwrap()); + + return AggegationConditionCompiler { + regex_patterns: regex_patterns, + }; + } + + pub fn compile(&self, condition_str: String) -> Result, String> { + let result = self.compile_body(condition_str); + if let Result::Err(msg) = result { + return Result::Err(format!( + "aggregation condition parse error has occurred. {}", + msg + )); + } else { + return result; + } + } + + pub fn compile_body( + &self, + condition_str: String, + ) -> Result, String> { + // パイプの部分だけを取り出す + let re_pipe = Regex::new(r"\|.*").unwrap(); + let captured = re_pipe.captures(&condition_str); + if captured.is_none() { + // パイプが無いので終了 + return Result::Ok(Option::None); + } + // ハイプ自体は削除してからパースする。 + let aggregation_str = captured + .unwrap() + .get(0) + .unwrap() + .as_str() + .to_string() + .replacen("|", "", 1); + + let tokens = self.tokenize(aggregation_str)?; + + return self.parse(tokens); + } + + /// 字句解析します。 + pub fn tokenize( + &self, + condition_str: String, + ) -> Result, String> { + let mut cur_condition_str = condition_str.clone(); + + let mut tokens = Vec::new(); + while cur_condition_str.len() != 0 { + let captured = self.regex_patterns.iter().find_map(|regex| { + return regex.captures(cur_condition_str.as_str()); + }); + if captured.is_none() { + // トークンにマッチしないのはありえないという方針でパースしています。 + return Result::Err("An unusable character was found.".to_string()); + } + + let mached_str = captured.unwrap().get(0).unwrap().as_str(); + let token = self.to_enum(mached_str.to_string()); + + if let AggregationConditionToken::SPACE = token { + // 空白は特に意味ないので、読み飛ばす。 + cur_condition_str = cur_condition_str.replacen(mached_str, "", 1); + continue; + } + + tokens.push(token); + cur_condition_str = cur_condition_str.replacen(mached_str, "", 1); + } + + return Result::Ok(tokens); + } + + /// 比較演算子かどうか判定します。 + fn is_cmp_op(&self, token: &AggregationConditionToken) -> bool { + return match token { + AggregationConditionToken::EQ => true, + AggregationConditionToken::LE => true, + AggregationConditionToken::LT => true, + AggregationConditionToken::GE => true, + AggregationConditionToken::GT => true, + _ => false, + }; + } + + /// 構文解析します。 + fn parse( + &self, + tokens: Vec, + ) -> Result, String> { + if tokens.is_empty() { + // パイプしか無いのはおかしいのでエラー + return Result::Err("There are no strings after pipe(|).".to_string()); + } + + let mut token_ite = tokens.into_iter(); + let token = token_ite.next().unwrap(); + + let mut count_field_name: Option = Option::None; + if let AggregationConditionToken::COUNT(field_name) = token { + if !field_name.is_empty() { + count_field_name = Option::Some(field_name); + } + } else { + // いろんなパターンがあるので難しいが、countというキーワードしか使えないことを説明しておく。 + return Result::Err("aggregation condition can use count only.".to_string()); + } + + let token = token_ite.next(); + if token.is_none() { + // 論理演算子がないのはだめ + return Result::Err( + "count keyword needs compare operator and number like '> 3'".to_string(), + ); + } + + // BYはオプションでつけなくても良い + let mut by_field_name = Option::None; + let token = token.unwrap(); + let token = if let AggregationConditionToken::BY = token { + let after_by = token_ite.next(); + if after_by.is_none() { + // BYの後に何もないのはだめ + return Result::Err("by keyword needs field name like 'by EventID'".to_string()); + } + + if let AggregationConditionToken::KEYWORD(keyword) = after_by.unwrap() { + by_field_name = Option::Some(keyword); + token_ite.next() + } else { + return Result::Err("by keyword needs field name like 'by EventID'".to_string()); + } + } else { + Option::Some(token) + }; + + // 比較演算子と数値をパース + if token.is_none() { + // 論理演算子がないのはだめ + return Result::Err( + "count keyword needs compare operator and number like '> 3'".to_string(), + ); + } + + let cmp_token = token.unwrap(); + if !self.is_cmp_op(&cmp_token) { + return Result::Err( + "count keyword needs compare operator and number like '> 3'".to_string(), + ); + } + + let token = token_ite.next().unwrap_or(AggregationConditionToken::SPACE); + let cmp_number = if let AggregationConditionToken::KEYWORD(number) = token { + let number: Result = number.parse(); + if number.is_err() { + // 比較演算子の後に数値が無い。 + return Result::Err("compare operator needs a number like '> 3'.".to_string()); + } else { + number.unwrap() + } + } else { + // 比較演算子の後に数値が無い。 + return Result::Err("compare operator needs a number like '> 3'.".to_string()); + }; + + if token_ite.next().is_some() { + return Result::Err("unnecessary word was found.".to_string()); + } + + let info = AggregationParseInfo { + _field_name: count_field_name, + _by_field_name: by_field_name, + _cmp_op: cmp_token, + _cmp_num: cmp_number, + }; + return Result::Ok(Option::Some(info)); + } + + /// 文字列をConditionTokenに変換する。 + fn to_enum(&self, token: String) -> AggregationConditionToken { + if token.starts_with("count(") { + let count_field = token + .replacen("count(", "", 1) + .replacen(")", "", 1) + .replace(" ", ""); + return AggregationConditionToken::COUNT(count_field); + } else if token == " " { + return AggregationConditionToken::SPACE; + } else if token == "by" { + return AggregationConditionToken::BY; + } else if token == "==" { + return AggregationConditionToken::EQ; + } else if token == "<=" { + return AggregationConditionToken::LE; + } else if token == ">=" { + return AggregationConditionToken::GE; + } else if token == "<" { + return AggregationConditionToken::LT; + } else if token == ">" { + return AggregationConditionToken::GT; + } else { + return AggregationConditionToken::KEYWORD(token); + } + } +} + +/// Ruleファイルを表すノード pub struct RuleNode { pub yaml: Yaml, detection: Option, @@ -90,22 +779,28 @@ pub struct RuleNode { unsafe impl Sync for RuleNode {} impl RuleNode { + pub fn new(yaml: Yaml) -> RuleNode { + return RuleNode { + yaml: yaml, + detection: Option::None, + }; + } + pub fn init(&mut self) -> Result<(), Vec> { let mut errmsgs: Vec = vec![]; - // field check - if self.yaml["output"].as_str().unwrap_or("").is_empty() { - errmsgs.push("Cannot find required key. key:output".to_string()); - } + // SIGMAルールを受け入れるため、outputがなくてもOKにする。 + // if self.yaml["output"].as_str().unwrap_or("").is_empty() { + // errmsgs.push("Cannot find required key. key:output".to_string()); + // } // detection node initialization - self.detection.as_mut().and_then(|detection| { - let detection_result = detection.init(); - if detection_result.is_err() { - errmsgs.extend(detection_result.unwrap_err()); - } - return Option::Some(detection); - }); + let mut detection = DetectionNode::new(); + let detection_result = detection.init(&self.yaml["detection"]); + if detection_result.is_err() { + errmsgs.extend(detection_result.unwrap_err()); + } + self.detection = Option::Some(detection); if errmsgs.is_empty() { return Result::Ok(()); @@ -115,83 +810,205 @@ impl RuleNode { } pub fn select(&self, event_record: &Value) -> bool { - let selection = self - .detection - .as_ref() - .and_then(|detect_node| detect_node.selection.as_ref()); - if selection.is_none() { + if self.detection.is_none() { return false; } - return selection.unwrap().select(event_record); + return self.detection.as_ref().unwrap().select(event_record); } - - // pub fn get_event_ids(&self) -> Vec { - // let selection = self - // .detection - // .as_ref() - // .and_then(|detection| detection.selection.as_ref()); - // if selection.is_none() { - // return vec![]; - // } - - // return selection - // .unwrap() - // .get_descendants() - // .iter() - // .filter_map(|node| return node.downcast_ref::()) // mopaというライブラリを使うと簡単にダウンキャストできるらしいです。https://crates.io/crates/mopa - // .filter(|node| { - // // キーがEventIDのノードである - // let key = utils::get_event_id_key(); - // if node.get_key() == key { - // return true; - // } - - // // EventIDのAliasに一致しているかどうか - // let alias = utils::get_alias(&key); - // if alias.is_none() { - // return false; - // } else { - // return node.get_key() == alias.unwrap(); - // } - // }) - // .filter_map(|node| { - // return node.select_value.as_i64(); - // }) - // .collect(); - // } } -// Ruleファイルのdetectionを表すノード +/// Ruleファイルのdetectionを表すノード struct DetectionNode { - pub selection: Option>, + pub name_to_selection: HashMap>>, + pub condition: Option>, + pub aggregation_condition: Option, } impl DetectionNode { - fn init(&mut self) -> Result<(), Vec> { - if self.selection.is_none() { - return Result::Ok(()); + fn new() -> DetectionNode { + return DetectionNode { + name_to_selection: HashMap::new(), + condition: Option::None, + aggregation_condition: Option::None, + }; + } + + fn init(&mut self, detection_yaml: &Yaml) -> Result<(), Vec> { + // selection nodeの初期化 + self.parse_name_to_selection(detection_yaml)?; + + // conditionに指定されている式を取得 + let condition = &detection_yaml["condition"].as_str(); + let condition_str = if let Some(cond_str) = condition { + cond_str.to_string() + } else { + // conditionが指定されていない場合、selectionが一つだけならそのselectionを採用することにする。 + let mut keys = self.name_to_selection.keys().clone(); + if keys.len() >= 2 { + return Result::Err(vec![ + "There are no condition node under detection.".to_string() + ]); + } + + keys.nth(0).unwrap().to_string() + }; + + // conditionをパースして、SelectionNodeに変換する + let mut err_msgs = vec![]; + let compiler = ConditionCompiler::new(); + let compile_result = + compiler.compile_condition(condition_str.clone(), &self.name_to_selection); + if let Result::Err(err_msg) = compile_result { + err_msgs.extend(vec![err_msg]); + } else { + self.condition = Option::Some(compile_result.unwrap()); } - return self.selection.as_mut().unwrap().init(); + // aggregation condition(conditionのパイプ以降の部分)をパース + let agg_compiler = AggegationConditionCompiler::new(); + let compile_result = agg_compiler.compile(condition_str); + if let Result::Err(err_msg) = compile_result { + err_msgs.push(err_msg); + } else if let Result::Ok(info) = compile_result { + self.aggregation_condition = info; + } + + if err_msgs.is_empty() { + return Result::Ok(()); + } else { + return Result::Err(err_msgs); + } + } + + pub fn select(&self, event_record: &Value) -> bool { + if self.condition.is_none() { + return false; + } + + let condition = &self.condition.as_ref().unwrap(); + return condition.select(event_record); + } + + /// selectionノードをパースします。 + fn parse_name_to_selection(&mut self, detection_yaml: &Yaml) -> Result<(), Vec> { + let detection_hash = detection_yaml.as_hash(); + if detection_hash.is_none() { + return Result::Err(vec!["not found detection node".to_string()]); + } + + // selectionをパースする。 + let detection_hash = detection_hash.unwrap(); + let keys = detection_hash.keys(); + let mut err_msgs = vec![]; + for key in keys { + let name = key.as_str().unwrap_or(""); + if name.len() == 0 { + continue; + } + // condition等、特殊なキーワードを無視する。 + if name == "condition" { + continue; + } + + // パースして、エラーメッセージがあれば配列にためて、戻り値で返す。 + let selection_node = self.parse_selection(&detection_hash[key]); + if selection_node.is_some() { + let mut selection_node = selection_node.unwrap(); + let init_result = selection_node.init(); + if init_result.is_err() { + err_msgs.extend(init_result.unwrap_err()); + } else { + let rc_selection = Arc::new(selection_node); + self.name_to_selection + .insert(name.to_string(), rc_selection); + } + } + } + if !err_msgs.is_empty() { + return Result::Err(err_msgs); + } + + // selectionノードが無いのはエラー + if self.name_to_selection.len() == 0 { + return Result::Err(vec![ + "There are no selection node under detection.".to_string() + ]); + } + + return Result::Ok(()); + } + + /// selectionをパースします。 + fn parse_selection( + &self, + selection_yaml: &Yaml, + ) -> Option> { + return Option::Some(self.parse_selection_recursively(vec![], selection_yaml)); + } + + /// selectionをパースします。 + fn parse_selection_recursively( + &self, + key_list: Vec, + yaml: &Yaml, + ) -> Box { + if yaml.as_hash().is_some() { + // 連想配列はAND条件と解釈する + let yaml_hash = yaml.as_hash().unwrap(); + let mut and_node = AndSelectionNode::new(); + + yaml_hash.keys().for_each(|hash_key| { + let child_yaml = yaml_hash.get(hash_key).unwrap(); + let mut child_key_list = key_list.clone(); + child_key_list.push(hash_key.as_str().unwrap().to_string()); + let child_node = self.parse_selection_recursively(child_key_list, child_yaml); + and_node.child_nodes.push(child_node); + }); + return Box::new(and_node); + } else if yaml.as_vec().is_some() { + // 配列はOR条件と解釈する。 + let mut or_node = OrSelectionNode::new(); + yaml.as_vec().unwrap().iter().for_each(|child_yaml| { + let child_node = self.parse_selection_recursively(key_list.clone(), child_yaml); + or_node.child_nodes.push(child_node); + }); + + return Box::new(or_node); + } else { + // 連想配列と配列以外は末端ノード + return Box::new(LeafSelectionNode::new(key_list, yaml.clone())); + } } } // Ruleファイルの detection- selection配下のノードはこのtraitを実装する。 trait SelectionNode: mopa::Any { + // 引数で指定されるイベントログのレコードが、条件に一致するかどうかを判定する + // このトレイトを実装する構造体毎に適切な判定処理を書く必要がある。 fn select(&self, event_record: &Value) -> bool; + + // 初期化処理を行う + // 戻り値としてエラーを返却できるようになっているので、Ruleファイルが間違っていて、SelectionNodeを構成出来ない時はここでエラーを出す + // AndSelectionNode等ではinit()関数とは別にnew()関数を実装しているが、new()関数はただインスタンスを作るだけにして、あまり長い処理を書かないようにしている。 + // これはRuleファイルのパースのエラー処理をinit()関数にまとめるためにこうしている。 fn init(&mut self) -> Result<(), Vec>; - fn get_childs(&self) -> Vec<&Box>; - fn get_descendants(&self) -> Vec<&Box>; + + // 子ノードを取得する(グラフ理論のchildと同じ意味) + fn get_childs(&self) -> Vec<&Box>; + + // 子孫ノードを取得する(グラフ理論のdescendantと同じ意味) + fn get_descendants(&self) -> Vec<&Box>; } mopafy!(SelectionNode); -// detection - selection配下でAND条件を表すノード +/// detection - selection配下でAND条件を表すノード struct AndSelectionNode { - pub child_nodes: Vec>, + pub child_nodes: Vec>, } unsafe impl Send for AndSelectionNode {} +unsafe impl Sync for AndSelectionNode {} impl AndSelectionNode { pub fn new() -> AndSelectionNode { @@ -235,7 +1052,7 @@ impl SelectionNode for AndSelectionNode { } } - fn get_childs(&self) -> Vec<&Box> { + fn get_childs(&self) -> Vec<&Box> { let mut ret = vec![]; self.child_nodes.iter().for_each(|child_node| { ret.push(child_node); @@ -244,7 +1061,7 @@ impl SelectionNode for AndSelectionNode { return ret; } - fn get_descendants(&self) -> Vec<&Box> { + fn get_descendants(&self) -> Vec<&Box> { let mut ret = self.get_childs(); self.child_nodes @@ -261,12 +1078,13 @@ impl SelectionNode for AndSelectionNode { } } -// detection - selection配下でOr条件を表すノード +/// detection - selection配下でOr条件を表すノード struct OrSelectionNode { - pub child_nodes: Vec>, + pub child_nodes: Vec>, } unsafe impl Send for OrSelectionNode {} +unsafe impl Sync for OrSelectionNode {} impl OrSelectionNode { pub fn new() -> OrSelectionNode { @@ -310,7 +1128,7 @@ impl SelectionNode for OrSelectionNode { } } - fn get_childs(&self) -> Vec<&Box> { + fn get_childs(&self) -> Vec<&Box> { let mut ret = vec![]; self.child_nodes.iter().for_each(|child_node| { ret.push(child_node); @@ -319,7 +1137,7 @@ impl SelectionNode for OrSelectionNode { return ret; } - fn get_descendants(&self) -> Vec<&Box> { + fn get_descendants(&self) -> Vec<&Box> { let mut ret = self.get_childs(); self.child_nodes @@ -336,7 +1154,76 @@ impl SelectionNode for OrSelectionNode { } } -// detection - selection配下の末端ノード +/// conditionでNotを表すノード +struct NotSelectionNode { + node: Box, +} + +unsafe impl Send for NotSelectionNode {} +unsafe impl Sync for NotSelectionNode {} + +impl NotSelectionNode { + pub fn new(node: Box) -> NotSelectionNode { + return NotSelectionNode { node: node }; + } +} + +impl SelectionNode for NotSelectionNode { + fn select(&self, event_record: &Value) -> bool { + return !self.node.select(event_record); + } + + fn init(&mut self) -> Result<(), Vec> { + return Result::Ok(()); + } + + fn get_childs(&self) -> Vec<&Box> { + return vec![]; + } + + fn get_descendants(&self) -> Vec<&Box> { + return self.get_childs(); + } +} + +/// detectionで定義した条件をconditionで参照するためのもの +struct RefSelectionNode { + // selection_nodeはDetectionNodeのname_2_nodeが所有権を持っていて、RefSelectionNodeのselection_nodeに所有権を持たせることができない。 + // そこでArcを使って、DetectionNodeのname_2_nodeとRefSelectionNodeのselection_nodeで所有権を共有する。 + // RcじゃなくてArcなのはマルチスレッド対応のため + selection_node: Arc>, +} + +unsafe impl Send for RefSelectionNode {} +unsafe impl Sync for RefSelectionNode {} + +impl RefSelectionNode { + pub fn new(selection_node: Arc>) -> RefSelectionNode { + return RefSelectionNode { + selection_node: selection_node, + }; + } +} + +impl SelectionNode for RefSelectionNode { + fn select(&self, event_record: &Value) -> bool { + return self.selection_node.select(event_record); + } + + fn init(&mut self) -> Result<(), Vec> { + return Result::Ok(()); + } + + fn get_childs(&self) -> Vec<&Box> { + return vec![&self.selection_node]; + } + + fn get_descendants(&self) -> Vec<&Box> { + return self.get_childs(); + } +} + +/// detection - selection配下の末端ノード struct LeafSelectionNode { key_list: Vec, select_value: Yaml, @@ -344,6 +1231,7 @@ struct LeafSelectionNode { } unsafe impl Send for LeafSelectionNode {} +unsafe impl Sync for LeafSelectionNode {} impl LeafSelectionNode { fn new(key_list: Vec, value_yaml: Yaml) -> LeafSelectionNode { @@ -362,7 +1250,7 @@ impl LeafSelectionNode { return self.key_list[0].to_string(); } - // JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。 + /// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。 fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> { if self.key_list.is_empty() { return Option::None; @@ -371,8 +1259,8 @@ impl LeafSelectionNode { return utils::get_event_value(&self.get_key(), event_value); } - // LeafMatcherの一覧を取得する。 - // 上から順番に調べて、一番始めに一致したMatcherが適用される + /// LeafMatcherの一覧を取得する。 + /// 上から順番に調べて、一番始めに一致したMatcherが適用される fn get_matchers(&self) -> Vec> { return vec![ Box::new(RegexMatcher::new()), @@ -501,11 +1389,11 @@ impl SelectionNode for LeafSelectionNode { .init(&match_key_list, &self.select_value); } - fn get_childs(&self) -> Vec<&Box> { + fn get_childs(&self) -> Vec<&Box> { return vec![]; } - fn get_descendants(&self) -> Vec<&Box> { + fn get_descendants(&self) -> Vec<&Box> { return vec![]; } } @@ -524,7 +1412,7 @@ trait LeafMatcher: mopa::Any { } mopafy!(LeafMatcher); -// 正規表現で比較するロジックを表すクラス +/// 正規表現で比較するロジックを表すクラス struct RegexMatcher { re: Option, } @@ -615,7 +1503,7 @@ impl LeafMatcher for RegexMatcher { } } -// 指定された文字数以上であることをチェックするクラス。 +/// 指定された文字数以上であることをチェックするクラス。 struct MinlengthMatcher { min_len: i64, } @@ -658,8 +1546,8 @@ impl LeafMatcher for MinlengthMatcher { } } -// 正規表現のリストが記載されたファイルを読み取って、比較するロジックを表すクラス -// DeepBlueCLIのcheck_cmdメソッドの一部に同様の処理が実装されていた。 +/// 正規表現のリストが記載されたファイルを読み取って、比較するロジックを表すクラス +/// DeepBlueCLIのcheck_cmdメソッドの一部に同様の処理が実装されていた。 struct RegexesFileMatcher { regexes_csv_content: Vec>, } @@ -720,8 +1608,8 @@ impl LeafMatcher for RegexesFileMatcher { } } -// ファイルに列挙された文字列に一致する場合に検知するロジックを表す -// DeepBlueCLIのcheck_cmdメソッドの一部に同様の処理が実装されていた。 +/// ファイルに列挙された文字列に一致する場合に検知するロジックを表す +/// DeepBlueCLIのcheck_cmdメソッドの一部に同様の処理が実装されていた。 struct WhitelistFileMatcher { whitelist_csv_content: Vec>, } @@ -783,7 +1671,7 @@ impl LeafMatcher for WhitelistFileMatcher { } } -// 指定された文字列で始まるか調べるクラス +/// 指定された文字列で始まるか調べるクラス struct StartsWithMatcher { start_text: String, } @@ -837,7 +1725,7 @@ impl LeafMatcher for StartsWithMatcher { } } -// 指定された文字列で終わるか調べるクラス +/// 指定された文字列で終わるか調べるクラス struct EndsWithMatcher { end_text: String, } @@ -891,7 +1779,7 @@ impl LeafMatcher for EndsWithMatcher { } } -// 指定された文字列が含まれるか調べるクラス +/// 指定された文字列が含まれるか調べるクラス struct ContainsMatcher { pattern: String, } @@ -947,14 +1835,31 @@ impl LeafMatcher for ContainsMatcher { #[cfg(test)] mod tests { + use crate::detections::rule::{ + create_rule, AggregationConditionToken, AndSelectionNode, LeafSelectionNode, + MinlengthMatcher, OrSelectionNode, RegexMatcher, RegexesFileMatcher, SelectionNode, + WhitelistFileMatcher, + }; use yaml_rust::YamlLoader; - use crate::detections::rule::{ - parse_rule, AndSelectionNode, LeafSelectionNode, MinlengthMatcher, OrSelectionNode, - RegexMatcher, RegexesFileMatcher, SelectionNode, WhitelistFileMatcher, - }; + use super::{AggegationConditionCompiler, RuleNode}; - use super::RuleNode; + const SIMPLE_RECORD_STR: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System" + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; #[test] fn test_rule_parse() { @@ -985,7 +1890,7 @@ mod tests { updated_date: 2020/11/8 "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); + let selection_node = &rule_node.detection.unwrap().name_to_selection["selection"]; // Root let detection_childs = selection_node.get_childs(); @@ -994,7 +1899,7 @@ mod tests { // Channel { // LeafSelectionNodeが正しく読み込めることを確認 - let child_node = detection_childs[0]; + let child_node = detection_childs[0].as_ref() as &dyn SelectionNode; // TODO キャストしないとエラーでるけど、このキャストよく分からん。 assert_eq!(child_node.is::(), true); let child_node = child_node.downcast_ref::().unwrap(); assert_eq!(child_node.get_key(), "Channel"); @@ -1018,7 +1923,7 @@ mod tests { // EventID { // LeafSelectionNodeが正しく読み込めることを確認 - let child_node = detection_childs[1]; + let child_node = detection_childs[1].as_ref() as &dyn SelectionNode; assert_eq!(child_node.is::(), true); let child_node = child_node.downcast_ref::().unwrap(); assert_eq!(child_node.get_key(), "EventID"); @@ -1039,7 +1944,7 @@ mod tests { // ContextInfo { // OrSelectionNodeを正しく読み込めることを確認 - let child_node = detection_childs[2]; + let child_node = detection_childs[2].as_ref() as &dyn SelectionNode; assert_eq!(child_node.is::(), true); let child_node = child_node.downcast_ref::().unwrap(); let ancestors = child_node.get_childs(); @@ -1047,7 +1952,7 @@ mod tests { // OrSelectionNodeの下にLeafSelectionNodeがあるパターンをテスト // LeafSelectionNodeである、Host Applicationノードが正しいことを確認 - let hostapp_en_node = ancestors[0]; + let hostapp_en_node = ancestors[0].as_ref() as &dyn SelectionNode; assert_eq!(hostapp_en_node.is::(), true); let hostapp_en_node = hostapp_en_node.downcast_ref::().unwrap(); @@ -1061,7 +1966,7 @@ mod tests { assert_eq!(re.unwrap().as_str(), "Host Application"); // LeafSelectionNodeである、ホスト アプリケーションノードが正しいことを確認 - let hostapp_jp_node = ancestors[1]; + let hostapp_jp_node = ancestors[1].as_ref() as &dyn SelectionNode; assert_eq!(hostapp_jp_node.is::(), true); let hostapp_jp_node = hostapp_jp_node.downcast_ref::().unwrap(); @@ -1078,7 +1983,7 @@ mod tests { // ImagePath { // AndSelectionNodeを正しく読み込めることを確認 - let child_node = detection_childs[3]; + let child_node = detection_childs[3].as_ref() as &dyn SelectionNode; assert_eq!(child_node.is::(), true); let child_node = child_node.downcast_ref::().unwrap(); let ancestors = child_node.get_childs(); @@ -1086,7 +1991,7 @@ mod tests { // min-lenが正しく読み込めることを確認 { - let ancestor_node = ancestors[0]; + let ancestor_node = ancestors[0].as_ref() as &dyn SelectionNode; assert_eq!(ancestor_node.is::(), true); let ancestor_node = ancestor_node.downcast_ref::().unwrap(); @@ -1100,7 +2005,7 @@ mod tests { // regexesが正しく読み込めることを確認 { - let ancestor_node = ancestors[1]; + let ancestor_node = ancestors[1].as_ref() as &dyn SelectionNode; assert_eq!(ancestor_node.is::(), true); let ancestor_node = ancestor_node.downcast_ref::().unwrap(); @@ -1140,7 +2045,7 @@ mod tests { // whitelist.txtが読み込めることを確認 { - let ancestor_node = ancestors[2]; + let ancestor_node = ancestors[2].as_ref() as &dyn SelectionNode; assert_eq!(ancestor_node.is::(), true); let ancestor_node = ancestor_node.downcast_ref::().unwrap(); @@ -1200,11 +2105,10 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1230,11 +2134,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1260,11 +2162,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1291,11 +2191,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1322,11 +2220,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1351,11 +2247,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1381,11 +2275,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1412,11 +2304,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1445,11 +2335,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1475,11 +2363,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1505,11 +2391,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1535,11 +2419,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1567,11 +2449,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1599,11 +2479,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1631,11 +2509,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1661,11 +2537,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1692,11 +2566,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1723,11 +2595,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1754,11 +2624,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1786,11 +2654,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1818,11 +2684,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1848,11 +2712,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1882,11 +2744,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1916,11 +2776,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1949,11 +2807,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2032,11 +2888,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2091,11 +2945,9 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2157,11 +3009,9 @@ mod tests { "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2201,11 +3051,9 @@ mod tests { "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2246,11 +3094,9 @@ mod tests { "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2310,11 +3156,9 @@ mod tests { "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2374,11 +3218,9 @@ mod tests { "#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -2391,7 +3233,7 @@ mod tests { assert_eq!(rule_yaml.is_ok(), true); let rule_yamls = rule_yaml.unwrap(); let mut rule_yaml = rule_yamls.into_iter(); - let mut rule_node = parse_rule(rule_yaml.next().unwrap()); + let mut rule_node = create_rule(rule_yaml.next().unwrap()); assert_eq!(rule_node.init().is_ok(), true); return rule_node; } @@ -2426,13 +3268,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2468,13 +3308,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2510,13 +3348,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2552,13 +3388,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2594,13 +3428,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2636,13 +3468,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), false); + assert_eq!(rule_node.select(&record), false); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2682,13 +3512,11 @@ mod tests { }"#; let rule_node = parse_rule_from_str(rule_str); - let selection_node = rule_node.detection.unwrap().selection.unwrap(); - match serde_json::from_str(record_json_str) { Ok(record) => { - assert_eq!(selection_node.select(&record), true); + assert_eq!(rule_node.select(&record), true); } - Err(rec) => { + Err(_rec) => { assert!(false, "failed to parse json record."); } } @@ -2706,11 +3534,1116 @@ mod tests { output: 'Rule parse test' "#; let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); - let mut rule_node = parse_rule(rule_yaml.next().unwrap()); + let mut rule_node = create_rule(rule_yaml.next().unwrap()); assert_eq!( rule_node.init(), Err(vec!["Found unknown key option. option: failed".to_string()]) ); } + + #[test] + fn test_detect_not_defined_selection() { + // 不明な文字列オプションがルールに書かれていたら警告するテスト + let rule_str = r#" + enabled: true + detection: + output: 'Rule parse test' + "#; + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let mut rule_node = create_rule(rule_yaml.next().unwrap()); + + assert_eq!( + rule_node.init(), + Err(vec!["not found detection node".to_string()]) + ); + } + + #[test] + fn test_no_condition() { + // condition式が無くても、selectionが一つだけなら、正しくパースできることを確認 + let rule_str = r#" + enabled: true + detection: + selection: + Channel: 'System' + EventID: 7040 + param1: 'Windows Event Log' + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + let record_json_str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System" + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + + let rule_node = parse_rule_from_str(rule_str); + match serde_json::from_str(record_json_str) { + Ok(record) => { + assert_eq!(rule_node.select(&record), true); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } + + #[test] + fn test_no_condition_notdetect() { + // condition式が無くても、selectionが一つだけなら、正しくパースできることを確認 + // これは検知しないパターン + let rule_str = r#" + enabled: true + detection: + selection: + Channel: 'System' + EventID: 7041 + param1: 'Windows Event Log' + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + let record_json_str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System" + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + + let rule_node = parse_rule_from_str(rule_str); + match serde_json::from_str(record_json_str) { + Ok(record) => { + assert_eq!(rule_node.select(&record), false); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } + + #[test] + fn test_condition_and_detect() { + // conditionにandを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_and_notdetect() { + // conditionにandを使ったパターンのテスト + // これはHitしないパターン + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_and_notdetect2() { + // conditionにandを使ったパターンのテスト + // これはHitしないパターン + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_and_detect3() { + // conditionにandを使ったパターンのテスト + // これはHitしないパターン + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_and_notdetect4() { + // conditionにandを使ったパターンのテスト + // これはHitしないパターン + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_and_notdetect5() { + // conditionにandを使ったパターンのテスト + // これはHitしないパターン + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Logn' + condition: selection1 and selection2 and selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_or_detect() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect2() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect3() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Log' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect4() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect5() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Log' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect6() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Logn' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_detect7() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_or_notdetect() { + // conditionにorを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + selection2: + EventID: 7041 + selection3: + param1: 'Windows Event Logn' + condition: selection1 or selection2 or selection3 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_not_detect() { + // conditionにnotを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'Systemn' + condition: not selection1 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_not_notdetect() { + // conditionにnotを使ったパターンのテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + condition: not selection1 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_parenthesis_detect() { + // conditionに括弧を使ったテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection2 and (selection2 or selection3) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_parenthesis_not_detect() { + // conditionに括弧を使ったテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection2 and (selection2 and selection3) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_many_parenthesis_detect() { + // conditionに括弧を沢山使ったテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection2 and (((selection2 or selection3))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_manyparenthesis_not_detect() { + // conditionに括弧を沢山使ったテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: selection2 and ((((selection2 and selection3)))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_notparenthesis_detect() { + // conditionに括弧を沢山使ったテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: (selection2 and selection1) and not ((selection2 and selection3)) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_notparenthesis_notdetect() { + // conditionに括弧とnotを組み合わせたテスト + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: (selection2 and selection1) and not (not(selection2 and selection3)) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_manyparenthesis_detect2() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: (selection2 and selection1) and (selection2 or selection3) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_manyparenthesis_notdetect2() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + condition: (selection2 and selection1) and (selection2 and selection3) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_manyparenthesis_detect3() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + selection4: + param2: 'auto start' + condition: (selection1 and (selection2 and ( selection3 and selection4 ))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_manyparenthesis_notdetect3() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + selection4: + param2: 'auto start' + condition: (selection1 and (selection2 and ( selection3 and selection4 ))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_condition_manyparenthesis_detect4() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + selection4: + param2: 'auto start' + condition: (selection1 and (selection2 and ( selection3 or selection4 ))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, true); + } + + #[test] + fn test_condition_manyparenthesis_notdetect4() { + // 括弧を色々使ったケース + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Logn' + selection4: + param2: 'auto startn' + condition: (selection1 and (selection2 and ( selection3 or selection4 ))) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_select(rule_str, SIMPLE_RECORD_STR, false); + } + + #[test] + fn test_rule_parseerror_no_condition() { + // selectionが複数あるのにconditionが無いのはエラー + let rule_str = r#" + enabled: true + detection: + selection: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let mut rule_node = create_rule(rule_yaml.next().unwrap()); + + assert_eq!( + rule_node.init(), + Err(vec![ + "There are no condition node under detection.".to_string() + ]) + ); + } + + #[test] + fn test_condition_err_condition_forbit_character() { + // conditionに読み込めない文字が指定されている。 + let rule_str = r#" + enabled: true + detection: + selection-1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection-1 and selection2 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. An unusable character was found.".to_string()], + ); + } + + #[test] + fn test_condition_err_leftparenthesis_over() { + // 左括弧が多い + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 and ((selection2) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. expected ')'. but not found.".to_string()], + ); + } + + #[test] + fn test_condition_err_rightparenthesis_over() { + // 右括弧が多い + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 and (selection2)) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. expected '('. but not found.".to_string()], + ); + } + + #[test] + fn test_condition_err_parenthesis_direction_wrong() { + // 括弧の向きが違う + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 and )selection2( + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. expected ')'. but not found.".to_string()], + ); + } + + #[test] + fn test_condition_err_no_logical() { + // ANDとかORで結合してない + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 selection2 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error(rule_str,vec!["condition parse error has occured. unknown error. maybe it\'s because there are multiple name of selection node.".to_string()]); + } + + #[test] + fn test_condition_err_first_logical() { + // + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: and selection1 or selection2 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec![ + "condition parse error has occured. illegal Logical Operator(and, or) was found." + .to_string(), + ], + ); + } + + #[test] + fn test_condition_err_last_logical() { + // + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 or selection2 or + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec![ + "condition parse error has occured. illegal Logical Operator(and, or) was found." + .to_string(), + ], + ); + } + + #[test] + fn test_condition_err_consecutive_logical() { + // + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 or or selection2 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error(rule_str,vec!["condition parse error has occured. The use of logical operator(and, or) was wrong.".to_string()]); + } + + #[test] + fn test_condition_err_only_not() { + // + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 or ( not ) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. illegal not was found.".to_string()], + ); + } + + #[test] + fn test_condition_err_not_not() { + // notが続くのはだめ + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + EventID: 7041 + selection2: + param1: 'Windows Event Log' + condition: selection1 or ( not not ) + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + check_rule_parse_error( + rule_str, + vec!["condition parse error has occured. not is continuous.".to_string()], + ); + } + + #[test] + fn test_aggegation_condition_compiler_no_count() { + // countが無いパターン + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 and select2".to_string()); + assert_eq!(true, result.is_ok()); + let result = result.unwrap(); + assert_eq!(true, result.is_none()); + } + + #[test] + fn test_aggegation_condition_compiler_count_ope() { + // 正常系 countの中身にフィールドが無い 各種演算子を試す + let token = + check_aggregation_condition_ope("select1 and select2|count() > 32".to_string(), 32); + let is_gt = match token { + AggregationConditionToken::GT => true, + _ => false, + }; + assert_eq!(is_gt, true); + + let token = + check_aggregation_condition_ope("select1 and select2|count() >= 43".to_string(), 43); + let is_gt = match token { + AggregationConditionToken::GE => true, + _ => false, + }; + assert_eq!(is_gt, true); + + let token = + check_aggregation_condition_ope("select1 and select2|count() < 59".to_string(), 59); + let is_gt = match token { + AggregationConditionToken::LT => true, + _ => false, + }; + assert_eq!(is_gt, true); + + let token = + check_aggregation_condition_ope("select1 and select2|count() <= 12".to_string(), 12); + let is_gt = match token { + AggregationConditionToken::LE => true, + _ => false, + }; + assert_eq!(is_gt, true); + + let token = + check_aggregation_condition_ope("select1 and select2|count() == 28".to_string(), 28); + let is_gt = match token { + AggregationConditionToken::EQ => true, + _ => false, + }; + assert_eq!(is_gt, true); + } + + #[test] + fn test_aggegation_condition_compiler_count_by() { + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 or select2 | count() by iiibbb > 27".to_string()); + + assert_eq!(true, result.is_ok()); + let result = result.unwrap(); + assert_eq!(true, result.is_some()); + + let result = result.unwrap(); + assert_eq!("iiibbb".to_string(), result._by_field_name.unwrap()); + assert_eq!(true, result._field_name.is_none()); + assert_eq!(27, result._cmp_num); + let is_ok = match result._cmp_op { + AggregationConditionToken::GT => true, + _ => false, + }; + assert_eq!(true, is_ok); + } + + #[test] + fn test_aggegation_condition_compiler_count_field() { + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 or select2 | count( hogehoge ) > 3".to_string()); + + assert_eq!(true, result.is_ok()); + let result = result.unwrap(); + assert_eq!(true, result.is_some()); + + let result = result.unwrap(); + assert_eq!(true, result._by_field_name.is_none()); + assert_eq!("hogehoge", result._field_name.unwrap()); + assert_eq!(3, result._cmp_num); + let is_ok = match result._cmp_op { + AggregationConditionToken::GT => true, + _ => false, + }; + assert_eq!(true, is_ok); + } + + #[test] + fn test_aggegation_condition_compiler_count_all_field() { + let compiler = AggegationConditionCompiler::new(); + let result = + compiler.compile("select1 or select2 | count( hogehoge) by snsn > 3".to_string()); + + assert_eq!(true, result.is_ok()); + let result = result.unwrap(); + assert_eq!(true, result.is_some()); + + let result = result.unwrap(); + assert_eq!("snsn".to_string(), result._by_field_name.unwrap()); + assert_eq!("hogehoge", result._field_name.unwrap()); + assert_eq!(3, result._cmp_num); + let is_ok = match result._cmp_op { + AggregationConditionToken::GT => true, + _ => false, + }; + assert_eq!(true, is_ok); + } + + #[test] + fn test_aggegation_condition_compiler_only_pipe() { + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 or select2 |".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!( + "aggregation condition parse error has occurred. There are no strings after pipe(|)." + .to_string(), + result.unwrap_err() + ); + } + + #[test] + fn test_aggegation_condition_compiler_unused_character() { + let compiler = AggegationConditionCompiler::new(); + let result = + compiler.compile("select1 or select2 | count( hogeess ) by ii-i > 33".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!( + "aggregation condition parse error has occurred. An unusable character was found." + .to_string(), + result.unwrap_err() + ); + } + + #[test] + fn test_aggegation_condition_compiler_not_count() { + // countじゃないものが先頭に来ている。 + let compiler = AggegationConditionCompiler::new(); + let result = + compiler.compile("select1 or select2 | by count( hogehoge) by snsn > 3".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!("aggregation condition parse error has occurred. aggregation condition can use count only.".to_string(),result.unwrap_err()); + } + + #[test] + fn test_aggegation_condition_compiler_no_ope() { + // 比較演算子がない + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 or select2 | count( hogehoge) 3".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!("aggregation condition parse error has occurred. count keyword needs compare operator and number like '> 3'".to_string(),result.unwrap_err()); + } + + #[test] + fn test_aggegation_condition_compiler_by() { + // byの後に何もない + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile("select1 or select2 | count( hogehoge) by".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!("aggregation condition parse error has occurred. by keyword needs field name like 'by EventID'".to_string(),result.unwrap_err()); + } + + #[test] + fn test_aggegation_condition_compiler_no_ope_afterby() { + // byの後に何もない + let compiler = AggegationConditionCompiler::new(); + let result = + compiler.compile("select1 or select2 | count( hogehoge ) by hoe >".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!("aggregation condition parse error has occurred. compare operator needs a number like '> 3'.".to_string(),result.unwrap_err()); + } + + #[test] + fn test_aggegation_condition_compiler_unneccesary_word() { + // byの後に何もない + let compiler = AggegationConditionCompiler::new(); + let result = + compiler.compile("select1 or select2 | count( hogehoge ) by hoe > 3 33".to_string()); + + assert_eq!(true, result.is_err()); + assert_eq!( + "aggregation condition parse error has occurred. unnecessary word was found." + .to_string(), + result.unwrap_err() + ); + } + + fn check_aggregation_condition_ope(expr: String, cmp_num: i32) -> AggregationConditionToken { + let compiler = AggegationConditionCompiler::new(); + let result = compiler.compile(expr); + + assert_eq!(true, result.is_ok()); + let result = result.unwrap(); + assert_eq!(true, result.is_some()); + + let result = result.unwrap(); + assert_eq!(true, result._by_field_name.is_none()); + assert_eq!(true, result._field_name.is_none()); + assert_eq!(cmp_num, result._cmp_num); + return result._cmp_op; + } + + fn check_rule_parse_error(rule_str: &str, errmsgs: Vec) { + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let mut rule_node = create_rule(rule_yaml.next().unwrap()); + + assert_eq!(rule_node.init(), Err(errmsgs)); + } + + fn check_select(rule_str: &str, record_str: &str, expect_select: bool) { + let rule_node = parse_rule_from_str(rule_str); + match serde_json::from_str(record_str) { + Ok(record) => { + assert_eq!(rule_node.select(&record), expect_select); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } }