-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Labels
priority:highHigh priority issueHigh priority issuestatus:doneCompletedCompletedtype:enhancementNew feature or requestNew feature or request
Description
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
- Implement bssh-server with SFTP/SCP support #123 - bssh-server 추가 구현
- Depends on: Create shared module structure for client/server code reuse #124 (shared module structure)
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
- Unit test: AuditEvent creation and serialization
- Unit test: AuditManager with NullExporter
- Unit test: Buffer flush logic
- 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
priority:highHigh priority issueHigh priority issuestatus:doneCompletedCompletedtype:enhancementNew feature or requestNew feature or request