pub mod backends;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum EventType {
ConfigUpdate,
ConfigDelete,
ConfigCreate,
SecretAccess,
SecretRotation,
}
impl EventType {
pub fn as_str(&self) -> &str {
match self {
EventType::ConfigUpdate => "config_update",
EventType::ConfigDelete => "config_delete",
EventType::ConfigCreate => "config_create",
EventType::SecretAccess => "secret_access",
EventType::SecretRotation => "secret_rotation",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeRecord {
pub old_value: Option<serde_json::Value>,
pub new_value: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub timestamp: DateTime<Utc>,
pub event_type: EventType,
pub user: Option<String>,
pub changes: HashMap<String, ChangeRecord>,
}
impl AuditEvent {
pub fn new(
event_type: EventType,
user: Option<String>,
changes: HashMap<String, ChangeRecord>,
) -> Self {
Self {
timestamp: Utc::now(),
event_type,
user,
changes,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct EventFilter {
pub event_type: Option<EventType>,
pub user: Option<String>,
pub start_time: Option<DateTime<Utc>>,
pub end_time: Option<DateTime<Utc>>,
}
#[async_trait::async_trait]
pub trait AuditBackend: Send + Sync {
async fn log_event(&self, event: AuditEvent) -> Result<(), String>;
async fn get_events(&self, filter: Option<EventFilter>) -> Result<Vec<AuditEvent>, String>;
}
pub struct AuditLogger {
backend: Arc<dyn AuditBackend>,
}
impl AuditLogger {
pub fn new(backend: Arc<dyn AuditBackend>) -> Self {
Self { backend }
}
pub async fn log_event(&self, event: AuditEvent) -> Result<(), String> {
self.backend.log_event(event).await
}
pub async fn get_events(&self, filter: Option<EventFilter>) -> Result<Vec<AuditEvent>, String> {
self.backend.get_events(filter).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use backends::MemoryAuditBackend;
use serde_json::json;
#[test]
fn test_event_type_as_str() {
assert_eq!(EventType::ConfigUpdate.as_str(), "config_update");
assert_eq!(EventType::ConfigDelete.as_str(), "config_delete");
assert_eq!(EventType::ConfigCreate.as_str(), "config_create");
assert_eq!(EventType::SecretAccess.as_str(), "secret_access");
assert_eq!(EventType::SecretRotation.as_str(), "secret_rotation");
}
#[test]
fn test_change_record_creation() {
let record = ChangeRecord {
old_value: Some(json!(false)),
new_value: Some(json!(true)),
};
assert_eq!(record.old_value, Some(json!(false)));
assert_eq!(record.new_value, Some(json!(true)));
}
#[test]
fn test_audit_event_creation() {
let mut changes = HashMap::new();
changes.insert(
"test_key".to_string(),
ChangeRecord {
old_value: Some(json!("old")),
new_value: Some(json!("new")),
},
);
let event = AuditEvent::new(
EventType::ConfigUpdate,
Some("test_user".to_string()),
changes.clone(),
);
assert_eq!(event.event_type, EventType::ConfigUpdate);
assert_eq!(event.user, Some("test_user".to_string()));
assert_eq!(event.changes.len(), 1);
assert!(event.timestamp <= Utc::now());
}
#[tokio::test]
async fn test_audit_logger_log_event() {
let backend = Arc::new(MemoryAuditBackend::new());
let logger = AuditLogger::new(backend.clone());
let mut changes = HashMap::new();
changes.insert(
"setting".to_string(),
ChangeRecord {
old_value: None,
new_value: Some(json!("value")),
},
);
let event = AuditEvent::new(EventType::ConfigCreate, Some("user".to_string()), changes);
let result = logger.log_event(event).await;
assert!(result.is_ok());
let events = logger.get_events(None).await.unwrap();
assert_eq!(events.len(), 1);
}
#[tokio::test]
async fn test_audit_logger_with_filter() {
let backend = Arc::new(MemoryAuditBackend::new());
let logger = AuditLogger::new(backend);
for i in 0..3 {
let mut changes = HashMap::new();
changes.insert(
format!("key_{}", i),
ChangeRecord {
old_value: None,
new_value: Some(json!(i)),
},
);
let event = AuditEvent::new(EventType::ConfigCreate, Some("user".to_string()), changes);
logger.log_event(event).await.unwrap();
}
let filter = EventFilter {
event_type: Some(EventType::ConfigCreate),
user: Some("user".to_string()),
..Default::default()
};
let events = logger.get_events(Some(filter)).await.unwrap();
assert_eq!(events.len(), 3);
}
}