diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 9714602df..c55132efd 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -4,7 +4,7 @@ **改善:** -- XXX +- 指定した`status`のルールのみを利用する`--include-status`オプションを追加した。 (#1193) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a145ccd..7a4711790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **Enhancements:** -- XXX +- Added `--include-status` option: You can specify rules based on their `status`. (#1193) (@hitenkoku) **Bug Fixes:** diff --git a/src/afterfact.rs b/src/afterfact.rs index fdcd2aa31..729736ff8 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -2060,6 +2060,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv.csv").to_path_buf()), @@ -2151,6 +2152,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -2392,6 +2394,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv_multiline.csv").to_path_buf()), @@ -2485,6 +2488,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -2712,6 +2716,7 @@ mod tests { remove_duplicate_data: true, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv_remove_duplicate.csv").to_path_buf()), @@ -2803,6 +2808,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -3041,6 +3047,7 @@ mod tests { remove_duplicate_data: true, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv_remove_duplicate.json").to_path_buf()), @@ -3132,6 +3139,7 @@ mod tests { remove_duplicate_data: true, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -3218,7 +3226,7 @@ mod tests { println!("message: {detect_infos:?}"); } - let expect_target = vec![ + let expect_target = [ vec![ ( "Timestamp", @@ -3444,6 +3452,7 @@ mod tests { remove_duplicate_data: true, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_multiple_data_in_details.json").to_path_buf()), @@ -3536,6 +3545,7 @@ mod tests { remove_duplicate_data: true, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -3597,8 +3607,7 @@ mod tests { Profile::Computer(test_computername.into()); } - let expect_target = vec![ - vec![ + let expect_target = [vec![ ( "Timestamp", CompactString::from( @@ -3652,8 +3661,7 @@ mod tests { "Tags", CompactString::from("[\n \"".to_string() + test_attack + "\"\n ]"), ), - ] - ]; + ]]; let mut expect_str = String::default(); for (target_idx, target) in expect_target.iter().enumerate() { let mut expect_json = "{\n".to_string(); @@ -3792,6 +3800,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv_json.json").to_path_buf()), @@ -3882,6 +3891,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) @@ -4061,6 +4071,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv_jsonl.jsonl").to_path_buf()), @@ -4151,6 +4162,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }; let ch = mock_ch_filter .get(&CompactString::from("security")) diff --git a/src/detections/configs.rs b/src/detections/configs.rs index cf3573b60..36c6ccea1 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -530,6 +530,32 @@ impl StoredStatic { Some(Action::ComputerMetrics(opt)) => opt.input_args.timeline_offset.clone(), _ => None, }; + let include_status: HashSet = match &input_config.as_ref().unwrap().action { + Some(Action::CsvTimeline(opt)) => opt + .output_options + .include_status + .as_ref() + .unwrap_or(&vec![]) + .iter() + .map(|x| x.into()) + .collect(), + Some(Action::JsonTimeline(opt)) => opt + .output_options + .include_status + .as_ref() + .unwrap_or(&vec![]) + .iter() + .map(|x| x.into()) + .collect(), + Some(Action::PivotKeywordsList(opt)) => opt + .include_status + .as_ref() + .unwrap_or(&vec![]) + .iter() + .map(|x| x.into()) + .collect(), + _ => HashSet::default(), + }; let mut ret = StoredStatic { config: input_config.as_ref().unwrap().to_owned(), @@ -645,7 +671,7 @@ impl StoredStatic { no_pwsh_field_extraction: no_pwsh_field_extraction_flag, enable_recover_records, timeline_offset, - include_status: HashSet::new(), + include_status, }; ret.profiles = load_profile( check_setting_path( @@ -1200,11 +1226,15 @@ pub struct PivotKeywordOption { pub enable_unsupported_rules: bool, /// Do not load rules according to status (ex: experimental) (ex: stable,test) - #[arg(help_heading = Some("Filtering"), long = "exclude-status", value_name = "STATUS...", requires = "no_wizard", use_value_delimiter = true, value_delimiter = ',', display_order = 316)] + #[arg(help_heading = Some("Filtering"), long = "exclude-status", value_name = "STATUS...", requires = "no_wizard", conflicts_with = "include_status",use_value_delimiter = true, value_delimiter = ',', display_order = 316)] pub exclude_status: Option>, + /// Only load rules with specific status (ex: experimental) (ex: stable,test) + #[arg(help_heading = Some("Filtering"), long = "include-status", value_name = "STATUS...", requires = "no_wizard", conflicts_with = "exclude_status", use_value_delimiter = true, value_delimiter = ',', display_order = 353)] + pub include_status: Option>, + /// Only load rules with specific tags (ex: attack.execution,attack.discovery) - #[arg(help_heading = Some("Filtering"), long = "include-tag", value_name = "TAG...", requires = "no_wizard", conflicts_with = "exclude_tag", use_value_delimiter = true, value_delimiter = ',', display_order = 353)] + #[arg(help_heading = Some("Filtering"), long = "include-tag", value_name = "TAG...", requires = "no_wizard", conflicts_with = "exclude_tag", use_value_delimiter = true, value_delimiter = ',', display_order = 354)] pub include_tag: Option>, /// Do not load rules with specific tags (ex: sysmon) @@ -1352,11 +1382,15 @@ pub struct OutputOption { pub enable_unsupported_rules: bool, /// Do not load rules according to status (ex: experimental) (ex: stable,test) - #[arg(help_heading = Some("Filtering"), long = "exclude-status", value_name = "STATUS...", requires = "no_wizard", use_value_delimiter = true, value_delimiter = ',', display_order = 316)] + #[arg(help_heading = Some("Filtering"), long = "exclude-status", value_name = "STATUS...", requires = "no_wizard", conflicts_with = "include_status", use_value_delimiter = true, value_delimiter = ',', display_order = 316)] pub exclude_status: Option>, + /// Only load rules with specific status (ex: experimental) (ex: stable,test) + #[arg(help_heading = Some("Filtering"), long = "include-status", value_name = "STATUS...", requires = "no_wizard", conflicts_with = "exclude_status", use_value_delimiter = true, value_delimiter = ',', display_order = 353)] + pub include_status: Option>, + /// Only load rules with specific tags (ex: attack.execution,attack.discovery) - #[arg(help_heading = Some("Filtering"), long = "include-tag", value_name = "TAG...", requires = "no_wizard", conflicts_with = "exclude_tag", use_value_delimiter = true, value_delimiter = ',', display_order = 353)] + #[arg(help_heading = Some("Filtering"), long = "include-tag", value_name = "TAG...", requires = "no_wizard", conflicts_with = "exclude_tag", use_value_delimiter = true, value_delimiter = ',', display_order = 354)] pub include_tag: Option>, /// Only load rules with specified logsource categories (ex: process_creation,pipe_created) @@ -2180,6 +2214,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: option.no_wizard, + include_status: option.include_status.clone(), }), Action::EidMetrics(option) => Some(OutputOption { input_args: option.input_args.clone(), @@ -2219,6 +2254,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), Action::LogonSummary(option) => Some(OutputOption { input_args: option.input_args.clone(), @@ -2258,6 +2294,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), Action::ComputerMetrics(option) => Some(OutputOption { input_args: option.input_args.clone(), @@ -2306,6 +2343,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), Action::Search(option) => Some(OutputOption { input_args: option.input_args.clone(), @@ -2354,6 +2392,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), Action::SetDefaultProfile(option) => Some(OutputOption { input_args: InputOption { @@ -2408,6 +2447,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), Action::UpdateRules(option) => Some(OutputOption { input_args: InputOption { @@ -2462,6 +2502,7 @@ fn extract_output_options(config: &Config) -> Option { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }), _ => None, } @@ -2713,6 +2754,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -2787,6 +2829,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -2980,6 +3023,7 @@ mod tests { no_wizard: true, include_tag: None, exclude_tag: None, + include_status: None, })), debug: false, })); diff --git a/src/detections/detection.rs b/src/detections/detection.rs index ecceaedb8..8d5900941 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1263,6 +1263,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -1526,6 +1527,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: Some(Path::new("test_files/mmdb").to_path_buf()), output: Some(Path::new("./test_emit_csv.csv").to_path_buf()), @@ -1663,6 +1665,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: Some(Path::new("test_files/mmdb").to_path_buf()), output: Some(Path::new("./test_emit_csv.csv").to_path_buf()), @@ -1796,6 +1799,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv.csv").to_path_buf()), @@ -1942,6 +1946,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("./test_emit_csv.csv").to_path_buf()), diff --git a/src/detections/rule/condition_parser.rs b/src/detections/rule/condition_parser.rs index cac57c94a..2ed2c9621 100644 --- a/src/detections/rule/condition_parser.rs +++ b/src/detections/rule/condition_parser.rs @@ -507,6 +507,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index ebabb3fac..d1ba40b2e 100644 --- a/src/detections/rule/count.rs +++ b/src/detections/rule/count.rs @@ -635,6 +635,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/detections/rule/matchers.rs b/src/detections/rule/matchers.rs index 25981e69a..c237df86a 100644 --- a/src/detections/rule/matchers.rs +++ b/src/detections/rule/matchers.rs @@ -870,6 +870,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/detections/rule/mod.rs b/src/detections/rule/mod.rs index f31a63979..9e2e26dd9 100644 --- a/src/detections/rule/mod.rs +++ b/src/detections/rule/mod.rs @@ -452,6 +452,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/detections/rule/selectionnodes.rs b/src/detections/rule/selectionnodes.rs index a24ac3d64..1df26cdd0 100644 --- a/src/detections/rule/selectionnodes.rs +++ b/src/detections/rule/selectionnodes.rs @@ -575,6 +575,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 6bfc28cad..481499739 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -1076,6 +1076,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/main.rs b/src/main.rs index fb6815ae7..1ceb0cdc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1303,7 +1303,7 @@ impl App { stored_static.output_option.as_mut().unwrap().exclude_tag = Some(exclude_tags.to_owned()); } - } else { + } else if stored_static.include_status.is_empty() { stored_static.include_status.insert("*".into()); } @@ -2100,6 +2100,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -2147,7 +2148,7 @@ mod tests { stored_static.config.action = None; stored_static.html_report_flag = true; app.exec(&mut config_reader.app, &mut stored_static); - let expect_general_contents = vec![ + let expect_general_contents = [ format!("- Command line: {}", std::env::args().join(" ")), format!("- Start time: {}", Local::now().format("%Y/%m/%d %H:%M")), ]; @@ -2264,6 +2265,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("overwrite.csv").to_path_buf()), @@ -2349,6 +2351,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("overwrite.csv").to_path_buf()), @@ -2432,6 +2435,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("overwrite.json").to_path_buf()), @@ -2517,6 +2521,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: Some(Path::new("overwrite.json").to_path_buf()), diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index cad84aa95..2bd6bef6f 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -301,6 +301,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -367,6 +368,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -436,6 +438,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, jsonl_timeline: false, geo_ip: None, @@ -502,6 +505,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, jsonl_timeline: false, geo_ip: None, diff --git a/src/options/profile.rs b/src/options/profile.rs index dd93e81e8..1f45f4fd0 100644 --- a/src/options/profile.rs +++ b/src/options/profile.rs @@ -536,6 +536,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -613,6 +614,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, @@ -720,6 +722,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None, diff --git a/src/yaml.rs b/src/yaml.rs index 5e2dfa32e..11df79ff9 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -765,6 +765,7 @@ mod tests { remove_duplicate_data: false, remove_duplicate_detections: false, no_wizard: true, + include_status: None, }, geo_ip: None, output: None,