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 FromStr for Level {
58 type Err = ();
59 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
60 match s {
61 "informational" => Ok(Level::Informational),
62 "low" => Ok(Level::Low),
63 "medium" => Ok(Level::Medium),
64 "high" => Ok(Level::High),
65 "critical" => Ok(Level::Critical),
66 _ => Err(()),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "lowercase")]
74pub enum RelationType {
75 Derived,
76 Obsolete,
77 Merged,
78 Renamed,
79 Similar,
80}
81
82impl FromStr for RelationType {
83 type Err = ();
84 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
85 match s {
86 "derived" => Ok(RelationType::Derived),
87 "obsolete" => Ok(RelationType::Obsolete),
88 "merged" => Ok(RelationType::Merged),
89 "renamed" => Ok(RelationType::Renamed),
90 "similar" => Ok(RelationType::Similar),
91 _ => Err(()),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
104#[serde(rename_all = "lowercase")]
105pub enum Modifier {
106 Contains,
108 StartsWith,
109 EndsWith,
110
111 All,
113
114 Base64,
116 Base64Offset,
117 Wide,
118 Utf16be,
119 Utf16,
120 WindAsh,
121
122 Re,
124 Cidr,
125
126 Cased,
128
129 Exists,
131
132 Expand,
134
135 FieldRef,
137
138 Gt,
140 Gte,
141 Lt,
142 Lte,
143 Neq,
145
146 #[serde(rename = "i")]
148 IgnoreCase,
149 #[serde(rename = "m")]
150 Multiline,
151 #[serde(rename = "s")]
152 DotAll,
153
154 Minute,
156 Hour,
157 Day,
158 Week,
159 Month,
160 Year,
161}
162
163impl FromStr for Modifier {
167 type Err = ();
168 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
169 match s {
170 "contains" => Ok(Modifier::Contains),
171 "startswith" => Ok(Modifier::StartsWith),
172 "endswith" => Ok(Modifier::EndsWith),
173 "all" => Ok(Modifier::All),
174 "base64" => Ok(Modifier::Base64),
175 "base64offset" => Ok(Modifier::Base64Offset),
176 "wide" | "utf16le" => Ok(Modifier::Wide),
177 "utf16be" => Ok(Modifier::Utf16be),
178 "utf16" => Ok(Modifier::Utf16),
179 "windash" => Ok(Modifier::WindAsh),
180 "re" => Ok(Modifier::Re),
181 "cidr" => Ok(Modifier::Cidr),
182 "cased" => Ok(Modifier::Cased),
183 "exists" => Ok(Modifier::Exists),
184 "expand" => Ok(Modifier::Expand),
185 "fieldref" => Ok(Modifier::FieldRef),
186 "gt" => Ok(Modifier::Gt),
187 "gte" => Ok(Modifier::Gte),
188 "lt" => Ok(Modifier::Lt),
189 "lte" => Ok(Modifier::Lte),
190 "neq" => Ok(Modifier::Neq),
191 "i" | "ignorecase" => Ok(Modifier::IgnoreCase),
192 "m" | "multiline" => Ok(Modifier::Multiline),
193 "s" | "dotall" => Ok(Modifier::DotAll),
194 "minute" => Ok(Modifier::Minute),
195 "hour" => Ok(Modifier::Hour),
196 "day" => Ok(Modifier::Day),
197 "week" => Ok(Modifier::Week),
198 "month" => Ok(Modifier::Month),
199 "year" => Ok(Modifier::Year),
200 _ => Err(()),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
214pub struct FieldSpec {
215 pub name: Option<String>,
217 pub modifiers: Vec<Modifier>,
219}
220
221impl FieldSpec {
222 pub fn new(name: Option<String>, modifiers: Vec<Modifier>) -> Self {
223 FieldSpec { name, modifiers }
224 }
225
226 pub fn has_modifier(&self, m: Modifier) -> bool {
227 self.modifiers.contains(&m)
228 }
229
230 pub fn is_keyword(&self) -> bool {
231 self.name.is_none()
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
246pub enum ConditionExpr {
247 And(Vec<ConditionExpr>),
249 Or(Vec<ConditionExpr>),
251 Not(Box<ConditionExpr>),
253 Identifier(String),
255 Selector {
257 quantifier: Quantifier,
258 pattern: SelectorPattern,
259 },
260}
261
262impl fmt::Display for ConditionExpr {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 match self {
265 ConditionExpr::And(args) => {
266 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
267 write!(f, "({})", parts.join(" and "))
268 }
269 ConditionExpr::Or(args) => {
270 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
271 write!(f, "({})", parts.join(" or "))
272 }
273 ConditionExpr::Not(arg) => write!(f, "not {arg}"),
274 ConditionExpr::Identifier(id) => write!(f, "{id}"),
275 ConditionExpr::Selector {
276 quantifier,
277 pattern,
278 } => write!(f, "{quantifier} of {pattern}"),
279 }
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
285pub enum Quantifier {
286 Any,
288 All,
290 Count(u64),
292}
293
294impl fmt::Display for Quantifier {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 Quantifier::Any => write!(f, "1"),
298 Quantifier::All => write!(f, "all"),
299 Quantifier::Count(n) => write!(f, "{n}"),
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
306pub enum SelectorPattern {
307 Them,
309 Pattern(String),
311}
312
313impl fmt::Display for SelectorPattern {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 match self {
316 SelectorPattern::Them => write!(f, "them"),
317 SelectorPattern::Pattern(p) => write!(f, "{p}"),
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Serialize)]
335pub struct DetectionItem {
336 pub field: FieldSpec,
338 pub values: Vec<SigmaValue>,
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize)]
349pub enum Detection {
350 AllOf(Vec<DetectionItem>),
352 AnyOf(Vec<Detection>),
354 Keywords(Vec<SigmaValue>),
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize)]
364pub struct Detections {
365 pub named: HashMap<String, Detection>,
367 pub conditions: Vec<ConditionExpr>,
369 pub condition_strings: Vec<String>,
371 pub timeframe: Option<String>,
373}
374
375#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
383pub struct LogSource {
384 pub category: Option<String>,
385 pub product: Option<String>,
386 pub service: Option<String>,
387 pub definition: Option<String>,
388 #[serde(flatten)]
390 pub custom: HashMap<String, String>,
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
399pub struct Related {
400 pub id: String,
401 pub relation_type: RelationType,
402}
403
404#[derive(Debug, Clone, PartialEq, Serialize)]
412pub struct SigmaRule {
413 pub title: String,
415 pub logsource: LogSource,
416 pub detection: Detections,
417
418 pub id: Option<String>,
420 pub name: Option<String>,
421 pub related: Vec<Related>,
422 pub taxonomy: Option<String>,
423 pub status: Option<Status>,
424 pub description: Option<String>,
425 pub license: Option<String>,
426 pub author: Option<String>,
427 pub references: Vec<String>,
428 pub date: Option<String>,
429 pub modified: Option<String>,
430 pub fields: Vec<String>,
431 pub falsepositives: Vec<String>,
432 pub level: Option<Level>,
433 pub tags: Vec<String>,
434 pub scope: Vec<String>,
435
436 #[serde(skip_serializing_if = "HashMap::is_empty")]
447 pub custom_attributes: HashMap<String, serde_yaml::Value>,
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
458#[serde(rename_all = "snake_case")]
459pub enum CorrelationType {
460 EventCount,
461 ValueCount,
462 Temporal,
463 TemporalOrdered,
464 ValueSum,
465 ValueAvg,
466 ValuePercentile,
467 ValueMedian,
468}
469
470impl FromStr for CorrelationType {
471 type Err = ();
472 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
473 match s {
474 "event_count" => Ok(CorrelationType::EventCount),
475 "value_count" => Ok(CorrelationType::ValueCount),
476 "temporal" => Ok(CorrelationType::Temporal),
477 "temporal_ordered" => Ok(CorrelationType::TemporalOrdered),
478 "value_sum" => Ok(CorrelationType::ValueSum),
479 "value_avg" => Ok(CorrelationType::ValueAvg),
480 "value_percentile" => Ok(CorrelationType::ValuePercentile),
481 "value_median" => Ok(CorrelationType::ValueMedian),
482 _ => Err(()),
483 }
484 }
485}
486
487#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
491pub enum ConditionOperator {
492 Lt,
493 Lte,
494 Gt,
495 Gte,
496 Eq,
497 Neq,
498}
499
500impl FromStr for ConditionOperator {
501 type Err = ();
502 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
503 match s {
504 "lt" => Ok(ConditionOperator::Lt),
505 "lte" => Ok(ConditionOperator::Lte),
506 "gt" => Ok(ConditionOperator::Gt),
507 "gte" => Ok(ConditionOperator::Gte),
508 "eq" => Ok(ConditionOperator::Eq),
509 "neq" => Ok(ConditionOperator::Neq),
510 _ => Err(()),
511 }
512 }
513}
514
515#[derive(Debug, Clone, PartialEq, Serialize)]
519pub enum CorrelationCondition {
520 Threshold {
525 predicates: Vec<(ConditionOperator, u64)>,
527 field: Option<String>,
529 },
530 Extended(ConditionExpr),
532}
533
534#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
540pub struct FieldAlias {
541 pub alias: String,
542 pub mapping: HashMap<String, String>,
544}
545
546#[derive(Debug, Clone, PartialEq, Serialize)]
550pub struct CorrelationRule {
551 pub title: String,
553 pub id: Option<String>,
554 pub name: Option<String>,
555 pub status: Option<Status>,
556 pub description: Option<String>,
557 pub author: Option<String>,
558 pub date: Option<String>,
559 pub modified: Option<String>,
560 pub references: Vec<String>,
561 pub taxonomy: Option<String>,
562 pub tags: Vec<String>,
563 pub falsepositives: Vec<String>,
564 pub level: Option<Level>,
565
566 pub correlation_type: CorrelationType,
568 pub rules: Vec<String>,
569 pub group_by: Vec<String>,
570 pub timespan: Timespan,
571 pub condition: CorrelationCondition,
572 pub aliases: Vec<FieldAlias>,
573 pub generate: bool,
574
575 #[serde(skip_serializing_if = "HashMap::is_empty")]
583 pub custom_attributes: HashMap<String, serde_yaml::Value>,
584}
585
586#[derive(Debug, Clone, PartialEq, Serialize)]
595pub struct FilterRule {
596 pub title: String,
597 pub id: Option<String>,
598 pub name: Option<String>,
599 pub status: Option<Status>,
600 pub description: Option<String>,
601 pub author: Option<String>,
602 pub date: Option<String>,
603 pub modified: Option<String>,
604 pub logsource: Option<LogSource>,
605
606 pub rules: Vec<String>,
608 pub detection: Detections,
610}
611
612#[derive(Debug, Clone, PartialEq, Serialize)]
621pub enum SigmaDocument {
622 Rule(Box<SigmaRule>),
623 Correlation(CorrelationRule),
624 Filter(FilterRule),
625}
626
627#[derive(Debug, Clone, Serialize)]
629pub struct SigmaCollection {
630 pub rules: Vec<SigmaRule>,
631 pub correlations: Vec<CorrelationRule>,
632 pub filters: Vec<FilterRule>,
633 #[serde(skip)]
635 pub errors: Vec<String>,
636}
637
638impl SigmaCollection {
639 pub fn new() -> Self {
640 SigmaCollection {
641 rules: Vec::new(),
642 correlations: Vec::new(),
643 filters: Vec::new(),
644 errors: Vec::new(),
645 }
646 }
647
648 pub fn len(&self) -> usize {
650 self.rules.len() + self.correlations.len() + self.filters.len()
651 }
652
653 pub fn is_empty(&self) -> bool {
654 self.len() == 0
655 }
656}
657
658impl Default for SigmaCollection {
659 fn default() -> Self {
660 Self::new()
661 }
662}