Skip to content

Design and implement audit event types and logging infrastructure #134

@inureyes

Description

@inureyes

Summary

Design and implement the audit logging infrastructure for bssh-server. This provides detailed tracking of file transfers and security-relevant events for compliance and monitoring.

Parent Epic

Implementation Details

1. Audit Event Types

// src/server/audit/event.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::path::PathBuf;

/// Audit event for logging
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
    /// Unique event ID
    pub id: String,
    
    /// Event timestamp
    pub timestamp: DateTime<Utc>,
    
    /// Event type
    pub event_type: EventType,
    
    /// Session ID
    pub session_id: String,
    
    /// Username
    pub user: String,
    
    /// Client IP address
    pub client_ip: Option<IpAddr>,
    
    /// File path (for file operations)
    pub path: Option<PathBuf>,
    
    /// Destination path (for rename/copy)
    pub dest_path: Option<PathBuf>,
    
    /// Bytes transferred
    pub bytes: Option<u64>,
    
    /// Operation result
    pub result: EventResult,
    
    /// Additional details
    pub details: Option<String>,
    
    /// Protocol used (ssh, sftp, scp)
    pub protocol: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
    // Authentication events
    AuthSuccess,
    AuthFailure,
    AuthRateLimited,
    
    // Session events
    SessionStart,
    SessionEnd,
    
    // Command execution
    CommandExecuted,
    CommandBlocked,
    
    // File operations
    FileOpenRead,
    FileOpenWrite,
    FileRead,
    FileWrite,
    FileClose,
    FileUploaded,
    FileDownloaded,
    FileDeleted,
    FileRenamed,
    
    // Directory operations
    DirectoryCreated,
    DirectoryDeleted,
    DirectoryListed,
    
    // Filter events
    TransferDenied,
    TransferAllowed,
    
    // Security events
    IpBlocked,
    IpUnblocked,
    SuspiciousActivity,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum EventResult {
    Success,
    Failure,
    Denied,
    Error,
}

impl AuditEvent {
    /// Create a new audit event
    pub fn new(event_type: EventType, user: String, session_id: String) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            timestamp: Utc::now(),
            event_type,
            session_id,
            user,
            client_ip: None,
            path: None,
            dest_path: None,
            bytes: None,
            result: EventResult::Success,
            details: None,
            protocol: None,
        }
    }

    /// Builder methods
    pub fn with_client_ip(mut self, ip: IpAddr) -> Self {
        self.client_ip = Some(ip);
        self
    }

    pub fn with_path(mut self, path: PathBuf) -> Self {
        self.path = Some(path);
        self
    }

    pub fn with_bytes(mut self, bytes: u64) -> Self {
        self.bytes = Some(bytes);
        self
    }

    pub fn with_result(mut self, result: EventResult) -> Self {
        self.result = result;
        self
    }

    pub fn with_details(mut self, details: String) -> Self {
        self.details = Some(details);
        self
    }

    pub fn with_protocol(mut self, protocol: &str) -> Self {
        self.protocol = Some(protocol.to_string());
        self
    }
}

impl Default for AuditEvent {
    fn default() -> Self {
        Self::new(EventType::CommandExecuted, String::new(), String::new())
    }
}

2. Audit Exporter Trait

// src/server/audit/exporter.rs
use async_trait::async_trait;

/// Trait for audit log exporters
#[async_trait]
pub trait AuditExporter: Send + Sync {
    /// Export a single audit event
    async fn export(&self, event: AuditEvent) -> Result<()>;
    
    /// Export multiple events (batch)
    async fn export_batch(&self, events: Vec<AuditEvent>) -> Result<()> {
        for event in events {
            self.export(event).await?;
        }
        Ok(())
    }
    
    /// Flush any buffered events
    async fn flush(&self) -> Result<()>;
    
    /// Close the exporter
    async fn close(&self) -> Result<()>;
}

/// Null exporter (discards events)
pub struct NullExporter;

#[async_trait]
impl AuditExporter for NullExporter {
    async fn export(&self, _event: AuditEvent) -> Result<()> {
        Ok(())
    }
    async fn flush(&self) -> Result<()> {
        Ok(())
    }
    async fn close(&self) -> Result<()> {
        Ok(())
    }
}

