use crate::detection::Detection;
use crate::event::Event;
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Status {
Stable,
Test,
Experimental,
Deprecated,
Unsupported,
}
#[derive(Deserialize, Debug)]
pub struct Related {
pub id: String,
#[serde(rename = "type")]
pub related_type: RelatedType,
}
#[derive(Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RelatedType {
Derived,
Obsolete,
Merged,
Renamed,
Similar,
}
#[derive(Deserialize, Debug)]
pub struct Logsource {
pub category: Option<String>,
pub product: Option<String>,
pub service: Option<String>,
pub definition: Option<String>,
}
#[derive(Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Level {
Informational,
Low,
Medium,
High,
Critical,
}
#[derive(Deserialize, Debug)]
pub struct Rule {
pub title: String,
pub id: Option<String>,
pub name: Option<String>,
pub related: Option<Vec<Related>>,
pub taxonomy: Option<String>,
pub status: Option<Status>,
pub description: Option<String>,
pub license: Option<String>,
pub author: Option<String>,
pub references: Option<Vec<String>>,
pub date: Option<String>,
pub modified: Option<String>,
pub logsource: Logsource,
pub detection: Detection,
pub fields: Option<Vec<String>>,
pub falsepositives: Option<Vec<String>>,
pub level: Option<Level>,
pub tags: Option<Vec<String>>,
#[serde(flatten)]
pub custom_fields: HashMap<String, serde_norway::Value>,
}
impl Rule {
pub fn is_match(&self, event: &Event) -> bool {
self.detection.evaluate(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::FieldValue;
use crate::selection::Selection;
use crate::wildcard::WildcardToken;
#[test]
fn test_load_from_yaml() {
let rule_yaml = r#"
title: Some test title
id: fb97a1c5-9e86-4e15-9fd9-7d82a05a384e
name: a unique name
related:
- id: ab97a1c5-9e86-4e15-9fd9-7d82a05a384e
type: derived
- id: bb97a1c5-9e86-4e15-9fd9-7d82a05a384e
type: obsolete
status: stable
license: MIT
author: Chuck Norris
date: 2020-12-30
logsource:
category: process_creation
product: windows
level: medium
detection:
selection:
field_name:
- this # or
- that
condition: selection
custom_field: some value
another_custom_field:
nested: nested_value
"#;
let rule: Rule = serde_norway::from_str(rule_yaml).unwrap();
assert_eq!(rule.title, "Some test title");
assert_eq!(
rule.id,
Some("fb97a1c5-9e86-4e15-9fd9-7d82a05a384e".to_string())
);
assert_eq!(rule.name, Some("a unique name".to_string()));
let related = rule.related.as_ref().unwrap();
assert_eq!(related.len(), 2);
assert_eq!(related[0].id, "ab97a1c5-9e86-4e15-9fd9-7d82a05a384e");
assert_eq!(related[0].related_type, RelatedType::Derived);
assert_eq!(related[1].id, "bb97a1c5-9e86-4e15-9fd9-7d82a05a384e");
assert_eq!(related[1].related_type, RelatedType::Obsolete);
assert!(rule.taxonomy.is_none());
assert_eq!(rule.status, Some(Status::Stable));
assert!(rule.description.is_none());
assert_eq!(rule.license, Some("MIT".to_string()));
assert_eq!(rule.author, Some("Chuck Norris".to_string()));
assert!(rule.references.is_none());
assert_eq!(rule.date, Some("2020-12-30".to_string()));
assert!(rule.modified.is_none());
assert_eq!(
rule.logsource.category.as_ref().unwrap(),
"process_creation"
);
assert_eq!(rule.logsource.product.as_ref().unwrap(), "windows");
assert!(rule.logsource.service.is_none());
assert!(rule.logsource.definition.is_none());
assert!(rule.fields.is_none());
assert!(rule.falsepositives.is_none());
assert_eq!(rule.level.as_ref().unwrap(), &Level::Medium);
assert!(rule.tags.is_none());
assert_eq!(rule.detection.get_selections().len(), 1);
match rule.detection.get_selections().get("selection").unwrap() {
Selection::Keyword(_) => panic!("Wrong selection type"),
Selection::Field(field_groups) => {
assert_eq!(field_groups.len(), 1);
let fields = &field_groups[0].fields;
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].name, "field_name");
assert_eq!(fields[0].values.len(), 2);
assert!(
matches!(&fields[0].values[0], FieldValue::WildcardPattern(pattern) if pattern.len() == 1 && matches!(&pattern[0], WildcardToken::Pattern(p) if p == &"this".chars().collect::<Vec<char>>()))
);
assert!(
matches!(&fields[0].values[1], FieldValue::WildcardPattern(pattern) if pattern.len() == 1 && matches!(&pattern[0], WildcardToken::Pattern(p) if p == &"that".chars().collect::<Vec<char>>()))
);
}
}
assert_eq!(rule.detection.get_condition(), "selection".to_string());
assert_eq!(rule.custom_fields["custom_field"], "some value");
assert_eq!(
rule.custom_fields["another_custom_field"]["nested"],
"nested_value"
);
let event = Event::from([("field_name", "this")]);
assert!(rule.is_match(&event));
}
}