Merge branch 'main' into 654-enhancement-output-to-json
This commit is contained in:
@@ -10,10 +10,14 @@
|
||||
|
||||
- 結果概要に各レベルで検知した上位5つのルールを表示するようにした。 (#667) (@hitenkoku)
|
||||
- 結果概要を出力しないようにするために `--no-summary` オプションを追加した。 (#672) (@hitenkoku)
|
||||
- 結果概要の表示を短縮させた。 (#675 #678) (@hitenkoku)
|
||||
- channel_abbreviations.txtによるChannelフィールドのチェックを大文字小文字の区別をなくした。 (#685) (@hitenkoku)
|
||||
|
||||
**バグ修正:**
|
||||
|
||||
- ログオン情報の要約オプションを追加した場合に、Hayabusaがクラッシュしていたのを修正した。 (#674) (@hitenkoku)
|
||||
- configオプションで指定したルールコンフィグの読み込みができていない問題を修正した。 (#681) (@hitenkoku)
|
||||
- 結果概要のtotal eventsで読み込んだレコード数が出力されていたのを、検査対象にしているevtxファイルの実際のレコード数に修正した。 (#683) (@hitenkoku)
|
||||
|
||||
## v1.5.1 [2022/08/20]
|
||||
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
|
||||
- Added top alerts to results summary. (#667) (@hitenkoku)
|
||||
- Added `--no-summary` option to not display the results summary. (#672) (@hitenkoku)
|
||||
- Made the results summary more compact. (#675 #678) (@hitenkoku)
|
||||
- Made Channel field in channel_abbreviations.txt case-insensitive. (#685) (@hitenkoku)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Hayabusa would crash with `-L` option (logon summary option). (#674) (@hitenkoku)
|
||||
- Hayabusa would continue to scan without the correct config files but now will print and error and gracefully terminate. (#681) (@hitenkoku)
|
||||
- Fixed total events from the number of scanned events to actual events in evtx. (#683) (@hitenkoku)
|
||||
|
||||
## v1.5.1 [2022/08/20]
|
||||
|
||||
|
||||
Generated
+99
-12
@@ -254,6 +254,18 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85914173c2f558d61613bfbbf1911f14e630895087a7ed2fafc0f5319e1536e7"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.1"
|
||||
@@ -334,6 +346,31 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.6"
|
||||
@@ -533,7 +570,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"jemallocator",
|
||||
"log",
|
||||
"quick-xml",
|
||||
"quick-xml 0.23.0",
|
||||
"rayon",
|
||||
"rpmalloc",
|
||||
"serde",
|
||||
@@ -692,6 +729,7 @@ dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
"clap 3.2.17",
|
||||
"comfy-table",
|
||||
"crossbeam-utils",
|
||||
"csv",
|
||||
"dashmap",
|
||||
@@ -714,7 +752,7 @@ dependencies = [
|
||||
"openssl",
|
||||
"pbr",
|
||||
"prettytable-rs",
|
||||
"quick-xml",
|
||||
"quick-xml 0.24.0",
|
||||
"rand",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -1034,9 +1072,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
@@ -1377,6 +1415,15 @@ name = "quick-xml"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9279fbdacaad3baf559d8cabe0acc3d06e30ea14931af31af79578ac0946decc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678404d55890514fa1c01fe98cf280b674db93944fdcb70310dd3be1d0d63be7"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -1579,18 +1626,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1599,9 +1646,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"ryu",
|
||||
@@ -1623,6 +1670,27 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
@@ -1666,9 +1734,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
@@ -1750,6 +1818,25 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
|
||||
@@ -38,6 +38,7 @@ hyper = "0.14.*"
|
||||
lock_api = "0.4.*"
|
||||
crossbeam-utils = "0.8.*"
|
||||
num-format = "*"
|
||||
comfy-table = "6.*"
|
||||
|
||||
[build-dependencies]
|
||||
static_vcruntime = "2.*"
|
||||
|
||||
+1
-1
Submodule rules updated: c5110b27f6...ff5732fa17
Binary file not shown.
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 488 KiB |
+147
-69
@@ -9,12 +9,15 @@ use bytesize::ByteSize;
|
||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||
use core::cmp::max;
|
||||
use csv::{QuoteStyle, WriterBuilder};
|
||||
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
|
||||
use comfy_table::presets::UTF8_FULL;
|
||||
use itertools::Itertools;
|
||||
use krapslog::{build_sparkline, build_time_markers};
|
||||
use lazy_static::lazy_static;
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use comfy_table::*;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
use std::cmp::min;
|
||||
@@ -31,17 +34,27 @@ use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||
use terminal_size::Width;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref OUTPUT_COLOR: HashMap<String, Color> = set_output_color();
|
||||
pub static ref OUTPUT_COLOR: HashMap<String, Colors> = set_output_color();
|
||||
}
|
||||
|
||||
pub struct Colors {
|
||||
pub output_color: termcolor::Color,
|
||||
pub table_color: comfy_table::Color,
|
||||
}
|
||||
|
||||
/// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数
|
||||
pub fn set_output_color() -> HashMap<String, Color> {
|
||||
pub fn set_output_color() -> HashMap<String, Colors> {
|
||||
let read_result = utils::read_csv(
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/level_color.txt")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"config/level_color.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
let mut color_map: HashMap<String, Color> = HashMap::new();
|
||||
let mut color_map: HashMap<String, Colors> = HashMap::new();
|
||||
if configs::CONFIG.read().unwrap().args.no_color {
|
||||
return color_map;
|
||||
}
|
||||
@@ -71,16 +84,34 @@ pub fn set_output_color() -> HashMap<String, Color> {
|
||||
}
|
||||
color_map.insert(
|
||||
level.to_lowercase(),
|
||||
Color::Rgb(color_code[0], color_code[1], color_code[2]),
|
||||
Colors {
|
||||
output_color: termcolor::Color::Rgb(color_code[0], color_code[1], color_code[2]),
|
||||
table_color: comfy_table::Color::Rgb {
|
||||
r: color_code[0],
|
||||
g: color_code[1],
|
||||
b: color_code[2],
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
color_map
|
||||
}
|
||||
|
||||
fn _get_output_color(color_map: &HashMap<String, Color>, level: &str) -> Option<Color> {
|
||||
fn _get_output_color(color_map: &HashMap<String, Colors>, level: &str) -> Option<Color> {
|
||||
let mut color = None;
|
||||
if let Some(c) = color_map.get(&level.to_lowercase()) {
|
||||
color = Some(c.to_owned());
|
||||
color = Some(c.output_color.to_owned());
|
||||
}
|
||||
color
|
||||
}
|
||||
|
||||
fn _get_table_color(
|
||||
color_map: &HashMap<String, Colors>,
|
||||
level: &str,
|
||||
) -> Option<comfy_table::Color> {
|
||||
let mut color = None;
|
||||
if let Some(c) = color_map.get(&level.to_lowercase()) {
|
||||
color = Some(c.table_color.to_owned());
|
||||
}
|
||||
color
|
||||
}
|
||||
@@ -174,7 +205,7 @@ pub fn after_fact(all_record_cnt: usize) {
|
||||
fn emit_csv<W: std::io::Write>(
|
||||
writer: &mut W,
|
||||
displayflag: bool,
|
||||
color_map: HashMap<String, Color>,
|
||||
color_map: HashMap<String, Colors>,
|
||||
all_record_cnt: u128,
|
||||
profile: LinkedHashMap<String, String>,
|
||||
) -> io::Result<()> {
|
||||
@@ -396,17 +427,9 @@ fn emit_csv<W: std::io::Write>(
|
||||
&disp_wtr,
|
||||
get_writable_color(None),
|
||||
&format!(
|
||||
"Total events: {}",
|
||||
all_record_cnt.to_formatted_string(&Locale::en)
|
||||
),
|
||||
true,
|
||||
)
|
||||
.ok();
|
||||
write_color_buffer(
|
||||
&disp_wtr,
|
||||
get_writable_color(None),
|
||||
&format!(
|
||||
"Data reduction: {} events ({:.2}%)",
|
||||
"Saved alerts and events / Total events analyzed: {} / {} (Data reduction: {} events ({:.2}%))",
|
||||
(all_record_cnt - reducted_record_cnt).to_formatted_string(&Locale::en),
|
||||
all_record_cnt.to_formatted_string(&Locale::en),
|
||||
reducted_record_cnt.to_formatted_string(&Locale::en),
|
||||
reducted_percent
|
||||
),
|
||||
@@ -417,15 +440,8 @@ fn emit_csv<W: std::io::Write>(
|
||||
|
||||
_print_unique_results(
|
||||
total_detect_counts_by_level,
|
||||
"Total".to_string(),
|
||||
"detections".to_string(),
|
||||
&color_map,
|
||||
);
|
||||
println!();
|
||||
|
||||
_print_unique_results(
|
||||
unique_detect_counts_by_level,
|
||||
"Unique".to_string(),
|
||||
"Total | Unique".to_string(),
|
||||
"detections".to_string(),
|
||||
&color_map,
|
||||
);
|
||||
@@ -433,11 +449,13 @@ fn emit_csv<W: std::io::Write>(
|
||||
|
||||
_print_detection_summary_by_date(detect_counts_by_date_and_level, &color_map);
|
||||
println!();
|
||||
println!();
|
||||
|
||||
_print_detection_summary_by_computer(detect_counts_by_computer_and_level, &color_map);
|
||||
println!();
|
||||
|
||||
_print_detection_summary_by_rule(detect_counts_by_rule_and_level, &color_map);
|
||||
_print_detection_summary_tables(detect_counts_by_rule_and_level, &color_map);
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -491,26 +509,30 @@ fn _format_cellpos(colval: &str, column: ColPos) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// output info which unique detection count and all detection count information(devided by level and total) to stdout.
|
||||
/// output info which unique detection count and all detection count information(separated by level and total) to stdout.
|
||||
fn _print_unique_results(
|
||||
mut counts_by_level: Vec<u128>,
|
||||
mut unique_counts_by_level: Vec<u128>,
|
||||
head_word: String,
|
||||
tail_word: String,
|
||||
color_map: &HashMap<String, Color>,
|
||||
color_map: &HashMap<String, Colors>,
|
||||
) {
|
||||
// the order in which are registered and the order of levels to be displayed are reversed
|
||||
counts_by_level.reverse();
|
||||
unique_counts_by_level.reverse();
|
||||
|
||||
let total_count = counts_by_level.iter().sum::<u128>();
|
||||
let unique_total_count = unique_counts_by_level.iter().sum::<u128>();
|
||||
// output total results
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
None,
|
||||
&format!(
|
||||
"{} {}: {}",
|
||||
"{} {}: {} | {}",
|
||||
head_word,
|
||||
tail_word,
|
||||
total_count.to_formatted_string(&Locale::en),
|
||||
unique_total_count.to_formatted_string(&Locale::en)
|
||||
),
|
||||
true,
|
||||
)
|
||||
@@ -525,13 +547,20 @@ fn _print_unique_results(
|
||||
} else {
|
||||
(counts_by_level[i] as f64) / (total_count as f64) * 100.0
|
||||
};
|
||||
let unique_percent = if unique_total_count == 0 {
|
||||
0 as f64
|
||||
} else {
|
||||
(unique_counts_by_level[i] as f64) / (unique_total_count as f64) * 100.0
|
||||
};
|
||||
let output_raw_str = format!(
|
||||
"{} {} {}: {} ({:.2}%)",
|
||||
"{} {} {}: {} ({:.2}%) | {} ({:.2}%)",
|
||||
head_word,
|
||||
level_name,
|
||||
tail_word,
|
||||
counts_by_level[i].to_formatted_string(&Locale::en),
|
||||
percent
|
||||
percent,
|
||||
unique_counts_by_level[i].to_formatted_string(&Locale::en),
|
||||
unique_percent
|
||||
);
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
@@ -546,13 +575,15 @@ fn _print_unique_results(
|
||||
/// 各レベル毎で最も高い検知数を出した日付を出力する
|
||||
fn _print_detection_summary_by_date(
|
||||
detect_counts_by_date: HashMap<String, HashMap<String, u128>>,
|
||||
color_map: &HashMap<String, Color>,
|
||||
color_map: &HashMap<String, Colors>,
|
||||
) {
|
||||
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
|
||||
let mut wtr = buf_wtr.buffer();
|
||||
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
|
||||
|
||||
for level in LEVEL_ABBR.values() {
|
||||
writeln!(wtr, "Dates with most total detections:").ok();
|
||||
|
||||
for (idx, level) in LEVEL_ABBR.values().enumerate() {
|
||||
// output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施
|
||||
let detections_by_day = detect_counts_by_date.get(level).unwrap();
|
||||
let mut max_detect_str = String::default();
|
||||
@@ -573,13 +604,18 @@ fn _print_detection_summary_by_date(
|
||||
if !exist_max_data {
|
||||
max_detect_str = "n/a".to_string();
|
||||
}
|
||||
writeln!(
|
||||
write!(
|
||||
wtr,
|
||||
"Date with most total {} detections: {}",
|
||||
"{}: {}",
|
||||
LEVEL_FULL.get(level.as_str()).unwrap(),
|
||||
&max_detect_str
|
||||
)
|
||||
.ok();
|
||||
if idx != LEVEL_ABBR.len() - 1 {
|
||||
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
|
||||
|
||||
write!(wtr, ", ").ok();
|
||||
}
|
||||
}
|
||||
buf_wtr.print(&wtr).ok();
|
||||
}
|
||||
@@ -587,12 +623,13 @@ fn _print_detection_summary_by_date(
|
||||
/// 各レベル毎で最も高い検知数を出した日付を出力する
|
||||
fn _print_detection_summary_by_computer(
|
||||
detect_counts_by_computer: HashMap<String, HashMap<String, i128>>,
|
||||
color_map: &HashMap<String, Color>,
|
||||
color_map: &HashMap<String, Colors>,
|
||||
) {
|
||||
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
|
||||
let mut wtr = buf_wtr.buffer();
|
||||
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
|
||||
|
||||
writeln!(wtr, "Top 5 computers with most unique detections:").ok();
|
||||
for level in LEVEL_ABBR.values() {
|
||||
// output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施
|
||||
let detections_by_computer = detect_counts_by_computer.get(level).unwrap();
|
||||
@@ -625,7 +662,7 @@ fn _print_detection_summary_by_computer(
|
||||
.ok();
|
||||
writeln!(
|
||||
wtr,
|
||||
"Top 5 computers with most unique {} detections: {}",
|
||||
"{}: {}",
|
||||
LEVEL_FULL.get(level.as_str()).unwrap(),
|
||||
&result_str
|
||||
)
|
||||
@@ -634,53 +671,94 @@ fn _print_detection_summary_by_computer(
|
||||
buf_wtr.print(&wtr).ok();
|
||||
}
|
||||
|
||||
/// 各レベルごとで検出数が多かったルールのタイトルを出力する関数
|
||||
fn _print_detection_summary_by_rule(
|
||||
/// 各レベルごとで検出数が多かったルールを表形式で出力する関数
|
||||
fn _print_detection_summary_tables(
|
||||
detect_counts_by_rule_and_level: HashMap<String, HashMap<String, i128>>,
|
||||
color_map: &HashMap<String, Color>,
|
||||
color_map: &HashMap<String, Colors>,
|
||||
) {
|
||||
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
|
||||
let mut wtr = buf_wtr.buffer();
|
||||
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
|
||||
let level_cnt = detect_counts_by_rule_and_level.len();
|
||||
for (idx, level) in LEVEL_ABBR.values().enumerate() {
|
||||
let mut output = vec![];
|
||||
let mut col_color = vec![];
|
||||
for level in LEVEL_ABBR.values() {
|
||||
let mut col_output: Vec<String> = vec![];
|
||||
col_output.push(format!(
|
||||
"Top {} alerts:",
|
||||
LEVEL_FULL.get(level.as_str()).unwrap()
|
||||
));
|
||||
|
||||
col_color.push(_get_table_color(
|
||||
color_map,
|
||||
LEVEL_FULL.get(level.as_str()).unwrap(),
|
||||
));
|
||||
|
||||
// output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施
|
||||
let detections_by_computer = detect_counts_by_rule_and_level.get(level).unwrap();
|
||||
let mut result_vec: Vec<String> = Vec::new();
|
||||
let mut sorted_detections: Vec<(&String, &i128)> = detections_by_computer.iter().collect();
|
||||
|
||||
sorted_detections.sort_by(|a, b| (-a.1).cmp(&(-b.1)));
|
||||
|
||||
for x in sorted_detections.iter().take(5) {
|
||||
result_vec.push(format!(
|
||||
let take_cnt =
|
||||
if LEVEL_FULL.get(level.as_str()).unwrap_or(&"-".to_string()) == "informational" {
|
||||
10
|
||||
} else {
|
||||
5
|
||||
};
|
||||
for x in sorted_detections.iter().take(take_cnt) {
|
||||
col_output.push(format!(
|
||||
"{} ({})",
|
||||
x.0,
|
||||
x.1.to_formatted_string(&Locale::en)
|
||||
));
|
||||
}
|
||||
let result_str = if result_vec.is_empty() {
|
||||
"None".to_string()
|
||||
let na_cnt = if sorted_detections.len() > take_cnt {
|
||||
0
|
||||
} else {
|
||||
result_vec.join("\n")
|
||||
take_cnt - sorted_detections.len()
|
||||
};
|
||||
|
||||
wtr.set_color(ColorSpec::new().set_fg(_get_output_color(
|
||||
color_map,
|
||||
LEVEL_FULL.get(level.as_str()).unwrap(),
|
||||
)))
|
||||
.ok();
|
||||
writeln!(
|
||||
wtr,
|
||||
"Top {} alerts:\n{}",
|
||||
LEVEL_FULL.get(level.as_str()).unwrap(),
|
||||
&result_str
|
||||
)
|
||||
.ok();
|
||||
if idx != level_cnt - 1 {
|
||||
writeln!(wtr).ok();
|
||||
for _x in 0..na_cnt {
|
||||
col_output.push("n/a".to_string());
|
||||
}
|
||||
output.push(col_output);
|
||||
}
|
||||
buf_wtr.print(&wtr).ok();
|
||||
|
||||
let mut tb = Table::new();
|
||||
tb.load_preset(UTF8_FULL)
|
||||
.apply_modifier(UTF8_ROUND_CORNERS)
|
||||
.set_style(TableComponent::VerticalLines, ' ');
|
||||
for x in 0..output.len() / 2 {
|
||||
let hlch = tb.style(TableComponent::HorizontalLines).unwrap();
|
||||
let tbch = tb.style(TableComponent::TopBorder).unwrap();
|
||||
|
||||
tb.add_row(vec![
|
||||
Cell::new(&output[2 * x][0]).fg(col_color[2 * x].unwrap_or(comfy_table::Color::Reset)),
|
||||
Cell::new(&output[2 * x + 1][0])
|
||||
.fg(col_color[2 * x + 1].unwrap_or(comfy_table::Color::Reset)),
|
||||
])
|
||||
.set_style(TableComponent::MiddleIntersections, hlch)
|
||||
.set_style(TableComponent::TopBorderIntersections, tbch)
|
||||
.set_style(TableComponent::BottomBorderIntersections, hlch);
|
||||
|
||||
tb.add_row(vec![
|
||||
Cell::new(&output[2 * x][1..].join("\n"))
|
||||
.fg(col_color[2 * x].unwrap_or(comfy_table::Color::Reset)),
|
||||
Cell::new(&output[2 * x + 1][1..].join("\n"))
|
||||
.fg(col_color[2 * x + 1].unwrap_or(comfy_table::Color::Reset)),
|
||||
]);
|
||||
}
|
||||
|
||||
let odd_row = &output[4][1..6];
|
||||
let even_row = &output[4][6..11];
|
||||
tb.add_row(vec![
|
||||
Cell::new(&output[4][0]).fg(col_color[4].unwrap_or(comfy_table::Color::Reset)),
|
||||
Cell::new(""),
|
||||
]);
|
||||
tb.add_row(vec![
|
||||
Cell::new(odd_row.join("\n")).fg(col_color[4].unwrap_or(comfy_table::Color::Reset)),
|
||||
Cell::new(even_row.join("\n")).fg(col_color[4].unwrap_or(comfy_table::Color::Reset)),
|
||||
]);
|
||||
println!("{tb}");
|
||||
}
|
||||
|
||||
/// get timestamp to input datetime.
|
||||
@@ -947,7 +1025,7 @@ mod tests {
|
||||
(
|
||||
"%Channel%".to_owned(),
|
||||
mock_ch_filter
|
||||
.get("Security")
|
||||
.get(&"Security".to_ascii_lowercase())
|
||||
.unwrap_or(&String::default())
|
||||
.to_string(),
|
||||
),
|
||||
|
||||
+41
-18
@@ -23,10 +23,23 @@ lazy_static! {
|
||||
levelmap.insert("CRITICAL".to_owned(), 5);
|
||||
levelmap
|
||||
};
|
||||
pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias(&format!(
|
||||
"{}/eventkey_alias.txt",
|
||||
CONFIG.read().unwrap().args.config.as_path().display()
|
||||
));
|
||||
pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias(
|
||||
utils::check_setting_path(
|
||||
&CONFIG.read().unwrap().args.config,
|
||||
"eventkey_alias.txt",
|
||||
false
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/eventkey_alias.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
pub static ref IDS_REGEX: Regex =
|
||||
Regex::new(r"^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$").unwrap();
|
||||
pub static ref TERM_SIZE: Option<(Width, Height)> = terminal_size();
|
||||
@@ -52,7 +65,7 @@ impl Default for ConfigReader<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Parser, Clone)]
|
||||
#[clap(
|
||||
name = "Hayabusa",
|
||||
usage = "hayabusa.exe <INPUT> [OTHER-ACTIONS] [OPTIONS]",
|
||||
@@ -246,23 +259,33 @@ impl ConfigReader<'_> {
|
||||
.help_template("\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}");
|
||||
ConfigReader {
|
||||
app: build_cmd,
|
||||
args: parse,
|
||||
args: parse.clone(),
|
||||
headless_help: String::default(),
|
||||
event_timeline_config: load_eventcode_info(
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/statistics_event_info.txt",
|
||||
)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
utils::check_setting_path(&parse.config, "statistics_event_info.txt", false)
|
||||
.unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/statistics_event_info.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
),
|
||||
target_eventids: load_target_ids(
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/target_event_IDs.txt",
|
||||
)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
utils::check_setting_path(&parse.config, "target_event_IDs.txt", false)
|
||||
.unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/target_event_IDs.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,10 @@ impl Detection {
|
||||
"%Channel%" => {
|
||||
profile_converter.insert(
|
||||
"%Channel%".to_string(),
|
||||
CH_CONFIG.get(ch_str).unwrap_or(ch_str).to_string(),
|
||||
CH_CONFIG
|
||||
.get(&ch_str.to_ascii_lowercase())
|
||||
.unwrap_or(ch_str)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
"%Level%" => {
|
||||
|
||||
+21
-19
@@ -49,30 +49,32 @@ lazy_static! {
|
||||
pub static ref STATISTICS_FLAG: bool = configs::CONFIG.read().unwrap().args.statistics;
|
||||
pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary;
|
||||
pub static ref TAGS_CONFIG: HashMap<String, String> = create_output_filter_config(
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/mitre_tactics.txt")
|
||||
.to_str()
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/mitre_tactics.txt", true)
|
||||
.unwrap().to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
pub static ref CH_CONFIG: HashMap<String, String> = create_output_filter_config(
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/channel_abbreviations.txt"
|
||||
)
|
||||
utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "channel_abbreviations.txt", false).unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/channel_abbreviations.txt", true
|
||||
).unwrap()
|
||||
})
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
pub static ref PIVOT_KEYWORD_LIST_FLAG: bool =
|
||||
configs::CONFIG.read().unwrap().args.pivot_keywords_list;
|
||||
pub static ref DEFAULT_DETAILS: HashMap<String, String> = get_default_details(&format!(
|
||||
"{}/default_details.txt",
|
||||
configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.config
|
||||
.as_path()
|
||||
.display()
|
||||
));
|
||||
pub static ref DEFAULT_DETAILS: HashMap<String, String> = get_default_details(
|
||||
utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "default_details.txt", false).unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/default_details.txt", true
|
||||
).unwrap()
|
||||
})
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
pub static ref LEVEL_ABBR: LinkedHashMap<String, String> = LinkedHashMap::from_iter([
|
||||
("critical".to_string(), "crit".to_string()),
|
||||
("high".to_string(), "high".to_string()),
|
||||
@@ -103,10 +105,10 @@ pub fn create_output_filter_config(path: &str) -> HashMap<String, String> {
|
||||
return;
|
||||
}
|
||||
|
||||
let tag_full_str = line[0].trim();
|
||||
let tag_full_str = line[0].trim().to_ascii_lowercase();
|
||||
let tag_replace_str = line[1].trim();
|
||||
|
||||
ret.insert(tag_full_str.to_owned(), tag_replace_str.to_owned());
|
||||
ret.insert(tag_full_str, tag_replace_str.to_owned());
|
||||
});
|
||||
ret
|
||||
}
|
||||
@@ -597,7 +599,7 @@ mod tests {
|
||||
let actual = create_output_filter_config("test_files/config/channel_abbreviations.txt");
|
||||
let actual2 = create_output_filter_config("test_files/config/channel_abbreviations.txt");
|
||||
let expected: HashMap<String, String> = HashMap::from([
|
||||
("Security".to_string(), "Sec".to_string()),
|
||||
("Security".to_ascii_lowercase(), "Sec".to_string()),
|
||||
("xxx".to_string(), "yyy".to_string()),
|
||||
]);
|
||||
_check_hashmap_element(&expected, actual);
|
||||
|
||||
+62
-8
@@ -73,7 +73,8 @@ pub fn value_to_string(value: &Value) -> Option<String> {
|
||||
|
||||
pub fn read_txt(filename: &str) -> Result<Vec<String>, String> {
|
||||
let filepath = if filename.starts_with("./") {
|
||||
check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), filename)
|
||||
check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), filename, true)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
@@ -380,14 +381,59 @@ pub fn make_ascii_titlecase(s: &mut str) -> String {
|
||||
}
|
||||
|
||||
/// base_path/path が存在するかを確認し、存在しなければカレントディレクトリを参照するpathを返す関数
|
||||
pub fn check_setting_path(base_path: &Path, path: &str) -> PathBuf {
|
||||
pub fn check_setting_path(base_path: &Path, path: &str, ignore_err: bool) -> Option<PathBuf> {
|
||||
if base_path.join(path).exists() {
|
||||
base_path.join(path)
|
||||
Some(base_path.join(path))
|
||||
} else if ignore_err {
|
||||
Some(Path::new(path).to_path_buf())
|
||||
} else {
|
||||
Path::new(path).to_path_buf()
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// rule configのファイルの所在を確認する関数。
|
||||
pub fn check_rule_config() -> Result<(), String> {
|
||||
// rules/configのフォルダが存在するかを確認する
|
||||
let exist_rule_config_folder =
|
||||
if configs::CONFIG.read().unwrap().args.config == CURRENT_EXE_PATH.to_path_buf() {
|
||||
check_setting_path(
|
||||
&configs::CONFIG.read().unwrap().args.config,
|
||||
"rules/config",
|
||||
false,
|
||||
)
|
||||
.is_some()
|
||||
} else {
|
||||
check_setting_path(&configs::CONFIG.read().unwrap().args.config, "", false).is_some()
|
||||
};
|
||||
if !exist_rule_config_folder {
|
||||
return Err("The required rules config files were not found. Please download them with --update-rules".to_string());
|
||||
}
|
||||
|
||||
// 各種ファイルを確認する
|
||||
let files = vec![
|
||||
"channel_abbreviations.txt",
|
||||
"target_event_IDs.txt",
|
||||
"default_details.txt",
|
||||
"level_tuning.txt",
|
||||
"statistics_event_info.txt",
|
||||
"eventkey_alias.txt",
|
||||
];
|
||||
let mut not_exist_file = vec![];
|
||||
for file in &files {
|
||||
if check_setting_path(&configs::CONFIG.read().unwrap().args.config, file, false).is_none() {
|
||||
not_exist_file.push(*file);
|
||||
}
|
||||
}
|
||||
|
||||
if !not_exist_file.is_empty() {
|
||||
return Err(format!(
|
||||
"Could not find the following config files: {}\nPlease specify a correct rules config directory.\n",
|
||||
not_exist_file.join(", ")
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///タイムゾーンに合わせた情報を情報を取得する関数
|
||||
pub fn format_time(time: &DateTime<Utc>, date_only: bool) -> String {
|
||||
if configs::CONFIG.read().unwrap().args.utc {
|
||||
@@ -613,23 +659,31 @@ mod tests {
|
||||
let exist_path = Path::new("./test_files").to_path_buf();
|
||||
let not_exist_path = Path::new("not_exist_path").to_path_buf();
|
||||
assert_eq!(
|
||||
check_setting_path(¬_exist_path, "rules")
|
||||
check_setting_path(¬_exist_path, "rules", true)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"rules"
|
||||
);
|
||||
assert_eq!(
|
||||
check_setting_path(¬_exist_path, "fake")
|
||||
check_setting_path(¬_exist_path, "fake", true)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"fake"
|
||||
);
|
||||
assert_eq!(
|
||||
check_setting_path(&exist_path, "rules").to_str().unwrap(),
|
||||
check_setting_path(&exist_path, "rules", true)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
exist_path.join("rules").to_str().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
check_setting_path(&exist_path, "fake").to_str().unwrap(),
|
||||
check_setting_path(&exist_path, "fake", true)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"fake"
|
||||
);
|
||||
}
|
||||
|
||||
+32
-13
@@ -7,8 +7,8 @@ use bytesize::ByteSize;
|
||||
use chrono::{DateTime, Datelike, Local};
|
||||
use evtx::{EvtxParser, ParserSettings};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use hayabusa::detections::configs::CURRENT_EXE_PATH;
|
||||
use hayabusa::detections::configs::{load_pivot_keywords, TargetEventTime, TARGET_EXTENSIONS};
|
||||
use hayabusa::detections::configs::{CONFIG, CURRENT_EXE_PATH};
|
||||
use hayabusa::detections::detection::{self, EvtxRecordInfo};
|
||||
use hayabusa::detections::message::{
|
||||
AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG,
|
||||
@@ -80,7 +80,9 @@ impl App {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"config/pivot_keywords.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -148,13 +150,19 @@ impl App {
|
||||
// カレントディレクトリ以外からの実行の際にrules-configオプションの指定がないとエラーが発生することを防ぐための処理
|
||||
if configs::CONFIG.read().unwrap().args.config == Path::new("./rules/config") {
|
||||
configs::CONFIG.write().unwrap().args.config =
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "./rules/config");
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules/config", true)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// カレントディレクトリ以外からの実行の際にrulesオプションの指定がないとエラーが発生することを防ぐための処理
|
||||
if configs::CONFIG.read().unwrap().args.rules == Path::new("./rules") {
|
||||
configs::CONFIG.write().unwrap().args.rules =
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "./rules");
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules", true).unwrap();
|
||||
}
|
||||
// rule configのフォルダ、ファイルを確認してエラーがあった場合は終了とする
|
||||
if let Err(e) = utils::check_rule_config() {
|
||||
AlertMessage::alert(&e).ok();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output {
|
||||
@@ -264,9 +272,18 @@ impl App {
|
||||
let level_tuning_config_path = match level_tuning_val {
|
||||
Some(path) => path.to_owned(),
|
||||
_ => utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"./rules/config/level_tuning.txt",
|
||||
&CONFIG.read().unwrap().args.config,
|
||||
"level_tuning.txt",
|
||||
false,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"rules/config/level_tuning.txt",
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.display()
|
||||
.to_string(),
|
||||
};
|
||||
@@ -469,10 +486,10 @@ impl App {
|
||||
}
|
||||
|
||||
fn print_contributors(&self) {
|
||||
match fs::read_to_string(utils::check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"contributors.txt",
|
||||
)) {
|
||||
match fs::read_to_string(
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "contributors.txt", true)
|
||||
.unwrap(),
|
||||
) {
|
||||
Ok(contents) => {
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
@@ -570,6 +587,7 @@ impl App {
|
||||
let mut tl = Timeline::new();
|
||||
let mut parser = parser.unwrap();
|
||||
let mut records = parser.records_json_value();
|
||||
|
||||
loop {
|
||||
let mut records_per_detect = vec![];
|
||||
while records_per_detect.len() < MAX_DETECT_RECORDS {
|
||||
@@ -578,6 +596,7 @@ impl App {
|
||||
if next_rec.is_none() {
|
||||
break;
|
||||
}
|
||||
record_cnt += 1;
|
||||
|
||||
let record_result = next_rec.unwrap();
|
||||
if record_result.is_err() {
|
||||
@@ -619,8 +638,6 @@ impl App {
|
||||
break;
|
||||
}
|
||||
|
||||
record_cnt += records_per_detect.len();
|
||||
|
||||
let records_per_detect = self.rt.block_on(App::create_rec_infos(
|
||||
records_per_detect,
|
||||
&path,
|
||||
@@ -721,7 +738,8 @@ impl App {
|
||||
|
||||
/// output logo
|
||||
fn output_logo(&self) {
|
||||
let fp = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "art/logo.txt");
|
||||
let fp = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "art/logo.txt", true)
|
||||
.unwrap();
|
||||
let content = fs::read_to_string(fp).unwrap_or_default();
|
||||
let output_color = if configs::CONFIG.read().unwrap().args.no_color {
|
||||
None
|
||||
@@ -748,7 +766,8 @@ impl App {
|
||||
match eggs.get(exec_datestr) {
|
||||
None => {}
|
||||
Some(path) => {
|
||||
let egg_path = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), path);
|
||||
let egg_path =
|
||||
utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), path, true).unwrap();
|
||||
let content = fs::read_to_string(egg_path).unwrap_or_default();
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
|
||||
+11
-4
@@ -15,13 +15,20 @@ lazy_static! {
|
||||
pub static ref PROFILES: Option<LinkedHashMap<String, String>> = load_profile(
|
||||
check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"config/default_profile.yaml"
|
||||
"config/default_profile.yaml",
|
||||
true
|
||||
)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/profiles.yaml")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
check_setting_path(
|
||||
&CURRENT_EXE_PATH.to_path_buf(),
|
||||
"config/profiles.yaml",
|
||||
true
|
||||
)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
pub static ref LOAEDED_PROFILE_ALIAS: HashSet<String> = HashSet::from_iter(
|
||||
PROFILES
|
||||
|
||||
Reference in New Issue
Block a user