We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
// Package audit provides compliance audit logging for NornicDB.
//
// This package implements immutable audit trails required by major regulatory frameworks:
// - GDPR Art.30: Records of processing activities
// - GDPR Art.15: Right of access (audit trail for data subject requests)
// - HIPAA §164.312(b): Audit controls and audit logs
// - HIPAA §164.308(a)(1)(ii)(D): Information system activity review
// - FISMA AU-2: Audit Events, AU-3: Content of Audit Records
// - SOC2 CC7.2: System monitoring controls
// - SOX §404: Internal controls over financial reporting
//
// Features:
// - Immutable audit log entries (append-only)
// - Structured JSON format for machine processing
// - Real-time security alerting
// - Compliance reporting (GDPR, HIPAA, SOC2)
// - User activity tracking
// - Data access logging
// - Retention management (7 years default for SOC2)
//
// Example Usage:
//
// // Initialize audit logger
// config := audit.DefaultConfig()
// config.LogPath = "/var/log/nornicdb/audit.log"
// config.RetentionDays = 2555 // 7 years for SOC2
//
// logger, err := audit.NewLogger(config)
// if err != nil {
// log.Fatal(err)
// }
// defer logger.Close()
//
// // Set up security alerting
// logger.SetAlertCallback(func(event audit.Event) {
// if event.Type == audit.EventBreach {
// sendSecurityAlert(event)
// }
// })
//
// // Log authentication events
// logger.LogAuth(audit.EventLogin, "user-123", "alice",
// "192.168.1.100", "Mozilla/5.0", true, "")
//
// // Log data access (GDPR compliance)
// logger.LogDataAccess("user-123", "alice", "node", "patient-456",
// "READ", true, "PHI")
//
// // Log GDPR erasure request
// logger.LogErasure("user-123", "alice", "patient-789", false,
// "Erasure request initiated")
//
// // Generate compliance reports
// reader := audit.NewReader(config.LogPath)
// report, _ := reader.GenerateComplianceReport(
// time.Now().AddDate(0, -1, 0), // Last month
// time.Now(),
// "Monthly Compliance Report",
// )
//
// fmt.Printf("Total events: %d\n", report.TotalEvents)
// fmt.Printf("Failed logins: %d\n", report.FailedLogins)
// fmt.Printf("GDPR erasures: %d\n", report.ErasureRequests)
//
// Event Types:
//
// Authentication:
// - LOGIN, LOGOUT, LOGIN_FAILED, PASSWORD_CHANGE
//
// Authorization:
// - ACCESS_DENIED, ROLE_CHANGE
//
// Data Operations (GDPR Art.15):
// - DATA_READ, DATA_CREATE, DATA_UPDATE, DATA_DELETE, DATA_EXPORT
//
// GDPR Rights:
// - ERASURE_REQUEST, ERASURE_COMPLETE, CONSENT_GIVEN, CONSENT_REVOKED
//
// System Events:
// - CONFIG_CHANGE, BACKUP, RESTORE, SCHEMA_CHANGE
//
// Security:
// - SECURITY_ALERT, BREACH_DETECTED
//
// Compliance Requirements:
//
// GDPR Art.30 (Records of Processing):
// - Who: UserID, Username (data controller/processor)
// - What: Resource, Action (categories of data)
// - When: Timestamp (retention periods)
// - Where: IPAddress (location of processing)
// - Why: Reason (legal basis)
//
// HIPAA §164.312(b) (Audit Controls):
// - User identification (UserID)
// - Type of action performed (EventType, Action)
// - Date and time (Timestamp)
// - Source of action (IPAddress, UserAgent)
// - Success or failure (Success, Reason)
//
// SOC2 CC7.2 (System Monitoring):
// - Logging of security events
// - Review of logs for anomalies
// - Retention of logs
// - Protection of log integrity
//
// ELI12 (Explain Like I'm 12):
//
// Think of audit logging like a security camera system for your data:
//
// 1. **Every action is recorded**: Like how security cameras record everything
// that happens, we record every time someone reads, changes, or deletes data.
//
// 2. **Can't be erased**: Just like how security footage is stored safely where
// bad guys can't delete it, our audit logs can't be changed or deleted.
//
// 3. **Shows who did what when**: If something goes wrong, we can look back and
// see exactly who did what and when, like reviewing security footage.
//
// 4. **Alerts for bad stuff**: If someone tries to break in or do something
// suspicious, the system sends an alert immediately, like a burglar alarm.
//
// 5. **Required by law**: Just like buildings need fire exits, companies that
// handle personal data need audit logs to prove they're being careful.
//
// The audit system helps keep everyone's data safe and proves we're following the rules!
package audit
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"time"
)
// EventType categorizes audit events for compliance reporting.
type EventType string
const (
// Authentication events
EventLogin EventType = "LOGIN"
EventLogout EventType = "LOGOUT"
EventLoginFailed EventType = "LOGIN_FAILED"
EventPasswordChange EventType = "PASSWORD_CHANGE"
// Authorization events
EventAccessDenied EventType = "ACCESS_DENIED"
EventRoleChange EventType = "ROLE_CHANGE"
// Data access events (GDPR Art.15 - right of access)
EventDataRead EventType = "DATA_READ"
EventDataCreate EventType = "DATA_CREATE"
EventDataUpdate EventType = "DATA_UPDATE"
EventDataDelete EventType = "DATA_DELETE"
EventDataExport EventType = "DATA_EXPORT"
// Data subject rights (GDPR)
EventErasureRequest EventType = "ERASURE_REQUEST"
EventErasureComplete EventType = "ERASURE_COMPLETE"
EventConsentGiven EventType = "CONSENT_GIVEN"
EventConsentRevoked EventType = "CONSENT_REVOKED"
// System events
EventConfigChange EventType = "CONFIG_CHANGE"
EventBackup EventType = "BACKUP"
EventRestore EventType = "RESTORE"
EventSchemaChange EventType = "SCHEMA_CHANGE"
// Security events
EventSecurityAlert EventType = "SECURITY_ALERT"
EventBreach EventType = "BREACH_DETECTED"
)
// Event represents an immutable audit log entry for compliance tracking.
//
// Events follow a structured format designed to meet regulatory requirements:
// - GDPR Art.30: Records of processing activities
// - HIPAA §164.312(b): Audit controls
// - SOC2 CC7.2: System monitoring
//
// All fields are optional except Type and Timestamp, but including more
// information improves compliance posture and forensic capabilities.
//
// Example:
//
// // Authentication event
// event := audit.Event{
// Type: audit.EventLogin,
// UserID: "user-123",
// Username: "alice",
// IPAddress: "192.168.1.100",
// UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
// Success: true,
// Metadata: map[string]string{
// "login_method": "password",
// "session_id": "sess-abc123",
// },
// }
//
// // Data access event (GDPR)
// event = audit.Event{
// Type: audit.EventDataRead,
// UserID: "user-123",
// Username: "alice",
// Resource: "patient_record",
// ResourceID: "patient-456",
// Action: "READ",
// Success: true,
// DataClassification: "PHI",
// RequestPath: "/api/patients/456",
// }
//
// Immutability:
// Once logged, events cannot be modified. This ensures audit trail integrity
// required by HIPAA and SOC2.
type Event struct {
// Unique event identifier
ID string `json:"id"`
// Timestamp in RFC3339 format (ISO 8601)
Timestamp time.Time `json:"timestamp"`
// Event classification
Type EventType `json:"type"`
// Actor information
UserID string `json:"user_id,omitempty"`
Username string `json:"username,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
// Resource information
Resource string `json:"resource,omitempty"` // e.g., "node", "edge", "user"
ResourceID string `json:"resource_id,omitempty"`
Action string `json:"action,omitempty"` // e.g., "create", "read", "update", "delete"
// Outcome
Success bool `json:"success"`
Reason string `json:"reason,omitempty"` // Failure reason or additional context
// Data classification (for HIPAA PHI tracking)
DataClassification string `json:"data_classification,omitempty"` // e.g., "PHI", "PII", "PUBLIC"
// Request context
RequestID string `json:"request_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
RequestPath string `json:"request_path,omitempty"`
// Additional metadata
Metadata map[string]string `json:"metadata,omitempty"`
}
// Logger handles audit log writing with compliance guarantees.
//
// The Logger provides:
// - Thread-safe concurrent logging
// - Immutable append-only log format
// - Automatic event ID generation
// - Real-time security alerting
// - Configurable retention and rotation
// - fsync support for durability
//
// Example:
//
// config := audit.DefaultConfig()
// config.LogPath = "/secure/audit.log"
// config.SyncWrites = true // Force fsync for durability
//
// logger, err := audit.NewLogger(config)
// if err != nil {
// log.Fatal(err)
// }
// defer logger.Close()
//
// // Set up alerting
// logger.SetAlertCallback(func(event audit.Event) {
// slack.SendAlert(fmt.Sprintf("Security event: %s", event.Type))
// })
//
// // Log events
// logger.Log(audit.Event{
// Type: audit.EventSecurityAlert,
// Reason: "Multiple failed login attempts",
// Success: false,
// })
//
// Thread Safety:
// All methods are thread-safe and can be called concurrently from
// multiple goroutines.
type Logger struct {
mu sync.Mutex
writer io.Writer
file *os.File
config Config
sequence uint64
closed bool
// Callback for real-time alerting (breach detection)
alertCallback func(Event)
}
// Config holds audit logger configuration.
type Config struct {
// Enabled controls whether audit logging is active
Enabled bool
// LogPath is the path to the audit log file
LogPath string
// RetentionDays is how long to keep audit logs (HIPAA: 2190 days/6 years, SOC2: 2555 days/7 years)
RetentionDays int
// RotationSize is the max file size before rotation (bytes)
RotationSize int64
// RotationInterval is how often to rotate logs
RotationInterval time.Duration
// SyncWrites forces fsync after each write (slower but more durable)
SyncWrites bool
// IncludeStackTrace adds stack traces to error events
IncludeStackTrace bool
// AlertOnEvents triggers alerts for specific event types
AlertOnEvents []EventType
}
// DefaultConfig returns sensible defaults for audit logging.
func DefaultConfig() Config {
return Config{
Enabled: true,
LogPath: "./logs/audit.log",
RetentionDays: 2555, // 7 years for SOC2
RotationSize: 100 * 1024 * 1024, // 100MB
RotationInterval: 24 * time.Hour,
SyncWrites: true,
AlertOnEvents: []EventType{EventBreach, EventSecurityAlert, EventAccessDenied},
}
}
// NewLogger creates a new audit logger with the given configuration.
//
// The logger creates the log directory if it doesn't exist and opens the
// log file in append mode. If logging is disabled in config, returns a
// no-op logger that discards all events.
//
// Parameters:
// - config: Logger configuration (use DefaultConfig() for defaults)
//
// Returns:
// - Logger instance ready for use
// - Error if failed to create directory or open log file
//
// Example:
//
// // Standard configuration
// config := audit.DefaultConfig()
// logger, err := audit.NewLogger(config)
// if err != nil {
// log.Fatal(err)
// }
//
// // Custom configuration
// config = audit.Config{
// Enabled: true,
// LogPath: "/var/log/nornicdb/audit.log",
// RetentionDays: 2555, // 7 years
// SyncWrites: true, // Force fsync
// AlertOnEvents: []audit.EventType{
// audit.EventBreach,
// audit.EventSecurityAlert,
// },
// }
// logger, err = audit.NewLogger(config)
//
// NewLogger creates a new audit logger with the specified configuration.
//
// The logger creates an append-only audit trail meeting compliance requirements
// for GDPR, HIPAA, FISMA, SOC2, and SOX. All events are timestamped, assigned
// unique IDs, and written in structured JSON format.
//
// Parameters:
// - config: Configuration for audit logging (use DefaultConfig() for defaults)
//
// Returns:
// - *Logger ready to record audit events
// - Error if log file/directory cannot be created
//
// Example 1 - Basic GDPR Compliance Logging:
//
// config := audit.DefaultConfig()
// config.LogPath = "/var/log/nornicdb/audit.log"
// config.RetentionDays = 2555 // 7 years for SOC2
//
// logger, err := audit.NewLogger(config)
// if err != nil {
// log.Fatal("Failed to initialize audit logging:", err)
// }
// defer logger.Close()
//
// // Log every data access (GDPR Art.30 requirement)
// logger.LogDataAccess("user-123", "alice", "patient_record",
// "patient-456", "READ", true, "PHI")
//
// Example 2 - HIPAA Audit Controls (§164.312(b)):
//
// config := audit.DefaultConfig()
// config.SyncWrites = true // Force fsync for durability
// config.LogPath = "/secure/logs/hipaa-audit.log"
//
// logger, err := audit.NewLogger(config)
// if err != nil {
// return fmt.Errorf("HIPAA audit init failed: %w", err)
// }
//
// // Set up real-time breach detection
// logger.SetAlertCallback(func(event audit.Event) {
// if event.Type == audit.EventBreach {
// notifySecurityTeam(event)
// escalateToCompliance(event)
// }
// if event.Type == audit.EventLoginFailed && event.Metadata["attempts"] == "5" {
// blockIP(event.IPAddress)
// }
// })
//
// // Log all authentication attempts
// logger.LogAuth(audit.EventLogin, userID, username, ipAddr, userAgent, true, "")
//
// Example 3 - Multi-Tenant SaaS with Compliance:
//
// // Create separate audit logs per tenant for data isolation
// func createTenantLogger(tenantID string) (*audit.Logger, error) {
// config := audit.DefaultConfig()
// config.LogPath = fmt.Sprintf("/logs/tenants/%s/audit.log", tenantID)
// config.RetentionDays = 2555 // 7 years
// config.MaxFileSizeMB = 100 // Rotate at 100MB
//
// logger, err := audit.NewLogger(config)
// if err != nil {
// return nil, err
// }
//
// // Log tenant creation
// logger.Log(audit.Event{
// Type: audit.EventSystemChange,
// UserID: "system",
// Resource: "tenant",
// ResourceID: tenantID,
// Action: "CREATE",
// Success: true,
// Metadata: map[string]string{
// "tenant_id": tenantID,
// "timestamp": time.Now().Format(time.RFC3339),
// },
// })
//
// return logger, nil
// }
//
// ELI12:
//
// Think of NewLogger like installing a security camera in a store:
//
// - The camera (logger) records everything that happens
// - The recordings (audit log) can NEVER be erased or edited
// - If someone steals (data breach), you can review the tape
// - If someone says "I didn't do that!", you have proof
//
// Why it's important for businesses:
// - GDPR: European law says "keep records of who accessed what data"
// - HIPAA: US health law says "log all medical record access"
// - SOC2: Security certification requires "prove your security works"
//
// The audit log answers questions like:
// - "Who accessed patient records last Tuesday?"
// - "Did anyone try to hack our system?"
// - "Can we prove we deleted user data when requested?"
// - "Who changed this important configuration?"
//
// Compliance Requirements Met:
// - GDPR Art.30: Records of processing activities ✓
// - GDPR Art.15: Audit trail for data subject requests ✓
// - HIPAA §164.312(b): Audit controls ✓
// - HIPAA §164.308(a)(1)(ii)(D): System activity review ✓
// - FISMA AU-2/AU-3: Audit events and content ✓
// - SOC2 CC7.2: System monitoring ✓
// - SOX §404: Internal controls ✓
//
// File Permissions:
// - Log files: 0640 (owner read/write, group read, no world access)
// - Log directories: 0750 (owner full, group read+exec, no world access)
// - This prevents unauthorized access to sensitive audit data
//
// Performance:
// - Async writes by default (SyncWrites=false for speed)
// - Optional fsync per write (SyncWrites=true for durability)
// - Minimal overhead: ~1ms per log entry
// - Automatic log rotation when size limit reached
//
// Thread Safety:
// Safe for concurrent logging from multiple goroutines.
func NewLogger(config Config) (*Logger, error) {
if !config.Enabled {
return &Logger{config: config}, nil
}
// Ensure log directory exists
dir := filepath.Dir(config.LogPath)
if err := os.MkdirAll(dir, 0750); err != nil {
return nil, fmt.Errorf("creating audit log directory: %w", err)
}
// Open log file with append mode
file, err := os.OpenFile(config.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
if err != nil {
return nil, fmt.Errorf("opening audit log file: %w", err)
}
return &Logger{
writer: file,
file: file,
config: config,
}, nil
}
// NewLoggerWithWriter creates a logger with a custom writer (for testing).
func NewLoggerWithWriter(writer io.Writer, config Config) *Logger {
return &Logger{
writer: writer,
config: config,
}
}
// SetAlertCallback sets a callback for real-time security alerting.
func (l *Logger) SetAlertCallback(fn func(Event)) {
l.mu.Lock()
defer l.mu.Unlock()
l.alertCallback = fn
}
// Log records an audit event to the audit trail.
//
// The event is automatically timestamped and assigned a unique ID if not provided.
// If SyncWrites is enabled, the log is fsynced to disk for durability.
//
// Parameters:
// - event: Event to log (Type is required, other fields optional)
//
// Returns:
// - nil on success
// - Error if logging fails or logger is closed
//
// Example:
//
// // Simple event
// err := logger.Log(audit.Event{
// Type: audit.EventLogin,
// UserID: "user-123",
// Success: true,
// })
//
// // Detailed event
// err = logger.Log(audit.Event{
// Type: audit.EventDataRead,
// UserID: "user-456",
// Username: "bob",
// IPAddress: "10.0.1.50",
// UserAgent: "NornicDB-Client/1.0",
// Resource: "patient_record",
// ResourceID: "patient-789",
// Action: "READ",
// Success: true,
// DataClassification: "PHI",
// RequestPath: "/api/patients/789",
// Metadata: map[string]string{
// "query_type": "medical_history",
// "department": "cardiology",
// },
// })
//
// Automatic Fields:
// - Timestamp: Set to current UTC time if zero
// - ID: Generated if empty (format: audit-{nanoseconds}-{sequence})
//
// Thread Safety:
// This method is thread-safe and can be called concurrently.
func (l *Logger) Log(event Event) error {
if !l.config.Enabled {
return nil
}
l.mu.Lock()
defer l.mu.Unlock()
if l.closed {
return fmt.Errorf("audit logger is closed")
}
// Set timestamp if not provided
if event.Timestamp.IsZero() {
event.Timestamp = time.Now().UTC()
}
// Generate ID if not provided
if event.ID == "" {
l.sequence++
event.ID = fmt.Sprintf("audit-%d-%d", event.Timestamp.UnixNano(), l.sequence)
}
// Serialize to JSON
data, err := json.Marshal(event)
if err != nil {
return fmt.Errorf("marshaling audit event: %w", err)
}
// Write with newline
if _, err := l.writer.Write(append(data, '\n')); err != nil {
return fmt.Errorf("writing audit event: %w", err)
}
// Sync if configured
if l.config.SyncWrites && l.file != nil {
if err := l.file.Sync(); err != nil {
return fmt.Errorf("syncing audit log: %w", err)
}
}
// Check for alert-worthy events
if l.alertCallback != nil {
for _, alertType := range l.config.AlertOnEvents {
if event.Type == alertType {
l.alertCallback(event)
break
}
}
}
return nil
}
// LogAuth logs an authentication event with standard fields.
//
// This is a convenience method for logging common authentication events
// like login, logout, and password changes.
//
// Parameters:
// - eventType: Type of auth event (EventLogin, EventLogout, etc.)
// - userID: Unique user identifier
// - username: Human-readable username
// - ip: Client IP address
// - userAgent: Client User-Agent string
// - success: Whether the operation succeeded
// - reason: Failure reason or additional context
//
// Example:
//
// // Successful login
// logger.LogAuth(audit.EventLogin, "user-123", "alice",
// "192.168.1.100", "Mozilla/5.0", true, "")
//
// // Failed login
// logger.LogAuth(audit.EventLoginFailed, "", "alice",
// "192.168.1.100", "Mozilla/5.0", false, "invalid password")
//
// // Password change
// logger.LogAuth(audit.EventPasswordChange, "user-123", "alice",
// "192.168.1.100", "Mozilla/5.0", true, "user initiated")
//
// Compliance:
// Satisfies HIPAA §164.312(b) audit control requirements.
func (l *Logger) LogAuth(eventType EventType, userID, username, ip, userAgent string, success bool, reason string) error {
return l.Log(Event{
Type: eventType,
UserID: userID,
Username: username,
IPAddress: ip,
UserAgent: userAgent,
Success: success,
Reason: reason,
Resource: "session",
})
}
// LogDataAccess logs a data access event for GDPR Art.15 compliance.
//
// This method records all data access operations to maintain a complete
// audit trail of personal data processing as required by GDPR.
//
// Parameters:
// - userID: User performing the access
// - username: Human-readable username
// - resourceType: Type of resource ("node", "edge", "user", etc.)
// - resourceID: Unique identifier of the accessed resource
// - action: Operation performed ("READ", "create", "update", "delete")
// - success: Whether the operation succeeded
// - classification: Data classification ("PHI", "PII", "PUBLIC", etc.)
//
// Example:
//
// // Reading patient data (HIPAA PHI)
// logger.LogDataAccess("user-123", "dr_smith", "patient_record",
// "patient-456", "READ", true, "PHI")
//
// // Creating user profile (PII)
// logger.LogDataAccess("user-789", "admin", "user_profile",
// "profile-123", "CREATE", true, "PII")
//
// // Failed access attempt
// logger.LogDataAccess("user-456", "alice", "financial_record",
// "record-789", "READ", false, "FINANCIAL")
//
// GDPR Compliance:
// - Art.15: Right of access - provides audit trail for data subject requests
// - Art.30: Records of processing - documents all processing activities
// - Recital 82: Security measures - demonstrates appropriate safeguards
func (l *Logger) LogDataAccess(userID, username, resourceType, resourceID, action string, success bool, classification string) error {
return l.Log(Event{
Type: EventType("DATA_" + action),
UserID: userID,
Username: username,
Resource: resourceType,
ResourceID: resourceID,
Action: action,
Success: success,
DataClassification: classification,
})
}
// LogErasure logs a GDPR Art.17 data erasure event ("right to be forgotten").
//
// This method records erasure requests and completions to demonstrate
// compliance with GDPR data subject rights.
//
// Parameters:
// - userID: User processing the erasure (admin/DPO)
// - username: Human-readable username of processor
// - targetUserID: Data subject whose data is being erased
// - complete: true if erasure is complete, false if request initiated
// - details: Additional context or reason
//
// Example:
//
// // Erasure request initiated
// logger.LogErasure("admin-123", "dpo_jane", "user-456", false,
// "GDPR erasure request received via email")
//
// // Erasure completed
// logger.LogErasure("admin-123", "dpo_jane", "user-456", true,
// "All personal data erased from system")
//
// // Partial erasure (legal hold)
// logger.LogErasure("admin-123", "dpo_jane", "user-789", false,
// "Partial erasure - financial records retained per legal hold")
//
// GDPR Requirements:
// - Art.17(1): Right to erasure
// - Art.17(3): Exceptions to erasure (legal obligations, etc.)
// - 30-day response deadline
// - Documentation of erasure actions
func (l *Logger) LogErasure(userID, username, targetUserID string, complete bool, details string) error {
eventType := EventErasureRequest
if complete {
eventType = EventErasureComplete
}
return l.Log(Event{
Type: eventType,
UserID: userID,
Username: username,
Resource: "user_data",
ResourceID: targetUserID,
Success: complete,
Reason: details,
Metadata: map[string]string{
"target_user_id": targetUserID,
},
})
}
// LogConsent logs a consent event (GDPR Art.7).
func (l *Logger) LogConsent(userID, username string, granted bool, consentType, version string) error {
eventType := EventConsentGiven
if !granted {
eventType = EventConsentRevoked
}
return l.Log(Event{
Type: eventType,
UserID: userID,
Username: username,
Resource: "consent",
Success: true,
Metadata: map[string]string{
"consent_type": consentType,
"consent_version": version,
},
})
}
// LogSecurityEvent logs a security-related event.
func (l *Logger) LogSecurityEvent(eventType EventType, userID, ip, details string, metadata map[string]string) error {
return l.Log(Event{
Type: eventType,
UserID: userID,
IPAddress: ip,
Success: false, // Security events are typically alerts
Reason: details,
Metadata: metadata,
})
}
// Close closes the audit logger.
func (l *Logger) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
l.closed = true
if l.file != nil {
return l.file.Close()
}
return nil
}
// Query allows searching audit logs (for compliance reporting).
type Query struct {
StartTime time.Time
EndTime time.Time
EventTypes []EventType
UserID string
ResourceID string
Success *bool
Limit int
Offset int
}
// QueryResult holds audit query results.
type QueryResult struct {
Events []Event
TotalCount int
HasMore bool
}
// Reader provides audit log reading capabilities.
type Reader struct {
path string
}
// NewReader creates an audit log reader.
func NewReader(path string) *Reader {
return &Reader{path: path}
}
// Query searches the audit log based on criteria.
// Note: For production, this should use an indexed storage backend.
func (r *Reader) Query(q Query) (*QueryResult, error) {
file, err := os.Open(r.path)
if err != nil {
if os.IsNotExist(err) {
return &QueryResult{Events: []Event{}}, nil
}
return nil, fmt.Errorf("opening audit log: %w", err)
}
defer file.Close()
var events []Event
decoder := json.NewDecoder(file)
for {
var event Event
if err := decoder.Decode(&event); err != nil {
if err == io.EOF {
break
}
// Skip malformed entries
continue
}
// Apply filters
if !q.StartTime.IsZero() && event.Timestamp.Before(q.StartTime) {
continue
}
if !q.EndTime.IsZero() && event.Timestamp.After(q.EndTime) {
continue
}
if len(q.EventTypes) > 0 && !containsEventType(q.EventTypes, event.Type) {
continue
}
if q.UserID != "" && event.UserID != q.UserID {
continue
}
if q.ResourceID != "" && event.ResourceID != q.ResourceID {
continue
}
if q.Success != nil && event.Success != *q.Success {
continue
}
events = append(events, event)
}
// Apply pagination
total := len(events)
if q.Offset > 0 {
if q.Offset >= len(events) {
events = nil
} else {
events = events[q.Offset:]
}
}
if q.Limit > 0 && len(events) > q.Limit {
events = events[:q.Limit]
}
return &QueryResult{
Events: events,
TotalCount: total,
HasMore: q.Offset+len(events) < total,
}, nil
}
// GetUserActivity retrieves all audit events for a user (GDPR Art.15 - right of access).
func (r *Reader) GetUserActivity(userID string, start, end time.Time) (*QueryResult, error) {
return r.Query(Query{
UserID: userID,
StartTime: start,
EndTime: end,
})
}
// GetDataAccessReport generates a data access report for compliance.
func (r *Reader) GetDataAccessReport(start, end time.Time) (*QueryResult, error) {
return r.Query(Query{
StartTime: start,
EndTime: end,
EventTypes: []EventType{
EventDataRead,
EventDataCreate,
EventDataUpdate,
EventDataDelete,
EventDataExport,
},
})
}
// GetSecurityReport generates a security events report.
func (r *Reader) GetSecurityReport(start, end time.Time) (*QueryResult, error) {
return r.Query(Query{
StartTime: start,
EndTime: end,
EventTypes: []EventType{
EventLoginFailed,
EventAccessDenied,
EventSecurityAlert,
EventBreach,
},
})
}
func containsEventType(types []EventType, t EventType) bool {
for _, et := range types {
if et == t {
return true
}
}
return false
}
// ComplianceReport generates a compliance report for a time period.
type ComplianceReport struct {
Period string `json:"period"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
TotalEvents int `json:"total_events"`
EventsByType map[EventType]int `json:"events_by_type"`
FailedLogins int `json:"failed_logins"`
AccessDenied int `json:"access_denied"`
DataAccesses int `json:"data_accesses"`
ErasureRequests int `json:"erasure_requests"`
SecurityAlerts int `json:"security_alerts"`
UniqueUsers int `json:"unique_users"`
GeneratedAt time.Time `json:"generated_at"`
}
// GenerateComplianceReport creates a comprehensive compliance report for auditors.
//
// This method analyzes audit logs for a time period and generates statistics
// required for GDPR, HIPAA, and SOC2 compliance reporting.
//
// Parameters:
// - start: Start of reporting period
// - end: End of reporting period
// - periodName: Human-readable period description
//
// Returns:
// - ComplianceReport with statistics and metrics
// - Error if log reading fails
//
// Example:
//
// reader := audit.NewReader("/var/log/nornicdb/audit.log")
//
// // Monthly report
// start := time.Now().AddDate(0, -1, 0)
// end := time.Now()
// report, err := reader.GenerateComplianceReport(start, end, "November 2025")
// if err != nil {
// log.Fatal(err)
// }
//
// // Print summary
// fmt.Printf("=== %s Compliance Report ===\n", report.Period)
// fmt.Printf("Total Events: %d\n", report.TotalEvents)
// fmt.Printf("Unique Users: %d\n", report.UniqueUsers)
// fmt.Printf("Failed Logins: %d\n", report.FailedLogins)
// fmt.Printf("Access Denied: %d\n", report.AccessDenied)
// fmt.Printf("Data Accesses: %d\n", report.DataAccesses)
// fmt.Printf("GDPR Erasures: %d\n", report.ErasureRequests)
// fmt.Printf("Security Alerts: %d\n", report.SecurityAlerts)
//
// // Export to JSON for compliance team
// reportJSON, _ := json.MarshalIndent(report, "", " ")
// os.WriteFile("compliance-report-nov-2025.json", reportJSON, 0644)
//
// Report Contents:
// - Event counts by type
// - Security metrics (failed logins, access denied)
// - Data access statistics
// - GDPR erasure tracking
// - Unique user activity
// - Time period coverage
func (r *Reader) GenerateComplianceReport(start, end time.Time, periodName string) (*ComplianceReport, error) {
result, err := r.Query(Query{
StartTime: start,
EndTime: end,
})
if err != nil {
return nil, err
}
report := &ComplianceReport{
Period: periodName,
StartTime: start,
EndTime: end,
TotalEvents: result.TotalCount,
EventsByType: make(map[EventType]int),
GeneratedAt: time.Now().UTC(),
}
uniqueUsers := make(map[string]bool)
for _, event := range result.Events {
report.EventsByType[event.Type]++
if event.UserID != "" {
uniqueUsers[event.UserID] = true
}
switch event.Type {
case EventLoginFailed:
report.FailedLogins++
case EventAccessDenied:
report.AccessDenied++
case EventDataRead, EventDataCreate, EventDataUpdate, EventDataDelete:
report.DataAccesses++
case EventErasureRequest, EventErasureComplete:
report.ErasureRequests++
case EventSecurityAlert, EventBreach:
report.SecurityAlerts++
}
}
report.UniqueUsers = len(uniqueUsers)
return report, nil
}