pub mod backends;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretAccessEvent {
pub timestamp: DateTime<Utc>,
pub secret_name: String,
pub accessor: String,
pub success: bool,
pub context: Option<String>,
}
impl SecretAccessEvent {
pub fn new(
secret_name: String,
accessor: String,
success: bool,
context: Option<String>,
) -> Self {
Self {
timestamp: Utc::now(),
secret_name,
accessor,
success,
context,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SecretAccessFilter {
pub secret_name: Option<String>,
pub accessor: Option<String>,
pub success_only: Option<bool>,
pub start_time: Option<DateTime<Utc>>,
pub end_time: Option<DateTime<Utc>>,
}
#[async_trait::async_trait]
pub trait SecretAuditBackend: Send + Sync {
async fn log_access(&self, event: SecretAccessEvent) -> Result<(), String>;
async fn get_accesses(
&self,
filter: Option<SecretAccessFilter>,
) -> Result<Vec<SecretAccessEvent>, String>;
}
pub struct SecretAuditLogger {
backend: Arc<dyn SecretAuditBackend>,
}
impl SecretAuditLogger {
pub fn new(backend: Arc<dyn SecretAuditBackend>) -> Self {
Self { backend }
}
pub async fn log_access(&self, event: SecretAccessEvent) -> Result<(), String> {
self.backend.log_access(event).await
}
pub async fn get_accesses(
&self,
filter: Option<SecretAccessFilter>,
) -> Result<Vec<SecretAccessEvent>, String> {
self.backend.get_accesses(filter).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use backends::MemorySecretAuditBackend;
#[test]
fn test_secret_access_event_creation() {
let event = SecretAccessEvent::new(
"test_secret".to_string(),
"test_accessor".to_string(),
true,
Some("context".to_string()),
);
assert_eq!(event.secret_name, "test_secret");
assert_eq!(event.accessor, "test_accessor");
assert!(event.success);
assert_eq!(event.context, Some("context".to_string()));
assert!(event.timestamp <= Utc::now());
}
#[tokio::test]
async fn test_secret_audit_logger() {
let backend = Arc::new(MemorySecretAuditBackend::new());
let logger = SecretAuditLogger::new(backend);
let event = SecretAccessEvent::new("secret".to_string(), "app".to_string(), true, None);
logger.log_access(event).await.unwrap();
let events = logger.get_accesses(None).await.unwrap();
assert_eq!(events.len(), 1);
}
#[tokio::test]
async fn test_secret_audit_logger_with_filter() {
let backend = Arc::new(MemorySecretAuditBackend::new());
let logger = SecretAuditLogger::new(backend);
let event1 = SecretAccessEvent::new("s1".to_string(), "app".to_string(), true, None);
let event2 = SecretAccessEvent::new("s2".to_string(), "app".to_string(), false, None);
let event3 = SecretAccessEvent::new("s1".to_string(), "app".to_string(), true, None);
logger.log_access(event1).await.unwrap();
logger.log_access(event2).await.unwrap();
logger.log_access(event3).await.unwrap();
let filter = SecretAccessFilter {
success_only: Some(true),
..Default::default()
};
let events = logger.get_accesses(Some(filter)).await.unwrap();
assert_eq!(events.len(), 2);
}
#[tokio::test]
async fn test_secret_audit_filter_by_name() {
let backend = Arc::new(MemorySecretAuditBackend::new());
let logger = SecretAuditLogger::new(backend);
let event1 = SecretAccessEvent::new("secret1".to_string(), "app".to_string(), true, None);
let event2 = SecretAccessEvent::new("secret2".to_string(), "app".to_string(), true, None);
let event3 = SecretAccessEvent::new("secret1".to_string(), "app".to_string(), true, None);
logger.log_access(event1).await.unwrap();
logger.log_access(event2).await.unwrap();
logger.log_access(event3).await.unwrap();
let filter = SecretAccessFilter {
secret_name: Some("secret1".to_string()),
..Default::default()
};
let events = logger.get_accesses(Some(filter)).await.unwrap();
assert_eq!(events.len(), 2);
}
}