usiem/events/
schema.rs

1use serde::Serialize;
2use std::collections::{BTreeMap, BTreeSet};
3
4use super::SiemLog;
5
6#[path = "base_schema.rs"]
7pub mod base_schema;
8
9/// Data schema that allows indexation of logs with field filtering
10#[derive(Serialize, Debug, Clone)]
11pub struct FieldSchema {
12    pub fields: BTreeMap<&'static str, FieldType>,
13    /// When used in table based ddbb, create an extra column to store the rest of the fields. Maybe a JSON file
14    pub allow_unknown_fields: bool,
15    /// GDPR protection of fields
16    pub gdpr: Option<GdprProtection>,
17}
18
19impl Default for FieldSchema {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl FieldSchema {
26    pub fn new() -> FieldSchema {
27        let mut basic_fields = BTreeMap::new();
28        basic_fields.insert(
29            "origin",
30            FieldType::Ip("IP or Hostname of the server that sent the log"),
31        );
32        basic_fields.insert(
33            "tenant",
34            FieldType::Text("Customer name for SOC environments. Ex: Contoso"),
35        );
36        basic_fields.insert(
37            "product",
38            FieldType::Text("Name of the product for wich the log belongs. Ex: ASA"),
39        );
40        basic_fields.insert("service", FieldType::Text("Subset of the product logs. Like a OS that can have multiple programs running inside generating multiple logs."));
41        basic_fields.insert(
42            "category",
43            FieldType::Text("Category of the device: Firewall, web, antivirus"),
44        );
45        basic_fields.insert(
46            "vendor",
47            FieldType::Text("Company that created the product. Ex: Cisco"),
48        );
49        basic_fields.insert("event.type", FieldType::Text("uSIEM log type: SiemEvent"));
50        basic_fields.insert("tags", FieldType::Text("Tags to better describe the event"));
51        basic_fields.insert(
52            "message",
53            FieldType::Text("Original log message including syslog header"),
54        );
55        basic_fields.insert(
56            "event_received",
57            FieldType::Date("Timestamp at witch the log arrived  "),
58        );
59        basic_fields.insert(
60            "event_created",
61            FieldType::Date("Timestamp at witch the log was generated"),
62        );
63        FieldSchema {
64            fields: basic_fields,
65            allow_unknown_fields: false,
66            gdpr: None,
67        }
68    }
69    pub fn add_schema(&mut self, schema: &FieldSchema) {
70        for (name, element) in &schema.fields {
71            match element {
72                FieldType::TextOptions(list_val, _doc) => match self.fields.get_mut(name) {
73                    Some(FieldType::TextOptions(alredy_val, _doc2)) => {
74                        for (vl_1, vl_2) in list_val {
75                            alredy_val.insert(vl_1, vl_2);
76                        }
77                    }
78                    _ => {
79                        self.fields.insert(name, element.clone());
80                    }
81                },
82                _ => {
83                    self.fields.insert(name, element.clone());
84                }
85            }
86        }
87    }
88    pub fn insert(&mut self, key: &'static str, value: FieldType) -> Option<FieldType> {
89        self.fields.insert(key, value)
90    }
91    pub fn set_gdpr(&mut self, protection: Option<GdprProtection>) {
92        self.gdpr = protection;
93    }
94    pub fn protected_field(&self, field: &str) -> bool {
95        match &self.gdpr {
96            Some(val) => val.fields.contains(field),
97            None => false,
98        }
99    }
100    pub fn get_field(&self, field: &str) -> Option<&FieldType> {
101        self.fields.get(field)
102    }
103    pub fn field_names(&self) -> Vec<String> {
104        let mut to_ret = Vec::with_capacity(self.fields.len());
105        for key in self.fields.keys() {
106            to_ret.push(key.to_string());
107        }
108        to_ret
109    }
110    /// Remove fields from the schema
111    pub fn remove_fields(&mut self, fields: &BTreeSet<&'static str>) {
112        for key in fields.iter() {
113            self.fields.remove(*key);
114        }
115    }
116
117    /// Apply the schema to the log removing fields not contemplated by the schema
118    pub fn apply(&self, log: &mut SiemLog) {
119        log.fields.retain(|k, _v| self.fields.contains_key(&k[..]));
120    }
121}
122
123#[derive(Serialize, Debug, Clone)]
124pub enum FieldType {
125    /// Save IP as text
126    Ip(&'static str),
127    Array(&'static str),
128    /// A basic String field
129    Text(&'static str),
130    /// Signed number with 64 bits
131    Numeric(&'static str),
132    /// Decimal number with 64 bits
133    Decimal(&'static str),
134    /// Date Type
135    Date(&'static str),
136    /// List of posible text values. This is like Text but with autocomplete help
137    TextOptions(BTreeMap<&'static str, &'static str>, &'static str),
138}
139
140#[derive(Serialize, Debug, Clone)]
141pub struct GdprProtection {
142    /// List of fields that must be protected
143    pub fields: BTreeSet<&'static str>,
144    pub method: GdprProtectionMethod,
145}
146
147#[derive(Serialize, Debug, Clone)]
148pub enum GdprProtectionMethod {
149    /// Encrypted at REST or similar
150    Storage,
151    /// Hide field from analysts
152    ApiProtected,
153}