use serde::Serialize;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
pub mod field;
pub mod firewall;
pub mod protocol;
pub mod webproxy;
pub mod common;
pub mod webserver;
pub mod intrusion;
pub mod dns;
pub mod field_dictionary;
pub mod auth;
use field::{SiemField, SiemIp};
use firewall::FirewallEvent;
use webproxy::WebProxyEvent;
use webserver::WebServerEvent;
use intrusion::IntrusionEvent;
use dns::{DnsEvent, DnsEventType};
use auth::{AuthEvent, AuthLoginType};
#[derive(Serialize, Debug)]
#[serde(tag = "type")]
pub enum SiemEvent {
Firewall(FirewallEvent),
Intrusion(IntrusionEvent),
Assessment,
WebProxy(WebProxyEvent),
WebServer(WebServerEvent),
Sandbox,
Antivirus,
DLP,
Partitioned,
EDR,
Mail,
DNS(DnsEvent),
DHCP,
Auth(AuthEvent),
Endpoint,
Json(serde_json::Value),
Unknown,
Artifacts
}
#[derive(Serialize,Debug)]
pub struct SiemLog {
origin: SiemIp,
tenant: Cow<'static, str>,
product: Cow<'static, str>,
service: Cow<'static, str>,
category: Cow<'static, str>,
vendor: Cow<'static, str>,
event: SiemEvent,
tags: BTreeSet<Cow<'static, str>>,
#[serde(flatten)]
fields: BTreeMap<Cow<'static, str>, SiemField>,
message: String,
event_received: i64,
event_created: i64,
}
impl<'a> SiemLog {
pub fn new(message: String, received: i64, origin: SiemIp) -> SiemLog {
SiemLog {
message,
event_received: received,
origin,
tenant: Cow::default(),
product: Cow::default(),
service: Cow::default(),
category: Cow::default(),
vendor: Cow::default(),
event: SiemEvent::Unknown,
tags: BTreeSet::default(),
fields: BTreeMap::default(),
event_created: received,
}
}
pub fn message(&'a self) -> &'a str {
&self.message
}
pub fn origin(&'a self) -> &'a SiemIp {
&self.origin
}
pub fn tenant(&'a self) -> &'a str {
&self.tenant
}
pub fn set_tenant(&mut self, tenant: Cow<'static, str>) {
self.tenant = tenant;
}
pub fn product(&'a self) -> &'a str {
&self.product
}
pub fn set_product(&mut self, val: Cow<'static, str>) {
self.product = val;
}
pub fn service(&'a self) -> &'a str {
&self.service
}
pub fn set_service(&mut self, val: Cow<'static, str>) {
self.service = val;
}
pub fn category(&'a self) -> &'a str {
&self.category
}
pub fn set_category(&mut self, val: Cow<'static, str>) {
self.category = val;
}
pub fn vendor(&'a self) -> &'a str {
&self.vendor
}
pub fn set_vendor(&mut self, val: Cow<'static, str>) {
self.vendor = val;
}
pub fn event_received(&'a self) -> i64 {
self.event_received
}
pub fn event_created(&'a self) -> i64 {
self.event_created
}
pub fn set_event_created(&mut self, date : i64) {
self.event_created = date;
}
pub fn has_tag(&self, tag: &str) -> bool {
self.tags.contains(tag)
}
pub fn add_tag(&mut self, tag: &str) {
self.tags.insert(Cow::Owned(tag.to_lowercase()));
}
pub fn tags(&'a self) -> &'a BTreeSet<Cow<'static, str>> {
&self.tags
}
pub fn field(&'a self, field_name: &str) -> Option<&SiemField> {
self.fields.get(field_name)
}
pub fn add_field(&mut self, field_name: &str, field_value: SiemField) {
self.fields
.insert(Cow::Owned(field_name.to_owned()), field_value);
}
pub fn has_field(&self, field_name: &str) -> bool {
self.fields.contains_key(field_name)
}
pub fn event(&self) -> &SiemEvent {
&self.event
}
pub fn set_event(&mut self, event: SiemEvent) {
match &event {
SiemEvent::Firewall(fw) => {
self.add_field(field_dictionary::SOURCE_IP, SiemField::IP(fw.source_ip().clone()));
self.add_field(field_dictionary::DESTINATION_IP, SiemField::IP(fw.destination_ip().clone()));
self.add_field(field_dictionary::SOURCE_PORT, SiemField::U32(fw.source_port as u32));
self.add_field(field_dictionary::DESTINATION_PORT, SiemField::U32(fw.destination_port as u32));
self.add_field(field_dictionary::EVENT_OUTCOME, SiemField::Text(Cow::Owned(fw.outcome().to_string())));
self.add_field(field_dictionary::IN_INTERFACE, SiemField::Text(Cow::Owned(fw.in_interface().to_string())));
self.add_field(field_dictionary::OUT_INTERFACE, SiemField::Text(Cow::Owned(fw.out_interface().to_string())));
self.add_field(field_dictionary::SOURCE_BYTES, SiemField::U32(fw.out_bytes));
self.add_field(field_dictionary::DESTINATION_BYTES, SiemField::U32(fw.in_bytes));
self.add_field(field_dictionary::NETWORK_TRANSPORT, SiemField::Text(Cow::Owned(fw.network_protocol().to_string())));
},
SiemEvent::WebProxy(fw) => {
self.add_field(field_dictionary::SOURCE_IP, SiemField::IP(fw.source_ip().clone()));
self.add_field(field_dictionary::DESTINATION_IP, SiemField::IP(fw.destination_ip().clone()));
self.add_field(field_dictionary::DESTINATION_PORT, SiemField::U32(fw.destination_port as u32));
self.add_field(field_dictionary::EVENT_OUTCOME, SiemField::Text(Cow::Owned(fw.outcome().to_string())));
self.add_field(field_dictionary::SOURCE_BYTES, SiemField::U32(fw.out_bytes));
self.add_field(field_dictionary::DESTINATION_BYTES, SiemField::U32(fw.in_bytes));
self.add_field(field_dictionary::NETWORK_PROTOCOL, SiemField::Text(Cow::Owned(fw.protocol().to_string())));
self.add_field(field_dictionary::HTTP_RESPONSE_STATUS_CODE, SiemField::U32(fw.http_code));
self.add_field(field_dictionary::HTTP_REQUEST_METHOD, SiemField::Text(Cow::Owned(fw.http_method().to_string())));
self.add_field(field_dictionary::URL_FULL, SiemField::Text(Cow::Owned(fw.url().to_string())));
self.add_field(field_dictionary::URL_DOMAIN, SiemField::Text(Cow::Owned(fw.domain().to_string())));
self.add_field(field_dictionary::USER_NAME, SiemField::User(fw.user_name().to_string()));
self.add_field(field_dictionary::HTTP_RESPONSE_MIME_TYPE, SiemField::from_str(fw.mime_type().to_string()));
match fw.rule_category() {
Some(rule_category) => {
self.add_field(field_dictionary::RULE_CATEGORY, SiemField::from_str(rule_category.to_string()));
},
None => {}
}
match fw.rule_name() {
Some(rule_name) => {
self.add_field(field_dictionary::RULE_NAME, SiemField::from_str(rule_name.to_string()));
},
None => {}
}
},
SiemEvent::DNS(fw) => {
self.add_field(field_dictionary::SOURCE_IP, SiemField::IP(fw.source_ip().clone()));
self.add_field(field_dictionary::DESTINATION_IP, SiemField::IP(fw.destination_ip().clone()));
match fw.op_code() {
DnsEventType::ANSWER => {
self.add_field(field_dictionary::DNS_OP_CODE, SiemField::Text(Cow::Borrowed("ANSWER")));
self.add_field(field_dictionary::DNS_ANSWER_NAME, SiemField::from_str(fw.record_name().to_string()));
match fw.data() {
Some(data) => {self.add_field(field_dictionary::DNS_ANSWER_DATA, SiemField::from_str(data.to_string()));},
None => {}
};
self.add_field(field_dictionary::DNS_ANSWER_TYPE, SiemField::Text(fw.record_type().as_cow()));
},
DnsEventType::QUERY => {
self.add_field(field_dictionary::DNS_OP_CODE, SiemField::Text(Cow::Borrowed("QUERY")));
self.add_field(field_dictionary::DNS_QUESTION_NAME, SiemField::from_str(fw.record_name().to_string()));
self.add_field(field_dictionary::DNS_QUESTION_TYPE, SiemField::Text(fw.record_type().as_cow()));
}
};
},
SiemEvent::Intrusion(fw) => {
self.add_field(field_dictionary::SOURCE_IP, SiemField::IP(fw.source_ip().clone()));
self.add_field(field_dictionary::SOURCE_PORT, SiemField::U32(fw.source_port as u32));
self.add_field(field_dictionary::DESTINATION_IP, SiemField::IP(fw.destination_ip().clone()));
self.add_field(field_dictionary::DESTINATION_PORT, SiemField::U32(fw.destination_port as u32));
self.add_field(field_dictionary::EVENT_OUTCOME, SiemField::Text(Cow::Owned(fw.outcome().to_string())));
self.add_field(field_dictionary::NETWORK_PROTOCOL, SiemField::Text(Cow::Owned(fw.network_protocol().to_string())));
self.add_field(field_dictionary::RULE_CATEGORY, SiemField::from_str(fw.rule_category().to_string()));
self.add_field(field_dictionary::RULE_NAME, SiemField::from_str(fw.rule_name().to_string()));
self.add_field(field_dictionary::RULE_ID, SiemField::U32(fw.rule_id));
},
SiemEvent::WebServer(fw) => {
self.add_field(field_dictionary::SOURCE_IP, SiemField::IP(fw.source_ip().clone()));
match fw.destination_ip() {
Some(ip) => {
self.add_field(field_dictionary::DESTINATION_IP, SiemField::IP(ip.clone()));
},
None => {}
};
self.add_field(field_dictionary::DESTINATION_PORT, SiemField::U32(fw.destination_port as u32));
self.add_field(field_dictionary::EVENT_OUTCOME, SiemField::Text(Cow::Owned(fw.outcome().to_string())));
self.add_field(field_dictionary::SOURCE_BYTES, SiemField::U32(fw.out_bytes));
self.add_field(field_dictionary::DESTINATION_BYTES, SiemField::U32(fw.in_bytes));
self.add_field(field_dictionary::NETWORK_PROTOCOL, SiemField::Text(Cow::Owned(fw.protocol().to_string())));
self.add_field(field_dictionary::HTTP_RESPONSE_STATUS_CODE, SiemField::U32(fw.http_code));
self.add_field(field_dictionary::HTTP_REQUEST_METHOD, SiemField::Text(Cow::Owned(fw.http_method().to_string())));
self.add_field(field_dictionary::URL_FULL, SiemField::Text(Cow::Owned(fw.url_full().to_string())));
self.add_field(field_dictionary::URL_DOMAIN, SiemField::Text(Cow::Owned(fw.url_domain().to_string())));
self.add_field(field_dictionary::URL_PATH, SiemField::Text(Cow::Owned(fw.url_path().to_string())));
self.add_field(field_dictionary::URL_QUERY, SiemField::Text(Cow::Owned(fw.url_query().to_string())));
self.add_field("url.extension", SiemField::Text(Cow::Owned(fw.url_extension().to_string())));
self.add_field(field_dictionary::USER_NAME, SiemField::User(fw.user_name().to_string()));
self.add_field(field_dictionary::HTTP_RESPONSE_MIME_TYPE, SiemField::from_str(fw.mime_type().to_string()));
self.add_field(field_dictionary::NETWORK_DURATION, SiemField::F64(fw.duration as f64));
self.add_field("user_agent.original", SiemField::Text(Cow::Owned(fw.user_agent().to_string())));
},
SiemEvent::Auth(fw) => {
self.add_field("host.hostname", SiemField::Text(Cow::Owned(fw.hostname().to_string())));
self.add_field(field_dictionary::EVENT_OUTCOME, SiemField::Text(Cow::Owned(fw.outcome().to_string())));
match fw.login_type() {
AuthLoginType::Local(evnt) => {
self.add_field(field_dictionary::USER_NAME, SiemField::User(evnt.user_name.to_string()));
self.add_field(field_dictionary::USER_DOMAIN, SiemField::Domain(evnt.domain.to_string()));
},
AuthLoginType::Remote(evnt) => {
self.add_field(field_dictionary::USER_NAME, SiemField::User(evnt.user_name.to_string()));
self.add_field(field_dictionary::USER_DOMAIN, SiemField::Domain(evnt.domain.to_string()));
self.add_field("source.address", SiemField::Text(Cow::Owned(evnt.source_address.to_string())));
},
AuthLoginType::Upgrade(evnt) => {
self.add_field(field_dictionary::USER_NAME, SiemField::User(evnt.destination_user.to_string()));
self.add_field("source.user.name", SiemField::User(evnt.source_user.to_string()));
self.add_field(field_dictionary::USER_DOMAIN, SiemField::Domain(evnt.destination_domain.to_string()));
}
};
},
_ => {}
}
self.event = event;
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::firewall::{FirewallOutcome};
use super::protocol::NetworkProtocol;
#[test]
fn check_log() {
let 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";
let mut log = SiemLog::new(message.to_owned(), 0, SiemIp::V4(0));
log.set_event(SiemEvent::Firewall(FirewallEvent{
source_ip : SiemIp::V4(0),
destination_ip : SiemIp::V4(10000),
source_port : 10000,
destination_port : 443,
outcome : FirewallOutcome::ALLOW,
in_bytes : 0,
out_bytes : 0,
in_interface : Cow::Borrowed("in123"),
out_interface : Cow::Borrowed("out123"),
network_protocol : NetworkProtocol::TCP
}));
log.add_field("event.dataset", SiemField::Text(Cow::Borrowed("filterlog")));
match log.field("event.dataset") {
Some(val) => {
match val {
SiemField::Text(val) => {
assert_eq!(val,"filterlog")
},
_ => assert_eq!(1,2)
}
},
_ => assert_eq!(1,2)
};
}
}