Merge branch 'main' into 583-enhancement-output-processing-rules-msg-and-rule-status-metrics

This commit is contained in:
DustInDark
2022-06-11 15:29:14 +09:00
committed by GitHub
9 changed files with 69 additions and 33 deletions

View File

@@ -12,6 +12,7 @@
- LinuxとmacOSのバイナリサイズをより小さくするために、デバッグシンボルをストリップします。(#568) (@YamatoSecurity) - LinuxとmacOSのバイナリサイズをより小さくするために、デバッグシンボルをストリップします。(#568) (@YamatoSecurity)
- 新たな時刻表示のオプションとして`--US-time``--US-military-time``--European-time`の3つを追加した (#574) (@hitenkoku) - 新たな時刻表示のオプションとして`--US-time``--US-military-time``--European-time`の3つを追加した (#574) (@hitenkoku)
- `--rfc-3339` オプションの時刻表示形式を変更した。 (#574) (@hitenkoku) - `--rfc-3339` オプションの時刻表示形式を変更した。 (#574) (@hitenkoku)
- `-R/ --display-record-id`オプションを`-R/ --hide-record-id`に変更。レコードIDはデフォルトで出力するようにして`-R`オプションを付けた際に表示しないように変更した。(#579) (@hitenkoku)
- ルール読み込み時のメッセージを追加した。 (#583) (@hitenkoku) - ルール読み込み時のメッセージを追加した。 (#583) (@hitenkoku)
**バグ修正:** **バグ修正:**

View File

@@ -12,6 +12,7 @@
- Strip debug symbols by default for smaller Linux and macOS binaries. (#568) (@YamatoSecurity) - Strip debug symbols by default for smaller Linux and macOS binaries. (#568) (@YamatoSecurity)
- Added new output time format options. (`--US-time`, `--US-military-time`, `--European-time`) (#574) (@hitenkoku) - Added new output time format options. (`--US-time`, `--US-military-time`, `--European-time`) (#574) (@hitenkoku)
- Changed output time format when `--rfc-3339` option is enabled. (#574) (@hitenkoku) - Changed output time format when `--rfc-3339` option is enabled. (#574) (@hitenkoku)
- Changed the `-R / --display-record-id` option to `-R / --hide-record-id` and now by default the event record ID is displayed. You can hide the record ID with `-R / --hide-record-id`. (#579) (@hitenkoku)
- Added rule loading message. (#583) (@hitenkoku) - Added rule loading message. (#583) (@hitenkoku)
**Bug Fixes:** **Bug Fixes:**

View File

@@ -61,6 +61,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/)
- [ログオン情報の要約](#ログオン情報の要約) - [ログオン情報の要約](#ログオン情報の要約)
- [サンプルevtxファイルでHayabusaをテストする](#サンプルevtxファイルでhayabusaをテストする) - [サンプルevtxファイルでHayabusaをテストする](#サンプルevtxファイルでhayabusaをテストする)
- [Hayabusaの出力](#hayabusaの出力) - [Hayabusaの出力](#hayabusaの出力)
- [Levelの省略](#levelの省略)
- [MITRE ATT&CK戦術の省略](#mitre-attck戦術の省略) - [MITRE ATT&CK戦術の省略](#mitre-attck戦術の省略)
- [Channel情報の省略](#channel情報の省略) - [Channel情報の省略](#channel情報の省略)
- [プログレスバー](#プログレスバー) - [プログレスバー](#プログレスバー)
@@ -329,7 +330,7 @@ USAGE:
-C, --config [RULE_CONFIG_DIRECTORY] 'ルールフォルダのコンフィグディレクトリ(デフォルト: .\rules\config)' -C, --config [RULE_CONFIG_DIRECTORY] 'ルールフォルダのコンフィグディレクトリ(デフォルト: .\rules\config)'
-o, --output [CSV_TIMELINE] 'タイムラインをCSV形式で保存する。(例: results.csv)' -o, --output [CSV_TIMELINE] 'タイムラインをCSV形式で保存する。(例: results.csv)'
--all-tags '出力したCSVファイルにルール内のタグ情報を全て出力する。' --all-tags '出力したCSVファイルにルール内のタグ情報を全て出力する。'
-R, --display-record-id 'イベントレコードIDを出力する。' -R, --hide-record-id 'イベントレコードIDを表示しない。'
-v, --verbose '詳細な情報を出力する。' -v, --verbose '詳細な情報を出力する。'
-V, --visualize-timeline 'イベント頻度タイムラインを出力する。' -V, --visualize-timeline 'イベント頻度タイムラインを出力する。'
-D, --enable-deprecated-rules 'Deprecatedルールを有効にする。' -D, --enable-deprecated-rules 'Deprecatedルールを有効にする。'
@@ -499,6 +500,7 @@ Hayabusaの結果を標準出力に表示しているときデフォルト
* `Event ID`: イベントログの`<Event><System><EventID>`フィールドから来ています。 * `Event ID`: イベントログの`<Event><System><EventID>`フィールドから来ています。
* `Level`: YML検知ルールの`level`フィールドから来ています。(例:`informational`, `low`, `medium`, `high`, `critical`) デフォルトでは、すべてのレベルのアラートとイベントが出力されますが、`-m`オプションで最低のレベルを指定することができます。例えば`-m high`オプションを付けると、`high``critical`アラートしか出力されません。 * `Level`: YML検知ルールの`level`フィールドから来ています。(例:`informational`, `low`, `medium`, `high`, `critical`) デフォルトでは、すべてのレベルのアラートとイベントが出力されますが、`-m`オプションで最低のレベルを指定することができます。例えば`-m high`オプションを付けると、`high``critical`アラートしか出力されません。
* `Title`: YML検知ルールの`title`フィールドから来ています。 * `Title`: YML検知ルールの`title`フィールドから来ています。
* `RecordID`: イベントレコードIDです。`<Event><System><EventRecordID>`フィールドから来ています。`-R`もしくは`--hide-record-id`オプションを付けると表示されません。
* `Details`: YML検知ルールの`details`フィールドから来ていますが、このフィールドはHayabusaルールにしかありません。このフィールドはアラートとイベントに関する追加情報を提供し、ログの`<Event><System><EventData>`部分から有用なデータを抽出することができます。イベントキーのマッピングが間違っている場合、もしくはフィールドが存在しない場合で抽出ができなかった箇所は`n/a` (not available)と記載されます。 * `Details`: YML検知ルールの`details`フィールドから来ていますが、このフィールドはHayabusaルールにしかありません。このフィールドはアラートとイベントに関する追加情報を提供し、ログの`<Event><System><EventData>`部分から有用なデータを抽出することができます。イベントキーのマッピングが間違っている場合、もしくはフィールドが存在しない場合で抽出ができなかった箇所は`n/a` (not available)と記載されます。
CSVファイルとして保存する場合、以下の列が追加されます: CSVファイルとして保存する場合、以下の列が追加されます:
@@ -507,9 +509,18 @@ CSVファイルとして保存する場合、以下の列が追加されます:
* `Rule Path`: アラートまたはイベントを生成した検知ルールへのパス。 * `Rule Path`: アラートまたはイベントを生成した検知ルールへのパス。
* `File Path`: アラートまたはイベントを起こしたevtxファイルへのパス。 * `File Path`: アラートまたはイベントを起こしたevtxファイルへのパス。
`-R`もしくは`--display-record-id`オプションを指定した場合、`<Event><System><EventRecordID>`の情報が`RecordID`カラムに出力されます。
`-F`もしくは`--full-data`オプションを指定した場合、全てのフィールド情報が`RecordInformation`カラムにで出力されます。 `-F`もしくは`--full-data`オプションを指定した場合、全てのフィールド情報が`RecordInformation`カラムにで出力されます。
## Levelの省略
簡潔に出力するためにLevelを以下のように省略し出力しています。
* `crit`: `critical`
* `high`: `high`
* `med `: `med`
* `low `: `low`
* `info`: `informational`
## MITRE ATT&CK戦術の省略 ## MITRE ATT&CK戦術の省略
簡潔に出力するためにMITRE ATT&CKの戦術を以下のように省略しています。 簡潔に出力するためにMITRE ATT&CKの戦術を以下のように省略しています。

View File

@@ -60,6 +60,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre
- [Logon Summary Generator](#logon-summary-generator) - [Logon Summary Generator](#logon-summary-generator)
- [Testing Hayabusa on Sample Evtx Files](#testing-hayabusa-on-sample-evtx-files) - [Testing Hayabusa on Sample Evtx Files](#testing-hayabusa-on-sample-evtx-files)
- [Hayabusa Output](#hayabusa-output) - [Hayabusa Output](#hayabusa-output)
- [Level Abbrevations](#level-abbrevations)
- [MITRE ATT&CK Tactics Abbreviations](#mitre-attck-tactics-abbreviations) - [MITRE ATT&CK Tactics Abbreviations](#mitre-attck-tactics-abbreviations)
- [Channel Abbreviations](#channel-abbreviations) - [Channel Abbreviations](#channel-abbreviations)
- [Progress Bar](#progress-bar) - [Progress Bar](#progress-bar)
@@ -327,7 +328,7 @@ USAGE:
-C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\rules\config)' -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\rules\config)'
-o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)'
--all-tags 'Output all tags when saving to a CSV file.' --all-tags 'Output all tags when saving to a CSV file.'
-R, --display-record-id 'Display event record ID.' -R, --hide-record-id 'Do not display the EventRecordID number.'
-v, --verbose 'Output verbose information.' -v, --verbose 'Output verbose information.'
-V, --visualize-timeline 'Output event frequency timeline.' -V, --visualize-timeline 'Output event frequency timeline.'
-D, --enable-deprecated-rules 'Enable rules marked as deprecated.' -D, --enable-deprecated-rules 'Enable rules marked as deprecated.'
@@ -497,6 +498,7 @@ When hayabusa output is being displayed to the screen (the default), it will dis
* `Channel`: The name of log. This comes from the `<Event><System><Channel>` field in the event log. * `Channel`: The name of log. This comes from the `<Event><System><Channel>` field in the event log.
* `Event ID`: This comes from the `<Event><System><EventID>` field in the event log. * `Event ID`: This comes from the `<Event><System><EventID>` field in the event log.
* `Level`: This comes from the `level` field in the YML detection rule. (`informational`, `low`, `medium`, `high`, `critical`) By default, all level alerts will be displayed but you can set the minimum level with `-m`. For example, you can set `-m high`) in order to only scan for and display high and critical alerts. * `Level`: This comes from the `level` field in the YML detection rule. (`informational`, `low`, `medium`, `high`, `critical`) By default, all level alerts will be displayed but you can set the minimum level with `-m`. For example, you can set `-m high`) in order to only scan for and display high and critical alerts.
* `RecordID`: This comes from the `<Event><System><EventRecordID>` field in the event log. You can hidde this output with the `-R` or `--hide-record-id` option.
* `Title`: This comes from the `title` field in the YML detection rule. * `Title`: This comes from the `title` field in the YML detection rule.
* `Details`: This comes from the `details` field in the YML detection rule, however, only hayabusa rules have this field. This field gives extra information about the alert or event and can extract useful data from the `<Event><System><EventData>` portion of the log. For example, usernames, command line information, process information, etc... When a placeholder points to a field that does not exist or there is an incorrect alias mapping, it will be outputted as `n/a` (not available). * `Details`: This comes from the `details` field in the YML detection rule, however, only hayabusa rules have this field. This field gives extra information about the alert or event and can extract useful data from the `<Event><System><EventData>` portion of the log. For example, usernames, command line information, process information, etc... When a placeholder points to a field that does not exist or there is an incorrect alias mapping, it will be outputted as `n/a` (not available).
@@ -506,9 +508,18 @@ The following additional columns will be added to the output when saving to a CS
* `Rule Path`: The path to the detection rule that generated the alert or event. * `Rule Path`: The path to the detection rule that generated the alert or event.
* `File Path`: The path to the evtx file that caused the alert or event. * `File Path`: The path to the evtx file that caused the alert or event.
If you add the `-R` or `--display-record-id` option, a `RecordId` column with event record ID values taken from `<Event><System><EventRecordID>` will also be added.
If you add the `-F` or `--full-data` option, a `RecordInformation` column with all field information will also be added. If you add the `-F` or `--full-data` option, a `RecordInformation` column with all field information will also be added.
## Level Abbrevations
In order to save space, we use the following abbrevations when displaying the alert `level`.
* `crit`: `critical`
* `high`: `high`
* `med `: `med`
* `low `: `low`
* `info`: `informational`
## MITRE ATT&CK Tactics Abbreviations ## MITRE ATT&CK Tactics Abbreviations
In order to save space, we use the following abbreviations when displaying MITRE ATT&CK tactic tags. In order to save space, we use the following abbreviations when displaying MITRE ATT&CK tactic tags.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 309 KiB

View File

@@ -1,6 +1,6 @@
use crate::detections::configs; use crate::detections::configs;
use crate::detections::print; use crate::detections::print;
use crate::detections::print::AlertMessage; use crate::detections::print::{AlertMessage, IS_HIDE_RECORD_ID};
use crate::detections::utils; use crate::detections::utils;
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};
@@ -29,10 +29,10 @@ pub struct CsvFormat<'a> {
event_i_d: &'a str, event_i_d: &'a str,
level: &'a str, level: &'a str,
mitre_attack: &'a str, mitre_attack: &'a str,
rule_title: &'a str,
details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
record_i_d: Option<&'a str>, record_i_d: Option<&'a str>,
rule_title: &'a str,
details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
record_information: Option<&'a str>, record_information: Option<&'a str>,
rule_path: &'a str, rule_path: &'a str,
@@ -47,10 +47,10 @@ pub struct DisplayFormat<'a> {
pub channel: &'a str, pub channel: &'a str,
pub event_i_d: &'a str, pub event_i_d: &'a str,
pub level: &'a str, pub level: &'a str,
pub rule_title: &'a str,
pub details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
record_i_d: Option<&'a str>, record_i_d: Option<&'a str>,
pub rule_title: &'a str,
pub details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub record_information: Option<&'a str>, pub record_information: Option<&'a str>,
} }
@@ -217,6 +217,13 @@ fn emit_csv<W: std::io::Write>(
"informational", "informational",
"undefined", "undefined",
]); ]);
let level_abbr: HashMap<String, String> = HashMap::from([
(String::from("cruitical"), String::from("crit")),
(String::from("high"), String::from("high")),
(String::from("medium"), String::from("med ")),
(String::from("low"), String::from("low ")),
(String::from("informational"), String::from("info")),
]);
// レベル別、日ごとの集計用変数の初期化 // レベル別、日ごとの集計用変数の初期化
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());
@@ -233,10 +240,7 @@ fn emit_csv<W: std::io::Write>(
timestamps.push(_get_timestamp(time)); timestamps.push(_get_timestamp(time));
for detect_info in detect_infos { for detect_info in detect_infos {
detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid)); detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid));
let mut level = detect_info.level.to_string(); let level = detect_info.level.to_string();
if level == "informational" {
level = "info".to_string();
}
let time_str = format_time(time, false); let time_str = format_time(time, false);
if displayflag { if displayflag {
let record_id = detect_info let record_id = detect_info
@@ -247,15 +251,24 @@ fn emit_csv<W: std::io::Write>(
.record_information .record_information
.as_ref() .as_ref()
.map(|recinfo| _format_cellpos(recinfo, ColPos::Last)); .map(|recinfo| _format_cellpos(recinfo, ColPos::Last));
let details = detect_info let ctr_char_exclude_details = detect_info
.detail .detail
.chars() .chars()
.filter(|&c| !c.is_control()) .filter(|&c| !c.is_control())
.collect::<String>(); .collect::<String>();
let dispformat = DisplayFormat { let details = if ctr_char_exclude_details.is_empty() {
"-".to_string()
} else {
ctr_char_exclude_details
};
let dispformat: _ = DisplayFormat {
timestamp: &_format_cellpos(&time_str, ColPos::First), timestamp: &_format_cellpos(&time_str, ColPos::First),
level: &_format_cellpos(&level, ColPos::Other), level: &_format_cellpos(
level_abbr.get(&level).unwrap_or(&level),
ColPos::Other,
),
computer: &_format_cellpos(&detect_info.computername, ColPos::Other), computer: &_format_cellpos(&detect_info.computername, ColPos::Other),
event_i_d: &_format_cellpos(&detect_info.eventid, ColPos::Other), event_i_d: &_format_cellpos(&detect_info.eventid, ColPos::Other),
channel: &_format_cellpos(&detect_info.channel, ColPos::Other), channel: &_format_cellpos(&detect_info.channel, ColPos::Other),
@@ -285,7 +298,7 @@ fn emit_csv<W: std::io::Write>(
// csv output format // csv output format
wtr.serialize(CsvFormat { wtr.serialize(CsvFormat {
timestamp: &time_str, timestamp: &time_str,
level: &level, level: level_abbr.get(&level).unwrap_or(&level).trim(),
computer: &detect_info.computername, computer: &detect_info.computername,
event_i_d: &detect_info.eventid, event_i_d: &detect_info.eventid,
channel: &detect_info.channel, channel: &detect_info.channel,
@@ -423,11 +436,10 @@ fn _get_serialized_disp_output(dispformat: Option<DisplayFormat>) -> String {
"RuleTitle", "RuleTitle",
"Details", "Details",
]; ];
let arg_match = &configs::CONFIG.read().unwrap().args; if !*IS_HIDE_RECORD_ID {
if arg_match.is_present("display-record-id") { titles.insert(5, "RecordID");
titles.push("RecordID");
} }
if arg_match.is_present("full-data") { if configs::CONFIG.read().unwrap().args.is_present("full-data") {
titles.push("RecordInformation"); titles.push("RecordInformation");
} }
return format!("{}\n", titles.join("|")); return format!("{}\n", titles.join("|"));
@@ -725,7 +737,7 @@ mod tests {
.unwrap(); .unwrap();
let expect_tz = expect_time.with_timezone(&Local); let expect_tz = expect_time.with_timezone(&Local);
let expect = let expect =
"Timestamp,Computer,Channel,EventID,Level,MitreAttack,RuleTitle,Details,RecordID,RecordInformation,RulePath,FilePath\n" "Timestamp,Computer,Channel,EventID,Level,MitreAttack,RecordID,RuleTitle,Details,RecordInformation,RulePath,FilePath\n"
.to_string() .to_string()
+ &expect_tz + &expect_tz
.clone() .clone()
@@ -742,12 +754,12 @@ mod tests {
+ "," + ","
+ test_attack + test_attack
+ "," + ","
+ test_record_id
+ ","
+ test_title + test_title
+ "," + ","
+ output + output
+ "," + ","
+ test_record_id
+ ","
+ test_recinfo + test_recinfo
+ "," + ","
+ test_rulepath + test_rulepath
@@ -779,7 +791,7 @@ mod tests {
let test_timestamp = Utc let test_timestamp = Utc
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ") .datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
.unwrap(); .unwrap();
let expect_header = "Timestamp|Computer|Channel|EventID|Level|RuleTitle|Details\n"; let expect_header = "Timestamp|Computer|Channel|EventID|Level|RecordID|RuleTitle|Details\n";
let expect_tz = test_timestamp.with_timezone(&Local); let expect_tz = test_timestamp.with_timezone(&Local);
let expect_no_header = expect_tz let expect_no_header = expect_tz
@@ -795,12 +807,12 @@ mod tests {
+ "|" + "|"
+ test_level + test_level
+ "|" + "|"
+ test_recid
+ "|"
+ test_title + test_title
+ "|" + "|"
+ output + output
+ "|" + "|"
+ test_recid
+ "|"
+ test_recinfo + test_recinfo
+ "\n"; + "\n";
assert_eq!(_get_serialized_disp_output(None,), expect_header); assert_eq!(_get_serialized_disp_output(None,), expect_header);

View File

@@ -76,7 +76,7 @@ fn build_app<'a>() -> ArgMatches<'a> {
-C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\\rules\\config)' -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\\rules\\config)'
-o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)'
--all-tags 'Output all tags when saving to a CSV file.' --all-tags 'Output all tags when saving to a CSV file.'
-R, --display-record-id 'Display event record ID.' -R, --hide-record-id 'Do not display EventRecordID number.'
-v, --verbose 'Output verbose information.' -v, --verbose 'Output verbose information.'
-V, --visualize-timeline 'Output event frequency timeline.' -V, --visualize-timeline 'Output event frequency timeline.'
-D, --enable-deprecated-rules 'Enable rules marked as deprecated.' -D, --enable-deprecated-rules 'Enable rules marked as deprecated.'

View File

@@ -9,7 +9,7 @@ use crate::detections::print::MESSAGES;
use crate::detections::print::PIVOT_KEYWORD_LIST_FLAG; use crate::detections::print::PIVOT_KEYWORD_LIST_FLAG;
use crate::detections::print::QUIET_ERRORS_FLAG; use crate::detections::print::QUIET_ERRORS_FLAG;
use crate::detections::print::STATISTICS_FLAG; use crate::detections::print::STATISTICS_FLAG;
use crate::detections::print::{CH_CONFIG, IS_DISPLAY_RECORD_ID, TAGS_CONFIG}; use crate::detections::print::{CH_CONFIG, IS_HIDE_RECORD_ID, TAGS_CONFIG};
use crate::detections::rule; use crate::detections::rule;
use crate::detections::rule::AggResult; use crate::detections::rule::AggResult;
use crate::detections::rule::RuleNode; use crate::detections::rule::RuleNode;
@@ -233,7 +233,7 @@ impl Detection {
.record_information .record_information
.as_ref() .as_ref()
.map(|recinfo| recinfo.to_string()); .map(|recinfo| recinfo.to_string());
let rec_id = if *IS_DISPLAY_RECORD_ID { let rec_id = if !*IS_HIDE_RECORD_ID {
Some( Some(
get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"]) get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"])
.unwrap_or_default(), .unwrap_or_default(),
@@ -281,7 +281,7 @@ impl Detection {
} else { } else {
Option::None Option::None
}; };
let rec_id = if *IS_DISPLAY_RECORD_ID { let rec_id = if !*IS_HIDE_RECORD_ID {
Some(String::default()) Some(String::default())
} else { } else {
None None

View File

@@ -79,11 +79,11 @@ lazy_static! {
.unwrap() .unwrap()
.args .args
.is_present("pivot-keywords-list"); .is_present("pivot-keywords-list");
pub static ref IS_DISPLAY_RECORD_ID: bool = configs::CONFIG pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG
.read() .read()
.unwrap() .unwrap()
.args .args
.is_present("display-record-id"); .is_present("hide-record-id");
} }
impl Default for Message { impl Default for Message {