u-siem 0.7.0

A framework for building custom SIEMs
Documentation
use dyn_clone::{clone_trait_object, DynClone};
use serde::{Deserialize, Serialize};

use crate::{
    events::{schema::FieldSchema, SiemLog},
    prelude::SiemIp,
};

use super::dataset::holder::DatasetHolder;

/// A simple object with the logic to parse Logs.
pub trait LogParser: DynClone + Send {
    /// Parse the log. If it fails it must give a reason why. This allow optimization of the parsing process.
    fn parse_log(&self, log: SiemLog, datasets: &DatasetHolder)
        -> Result<SiemLog, LogParsingError>;
    /// Name of the parser
    fn name(&self) -> &'static str;
    /// Description of the parser
    fn description(&self) -> &'static str;
    /// Get parser schema
    fn schema(&self) -> &FieldSchema;
    /// Get a log generator to test this parser
    fn generator(&self) -> Box<dyn LogGenerator>;
}
clone_trait_object!(LogParser);

/// This is the most complex type of parser. It's statefull to store past logs.
/// Think of the USB event in linux, we need the rest of the logs to extract all information.
/// The Parser component which uses this parsers must be able to store and load past Logs
/// if the user connects to a different SIEM node (LoadBalancing).
pub trait MultilineLogParser: DynClone + Send {
    /// Parse the log. If it fails it must give a reason why. This allow optimization of the parsing process.
    fn parse_log(
        &mut self,
        log: SiemLog,
        datasets: &DatasetHolder,
    ) -> Result<Option<SiemLog>, LogParsingError>;
    /// Name of the parser
    fn name(&self) -> &'static str;
    /// Description of the parser
    fn description(&self) -> &'static str;
    /// The connection with the origin has been closed. We must preserve the logs stored inside this parser
    /// so another node can use them to parse the logs of the same machine.
    fn cleaning(&mut self) -> Vec<SiemLog>;
    /// Return those logs that would not be used by the parser, or are older as to reduce the memmory usage.
    fn unused(&mut self) -> Vec<SiemLog>;
    /// Get parser schema
    fn schema(&self) -> &FieldSchema;
}

clone_trait_object!(MultilineLogParser);

/// Error at parsing a log
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum LogParsingError {
    /// The parser can't be used with this log
    NoValidParser(SiemLog),
    /// The log is for this parser but there is a bug in the code
    ParserError(SiemLog, String),
    /// The log is for this parser but the submodule has not been implemented.
    NotImplemented(SiemLog),
    /// The log has change format the parser cant process it.
    FormatError(SiemLog, String),
    /// Log was discarded. It does not have utility or there are storage limitations.
    Discard,
}

pub trait LogGenerator {
    fn configure(&mut self, config: GeneratorConfig);
    /// Generate a random log
    fn log(&self) -> String;
    /// Of the total overall logs that are generated in an organization,
    /// whats the procentage of logs generated by this source?
    /// The bigger, the most probability of being generated
    fn weight(&self) -> u8;
}

/// Helps to generate more realistic logs and use them to match rules and trigger alerts
pub struct GeneratorConfig {
    pub malicious_users: Vec<String>,
    pub user_generator: Box<dyn Fn() -> String>,
    pub public_networks: Vec<(SiemIp, u32)>,
    pub local_networks: Vec<(SiemIp, u32)>,
    pub domain: String,
    pub hostname_generator: Box<dyn Fn() -> String>,
    pub malicious_ips: Vec<SiemIp>,
}

impl Default for GeneratorConfig {
    fn default() -> Self {
        use std::time::{SystemTime, UNIX_EPOCH};
        // Poor mans random generator...
        Self {
            malicious_users: Vec::new(),
            user_generator: Box::new(|| {
                let nanos = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .subsec_nanos();
                let nanos = nanos & 0xffff;
                format!("User{}", nanos)
            }),
            public_networks: Default::default(),
            local_networks: vec![([192, 168, 1, 1].into(), 32)],
            domain: "Contoso".into(),
            hostname_generator: Box::new(|| {
                let nanos = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .subsec_nanos();
                let nanos = nanos & 0xffff;
                format!("Host{}", nanos)
            }),
            malicious_ips: Default::default(),
        }
    }
}