3. Audit Manager

// src/server/audit/mod.rs
pub mod event;
pub mod exporter;
pub mod file;    // #135
pub mod otel;    // #136
pub mod logstash; // #137

use std::sync::Arc;
use tokio::sync::mpsc;

/// Manages audit logging with multiple exporters
pub struct AuditManager {
    exporters: Vec<Arc<dyn AuditExporter>>,
    sender: mpsc::Sender<AuditEvent>,
    enabled: bool,
}

impl AuditManager {
    pub fn new(config: &AuditConfig) -> Result<Self> {
        let (sender, receiver) = mpsc::channel(1000);
        
        let mut exporters: Vec<Arc<dyn AuditExporter>> = Vec::new();
        
        for exporter_config in &config.exporters {
            let exporter: Arc<dyn AuditExporter> = match exporter_config {
                AuditExporterConfig::File { path } => {
                    Arc::new(file::FileExporter::new(path)?)
                }
                AuditExporterConfig::Otel { endpoint } => {
                    Arc::new(otel::OtelExporter::new(endpoint)?)
                }
                AuditExporterConfig::Logstash { host, port } => {
                    Arc::new(logstash::LogstashExporter::new(host, *port)?)
                }
            };
            exporters.push(exporter);
        }

        let manager = Self {
            exporters: exporters.clone(),
            sender,
            enabled: config.enabled,
        };

        // Start background worker
        if config.enabled {
            tokio::spawn(Self::worker(receiver, exporters));
        }

        Ok(manager)
    }

    /// Log an audit event
    pub async fn log(&self, event: AuditEvent) {
        if !self.enabled {
            return;
        }
        
        if let Err(e) = self.sender.send(event).await {
            tracing::warn!("Failed to send audit event: {}", e);
        }
    }

    /// Background worker for async event processing
    async fn worker(
        mut receiver: mpsc::Receiver<AuditEvent>,
        exporters: Vec<Arc<dyn AuditExporter>>,
    ) {
        let mut buffer = Vec::with_capacity(100);
        let flush_interval = tokio::time::interval(Duration::from_secs(5));
        tokio::pin!(flush_interval);

        loop {
            tokio::select! {
                Some(event) = receiver.recv() => {
                    buffer.push(event);
                    
                    // Flush if buffer is full
                    if buffer.len() >= 100 {
                        Self::flush_buffer(&exporters, &mut buffer).await;
                    }
                }
                _ = flush_interval.tick() => {
                    if !buffer.is_empty() {
                        Self::flush_buffer(&exporters, &mut buffer).await;
                    }
                }
                else => break,
            }
        }
    }

    async fn flush_buffer(
        exporters: &[Arc<dyn AuditExporter>],
        buffer: &mut Vec<AuditEvent>,
    ) {
        for exporter in exporters {
            if let Err(e) = exporter.export_batch(buffer.clone()).await {
                tracing::error!("Audit export failed: {}", e);
            }
        }
        buffer.clear();
    }

    /// Flush all pending events
    pub async fn flush(&self) {
        for exporter in &self.exporters {
            if let Err(e) = exporter.flush().await {
                tracing::error!("Audit flush failed: {}", e);
            }
        }
    }
}

4. Integration Points

Add audit logging to:

  • Authentication (success/failure)
  • Session lifecycle (start/end)
  • Command execution
  • SFTP operations
  • SCP operations
  • Security events (rate limiting, IP blocks)

Files to Create/Modify

File Action
src/server/audit/mod.rs Create - Audit manager
src/server/audit/event.rs Create - Event types
src/server/audit/exporter.rs Create - Exporter trait
src/server/mod.rs Modify - Add audit module

Testing Requirements

  1. Unit test: AuditEvent creation and serialization
  2. Unit test: AuditManager with NullExporter
  3. Unit test: Buffer flush logic
  4. Integration test: Events flow through system

Acceptance Criteria

  • AuditEvent type with all required fields
  • EventType enum covering all audit scenarios
  • AuditExporter trait defined
  • AuditManager with buffering
  • Background worker for async processing
  • NullExporter for testing
  • JSON serialization for events
  • Tests passing

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions