usiem/events/
log.rs

1use crate::prelude::{types::LogString, SiemField, SiemIp};
2use serde::{Deserialize, Serialize};
3use std::collections::{BTreeMap, BTreeSet};
4
5use super::ifield::InternalField;
6
7//use serde::ser::{Serializer, SerializeStruct};
8
9/// This is a simple log event. It contains information about the asset that generated
10/// this log, the client if we are working in a multi-client environments aka SOC,
11/// some fields to facilitate correlation with SIGMA rules, timestamps and tags to
12/// better describe the content inside.
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct SiemLog {
15    #[serde(skip, default)]
16    /// Tags to better describe the event.Must be in lowercase. Ex: vip_user, critical_asset, fake_account, honeypot
17    tags: BTreeSet<LogString>,
18    /// Map of fields extracted or generated for this log. Must follow the Elastic Common Schema (ECS v1.x)
19    #[serde(flatten)]
20    pub(crate) fields: BTreeMap<LogString, InternalField>,
21    #[serde(skip, default)]
22    ip_fields: BTreeSet<LogString>,
23}
24
25impl<'a> SiemLog {
26    pub fn new<S, M>(message: M, received: i64, origin: S) -> SiemLog
27    where
28        S: Into<LogString>,
29        M: Into<String>,
30    {
31        let cw = origin.into();
32        let ms = message.into();
33        let mut fields = BTreeMap::new();
34        fields.insert(
35            LogString::Borrowed("message"),
36            SiemField::Text(LogString::Owned(ms)).into(),
37        );
38        fields.insert(LogString::Borrowed("origin"), SiemField::Text(cw).into());
39        fields.insert(
40            LogString::Borrowed("event.created"),
41            SiemField::Date(received).into(),
42        );
43        fields.insert(
44            LogString::Borrowed("event.received"),
45            SiemField::Date(received).into(),
46        );
47        SiemLog {
48            tags: BTreeSet::default(),
49            fields,
50            ip_fields: BTreeSet::new(),
51        }
52    }
53
54    pub fn message(&'a self) -> &'a str {
55        match self.field("message") {
56            Some(SiemField::Text(v)) => v,
57            _ => "",
58        }
59    }
60    pub fn set_message(&mut self, msg: String) {
61        self.fields.insert(
62            LogString::Borrowed("message"),
63            SiemField::Text(LogString::Owned(msg)).into(),
64        );
65    }
66    pub fn origin(&'a self) -> &'a str {
67        match self.field("origin") {
68            Some(SiemField::Text(v)) => v,
69            _ => "",
70        }
71    }
72    pub fn set_origin(&mut self, msg: LogString) {
73        self.fields
74            .insert(LogString::Borrowed("origin"), SiemField::Text(msg).into());
75    }
76    pub fn tenant(&'a self) -> &'a str {
77        match self.field("tenant") {
78            Some(SiemField::Text(v)) => v,
79            _ => "",
80        }
81    }
82    pub fn set_tenant<S>(&mut self, tenant: S)
83    where
84        S: Into<LogString>,
85    {
86        self.fields.insert(
87            LogString::Borrowed("tenant"),
88            SiemField::Text(tenant.into()).into(),
89        );
90    }
91    /// Name of the product for wich the log belongs. Ex: ASA
92    pub fn product(&'a self) -> &'a str {
93        match self.field("product") {
94            Some(SiemField::Text(v)) => v,
95            _ => "",
96        }
97    }
98    pub fn set_product<S>(&mut self, product: S)
99    where
100        S: Into<LogString>,
101    {
102        let product = product.into();
103        self.fields.insert(
104            LogString::Borrowed("product"),
105            SiemField::Text(product.clone()).into(),
106        );
107    }
108    /// Subset of the product logs. Like a OS that can have multiple programs running inside generating multiple logs.
109    pub fn service(&'a self) -> &'a str {
110        match self.field("service") {
111            Some(SiemField::Text(v)) => v,
112            _ => "",
113        }
114    }
115
116    pub fn set_service<S>(&mut self, service: S)
117    where
118        S: Into<LogString>,
119    {
120        let service = service.into();
121        self.fields.insert(
122            LogString::Borrowed("service"),
123            SiemField::Text(service.clone()).into(),
124        );
125    }
126    /// Category of the device: Firewall, web, antivirus
127    pub fn category(&'a self) -> &'a str {
128        match self.field("category") {
129            Some(SiemField::Text(v)) => v,
130            _ => "",
131        }
132    }
133    pub fn set_category<S>(&mut self, category: S)
134    where
135        S: Into<LogString>,
136    {
137        let category = category.into();
138        self.fields.insert(
139            LogString::Borrowed("category"),
140            SiemField::Text(category.clone()).into(),
141        );
142    }
143    /// Company that created the product. Ex: Cisco
144    pub fn vendor(&'a self) -> &'a str {
145        self.field("vendor")
146            .map(|v| match v {
147                SiemField::Text(v) => v,
148                _ => "",
149            })
150            .unwrap_or("")
151    }
152    pub fn set_vendor<S>(&mut self, vendor: S)
153    where
154        S: Into<LogString>,
155    {
156        let vendor = vendor.into();
157        self.fields.insert(
158            LogString::Borrowed("vendor"),
159            SiemField::Text(vendor.clone()).into(),
160        );
161    }
162    /// Timestamp at witch the log arrived in milliseconds since UNIX
163    pub fn event_received(&'a self) -> i64 {
164        match self.field("event.received") {
165            Some(SiemField::Date(v)) => *v,
166            _ => 0,
167        }
168    }
169    /// Timestamp at witch the log was generated. The clocks at origin must be correctly configured.
170    pub fn event_created(&'a self) -> i64 {
171        match self.field("event.created") {
172            Some(SiemField::Date(v)) => *v,
173            _ => 0,
174        }
175    }
176    pub fn set_event_created(&mut self, date: i64) {
177        self.fields.insert(
178            LogString::Borrowed("event.created"),
179            SiemField::I64(date).into(),
180        );
181    }
182    pub fn has_tag(&self, tag: &str) -> bool {
183        self.tags.contains(tag)
184    }
185    pub fn add_tag(&mut self, tag: &str) {
186        self.tags.insert(LogString::Owned(tag.to_lowercase()));
187        self.fields.insert(
188            LogString::Borrowed("tags"),
189            SiemField::Array(
190                self.tags
191                    .iter()
192                    .map(|x| LogString::Owned(x.to_lowercase()))
193                    .collect::<Vec<LogString>>(),
194            )
195            .into(),
196        );
197    }
198    pub fn tags(&'a self) -> &'a BTreeSet<LogString> {
199        &self.tags
200    }
201    pub fn field(&'a self, field_name: &str) -> Option<&SiemField> {
202        Some(&self.fields.get(field_name)?.original)
203    }
204    pub fn field_mut(&'a mut self, field_name: &str) -> Option<&mut SiemField> {
205        Some(&mut self.fields.get_mut(field_name)?.original)
206    }
207    pub fn add_field(&mut self, field_name: &str, field_value: SiemField) {
208        let field_name = LogString::Owned(field_name.to_owned());
209        self.insert(field_name, field_value);
210    }
211    pub fn insert(&mut self, field_name: LogString, field_value: SiemField) {
212        if let SiemField::IP(_) = &field_value {
213            self.ip_fields.insert(field_name.clone());
214        }
215        self.fields.insert(field_name, field_value.into());
216    }
217    pub fn has_field(&self, field_name: &str) -> bool {
218        self.fields.contains_key(field_name)
219    }
220    pub fn fields(&self) -> EventIter<'_> {
221        EventIter {
222            children: self.fields.iter(),
223        }
224    }
225    pub fn iter(&self) -> EventIter<'_> {
226        EventIter {
227            children: self.fields.iter(),
228        }
229    }
230    pub fn iter_mut(&mut self) -> EventIterMut<'_> {
231        EventIterMut {
232            children: self.fields.iter_mut(),
233        }
234    }
235    pub fn ip_fields(&self) -> EventFieldIter<'_> {
236        EventFieldIter {
237            names: self.ip_fields.iter(),
238            fields: &self.fields,
239        }
240    }
241    /// Obtains the casted value of the field into i64 and caches it
242    pub fn i64_field(&'a mut self, field_name: &str) -> Option<i64> {
243        let field = self.fields.get_mut(field_name)?;
244        match field.ni64.as_ref() {
245            super::ifield::PreStoredField::Invalid => return None,
246            super::ifield::PreStoredField::None => {}
247            super::ifield::PreStoredField::Some(v) => return Some(*v),
248        };
249        let i64field: Option<i64> = (&field.original).try_into().ok();
250        let pfield = match i64field {
251            Some(v) => super::ifield::PreStoredField::Some(v),
252            None => super::ifield::PreStoredField::Invalid,
253        };
254        field.ni64 = Box::new(pfield);
255        match field.ni64.as_ref() {
256            super::ifield::PreStoredField::Some(v) => Some(*v),
257            _ => None,
258        }
259    }
260    /// Obtains the casted value of the field into f64 and caches it
261    pub fn f64_field(&'a mut self, field_name: &str) -> Option<f64> {
262        let field = self.fields.get_mut(field_name)?;
263        match field.nf64.as_ref() {
264            super::ifield::PreStoredField::Invalid => return None,
265            super::ifield::PreStoredField::None => {}
266            super::ifield::PreStoredField::Some(v) => return Some(*v),
267        };
268        let i64field: Option<f64> = (&field.original).try_into().ok();
269        let pfield = match i64field {
270            Some(v) => super::ifield::PreStoredField::Some(v),
271            None => super::ifield::PreStoredField::Invalid,
272        };
273        field.nf64 = Box::new(pfield);
274        match field.nf64.as_ref() {
275            super::ifield::PreStoredField::Some(v) => Some(*v),
276            _ => None,
277        }
278    }
279    /// Obtains the casted value of the field into u64 and caches it
280    pub fn u64_field(&'a mut self, field_name: &str) -> Option<u64> {
281        let field = self.fields.get_mut(field_name)?;
282        match field.nu64.as_ref() {
283            super::ifield::PreStoredField::Invalid => return None,
284            super::ifield::PreStoredField::None => {}
285            super::ifield::PreStoredField::Some(v) => return Some(*v),
286        };
287        let i64field: Option<u64> = (&field.original).try_into().ok();
288        let pfield = match i64field {
289            Some(v) => super::ifield::PreStoredField::Some(v),
290            None => super::ifield::PreStoredField::Invalid,
291        };
292        field.nu64 = Box::new(pfield);
293        match field.nu64.as_ref() {
294            super::ifield::PreStoredField::Some(v) => Some(*v),
295            _ => None,
296        }
297    }
298    /// Obtains the casted value of the field into IP and caches it
299    pub fn ip_field(&'a mut self, field_name: &str) -> Option<SiemIp> {
300        let field = self.fields.get_mut(field_name)?;
301        match field.ip.as_ref() {
302            super::ifield::PreStoredField::Invalid => return None,
303            super::ifield::PreStoredField::None => {}
304            super::ifield::PreStoredField::Some(v) => return Some(*v),
305        };
306        let i64field: Option<SiemIp> = (&field.original).try_into().ok();
307        let pfield = match i64field {
308            Some(v) => super::ifield::PreStoredField::Some(v),
309            None => super::ifield::PreStoredField::Invalid,
310        };
311        field.ip = Box::new(pfield);
312        match field.ip.as_ref() {
313            super::ifield::PreStoredField::Some(v) => Some(*v),
314            _ => None,
315        }
316    }
317    /// Obtains the casted value of the field into LogString and caches it
318    pub fn txt_field(&'a mut self, field_name: &str) -> Option<&LogString> {
319        let mut has_value = false;
320
321        let field = self.fields.get_mut(field_name)?;
322        match field.text.as_ref() {
323            super::ifield::PreStoredField::Invalid => return None,
324            super::ifield::PreStoredField::None => {}
325            super::ifield::PreStoredField::Some(_) => {
326                has_value = true;
327            }
328        };
329        if has_value {
330            match field.text.as_ref() {
331                super::ifield::PreStoredField::Some(v) => return Some(v),
332                _ => return None,
333            }
334        }
335        let txtfield: Option<LogString> = (&field.original).try_into().ok();
336        let pfield = match txtfield {
337            Some(v) => super::ifield::PreStoredField::Some(v),
338            None => super::ifield::PreStoredField::Invalid,
339        };
340        field.text = Box::new(pfield);
341        match field.text.as_ref() {
342            super::ifield::PreStoredField::Some(v) => Some(v),
343            _ => None,
344        }
345    }
346    /// Obtains the casted value of the field into Vec<LogString> and caches it
347    pub fn array_field(&'a mut self, field_name: &str) -> Option<&Vec<LogString>> {
348        let mut has_value = false;
349
350        let field = self.fields.get_mut(field_name)?;
351        match field.array.as_ref() {
352            super::ifield::PreStoredField::Invalid => return None,
353            super::ifield::PreStoredField::None => {}
354            super::ifield::PreStoredField::Some(_) => {
355                has_value = true;
356            }
357        };
358        if has_value {
359            match field.array.as_ref() {
360                super::ifield::PreStoredField::Some(v) => return Some(v),
361                _ => return None,
362            }
363        }
364        let txtfield: Option<Vec<LogString>> = (&field.original).try_into().ok();
365        let pfield = match txtfield {
366            Some(v) => super::ifield::PreStoredField::Some(v),
367            None => super::ifield::PreStoredField::Invalid,
368        };
369        field.array = Box::new(pfield);
370        match field.array.as_ref() {
371            super::ifield::PreStoredField::Some(v) => Some(v),
372            _ => None,
373        }
374    }
375}
376
377pub struct EventIter<'a> {
378    children: std::collections::btree_map::Iter<'a, LogString, InternalField>,
379}
380pub struct EventFieldIter<'a> {
381    names: std::collections::btree_set::Iter<'a, LogString>,
382    fields: &'a BTreeMap<LogString, InternalField>,
383}
384
385pub struct EventIterMut<'a> {
386    children: std::collections::btree_map::IterMut<'a, LogString, InternalField>,
387}
388
389impl<'a> Iterator for EventIter<'a> {
390    type Item = (&'a LogString, &'a SiemField);
391
392    fn next(&mut self) -> Option<Self::Item> {
393        let evt = self.children.next()?;
394        Some((evt.0, &evt.1.original))
395    }
396}
397impl<'a> Iterator for EventIterMut<'a> {
398    type Item = (&'a LogString, &'a mut SiemField);
399
400    fn next(&mut self) -> Option<Self::Item> {
401        let evt = self.children.next()?;
402        Some((evt.0, &mut evt.1.original))
403    }
404}
405impl<'a> Iterator for EventFieldIter<'a> {
406    type Item = (&'a LogString, &'a SiemField);
407
408    fn next(&mut self) -> Option<Self::Item> {
409        let field = self.names.next()?;
410        let value = self.fields.get(field)?;
411        Some((field, &value.original))
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418    use crate::prelude::event::SiemEvent;
419    use crate::prelude::{FirewallEvent, FirewallOutcome, NetworkProtocol, SiemIp};
420
421    #[test]
422    fn check_log() {
423        let event = SiemEvent::Firewall(FirewallEvent {
424            source_ip: SiemIp::V4(0),
425            destination_ip: SiemIp::V4(10000),
426            source_port: 10000,
427            destination_port: 443,
428            outcome: FirewallOutcome::ALLOW,
429            in_bytes: 0,
430            out_bytes: 0,
431            in_interface: LogString::Borrowed("in123"),
432            out_interface: LogString::Borrowed("out123"),
433            network_protocol: NetworkProtocol::TCP,
434        });
435        let mut log: SiemLog = event.into();
436        log.set_message("<134>Aug 23 20:30:25 OPNsense.localdomain filterlog[21853]: 82,,,0,igb0,match,pass,out,4,0x0,,62,25678,0,DF,17,udp,60,192.168.1.8,8.8.8.8,5074,53,40".to_string());
437        log.set_origin("localhost".into());
438        log.add_field(
439            "event.dataset",
440            SiemField::Text(LogString::Borrowed("filterlog")),
441        );
442        let val: &str = log.field("event.dataset").unwrap().try_into().unwrap();
443        assert_eq!("filterlog", val);
444    }
445
446    #[test]
447    fn casting_between_fields() {
448        let mut log = SiemLog::new("", 0, "");
449        let (name, value) = ("field_1", "value_1");
450        log.add_field(name, value.into());
451        assert_eq!(value, log.txt_field(name).unwrap());
452
453        let (name, value) = ("field_1", 100u64);
454        log.add_field(name, value.into());
455        assert_eq!(value as u64, log.u64_field(name).unwrap());
456        assert_eq!(value as i64, log.i64_field(name).unwrap());
457        assert_eq!(value as f64, log.f64_field(name).unwrap());
458
459        let (name, value) = ("field_1", -200i64);
460        log.add_field(name, value.into());
461        assert_eq!(value as u64, log.u64_field(name).unwrap());
462        assert_eq!(value as i64, log.i64_field(name).unwrap());
463        assert_eq!(value as f64, log.f64_field(name).unwrap());
464
465        let (name, value) = ("field_1", 300.512f64);
466        log.add_field(name, value.clone().into());
467        assert_eq!(value as u64, log.u64_field(name).unwrap());
468        assert_eq!(value as i64, log.i64_field(name).unwrap());
469        assert_eq!(value as f64, log.f64_field(name).unwrap());
470
471        let (name, value) = ("field_1", SiemIp::V4(1234));
472        log.add_field(name, value.clone().into());
473        assert_eq!(value, log.ip_field(name).unwrap());
474
475        let (name, value): (&'static str, Vec<LogString>) =
476            ("field_1", vec!["value_001".into(), "value_002".into()]);
477        log.add_field(name, value.clone().into());
478        assert_eq!(&value, log.array_field(name).unwrap());
479    }
480}