use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnterpriseAuditConfig {
pub enabled: bool,
pub storage: AuditStorageConfig,
pub filtering: AuditFilterConfig,
pub retention: AuditRetentionConfig,
pub compliance: ComplianceConfig,
pub encryption: AuditEncryptionConfig,
pub streaming: AuditStreamingConfig,
}
impl Default for EnterpriseAuditConfig {
fn default() -> Self {
Self {
enabled: true,
storage: AuditStorageConfig::default(),
filtering: AuditFilterConfig::default(),
retention: AuditRetentionConfig::default(),
compliance: ComplianceConfig::default(),
encryption: AuditEncryptionConfig::default(),
streaming: AuditStreamingConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditStorageConfig {
pub backend: AuditStorageBackend,
pub file_path: Option<PathBuf>,
pub database_url: Option<String>,
pub s3_config: Option<S3AuditConfig>,
pub buffer_size: usize,
pub flush_interval_secs: u64,
}
impl Default for AuditStorageConfig {
fn default() -> Self {
Self {
backend: AuditStorageBackend::File,
file_path: Some(PathBuf::from("/var/log/oxirs/audit.jsonl")),
database_url: None,
s3_config: None,
buffer_size: 1000,
flush_interval_secs: 60,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AuditStorageBackend {
File,
Database,
S3,
Elasticsearch,
Splunk,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct S3AuditConfig {
pub bucket: String,
pub region: String,
pub prefix: String,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub server_side_encryption: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditFilterConfig {
pub min_severity: AuditSeverity,
pub include_event_types: Vec<AuditEventType>,
pub exclude_event_types: Vec<AuditEventType>,
pub exclude_users: Vec<String>,
pub exclude_resources: Vec<String>,
}
impl Default for AuditFilterConfig {
fn default() -> Self {
Self {
min_severity: AuditSeverity::Info,
include_event_types: vec![],
exclude_event_types: vec![],
exclude_users: vec![],
exclude_resources: vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditRetentionConfig {
pub retention_days: u32,
pub archive_enabled: bool,
pub archive_destination: Option<String>,
pub archive_compression: CompressionType,
pub auto_cleanup: bool,
}
impl Default for AuditRetentionConfig {
fn default() -> Self {
Self {
retention_days: 365, archive_enabled: true,
archive_destination: None,
archive_compression: CompressionType::Gzip,
auto_cleanup: true,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum CompressionType {
None,
Gzip,
Zstd,
Bzip2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceConfig {
pub standards: Vec<ComplianceStandard>,
pub reporting_enabled: bool,
pub report_interval_days: u32,
pub report_destination: Option<String>,
}
impl Default for ComplianceConfig {
fn default() -> Self {
Self {
standards: vec![ComplianceStandard::SOC2],
reporting_enabled: true,
report_interval_days: 30,
report_destination: None,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ComplianceStandard {
GDPR,
HIPAA,
SOC2,
PCIDSS,
ISO27001,
}
impl fmt::Display for ComplianceStandard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ComplianceStandard::GDPR => write!(f, "GDPR"),
ComplianceStandard::HIPAA => write!(f, "HIPAA"),
ComplianceStandard::SOC2 => write!(f, "SOC2"),
ComplianceStandard::PCIDSS => write!(f, "PCI-DSS"),
ComplianceStandard::ISO27001 => write!(f, "ISO 27001"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEncryptionConfig {
pub enabled: bool,
pub algorithm: EncryptionAlgorithm,
pub key_management: KeyManagementConfig,
}
impl Default for AuditEncryptionConfig {
fn default() -> Self {
Self {
enabled: true,
algorithm: EncryptionAlgorithm::AES256GCM,
key_management: KeyManagementConfig::default(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
AES256GCM,
ChaCha20Poly1305,
AES256CBC,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyManagementConfig {
pub kms_type: KmsType,
pub kms_url: Option<String>,
pub rotation_interval_days: u32,
}
impl Default for KeyManagementConfig {
fn default() -> Self {
Self {
kms_type: KmsType::Local,
kms_url: None,
rotation_interval_days: 90,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum KmsType {
Local,
AwsKms,
AzureKeyVault,
GcpKms,
HashiCorpVault,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AuditStreamingConfig {
pub enabled: bool,
pub destinations: Vec<StreamingDestination>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamingDestination {
pub destination_type: DestinationType,
pub endpoint: String,
pub auth: Option<DestinationAuth>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum DestinationType {
Kafka,
Kinesis,
Webhook,
SIEM,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DestinationAuth {
pub auth_type: AuthType,
pub credentials: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AuthType {
ApiKey,
OAuth2,
Basic,
Certificate,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AuditEventType {
Authentication,
Authorization,
DataAccess,
DataModification,
ConfigurationChange,
Security,
System,
Administrative,
Compliance,
}
impl fmt::Display for AuditEventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuditEventType::Authentication => write!(f, "Authentication"),
AuditEventType::Authorization => write!(f, "Authorization"),
AuditEventType::DataAccess => write!(f, "DataAccess"),
AuditEventType::DataModification => write!(f, "DataModification"),
AuditEventType::ConfigurationChange => write!(f, "ConfigurationChange"),
AuditEventType::Security => write!(f, "Security"),
AuditEventType::System => write!(f, "System"),
AuditEventType::Administrative => write!(f, "Administrative"),
AuditEventType::Compliance => write!(f, "Compliance"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum AuditSeverity {
Debug,
Info,
Warning,
Error,
Critical,
}
impl fmt::Display for AuditSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuditSeverity::Debug => write!(f, "DEBUG"),
AuditSeverity::Info => write!(f, "INFO"),
AuditSeverity::Warning => write!(f, "WARNING"),
AuditSeverity::Error => write!(f, "ERROR"),
AuditSeverity::Critical => write!(f, "CRITICAL"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnterpriseAuditEvent {
pub event_id: String,
pub timestamp: DateTime<Utc>,
pub event_type: AuditEventType,
pub severity: AuditSeverity,
pub user_id: Option<String>,
pub source_ip: Option<String>,
pub resource: String,
pub action: String,
pub result: ActionResult,
pub details: HashMap<String, serde_json::Value>,
pub compliance_tags: Vec<ComplianceStandard>,
pub session_id: Option<String>,
pub request_id: Option<String>,
pub correlation_id: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ActionResult {
Success,
Failure,
PartialSuccess,
Denied,
}
impl fmt::Display for ActionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ActionResult::Success => write!(f, "SUCCESS"),
ActionResult::Failure => write!(f, "FAILURE"),
ActionResult::PartialSuccess => write!(f, "PARTIAL_SUCCESS"),
ActionResult::Denied => write!(f, "DENIED"),
}
}
}
pub struct EnterpriseAuditLogger {
config: EnterpriseAuditConfig,
buffer: Arc<RwLock<Vec<EnterpriseAuditEvent>>>,
metrics: Arc<RwLock<AuditMetrics>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AuditMetrics {
pub events_total: u64,
pub events_by_type: HashMap<String, u64>,
pub events_by_severity: HashMap<String, u64>,
pub events_by_result: HashMap<String, u64>,
pub events_failed: u64,
pub buffer_flushes: u64,
pub last_flush: Option<DateTime<Utc>>,
}
impl EnterpriseAuditLogger {
pub fn new(config: EnterpriseAuditConfig) -> Self {
Self {
config,
buffer: Arc::new(RwLock::new(Vec::new())),
metrics: Arc::new(RwLock::new(AuditMetrics::default())),
}
}
pub async fn initialize(&self) -> Result<()> {
if !self.config.enabled {
info!("Enterprise audit logging is disabled");
return Ok(());
}
info!("Initializing enterprise audit logger");
self.initialize_storage().await?;
self.start_background_flusher().await?;
if self.config.streaming.enabled {
self.initialize_streaming().await?;
}
info!("Enterprise audit logger initialized successfully");
Ok(())
}
async fn initialize_storage(&self) -> Result<()> {
match self.config.storage.backend {
AuditStorageBackend::File => {
if let Some(path) = &self.config.storage.file_path {
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
debug!("File storage initialized: {:?}", path);
}
}
AuditStorageBackend::Database => {
debug!("Database storage initialization (placeholder)");
}
AuditStorageBackend::S3 => {
debug!("S3 storage initialization (placeholder)");
}
AuditStorageBackend::Elasticsearch => {
debug!("Elasticsearch storage initialization (placeholder)");
}
AuditStorageBackend::Splunk => {
debug!("Splunk storage initialization (placeholder)");
}
AuditStorageBackend::Custom => {
debug!("Custom storage initialization (placeholder)");
}
}
Ok(())
}
async fn start_background_flusher(&self) -> Result<()> {
debug!("Starting background audit log flusher");
Ok(())
}
async fn initialize_streaming(&self) -> Result<()> {
debug!("Initializing audit event streaming");
for destination in &self.config.streaming.destinations {
debug!(
"Setting up streaming to {:?}: {}",
destination.destination_type, destination.endpoint
);
}
Ok(())
}
pub async fn log_event(&self, event: EnterpriseAuditEvent) -> Result<()> {
if !self.config.enabled {
return Ok(());
}
if !self.should_log_event(&event).await {
return Ok(());
}
{
let mut buffer = self.buffer.write().await;
buffer.push(event.clone());
if buffer.len() >= self.config.storage.buffer_size {
drop(buffer);
self.flush_buffer().await?;
}
}
{
let mut metrics = self.metrics.write().await;
metrics.events_total += 1;
*metrics
.events_by_type
.entry(event.event_type.to_string())
.or_insert(0) += 1;
*metrics
.events_by_severity
.entry(event.severity.to_string())
.or_insert(0) += 1;
*metrics
.events_by_result
.entry(event.result.to_string())
.or_insert(0) += 1;
}
if self.config.streaming.enabled {
self.stream_event(&event).await?;
}
Ok(())
}
async fn should_log_event(&self, event: &EnterpriseAuditEvent) -> bool {
if event.severity < self.config.filtering.min_severity {
return false;
}
if self
.config
.filtering
.exclude_event_types
.contains(&event.event_type)
{
return false;
}
if !self.config.filtering.include_event_types.is_empty()
&& !self
.config
.filtering
.include_event_types
.contains(&event.event_type)
{
return false;
}
if let Some(user_id) = &event.user_id {
if self.config.filtering.exclude_users.contains(user_id) {
return false;
}
}
true
}
pub async fn flush_buffer(&self) -> Result<()> {
let events = {
let mut buffer = self.buffer.write().await;
if buffer.is_empty() {
return Ok(());
}
std::mem::take(&mut *buffer)
};
debug!("Flushing {} audit events to storage", events.len());
self.write_to_storage(&events).await?;
{
let mut metrics = self.metrics.write().await;
metrics.buffer_flushes += 1;
metrics.last_flush = Some(Utc::now());
}
Ok(())
}
async fn write_to_storage(&self, events: &[EnterpriseAuditEvent]) -> Result<()> {
match self.config.storage.backend {
AuditStorageBackend::File => {
if let Some(path) = &self.config.storage.file_path {
let mut content = String::new();
for event in events {
let json = serde_json::to_string(event)?;
content.push_str(&json);
content.push('\n');
}
debug!("Would write {} bytes to {:?}", content.len(), path);
}
}
_ => {
debug!("Writing to {:?} (placeholder)", self.config.storage.backend);
}
}
Ok(())
}
async fn stream_event(&self, event: &EnterpriseAuditEvent) -> Result<()> {
for destination in &self.config.streaming.destinations {
debug!(
"Streaming event {} to {:?}",
event.event_id, destination.destination_type
);
}
Ok(())
}
pub async fn get_metrics(&self) -> AuditMetrics {
self.metrics.read().await.clone()
}
pub async fn generate_compliance_report(
&self,
standard: ComplianceStandard,
start_date: DateTime<Utc>,
end_date: DateTime<Utc>,
) -> Result<ComplianceReport> {
info!(
"Generating compliance report for {} from {} to {}",
standard, start_date, end_date
);
Ok(ComplianceReport {
standard,
report_id: Uuid::new_v4().to_string(),
generated_at: Utc::now(),
period_start: start_date,
period_end: end_date,
total_events: 0,
findings: vec![],
summary: "Compliance report placeholder".to_string(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceReport {
pub standard: ComplianceStandard,
pub report_id: String,
pub generated_at: DateTime<Utc>,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
pub total_events: u64,
pub findings: Vec<ComplianceFinding>,
pub summary: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceFinding {
pub finding_id: String,
pub finding_type: FindingType,
pub severity: AuditSeverity,
pub description: String,
pub affected_events: Vec<String>,
pub remediation: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum FindingType {
NonCompliance,
Warning,
BestPractice,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_audit_config_default() {
let config = EnterpriseAuditConfig::default();
assert!(config.enabled);
assert_eq!(config.retention.retention_days, 365);
}
#[tokio::test]
async fn test_audit_logger_creation() {
let config = EnterpriseAuditConfig::default();
let logger = EnterpriseAuditLogger::new(config);
let metrics = logger.get_metrics().await;
assert_eq!(metrics.events_total, 0);
}
#[tokio::test]
async fn test_compliance_standard_display() {
assert_eq!(ComplianceStandard::GDPR.to_string(), "GDPR");
assert_eq!(ComplianceStandard::HIPAA.to_string(), "HIPAA");
assert_eq!(ComplianceStandard::SOC2.to_string(), "SOC2");
}
#[tokio::test]
async fn test_audit_severity_ordering() {
assert!(AuditSeverity::Critical > AuditSeverity::Error);
assert!(AuditSeverity::Error > AuditSeverity::Warning);
assert!(AuditSeverity::Warning > AuditSeverity::Info);
assert!(AuditSeverity::Info > AuditSeverity::Debug);
}
#[tokio::test]
async fn test_action_result_display() {
assert_eq!(ActionResult::Success.to_string(), "SUCCESS");
assert_eq!(ActionResult::Failure.to_string(), "FAILURE");
assert_eq!(ActionResult::Denied.to_string(), "DENIED");
}
}