cargo fmt

This commit is contained in:
DastInDark
2022-07-24 19:39:00 +09:00
parent 86657ec6ac
commit a7033c4289
5 changed files with 154 additions and 74 deletions

View File

@@ -1,29 +1,29 @@
use crate::detections::configs; use crate::detections::configs;
use crate::detections::configs::{CURRENT_EXE_PATH, TERM_SIZE}; use crate::detections::configs::{CURRENT_EXE_PATH, TERM_SIZE};
use crate::detections::message::{self, LEVEL_ABBR};
use crate::detections::message::AlertMessage; use crate::detections::message::AlertMessage;
use crate::detections::message::{self, LEVEL_ABBR};
use crate::detections::utils::{self, format_time}; use crate::detections::utils::{self, format_time};
use crate::detections::utils::{get_writable_color, write_color_buffer}; use crate::detections::utils::{get_writable_color, write_color_buffer};
use crate::options::profile::PROFILES; use crate::options::profile::PROFILES;
use bytesize::ByteSize; use bytesize::ByteSize;
use chrono::{DateTime, Local, TimeZone, Utc}; use chrono::{DateTime, Local, TimeZone, Utc};
use csv::{QuoteStyle, Writer}; use csv::{QuoteStyle, Writer};
use linked_hash_map::LinkedHashMap;
use std::collections::{HashMap, HashSet, BTreeMap};
use itertools::Itertools; use itertools::Itertools;
use krapslog::{build_sparkline, build_time_markers}; use krapslog::{build_sparkline, build_time_markers};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use linked_hash_map::LinkedHashMap;
use serde::Serialize; use serde::Serialize;
use std::cmp::min; use std::cmp::min;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::error::Error; use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
use std::{fs, collections};
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::BufWriter; use std::io::BufWriter;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
use std::{collections, fs};
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use terminal_size::Width; use terminal_size::Width;
@@ -184,14 +184,7 @@ fn emit_csv<W: std::io::Write>(
let mut detect_counts_by_computer_and_level: HashMap<String, HashMap<String, i128>> = let mut detect_counts_by_computer_and_level: HashMap<String, HashMap<String, i128>> =
HashMap::new(); HashMap::new();
let levels = Vec::from([ let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]);
"crit",
"high",
"med ",
"low ",
"info",
"undefined",
]);
// レベル別、日ごとの集計用変数の初期化 // レベル別、日ごとの集計用変数の初期化
for level_init in levels { for level_init in levels {
detect_counts_by_date_and_level.insert(level_init.to_string(), HashMap::new()); detect_counts_by_date_and_level.insert(level_init.to_string(), HashMap::new());
@@ -223,7 +216,12 @@ fn emit_csv<W: std::io::Write>(
} }
write_color_buffer( write_color_buffer(
&disp_wtr, &disp_wtr,
get_writable_color(_get_output_color(&color_map, LEVEL_ABBR.get(&detect_info.level).unwrap_or(&String::default()))), get_writable_color(_get_output_color(
&color_map,
LEVEL_ABBR
.get(&detect_info.level)
.unwrap_or(&String::default()),
)),
&_get_serialized_disp_output(detect_info.ext_field.clone(), false), &_get_serialized_disp_output(detect_info.ext_field.clone(), false),
false, false,
) )
@@ -381,16 +379,16 @@ enum ColPos {
fn _get_serialized_disp_output(mut data: LinkedHashMap<String, String>, header: bool) -> String { fn _get_serialized_disp_output(mut data: LinkedHashMap<String, String>, header: bool) -> String {
let data_length = &data.len(); let data_length = &data.len();
let entries = data.entries(); let entries = data.entries();
let mut ret:Vec<String> = vec![]; let mut ret: Vec<String> = vec![];
if header { if header {
entries.for_each(|entry|{ entries.for_each(|entry| {
ret.push(entry.key().to_owned()); ret.push(entry.key().to_owned());
}); });
} else { } else {
entries.enumerate().for_each(|(i, entry)|{ entries.enumerate().for_each(|(i, entry)| {
if i == 0 { if i == 0 {
ret.push(_format_cellpos(entry.get(), ColPos::First)) ret.push(_format_cellpos(entry.get(), ColPos::First))
} else if i == data_length - 1{ } else if i == data_length - 1 {
ret.push(_format_cellpos(entry.get(), ColPos::Last)) ret.push(_format_cellpos(entry.get(), ColPos::Last))
} else { } else {
ret.push(_format_cellpos(entry.get(), ColPos::Other)) ret.push(_format_cellpos(entry.get(), ColPos::Other))
@@ -499,7 +497,10 @@ fn _print_detection_summary_by_date(
tmp_cnt = *cnt; tmp_cnt = *cnt;
} }
} }
wtr.set_color(ColorSpec::new().set_fg(_get_output_color(color_map, level_full_map.get(level).unwrap()))) wtr.set_color(ColorSpec::new().set_fg(_get_output_color(
color_map,
level_full_map.get(level).unwrap(),
)))
.ok(); .ok();
if date_str == String::default() { if date_str == String::default() {
max_detect_str = "n/a".to_string(); max_detect_str = "n/a".to_string();
@@ -507,7 +508,8 @@ fn _print_detection_summary_by_date(
writeln!( writeln!(
wtr, wtr,
"Date with most total {} detections: {}", "Date with most total {} detections: {}",
level_full_map.get(level).unwrap(), &max_detect_str level_full_map.get(level).unwrap(),
&max_detect_str
) )
.ok(); .ok();
} }
@@ -553,12 +555,16 @@ fn _print_detection_summary_by_computer(
result_vec.join(", ") result_vec.join(", ")
}; };
wtr.set_color(ColorSpec::new().set_fg(_get_output_color(color_map, level_full_map.get(level).unwrap()))) wtr.set_color(ColorSpec::new().set_fg(_get_output_color(
color_map,
level_full_map.get(level).unwrap(),
)))
.ok(); .ok();
writeln!( writeln!(
wtr, wtr,
"Top 5 computers with most unique {} detections: {}", "Top 5 computers with most unique {} detections: {}",
level_full_map.get(level).unwrap(), &result_str level_full_map.get(level).unwrap(),
&result_str
) )
.ok(); .ok();
} }
@@ -585,8 +591,8 @@ mod tests {
use crate::options::profile::load_profile; use crate::options::profile::load_profile;
use chrono::{Local, TimeZone, Utc}; use chrono::{Local, TimeZone, Utc};
use linked_hash_map::LinkedHashMap; use linked_hash_map::LinkedHashMap;
use std::collections::HashMap;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::fs::{read_to_string, remove_file}; use std::fs::{read_to_string, remove_file};
use std::io; use std::io;
@@ -609,7 +615,11 @@ mod tests {
let test_attack = "execution/txxxx.yyy"; let test_attack = "execution/txxxx.yyy";
let test_recinfo = "record_infoinfo11"; let test_recinfo = "record_infoinfo11";
let test_record_id = "11111"; let test_record_id = "11111";
let output_profile: LinkedHashMap<String, String> = load_profile("test_files/config/default_profile.txt", "test_files/config/profiles.txt").unwrap(); let output_profile: LinkedHashMap<String, String> = load_profile(
"test_files/config/default_profile.txt",
"test_files/config/profiles.txt",
)
.unwrap();
{ {
let messages = &message::MESSAGES; let messages = &message::MESSAGES;
messages.clear(); messages.clear();
@@ -695,7 +705,6 @@ mod tests {
} }
}; };
assert!(remove_file("./test_emit_csv.csv").is_ok()); assert!(remove_file("./test_emit_csv.csv").is_ok());
} }
#[test] #[test]
@@ -736,7 +745,7 @@ mod tests {
+ " | " + " | "
+ test_recinfo + test_recinfo
+ "\n"; + "\n";
let mut data:LinkedHashMap<String,String> = LinkedHashMap::new(); let mut data: LinkedHashMap<String, String> = LinkedHashMap::new();
data.insert("Timestamp".to_owned(), format_time(&test_timestamp, false)); data.insert("Timestamp".to_owned(), format_time(&test_timestamp, false));
data.insert("Computer".to_owned(), test_computername.to_owned()); data.insert("Computer".to_owned(), test_computername.to_owned());
data.insert("Channel".to_owned(), test_channel.to_owned()); data.insert("Channel".to_owned(), test_channel.to_owned());
@@ -747,7 +756,10 @@ mod tests {
data.insert("Details".to_owned(), output.to_owned()); data.insert("Details".to_owned(), output.to_owned());
data.insert("RecordInformation".to_owned(), test_recinfo.to_owned()); data.insert("RecordInformation".to_owned(), test_recinfo.to_owned());
assert_eq!(_get_serialized_disp_output(data.clone(), true), expect_header); assert_eq!(
_get_serialized_disp_output(data.clone(), true),
expect_header
);
assert_eq!( assert_eq!(
_get_serialized_disp_output(data.clone(), false), _get_serialized_disp_output(data.clone(), false),
expect_no_header expect_no_header

View File

@@ -209,9 +209,16 @@ impl Detection {
/// 条件に合致したレコードを格納するための関数 /// 条件に合致したレコードを格納するための関数
fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) { fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) {
let profile_all_alias = if PROFILES.is_some() { let profile_all_alias = if PROFILES.is_some() {
PROFILES.as_ref().unwrap().values().cloned().collect::<Vec<_>>().join("|") PROFILES
} .as_ref()
else{String::default()}; .unwrap()
.values()
.cloned()
.collect::<Vec<_>>()
.join("|")
} else {
String::default()
};
let tag_info: Vec<String> = match TAGS_CONFIG.is_empty() { let tag_info: Vec<String> = match TAGS_CONFIG.is_empty() {
false => rule.yaml["tags"] false => rule.yaml["tags"]
.as_vec() .as_vec()
@@ -262,7 +269,7 @@ impl Detection {
} else { } else {
None None
}; };
let level= rule.yaml["level"].as_str().unwrap_or("-").to_string(); let level = rule.yaml["level"].as_str().unwrap_or("-").to_string();
let detect_info = DetectInfo { let detect_info = DetectInfo {
filepath: record_info.evtx_filepath.to_string(), filepath: record_info.evtx_filepath.to_string(),
rulepath: (&rule.rulepath).to_owned(), rulepath: (&rule.rulepath).to_owned(),

View File

@@ -6,11 +6,11 @@ use crate::detections::utils::get_serde_number_to_string;
use crate::detections::utils::write_color_buffer; use crate::detections::utils::write_color_buffer;
use chrono::{DateTime, Local, TimeZone, Utc}; use chrono::{DateTime, Local, TimeZone, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use linked_hash_map::LinkedHashMap;
use std::collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use linked_hash_map::LinkedHashMap;
use regex::Regex; use regex::Regex;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap;
use std::env; use std::env;
use std::fs::create_dir; use std::fs::create_dir;
use std::fs::File; use std::fs::File;
@@ -135,7 +135,8 @@ pub fn insert_message(detect_info: DetectInfo, event_time: DateTime<Utc>) {
/// メッセージを設定 /// メッセージを設定
pub fn insert(event_record: &Value, output: String, mut detect_info: DetectInfo) { pub fn insert(event_record: &Value, output: String, mut detect_info: DetectInfo) {
let parsed_detail =parse_message(event_record, output).chars() let parsed_detail = parse_message(event_record, output)
.chars()
.filter(|&c| !c.is_control()) .filter(|&c| !c.is_control())
.collect::<String>(); .collect::<String>();
@@ -149,29 +150,43 @@ pub fn insert(event_record: &Value, output: String, mut detect_info: DetectInfo)
let time = get_event_time(event_record).unwrap_or(default_time); let time = get_event_time(event_record).unwrap_or(default_time);
for (k, v) in detect_info.ext_field.clone() { for (k, v) in detect_info.ext_field.clone() {
let converted_reserve_info = convert_profile_reserved_info(v, detect_info.clone(), time); let converted_reserve_info = convert_profile_reserved_info(v, detect_info.clone(), time);
detect_info.ext_field.insert(k, parse_message(event_record, converted_reserve_info)); detect_info
.ext_field
.insert(k, parse_message(event_record, converted_reserve_info));
} }
insert_message(detect_info, time) insert_message(detect_info, time)
} }
/// profileで用いられる予約語の情報を変換する関数 /// profileで用いられる予約語の情報を変換する関数
fn convert_profile_reserved_info (output:String, detect_info: DetectInfo, time: DateTime<Utc>) -> String { fn convert_profile_reserved_info(
let config_reserved_info:HashMap<String, String> = HashMap::from([ output: String,
("Timestamp".to_string(), format_time(&time,false)), detect_info: DetectInfo,
time: DateTime<Utc>,
) -> String {
let config_reserved_info: HashMap<String, String> = HashMap::from([
("Timestamp".to_string(), format_time(&time, false)),
("Computer".to_string(), detect_info.computername), ("Computer".to_string(), detect_info.computername),
("Channel".to_string(), detect_info.channel), ("Channel".to_string(), detect_info.channel),
("Level".to_string(), detect_info.level), ("Level".to_string(), detect_info.level),
("EventID".to_string(), detect_info.eventid), ("EventID".to_string(), detect_info.eventid),
("MitreAttack".to_string(), detect_info.tag_info), ("MitreAttack".to_string(), detect_info.tag_info),
("RecordID".to_string(), detect_info.record_id.unwrap_or_else(|| "-".to_string())), (
"RecordID".to_string(),
detect_info.record_id.unwrap_or_else(|| "-".to_string()),
),
("RuleTitle".to_string(), detect_info.alert), ("RuleTitle".to_string(), detect_info.alert),
("Details".to_string(), detect_info.detail), ("Details".to_string(), detect_info.detail),
("RecordInformation".to_string(), detect_info.record_information.unwrap_or_else(|| "-".to_string())), (
"RecordInformation".to_string(),
detect_info
.record_information
.unwrap_or_else(|| "-".to_string()),
),
("RuleFile".to_string(), detect_info.rulepath), ("RuleFile".to_string(), detect_info.rulepath),
("EvtxFile".to_string(), detect_info.filepath), ("EvtxFile".to_string(), detect_info.filepath),
]); ]);
let mut ret = output; let mut ret = output;
let mut convert_target:HashMap<String, String> = HashMap::new(); let mut convert_target: HashMap<String, String> = HashMap::new();
for caps in ALIASREGEX.captures_iter(&ret) { for caps in ALIASREGEX.captures_iter(&ret) {
let full_target_str = &caps[0]; let full_target_str = &caps[0];
let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent
@@ -385,8 +400,8 @@ impl AlertMessage {
mod tests { mod tests {
use crate::detections::message::AlertMessage; use crate::detections::message::AlertMessage;
use crate::detections::message::{parse_message, MESSAGES}; use crate::detections::message::{parse_message, MESSAGES};
use std::collections::HashMap;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap;
use super::{create_output_filter_config, get_default_details}; use super::{create_output_filter_config, get_default_details};

View File

@@ -21,7 +21,7 @@ use hayabusa::detections::pivot::PivotKeyword;
use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::pivot::PIVOT_KEYWORD;
use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::detections::rule::{get_detection_keys, RuleNode};
use hayabusa::omikuji::Omikuji; use hayabusa::omikuji::Omikuji;
use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules, profile::PROFILES}; use hayabusa::options::{level_tuning::LevelTuning, profile::PROFILES, update_rules::UpdateRules};
use hayabusa::{afterfact::after_fact, detections::utils}; use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timelines::Timeline}; use hayabusa::{detections::configs, timeline::timelines::Timeline};
use hayabusa::{detections::utils::write_color_buffer, filter}; use hayabusa::{detections::utils::write_color_buffer, filter};

View File

@@ -2,8 +2,8 @@ use crate::detections::configs::{self, CURRENT_EXE_PATH};
use crate::detections::message::AlertMessage; use crate::detections::message::AlertMessage;
use crate::detections::utils::check_setting_path; use crate::detections::utils::check_setting_path;
use crate::yaml; use crate::yaml;
use linked_hash_map::LinkedHashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use linked_hash_map::LinkedHashMap;
use std::fs::File; use std::fs::File;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
use std::path::Path; use std::path::Path;
@@ -17,10 +17,7 @@ lazy_static! {
) )
.to_str() .to_str()
.unwrap(), .unwrap(),
check_setting_path( check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/profiles.txt")
&CURRENT_EXE_PATH.to_path_buf(),
"config/profiles.txt"
)
.to_str() .to_str()
.unwrap() .unwrap()
); );
@@ -32,7 +29,7 @@ fn read_profile_data(profile_path: &str) -> Result<Vec<Yaml>, String> {
if let Ok(loaded_profile) = yml.read_file(Path::new(profile_path).to_path_buf()) { if let Ok(loaded_profile) = yml.read_file(Path::new(profile_path).to_path_buf()) {
match YamlLoader::load_from_str(&loaded_profile) { match YamlLoader::load_from_str(&loaded_profile) {
Ok(profile_yml) => Ok(profile_yml), Ok(profile_yml) => Ok(profile_yml),
Err(e) => Err(format!("Parse error: {}. {}", profile_path, e)) Err(e) => Err(format!("Parse error: {}. {}", profile_path, e)),
} }
} else { } else {
Err(format!( Err(format!(
@@ -74,8 +71,16 @@ pub fn load_profile(
let mut ret: LinkedHashMap<String, String> = LinkedHashMap::new(); let mut ret: LinkedHashMap<String, String> = LinkedHashMap::new();
if let Some(profile_name) = &conf.profile { if let Some(profile_name) = &conf.profile {
if !profile_data[profile_name.as_str()].is_badvalue() { if !profile_data[profile_name.as_str()].is_badvalue() {
profile_data[profile_name.as_str()].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| { profile_data[profile_name.as_str()]
ret.insert(k.as_str().unwrap().to_string(), v.as_str().unwrap().to_string()); .clone()
.as_hash()
.unwrap()
.into_iter()
.for_each(|(k, v)| {
ret.insert(
k.as_str().unwrap().to_string(),
v.as_str().unwrap().to_string(),
);
}); });
Some(ret) Some(ret)
} else { } else {
@@ -83,8 +88,16 @@ pub fn load_profile(
None None
} }
} else { } else {
profile_all[0].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| { profile_all[0]
ret.insert(k.as_str().unwrap().to_string(), v.as_str().unwrap().to_string()); .clone()
.as_hash()
.unwrap()
.into_iter()
.for_each(|(k, v)| {
ret.insert(
k.as_str().unwrap().to_string(),
v.as_str().unwrap().to_string(),
);
}); });
Some(ret) Some(ret)
} }
@@ -141,8 +154,8 @@ pub fn set_default_profile(default_profile_path: &str, profile_path: &str) -> Re
mod tests { mod tests {
use linked_hash_map::LinkedHashMap; use linked_hash_map::LinkedHashMap;
use crate::options::profile::load_profile;
use crate::detections::configs; use crate::detections::configs;
use crate::options::profile::load_profile;
#[test] #[test]
///オプションの設定が入ると値の冪等性が担保できないためテストを逐次的に処理する ///オプションの設定が入ると値の冪等性が担保できないためテストを逐次的に処理する
@@ -165,12 +178,21 @@ mod tests {
expect.insert("RecordID".to_owned(), "%RecordID%".to_owned()); expect.insert("RecordID".to_owned(), "%RecordID%".to_owned());
expect.insert("RuleTitle".to_owned(), "%RuleTitle%".to_owned()); expect.insert("RuleTitle".to_owned(), "%RuleTitle%".to_owned());
expect.insert("Details".to_owned(), "%Details%".to_owned()); expect.insert("Details".to_owned(), "%Details%".to_owned());
expect.insert("RecordInformation".to_owned(), "%RecordInformation%".to_owned()); expect.insert(
"RecordInformation".to_owned(),
"%RecordInformation%".to_owned(),
);
expect.insert("RuleFile".to_owned(), "%RuleFile%".to_owned()); expect.insert("RuleFile".to_owned(), "%RuleFile%".to_owned());
expect.insert("EvtxFile".to_owned(), "%EvtxFile%".to_owned()); expect.insert("EvtxFile".to_owned(), "%EvtxFile%".to_owned());
expect.insert("Tags".to_owned(), "%MitreAttack%".to_owned()); expect.insert("Tags".to_owned(), "%MitreAttack%".to_owned());
assert_eq!(Some(expect), load_profile("test_files/config/default_profile.txt", "test_files/config/profiles.txt")); assert_eq!(
Some(expect),
load_profile(
"test_files/config/default_profile.txt",
"test_files/config/profiles.txt"
)
);
} }
/// プロファイルオプションが設定されて`おり、そのオプションに該当するプロファイルが存在する場合のテスト /// プロファイルオプションが設定されて`おり、そのオプションに該当するプロファイルが存在する場合のテスト
@@ -185,7 +207,13 @@ mod tests {
expect.insert("RuleTitle".to_owned(), "%RuleTitle%".to_owned()); expect.insert("RuleTitle".to_owned(), "%RuleTitle%".to_owned());
expect.insert("Details".to_owned(), "%Details%".to_owned()); expect.insert("Details".to_owned(), "%Details%".to_owned());
assert_eq!(Some(expect), load_profile("test_files/config/default_profile.txt", "test_files/config/profiles.txt")); assert_eq!(
Some(expect),
load_profile(
"test_files/config/default_profile.txt",
"test_files/config/profiles.txt"
)
);
} }
/// プロファイルオプションが設定されているが、対象のオプションが存在しない場合のテスト /// プロファイルオプションが設定されているが、対象のオプションが存在しない場合のテスト
@@ -193,12 +221,30 @@ mod tests {
configs::CONFIG.write().unwrap().args.profile = Some("not_exist".to_string()); configs::CONFIG.write().unwrap().args.profile = Some("not_exist".to_string());
//両方のファイルが存在しない場合 //両方のファイルが存在しない場合
assert_eq!(None, load_profile("test_files/config/no_exist_default_profile.txt", "test_files/config/no_exist_profiles.txt")); assert_eq!(
None,
load_profile(
"test_files/config/no_exist_default_profile.txt",
"test_files/config/no_exist_profiles.txt"
)
);
//デフォルトプロファイルは存在しているがprofileオプションが指定されているため読み込み失敗の場合 //デフォルトプロファイルは存在しているがprofileオプションが指定されているため読み込み失敗の場合
assert_eq!(None, load_profile("test_files/config/profile/default_profile.txt", "test_files/config/profile/no_exist_profiles.txt")); assert_eq!(
None,
load_profile(
"test_files/config/profile/default_profile.txt",
"test_files/config/profile/no_exist_profiles.txt"
)
);
//オプション先のターゲットのプロファイルファイルが存在しているが、profileオプションで指定されたオプションが存在しない場合 //オプション先のターゲットのプロファイルファイルが存在しているが、profileオプションで指定されたオプションが存在しない場合
assert_eq!(None, load_profile("test_files/config/no_exist_default_profile.txt", "test_files/config/profiles.txt")); assert_eq!(
None,
load_profile(
"test_files/config/no_exist_default_profile.txt",
"test_files/config/profiles.txt"
)
);
} }
} }