mod helpers;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use regex::Regex;
use rsigma_parser::{SigmaRule, SigmaValue};
use super::conditions::{DetectionItemCondition, FieldNameCondition};
use super::state::PipelineState;
use crate::error::{EvalError, Result};
#[derive(Debug, Clone)]
pub enum Transformation {
FieldNameMapping {
mapping: HashMap<String, Vec<String>>,
},
FieldNamePrefixMapping { mapping: HashMap<String, String> },
FieldNamePrefix { prefix: String },
FieldNameSuffix { suffix: String },
DropDetectionItem,
AddCondition {
conditions: HashMap<String, SigmaValue>,
negated: bool,
},
ChangeLogsource {
category: Option<String>,
product: Option<String>,
service: Option<String>,
},
ReplaceString {
regex: String,
replacement: String,
skip_special: bool,
},
ValuePlaceholders,
WildcardPlaceholders,
QueryExpressionPlaceholders { expression: String },
SetState { key: String, value: String },
RuleFailure { message: String },
DetectionItemFailure { message: String },
FieldNameTransform {
transform_func: String,
mapping: HashMap<String, String>,
},
HashesFields {
valid_hash_algos: Vec<String>,
field_prefix: String,
drop_algo_prefix: bool,
},
MapString {
mapping: HashMap<String, Vec<String>>,
},
SetValue { value: SigmaValue },
ConvertType { target_type: String },
Regex,
AddField { field: String },
RemoveField { field: String },
SetField { fields: Vec<String> },
SetCustomAttribute { attribute: String, value: String },
CaseTransformation { case_type: String },
Nest {
items: Vec<super::TransformationItem>,
},
Include { template: String },
}
impl Transformation {
pub fn apply(
&self,
rule: &mut SigmaRule,
state: &mut PipelineState,
detection_item_conditions: &[DetectionItemCondition],
field_name_conditions: &[FieldNameCondition],
field_name_cond_not: bool,
) -> Result<bool> {
match self {
Transformation::FieldNameMapping { mapping } => {
helpers::apply_field_name_transform(
rule,
state,
field_name_conditions,
field_name_cond_not,
|name| mapping.get(name).cloned(),
)?;
Ok(true)
}
Transformation::FieldNamePrefixMapping { mapping } => {
helpers::apply_field_name_transform(
rule,
state,
field_name_conditions,
field_name_cond_not,
|name| {
for (prefix, replacement) in mapping {
if name.starts_with(prefix.as_str()) {
return Some(vec![format!(
"{}{}",
replacement,
&name[prefix.len()..]
)]);
}
}
None
},
)?;
Ok(true)
}
Transformation::FieldNamePrefix { prefix } => {
helpers::apply_field_name_transform(
rule,
state,
field_name_conditions,
field_name_cond_not,
|name| Some(vec![format!("{prefix}{name}")]),
)?;
Ok(true)
}
Transformation::FieldNameSuffix { suffix } => {
helpers::apply_field_name_transform(
rule,
state,
field_name_conditions,
field_name_cond_not,
|name| Some(vec![format!("{name}{suffix}")]),
)?;
Ok(true)
}
Transformation::DropDetectionItem => {
helpers::drop_detection_items(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
);
Ok(true)
}
Transformation::AddCondition {
conditions,
negated,
} => {
helpers::add_conditions(rule, conditions, *negated);
Ok(true)
}
Transformation::ChangeLogsource {
category,
product,
service,
} => {
if let Some(cat) = category {
rule.logsource.category = Some(cat.clone());
}
if let Some(prod) = product {
rule.logsource.product = Some(prod.clone());
}
if let Some(svc) = service {
rule.logsource.service = Some(svc.clone());
}
Ok(true)
}
Transformation::ReplaceString {
regex,
replacement,
skip_special,
} => {
let re = Regex::new(regex)
.map_err(|e| EvalError::InvalidModifiers(format!("bad regex: {e}")))?;
helpers::replace_strings_in_rule(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
&re,
replacement,
*skip_special,
);
Ok(true)
}
Transformation::ValuePlaceholders => {
helpers::expand_placeholders_in_rule(rule, state, false);
Ok(true)
}
Transformation::WildcardPlaceholders => {
helpers::expand_placeholders_in_rule(rule, state, true);
Ok(true)
}
Transformation::QueryExpressionPlaceholders { expression } => {
state.set_state(
"query_expression_template".to_string(),
serde_json::Value::String(expression.clone()),
);
Ok(true)
}
Transformation::SetState { key, value } => {
state.set_state(key.clone(), serde_json::Value::String(value.clone()));
Ok(true)
}
Transformation::RuleFailure { message } => Err(EvalError::InvalidModifiers(format!(
"Pipeline rule failure: {message} (rule: {})",
rule.title
))),
Transformation::DetectionItemFailure { message } => {
let has_match =
helpers::rule_has_matching_item(rule, state, detection_item_conditions);
if has_match {
Err(EvalError::InvalidModifiers(format!(
"Pipeline detection item failure: {message} (rule: {})",
rule.title
)))
} else {
Ok(false)
}
}
Transformation::FieldNameTransform {
transform_func,
mapping,
} => {
let func = transform_func.clone();
let map = mapping.clone();
helpers::apply_field_name_transform(
rule,
state,
field_name_conditions,
field_name_cond_not,
|name| {
if let Some(mapped) = map.get(name) {
return Some(vec![mapped.clone()]);
}
Some(vec![helpers::apply_named_string_fn(&func, name)])
},
)?;
Ok(true)
}
Transformation::HashesFields {
valid_hash_algos,
field_prefix,
drop_algo_prefix,
} => {
helpers::decompose_hashes_field(
rule,
valid_hash_algos,
field_prefix,
*drop_algo_prefix,
);
Ok(true)
}
Transformation::MapString { mapping } => {
helpers::map_string_values(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
mapping,
);
Ok(true)
}
Transformation::SetValue { value } => {
helpers::set_detection_item_values(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
value,
);
Ok(true)
}
Transformation::ConvertType { target_type } => {
helpers::convert_detection_item_types(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
target_type,
);
Ok(true)
}
Transformation::Regex => {
Ok(false)
}
Transformation::AddField { field } => {
if !rule.fields.contains(field) {
rule.fields.push(field.clone());
}
Ok(true)
}
Transformation::RemoveField { field } => {
rule.fields.retain(|f| f != field);
Ok(true)
}
Transformation::SetField { fields } => {
rule.fields = fields.clone();
Ok(true)
}
Transformation::SetCustomAttribute { attribute, value } => {
rule.custom_attributes
.insert(attribute.clone(), yaml_serde::Value::String(value.clone()));
Ok(true)
}
Transformation::CaseTransformation { case_type } => {
helpers::apply_case_transformation(
rule,
state,
detection_item_conditions,
field_name_conditions,
field_name_cond_not,
case_type,
);
Ok(true)
}
Transformation::Nest { items } => {
for item in items {
let mut merged_det_conds: Vec<DetectionItemCondition> =
detection_item_conditions.to_vec();
merged_det_conds.extend(item.detection_item_conditions.clone());
let mut merged_field_conds: Vec<FieldNameCondition> =
field_name_conditions.to_vec();
merged_field_conds.extend(item.field_name_conditions.clone());
let rule_ok = if item.rule_conditions.is_empty() {
true
} else {
super::conditions::all_rule_conditions_match(
&item.rule_conditions,
rule,
state,
)
};
if rule_ok {
item.transformation.apply(
rule,
state,
&merged_det_conds,
&merged_field_conds,
item.field_name_cond_not || field_name_cond_not,
)?;
if let Some(ref id) = item.id {
state.mark_applied(id);
}
}
}
Ok(true)
}
Transformation::Include { .. } => Ok(false),
}
}
}