diff --git a/README.md b/README.md index e3525fb..f06d5bf 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,5 @@ pub enum WebProxyRuleCategory { - [x] SIGMA rules support. - [x] Enforced storage schema for logs. Each source extracts multiple fields with different names. In elastic its not recomended to have more than 1000 fields. Also, it must allow renaming of fields because ECS uses dots in the field names but the majority of databases cant. - [ ] GDPR included for logs: An analyst does not need to know information about users, such as the websites they visit or the emails they receive. Integrated into the Storage Schema, like the url related fields must be stored encrypted for WebProxy events or the email.subject, email.files or email.source.user.name for Mail events. -- [ ] Mantaince calendar. Used to disable alerting of events related to device configurations, like FortiSIEM does. \ No newline at end of file +- [ ] Mantaince calendar. Used to disable alerting of events related to device configurations, like FortiSIEM does. +- [x] Internacionalization of texts \ No newline at end of file diff --git a/src/components/command_types.rs b/src/components/command_types.rs index bd57152..366eb0f 100644 --- a/src/components/command_types.rs +++ b/src/components/command_types.rs @@ -69,7 +69,14 @@ pub struct UseCaseDefinition { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LoginUser { +#[non_exhaustive] +pub enum LoginUser { + Password(LoginUserPass), + ApiKey(String) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LoginUserPass { pub username: String, pub password: String, } diff --git a/src/components/dataset/i18n.rs b/src/components/dataset/i18n.rs new file mode 100644 index 0000000..ebfc2b2 --- /dev/null +++ b/src/components/dataset/i18n.rs @@ -0,0 +1,196 @@ +use crate::prelude::types::LogString; +use crossbeam_channel::Sender; +use serde::{Serialize, Deserialize}; +use std::collections::BTreeMap; +use std::sync::Arc; + +#[derive(Serialize, Debug)] +pub enum UpdateI18n { + Add((LogString, LogString)), + Remove(LogString), + Replace(I18nDataset), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialOrd, PartialEq, Ord, Eq)] +pub enum Language { + ES, + #[default] + EN, + FR, + Other(&'static str) +} +impl Copy for Language { + +} + +#[derive(Debug, Clone)] +pub struct I18nSynDataset { + dataset: Arc, + comm: Sender, +} +impl I18nSynDataset { + pub fn new(dataset: Arc, comm: Sender) -> I18nSynDataset { + return I18nSynDataset { dataset, comm }; + } + pub fn empty() -> Self { + let (sender, _) = crossbeam_channel::bounded(1); + + return Self { dataset : Arc::new(I18nDataset::new()), comm : sender }; + } + pub fn insert(&self, key: S, data: S) + where + S: Into, + { + match self + .comm + .try_send(UpdateI18n::Add((key.into(), data.into()))) + { + Ok(_) => {} + Err(_) => {} + }; + } + pub fn remove(&self, key: S) + where + S: Into, + { + // Todo: improve with local cache to send retries + match self.comm.try_send(UpdateI18n::Remove(key.into())) { + Ok(_) => {} + Err(_) => {} + }; + } + pub fn update(&self, data: I18nDataset) { + // Todo: improve with local cache to send retries + match self.comm.try_send(UpdateI18n::Replace(data)) { + Ok(_) => {} + Err(_) => {} + }; + } + pub fn get(&self, text: &LogString, language : &Language) -> Option<&LogString> { + // Todo improve with cached content + self.dataset.get(text, language) + } + /// Obtains a text from the language library + /// + /// ```rust + /// use usiem::prelude::i18n::*; + /// use usiem::prelude::types::LogString; + /// let mut dataset = I18nDataset::new(); + /// dataset.insert( + /// Language::ES, + /// LogString::Borrowed("LocalIp"), + /// LogString::Borrowed("IP Local"), + /// ); + /// dataset.insert( + /// Language::EN, + /// LogString::Borrowed("LocalIp"), + /// LogString::Borrowed("Local IP"), + /// ); + /// assert_eq!( + /// dataset.get_or_default("LocalIp", &Language::ES), + /// Some(&LogString::Borrowed("IP Local")) + /// ); + /// assert_eq!( + /// dataset.get_or_default("LocalIp", &Language::FR), + /// Some(&LogString::Borrowed("Local IP")) + /// ); + /// ``` + pub fn get_or_default(&self, text: &str, language : &Language) -> Option<&LogString> { + self.dataset.get_or_default(text, language) + } +} +#[derive(Serialize, Debug)] +pub struct I18nDataset { + /// Language -> Text -> Value + data: BTreeMap>, +} + +impl I18nDataset { + pub fn new() -> I18nDataset { + return I18nDataset { + data: BTreeMap::new(), + }; + } + pub fn set_dictionary(&mut self, language: Language, data: M) + where + M: Into>, + { + let new_data : BTreeMap = data.into(); + for (k, value) in new_data { + if self.data.contains_key(&k) { + let map = match self.data.get_mut(&k) { + Some(v) => v, + None => return + }; + map.insert(language, value); + }else { + let mut map = BTreeMap::new(); + map.insert(language, value); + self.data.insert(k, map); + } + } + } + pub fn insert(&mut self, language: Language, text: LogString, value : LogString){ + if self.data.contains_key(&text) { + let map = match self.data.get_mut(&text) { + Some(v) => v, + None => return + }; + map.insert(language, value); + }else { + let mut map = BTreeMap::new(); + map.insert(language, value); + self.data.insert(text, map); + } + } + pub fn get(&self, text: &str, language : &Language) -> Option<&LogString> { + self.data.get(text)?.get(language) + } + pub fn get_or_default(&self, text: &str, language : &Language) -> Option<&LogString> { + let langs = self.data.get(text)?; + + match langs.get(language){ + Some(lang) => Some(lang), + None => { + match langs.get(&Language::EN) { + Some(lang) => Some(lang), + None => { + // Return first to be found + Some(langs.first_key_value()?.1) + } + } + } + } + } + pub fn internal_ref(&self) -> &BTreeMap> { + &self.data + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[test] + fn should_find_data_in_map() { + let mut dataset = I18nDataset::new(); + dataset.insert( + Language::ES, + LogString::Borrowed("LocalIp"), + LogString::Borrowed("IP Local"), + ); + dataset.insert( + Language::EN, + LogString::Borrowed("LocalIp"), + LogString::Borrowed("Local IP"), + ); + assert_eq!( + dataset.get_or_default("LocalIp", &Language::ES), + Some(&LogString::Borrowed("IP Local")) + ); + assert_eq!( + dataset.get_or_default("LocalIp", &Language::FR), + Some(&LogString::Borrowed("Local IP")) + ); + } +} diff --git a/src/components/dataset/mod.rs b/src/components/dataset/mod.rs index 33c9365..1568fd8 100644 --- a/src/components/dataset/mod.rs +++ b/src/components/dataset/mod.rs @@ -9,6 +9,7 @@ pub mod rules; pub mod text_map; pub mod text_map_list; pub mod text_set; +pub mod i18n; use crate::prelude::types::LogString; use calendar::{CalendarSynDataset, UpdateCalendar}; @@ -25,6 +26,7 @@ use std::fmt; use text_map::{TextMapSynDataset, UpdateTextMap}; use text_map_list::{TextMapListSynDataset, UpdateTextMapList}; use text_set::{TextSetSynDataset, UpdateTextSet}; +use i18n::{UpdateI18n, I18nSynDataset}; /// Commonly used datasets. They are filled with the information extracted form logs, from the CMDB, from user commands or from repetitive Task like GeoIP. /// Dataset are used, but not exclusivally, in the enrichment phase. @@ -84,6 +86,8 @@ pub enum SiemDataset { MantainceCalendar(CalendarSynDataset), /// Configuration of components. Allows modifications of component parameters in real time. Configuration(TextMapSynDataset), + /// Internacionalization of SIEM texts + I18n(I18nSynDataset), /// Secret store. A component will only be able to access his own secrets. Secrets((LogString, TextMapSynDataset)), } @@ -661,6 +665,8 @@ pub enum SiemDatasetType { MantainceCalendar, Configuration, Secrets(LogString), + /// Internacionalization + I18n } impl SiemDataset { @@ -693,7 +699,8 @@ impl SiemDataset { SiemDataset::CustomTextList((name, _)) => SiemDatasetType::CustomTextList(name.clone()), SiemDataset::CustomMapTextList((name, _)) => { SiemDatasetType::CustomMapTextList(name.clone()) - } + }, + SiemDataset::I18n(_) => SiemDatasetType::I18n, SiemDataset::Secrets((name, _)) => SiemDatasetType::Secrets(name.clone()), } } @@ -750,6 +757,7 @@ impl Serialize for SiemDataset { SiemDataset::MantainceCalendar(_) => "MantainceCalendar", SiemDataset::Configuration(_) => "Configuration", SiemDataset::HostVulnerable(_) => "HostVulnerable", + SiemDataset::I18n(_) => "I18n", SiemDataset::CustomMapIpNet((name, _)) => { state.serialize_field("name", name)?; "CustomMapIpNet" @@ -812,4 +820,5 @@ pub enum UpdateDataset { Secrets(UpdateTextMap), HostVulnerable(UpdateTextMapList), CorrelationRules(UpdateGeoIp), + I18n(UpdateI18n) }