1use std::collections::HashMap;
8use std::fmt;
9use std::str::FromStr;
10
11use serde::Serialize;
12
13use crate::value::{SigmaValue, Timespan};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
23#[serde(rename_all = "lowercase")]
24pub enum Status {
25 Stable,
26 Test,
27 Experimental,
28 Deprecated,
29 Unsupported,
30}
31
32impl FromStr for Status {
33 type Err = ();
34 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
35 match s {
36 "stable" => Ok(Status::Stable),
37 "test" => Ok(Status::Test),
38 "experimental" => Ok(Status::Experimental),
39 "deprecated" => Ok(Status::Deprecated),
40 "unsupported" => Ok(Status::Unsupported),
41 _ => Err(()),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
48#[serde(rename_all = "lowercase")]
49pub enum Level {
50 Informational,
51 Low,
52 Medium,
53 High,
54 Critical,
55}
56
57impl Level {
58 pub fn as_str(&self) -> &'static str {
59 match self {
60 Level::Informational => "informational",
61 Level::Low => "low",
62 Level::Medium => "medium",
63 Level::High => "high",
64 Level::Critical => "critical",
65 }
66 }
67}
68
69impl FromStr for Level {
70 type Err = ();
71 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
72 match s {
73 "informational" => Ok(Level::Informational),
74 "low" => Ok(Level::Low),
75 "medium" => Ok(Level::Medium),
76 "high" => Ok(Level::High),
77 "critical" => Ok(Level::Critical),
78 _ => Err(()),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
85#[serde(rename_all = "lowercase")]
86pub enum RelationType {
87 Correlation,
88 Derived,
89 Obsolete,
90 Merged,
91 Renamed,
92 Similar,
93}
94
95impl FromStr for RelationType {
96 type Err = ();
97 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
98 match s {
99 "correlation" => Ok(RelationType::Correlation),
100 "derived" => Ok(RelationType::Derived),
101 "obsolete" => Ok(RelationType::Obsolete),
102 "merged" => Ok(RelationType::Merged),
103 "renamed" => Ok(RelationType::Renamed),
104 "similar" => Ok(RelationType::Similar),
105 _ => Err(()),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
118#[serde(rename_all = "lowercase")]
119pub enum Modifier {
120 Contains,
122 StartsWith,
123 EndsWith,
124
125 All,
127
128 Base64,
130 Base64Offset,
131 Wide,
132 Utf16be,
133 Utf16,
134 WindAsh,
135
136 Re,
138 Cidr,
139
140 Cased,
142
143 Exists,
145
146 Expand,
148
149 FieldRef,
151
152 Gt,
154 Gte,
155 Lt,
156 Lte,
157 Neq,
159
160 #[serde(rename = "i")]
162 IgnoreCase,
163 #[serde(rename = "m")]
164 Multiline,
165 #[serde(rename = "s")]
166 DotAll,
167
168 Minute,
170 Hour,
171 Day,
172 Week,
173 Month,
174 Year,
175}
176
177impl FromStr for Modifier {
181 type Err = ();
182 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
183 match s {
184 "contains" => Ok(Modifier::Contains),
185 "startswith" => Ok(Modifier::StartsWith),
186 "endswith" => Ok(Modifier::EndsWith),
187 "all" => Ok(Modifier::All),
188 "base64" => Ok(Modifier::Base64),
189 "base64offset" => Ok(Modifier::Base64Offset),
190 "wide" | "utf16le" => Ok(Modifier::Wide),
191 "utf16be" => Ok(Modifier::Utf16be),
192 "utf16" => Ok(Modifier::Utf16),
193 "windash" => Ok(Modifier::WindAsh),
194 "re" => Ok(Modifier::Re),
195 "cidr" => Ok(Modifier::Cidr),
196 "cased" => Ok(Modifier::Cased),
197 "exists" => Ok(Modifier::Exists),
198 "expand" => Ok(Modifier::Expand),
199 "fieldref" => Ok(Modifier::FieldRef),
200 "gt" => Ok(Modifier::Gt),
201 "gte" => Ok(Modifier::Gte),
202 "lt" => Ok(Modifier::Lt),
203 "lte" => Ok(Modifier::Lte),
204 "neq" => Ok(Modifier::Neq),
205 "i" | "ignorecase" => Ok(Modifier::IgnoreCase),
206 "m" | "multiline" => Ok(Modifier::Multiline),
207 "s" | "dotall" => Ok(Modifier::DotAll),
208 "minute" => Ok(Modifier::Minute),
209 "hour" => Ok(Modifier::Hour),
210 "day" => Ok(Modifier::Day),
211 "week" => Ok(Modifier::Week),
212 "month" => Ok(Modifier::Month),
213 "year" => Ok(Modifier::Year),
214 _ => Err(()),
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
228pub struct FieldSpec {
229 pub name: Option<String>,
231 pub modifiers: Vec<Modifier>,
233}
234
235impl FieldSpec {
236 pub fn new(name: Option<String>, modifiers: Vec<Modifier>) -> Self {
237 FieldSpec { name, modifiers }
238 }
239
240 pub fn has_modifier(&self, m: Modifier) -> bool {
241 self.modifiers.contains(&m)
242 }
243
244 pub fn is_keyword(&self) -> bool {
245 self.name.is_none()
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
260pub enum ConditionExpr {
261 And(Vec<ConditionExpr>),
263 Or(Vec<ConditionExpr>),
265 Not(Box<ConditionExpr>),
267 Identifier(String),
269 Selector {
271 quantifier: Quantifier,
272 pattern: SelectorPattern,
273 },
274}
275
276impl fmt::Display for ConditionExpr {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 match self {
279 ConditionExpr::And(args) => {
280 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
281 write!(f, "({})", parts.join(" and "))
282 }
283 ConditionExpr::Or(args) => {
284 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
285 write!(f, "({})", parts.join(" or "))
286 }
287 ConditionExpr::Not(arg) => write!(f, "not {arg}"),
288 ConditionExpr::Identifier(id) => write!(f, "{id}"),
289 ConditionExpr::Selector {
290 quantifier,
291 pattern,
292 } => write!(f, "{quantifier} of {pattern}"),
293 }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
299pub enum Quantifier {
300 Any,
302 All,
304 Count(u64),
306}
307
308impl fmt::Display for Quantifier {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 match self {
311 Quantifier::Any => write!(f, "1"),
312 Quantifier::All => write!(f, "all"),
313 Quantifier::Count(n) => write!(f, "{n}"),
314 }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
320pub enum SelectorPattern {
321 Them,
323 Pattern(String),
325}
326
327impl fmt::Display for SelectorPattern {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 match self {
330 SelectorPattern::Them => write!(f, "them"),
331 SelectorPattern::Pattern(p) => write!(f, "{p}"),
332 }
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize)]
349pub struct DetectionItem {
350 pub field: FieldSpec,
352 pub values: Vec<SigmaValue>,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize)]
363pub enum Detection {
364 AllOf(Vec<DetectionItem>),
366 AnyOf(Vec<Detection>),
368 Keywords(Vec<SigmaValue>),
370}
371
372#[derive(Debug, Clone, PartialEq, Serialize)]
378pub struct Detections {
379 pub named: HashMap<String, Detection>,
381 pub conditions: Vec<ConditionExpr>,
383 pub condition_strings: Vec<String>,
385 pub timeframe: Option<String>,
387}
388
389#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
397pub struct LogSource {
398 pub category: Option<String>,
399 pub product: Option<String>,
400 pub service: Option<String>,
401 pub definition: Option<String>,
402 #[serde(flatten)]
404 pub custom: HashMap<String, String>,
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
413pub struct Related {
414 pub id: String,
415 pub relation_type: RelationType,
416}
417
418#[derive(Debug, Clone, PartialEq, Serialize)]
426pub struct SigmaRule {
427 pub title: String,
429 pub logsource: LogSource,
430 pub detection: Detections,
431
432 pub id: Option<String>,
434 pub name: Option<String>,
435 pub related: Vec<Related>,
436 pub taxonomy: Option<String>,
437 pub status: Option<Status>,
438 pub description: Option<String>,
439 pub license: Option<String>,
440 pub author: Option<String>,
441 pub references: Vec<String>,
442 pub date: Option<String>,
443 pub modified: Option<String>,
444 pub fields: Vec<String>,
445 pub falsepositives: Vec<String>,
446 pub level: Option<Level>,
447 pub tags: Vec<String>,
448 pub scope: Vec<String>,
449
450 #[serde(skip_serializing_if = "HashMap::is_empty")]
461 pub custom_attributes: HashMap<String, yaml_serde::Value>,
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
472#[serde(rename_all = "snake_case")]
473pub enum CorrelationType {
474 EventCount,
475 ValueCount,
476 Temporal,
477 TemporalOrdered,
478 ValueSum,
479 ValueAvg,
480 ValuePercentile,
481 ValueMedian,
482}
483
484impl CorrelationType {
485 pub fn as_str(&self) -> &'static str {
486 match self {
487 CorrelationType::EventCount => "event_count",
488 CorrelationType::ValueCount => "value_count",
489 CorrelationType::Temporal => "temporal",
490 CorrelationType::TemporalOrdered => "temporal_ordered",
491 CorrelationType::ValueSum => "value_sum",
492 CorrelationType::ValueAvg => "value_avg",
493 CorrelationType::ValuePercentile => "value_percentile",
494 CorrelationType::ValueMedian => "value_median",
495 }
496 }
497}
498
499impl FromStr for CorrelationType {
500 type Err = ();
501 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
502 match s {
503 "event_count" => Ok(CorrelationType::EventCount),
504 "value_count" => Ok(CorrelationType::ValueCount),
505 "temporal" => Ok(CorrelationType::Temporal),
506 "temporal_ordered" => Ok(CorrelationType::TemporalOrdered),
507 "value_sum" => Ok(CorrelationType::ValueSum),
508 "value_avg" => Ok(CorrelationType::ValueAvg),
509 "value_percentile" => Ok(CorrelationType::ValuePercentile),
510 "value_median" => Ok(CorrelationType::ValueMedian),
511 _ => Err(()),
512 }
513 }
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
520pub enum ConditionOperator {
521 Lt,
522 Lte,
523 Gt,
524 Gte,
525 Eq,
526 Neq,
527}
528
529impl FromStr for ConditionOperator {
530 type Err = ();
531 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
532 match s {
533 "lt" => Ok(ConditionOperator::Lt),
534 "lte" => Ok(ConditionOperator::Lte),
535 "gt" => Ok(ConditionOperator::Gt),
536 "gte" => Ok(ConditionOperator::Gte),
537 "eq" => Ok(ConditionOperator::Eq),
538 "neq" => Ok(ConditionOperator::Neq),
539 _ => Err(()),
540 }
541 }
542}
543
544#[derive(Debug, Clone, PartialEq, Serialize)]
548pub enum CorrelationCondition {
549 Threshold {
554 predicates: Vec<(ConditionOperator, u64)>,
556 field: Option<Vec<String>>,
559 percentile: Option<u64>,
562 },
563 Extended(ConditionExpr),
565}
566
567#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
573pub struct FieldAlias {
574 pub alias: String,
575 pub mapping: HashMap<String, String>,
577}
578
579#[derive(Debug, Clone, PartialEq, Serialize)]
583pub struct CorrelationRule {
584 pub title: String,
586 pub id: Option<String>,
587 pub name: Option<String>,
588 pub status: Option<Status>,
589 pub description: Option<String>,
590 pub author: Option<String>,
591 pub date: Option<String>,
592 pub modified: Option<String>,
593 pub related: Vec<Related>,
594 pub references: Vec<String>,
595 pub taxonomy: Option<String>,
596 pub license: Option<String>,
597 pub tags: Vec<String>,
598 pub fields: Vec<String>,
599 pub falsepositives: Vec<String>,
600 pub level: Option<Level>,
601 pub scope: Vec<String>,
602
603 pub correlation_type: CorrelationType,
605 pub rules: Vec<String>,
606 pub group_by: Vec<String>,
607 pub timespan: Timespan,
608 pub condition: CorrelationCondition,
609 pub aliases: Vec<FieldAlias>,
610 pub generate: bool,
611
612 #[serde(skip_serializing_if = "HashMap::is_empty")]
620 pub custom_attributes: HashMap<String, yaml_serde::Value>,
621}
622
623#[derive(Debug, Clone, PartialEq, Serialize)]
629pub enum FilterRuleTarget {
630 Any,
632 Specific(Vec<String>),
634}
635
636#[derive(Debug, Clone, PartialEq, Serialize)]
641pub struct FilterRule {
642 pub title: String,
643 pub id: Option<String>,
644 pub name: Option<String>,
645 pub taxonomy: Option<String>,
646 pub status: Option<Status>,
647 pub description: Option<String>,
648 pub author: Option<String>,
649 pub date: Option<String>,
650 pub modified: Option<String>,
651 pub related: Vec<Related>,
652 pub license: Option<String>,
653 pub references: Vec<String>,
654 pub tags: Vec<String>,
655 pub fields: Vec<String>,
656 pub falsepositives: Vec<String>,
657 pub level: Option<Level>,
658 pub scope: Vec<String>,
659 pub logsource: Option<LogSource>,
660
661 pub rules: FilterRuleTarget,
663 pub detection: Detections,
665
666 #[serde(skip_serializing_if = "HashMap::is_empty")]
668 pub custom_attributes: HashMap<String, yaml_serde::Value>,
669}
670
671#[derive(Debug, Clone, PartialEq, Serialize)]
680pub enum SigmaDocument {
681 Rule(Box<SigmaRule>),
682 Correlation(CorrelationRule),
683 Filter(FilterRule),
684}
685
686#[derive(Debug, Clone, Serialize)]
688pub struct SigmaCollection {
689 pub rules: Vec<SigmaRule>,
690 pub correlations: Vec<CorrelationRule>,
691 pub filters: Vec<FilterRule>,
692 #[serde(skip)]
694 pub errors: Vec<String>,
695}
696
697impl SigmaCollection {
698 pub fn new() -> Self {
699 SigmaCollection {
700 rules: Vec::new(),
701 correlations: Vec::new(),
702 filters: Vec::new(),
703 errors: Vec::new(),
704 }
705 }
706
707 pub fn len(&self) -> usize {
709 self.rules.len() + self.correlations.len() + self.filters.len()
710 }
711
712 pub fn is_empty(&self) -> bool {
713 self.len() == 0
714 }
715}
716
717impl Default for SigmaCollection {
718 fn default() -> Self {
719 Self::new()
720 }
721}