mod backends;
mod integrity;
mod processor;
pub use backends::*;
pub use integrity::*;
pub use processor::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::SystemTime;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
#[serde(with = "system_time_format")]
pub timestamp: SystemTime,
pub event_type: AuditEventType,
pub correlation_id: Option<String>,
pub remote_addr: Option<SocketAddr>,
pub principal: Option<String>,
pub method: Option<String>,
pub result: AuditResult,
pub severity: AuditSeverity,
pub metadata: HashMap<String, serde_json::Value>,
pub params: Option<serde_json::Value>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuditEventType {
ConnectionEstablished,
ConnectionClosed,
AuthenticationAttempt,
AuthorizationCheck,
MethodInvocation,
ErrorOccurred,
SecurityViolation,
ConfigurationChange,
AdminAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuditResult {
Success,
Failure,
Denied,
Violation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuditSeverity {
Info,
Warning,
Critical,
}
impl AuditEvent {
#[must_use]
pub fn builder() -> AuditEventBuilder {
AuditEventBuilder::default()
}
pub fn add_metadata<K: Into<String>, V: Into<serde_json::Value>>(&mut self, key: K, value: V) {
self.metadata.insert(key.into(), value.into());
}
#[must_use]
pub fn with_correlation_id(mut self, correlation_id: Option<String>) -> Self {
self.correlation_id = correlation_id;
self
}
#[must_use]
pub fn with_principal<S: Into<String>>(mut self, principal: S) -> Self {
self.principal = Some(principal.into());
self
}
#[must_use]
pub fn with_remote_addr(mut self, addr: SocketAddr) -> Self {
self.remote_addr = Some(addr);
self
}
}
#[derive(Debug, Default)]
pub struct AuditEventBuilder {
event_type: Option<AuditEventType>,
correlation_id: Option<String>,
remote_addr: Option<SocketAddr>,
principal: Option<String>,
method: Option<String>,
result: Option<AuditResult>,
severity: Option<AuditSeverity>,
metadata: HashMap<String, serde_json::Value>,
params: Option<serde_json::Value>,
error: Option<String>,
}
impl AuditEventBuilder {
#[must_use]
pub fn event_type(mut self, event_type: AuditEventType) -> Self {
self.event_type = Some(event_type);
self
}
#[must_use]
pub fn correlation_id<S: Into<String>>(mut self, id: S) -> Self {
self.correlation_id = Some(id.into());
self
}
#[must_use]
pub fn remote_addr(mut self, addr: SocketAddr) -> Self {
self.remote_addr = Some(addr);
self
}
#[must_use]
pub fn principal<S: Into<String>>(mut self, principal: S) -> Self {
self.principal = Some(principal.into());
self
}
#[must_use]
pub fn method<S: Into<String>>(mut self, method: S) -> Self {
self.method = Some(method.into());
self
}
#[must_use]
pub fn result(mut self, result: AuditResult) -> Self {
self.result = Some(result);
self
}
#[must_use]
pub fn severity(mut self, severity: AuditSeverity) -> Self {
self.severity = Some(severity);
self
}
#[must_use]
pub fn metadata<K: Into<String>, V: Into<serde_json::Value>>(
mut self,
key: K,
value: V,
) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
#[must_use]
pub fn error<S: Into<String>>(mut self, error: S) -> Self {
self.error = Some(error.into());
self
}
#[must_use]
#[allow(clippy::panic)]
pub fn build(self) -> AuditEvent {
let event_type = self
.event_type
.unwrap_or_else(|| panic!("event_type is required for AuditEvent"));
let result = self
.result
.unwrap_or_else(|| panic!("result is required for AuditEvent"));
let severity = self.severity.unwrap_or(match result {
AuditResult::Success => AuditSeverity::Info,
AuditResult::Failure => AuditSeverity::Warning,
AuditResult::Denied | AuditResult::Violation => AuditSeverity::Critical,
});
AuditEvent {
timestamp: SystemTime::now(),
event_type,
correlation_id: self.correlation_id,
remote_addr: self.remote_addr,
principal: self.principal,
method: self.method,
result,
severity,
metadata: self.metadata,
params: self.params,
error: self.error,
}
}
}
mod system_time_format {
use serde::{Deserialize, Deserializer, Serializer};
use std::time::{SystemTime, UNIX_EPOCH};
pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let duration = time
.duration_since(UNIX_EPOCH)
.map_err(serde::ser::Error::custom)?;
#[allow(clippy::arithmetic_side_effects)] let nanos = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
serializer.serialize_u64(nanos)
}
#[allow(clippy::arithmetic_side_effects, clippy::as_conversions)] pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
let nanos = u64::deserialize(deserializer)?;
let secs = nanos / 1_000_000_000;
let subsec_nanos = (nanos % 1_000_000_000) as u32;
Ok(UNIX_EPOCH + std::time::Duration::new(secs, subsec_nanos))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_event_builder() {
let event = AuditEvent::builder()
.event_type(AuditEventType::AuthenticationAttempt)
.principal("user@example.com")
.method("login")
.result(AuditResult::Success)
.build();
assert_eq!(event.event_type, AuditEventType::AuthenticationAttempt);
assert_eq!(event.principal, Some("user@example.com".to_string()));
assert_eq!(event.result, AuditResult::Success);
assert_eq!(event.severity, AuditSeverity::Info);
}
#[test]
fn test_audit_event_serialization() {
let event = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.principal("test_user")
.method("get_balance")
.result(AuditResult::Success)
.build();
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("method_invocation"));
assert!(json.contains("test_user"));
assert!(json.contains("success"));
}
#[test]
fn test_severity_defaults() {
let success = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.result(AuditResult::Success)
.build();
assert_eq!(success.severity, AuditSeverity::Info);
let denied = AuditEvent::builder()
.event_type(AuditEventType::AuthorizationCheck)
.result(AuditResult::Denied)
.build();
assert_eq!(denied.severity, AuditSeverity::Critical);
}
}