use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use serde::Serialize;
use crate::value::{SigmaValue, Timespan};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Stable,
Test,
Experimental,
Deprecated,
Unsupported,
}
impl FromStr for Status {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"stable" => Ok(Status::Stable),
"test" => Ok(Status::Test),
"experimental" => Ok(Status::Experimental),
"deprecated" => Ok(Status::Deprecated),
"unsupported" => Ok(Status::Unsupported),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Level {
Informational,
Low,
Medium,
High,
Critical,
}
impl FromStr for Level {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"informational" => Ok(Level::Informational),
"low" => Ok(Level::Low),
"medium" => Ok(Level::Medium),
"high" => Ok(Level::High),
"critical" => Ok(Level::Critical),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum RelationType {
Derived,
Obsolete,
Merged,
Renamed,
Similar,
}
impl FromStr for RelationType {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"derived" => Ok(RelationType::Derived),
"obsolete" => Ok(RelationType::Obsolete),
"merged" => Ok(RelationType::Merged),
"renamed" => Ok(RelationType::Renamed),
"similar" => Ok(RelationType::Similar),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Modifier {
Contains,
StartsWith,
EndsWith,
All,
Base64,
Base64Offset,
Wide,
Utf16be,
Utf16,
WindAsh,
Re,
Cidr,
Cased,
Exists,
Expand,
FieldRef,
Gt,
Gte,
Lt,
Lte,
Neq,
#[serde(rename = "i")]
IgnoreCase,
#[serde(rename = "m")]
Multiline,
#[serde(rename = "s")]
DotAll,
Minute,
Hour,
Day,
Week,
Month,
Year,
}
impl FromStr for Modifier {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"contains" => Ok(Modifier::Contains),
"startswith" => Ok(Modifier::StartsWith),
"endswith" => Ok(Modifier::EndsWith),
"all" => Ok(Modifier::All),
"base64" => Ok(Modifier::Base64),
"base64offset" => Ok(Modifier::Base64Offset),
"wide" | "utf16le" => Ok(Modifier::Wide),
"utf16be" => Ok(Modifier::Utf16be),
"utf16" => Ok(Modifier::Utf16),
"windash" => Ok(Modifier::WindAsh),
"re" => Ok(Modifier::Re),
"cidr" => Ok(Modifier::Cidr),
"cased" => Ok(Modifier::Cased),
"exists" => Ok(Modifier::Exists),
"expand" => Ok(Modifier::Expand),
"fieldref" => Ok(Modifier::FieldRef),
"gt" => Ok(Modifier::Gt),
"gte" => Ok(Modifier::Gte),
"lt" => Ok(Modifier::Lt),
"lte" => Ok(Modifier::Lte),
"neq" => Ok(Modifier::Neq),
"i" | "ignorecase" => Ok(Modifier::IgnoreCase),
"m" | "multiline" => Ok(Modifier::Multiline),
"s" | "dotall" => Ok(Modifier::DotAll),
"minute" => Ok(Modifier::Minute),
"hour" => Ok(Modifier::Hour),
"day" => Ok(Modifier::Day),
"week" => Ok(Modifier::Week),
"month" => Ok(Modifier::Month),
"year" => Ok(Modifier::Year),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct FieldSpec {
pub name: Option<String>,
pub modifiers: Vec<Modifier>,
}
impl FieldSpec {
pub fn new(name: Option<String>, modifiers: Vec<Modifier>) -> Self {
FieldSpec { name, modifiers }
}
pub fn has_modifier(&self, m: Modifier) -> bool {
self.modifiers.contains(&m)
}
pub fn is_keyword(&self) -> bool {
self.name.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum ConditionExpr {
And(Vec<ConditionExpr>),
Or(Vec<ConditionExpr>),
Not(Box<ConditionExpr>),
Identifier(String),
Selector {
quantifier: Quantifier,
pattern: SelectorPattern,
},
}
impl fmt::Display for ConditionExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConditionExpr::And(args) => {
let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
write!(f, "({})", parts.join(" and "))
}
ConditionExpr::Or(args) => {
let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
write!(f, "({})", parts.join(" or "))
}
ConditionExpr::Not(arg) => write!(f, "not {arg}"),
ConditionExpr::Identifier(id) => write!(f, "{id}"),
ConditionExpr::Selector {
quantifier,
pattern,
} => write!(f, "{quantifier} of {pattern}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum Quantifier {
Any,
All,
Count(u64),
}
impl fmt::Display for Quantifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Quantifier::Any => write!(f, "1"),
Quantifier::All => write!(f, "all"),
Quantifier::Count(n) => write!(f, "{n}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum SelectorPattern {
Them,
Pattern(String),
}
impl fmt::Display for SelectorPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SelectorPattern::Them => write!(f, "them"),
SelectorPattern::Pattern(p) => write!(f, "{p}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DetectionItem {
pub field: FieldSpec,
pub values: Vec<SigmaValue>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum Detection {
AllOf(Vec<DetectionItem>),
AnyOf(Vec<Detection>),
Keywords(Vec<SigmaValue>),
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Detections {
pub named: HashMap<String, Detection>,
pub conditions: Vec<ConditionExpr>,
pub condition_strings: Vec<String>,
pub timeframe: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct LogSource {
pub category: Option<String>,
pub product: Option<String>,
pub service: Option<String>,
pub definition: Option<String>,
#[serde(flatten)]
pub custom: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Related {
pub id: String,
pub relation_type: RelationType,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct SigmaRule {
pub title: String,
pub logsource: LogSource,
pub detection: Detections,
pub id: Option<String>,
pub name: Option<String>,
pub related: Vec<Related>,
pub taxonomy: Option<String>,
pub status: Option<Status>,
pub description: Option<String>,
pub license: Option<String>,
pub author: Option<String>,
pub references: Vec<String>,
pub date: Option<String>,
pub modified: Option<String>,
pub fields: Vec<String>,
pub falsepositives: Vec<String>,
pub level: Option<Level>,
pub tags: Vec<String>,
pub scope: Vec<String>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub custom_attributes: HashMap<String, serde_yaml::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CorrelationType {
EventCount,
ValueCount,
Temporal,
TemporalOrdered,
ValueSum,
ValueAvg,
ValuePercentile,
ValueMedian,
}
impl FromStr for CorrelationType {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"event_count" => Ok(CorrelationType::EventCount),
"value_count" => Ok(CorrelationType::ValueCount),
"temporal" => Ok(CorrelationType::Temporal),
"temporal_ordered" => Ok(CorrelationType::TemporalOrdered),
"value_sum" => Ok(CorrelationType::ValueSum),
"value_avg" => Ok(CorrelationType::ValueAvg),
"value_percentile" => Ok(CorrelationType::ValuePercentile),
"value_median" => Ok(CorrelationType::ValueMedian),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ConditionOperator {
Lt,
Lte,
Gt,
Gte,
Eq,
Neq,
}
impl FromStr for ConditionOperator {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"lt" => Ok(ConditionOperator::Lt),
"lte" => Ok(ConditionOperator::Lte),
"gt" => Ok(ConditionOperator::Gt),
"gte" => Ok(ConditionOperator::Gte),
"eq" => Ok(ConditionOperator::Eq),
"neq" => Ok(ConditionOperator::Neq),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum CorrelationCondition {
Threshold {
predicates: Vec<(ConditionOperator, u64)>,
field: Option<String>,
},
Extended(ConditionExpr),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct FieldAlias {
pub alias: String,
pub mapping: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct CorrelationRule {
pub title: String,
pub id: Option<String>,
pub name: Option<String>,
pub status: Option<Status>,
pub description: Option<String>,
pub author: Option<String>,
pub date: Option<String>,
pub modified: Option<String>,
pub references: Vec<String>,
pub taxonomy: Option<String>,
pub tags: Vec<String>,
pub falsepositives: Vec<String>,
pub level: Option<Level>,
pub correlation_type: CorrelationType,
pub rules: Vec<String>,
pub group_by: Vec<String>,
pub timespan: Timespan,
pub condition: CorrelationCondition,
pub aliases: Vec<FieldAlias>,
pub generate: bool,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub custom_attributes: HashMap<String, serde_yaml::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct FilterRule {
pub title: String,
pub id: Option<String>,
pub name: Option<String>,
pub status: Option<Status>,
pub description: Option<String>,
pub author: Option<String>,
pub date: Option<String>,
pub modified: Option<String>,
pub logsource: Option<LogSource>,
pub rules: Vec<String>,
pub detection: Detections,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum SigmaDocument {
Rule(Box<SigmaRule>),
Correlation(CorrelationRule),
Filter(FilterRule),
}
#[derive(Debug, Clone, Serialize)]
pub struct SigmaCollection {
pub rules: Vec<SigmaRule>,
pub correlations: Vec<CorrelationRule>,
pub filters: Vec<FilterRule>,
#[serde(skip)]
pub errors: Vec<String>,
}
impl SigmaCollection {
pub fn new() -> Self {
SigmaCollection {
rules: Vec::new(),
correlations: Vec::new(),
filters: Vec::new(),
errors: Vec::new(),
}
}
pub fn len(&self) -> usize {
self.rules.len() + self.correlations.len() + self.filters.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Default for SigmaCollection {
fn default() -> Self {
Self::new()
}
}