Files
Charon/backend/internal/security/audit_logger.go

96 lines
3.3 KiB
Go

// Package security provides audit logging for security-sensitive operations.
package security
import (
"encoding/json"
"log"
"time"
)
// AuditEvent represents a security audit log entry.
// All fields are included in JSON output for structured logging.
type AuditEvent struct {
Timestamp string `json:"timestamp"` // RFC3339 timestamp of the event
Action string `json:"action"` // Action being performed (e.g., "url_validation", "url_test")
Host string `json:"host"` // Target hostname from URL
RequestID string `json:"request_id"` // Unique request identifier for tracing
Result string `json:"result"` // Result of action: "allowed", "blocked", "error"
ResolvedIPs []string `json:"resolved_ips"` // DNS resolution results (for debugging)
BlockedReason string `json:"blocked_reason"` // Why the request was blocked
UserID string `json:"user_id"` // User who made the request (CRITICAL for attribution)
SourceIP string `json:"source_ip"` // IP address of the request originator
}
// AuditLogger provides structured security audit logging.
type AuditLogger struct {
// prefix is prepended to all log messages
prefix string
}
// NewAuditLogger creates a new security audit logger.
func NewAuditLogger() *AuditLogger {
return &AuditLogger{
prefix: "[SECURITY AUDIT]",
}
}
// LogURLValidation logs a URL validation event.
func (al *AuditLogger) LogURLValidation(event AuditEvent) {
// Ensure timestamp is set
if event.Timestamp == "" {
event.Timestamp = time.Now().UTC().Format(time.RFC3339)
}
// Serialize to JSON for structured logging
eventJSON, err := json.Marshal(event)
if err != nil {
log.Printf("%s ERROR: Failed to serialize audit event: %v", al.prefix, err)
return
}
// Log to standard logger (will be captured by application logger)
log.Printf("%s %s", al.prefix, string(eventJSON))
}
// LogURLTest is a convenience method for logging URL connectivity tests.
func (al *AuditLogger) LogURLTest(host, requestID, userID, sourceIP, result string) {
event := AuditEvent{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Action: "url_connectivity_test",
Host: host,
RequestID: requestID,
Result: result,
UserID: userID,
SourceIP: sourceIP,
}
al.LogURLValidation(event)
}
// LogSSRFBlock is a convenience method for logging blocked SSRF attempts.
func (al *AuditLogger) LogSSRFBlock(host string, resolvedIPs []string, reason, userID, sourceIP string) {
event := AuditEvent{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Action: "ssrf_block",
Host: host,
ResolvedIPs: resolvedIPs,
BlockedReason: reason,
Result: "blocked",
UserID: userID,
SourceIP: sourceIP,
}
al.LogURLValidation(event)
}
// Global audit logger instance
var globalAuditLogger = NewAuditLogger()
// LogURLTest logs a URL test event using the global logger.
func LogURLTest(host, requestID, userID, sourceIP, result string) {
globalAuditLogger.LogURLTest(host, requestID, userID, sourceIP, result)
}
// LogSSRFBlock logs a blocked SSRF attempt using the global logger.
func LogSSRFBlock(host string, resolvedIPs []string, reason, userID, sourceIP string) {
globalAuditLogger.LogSSRFBlock(host, resolvedIPs, reason, userID, sourceIP)
}