182 lines
6 KiB
Go
182 lines
6 KiB
Go
// Package auditlog provides security audit logging for compliance and security monitoring.
|
|
// Audit logs are separate from application logs and capture security-relevant events
|
|
// with consistent structure for analysis and alerting.
|
|
package auditlog
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/gocql/gocql"
|
|
"go.uber.org/zap"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
|
|
)
|
|
|
|
// EventType represents the type of security event
|
|
type EventType string
|
|
|
|
const (
|
|
// Authentication events
|
|
EventTypeLoginAttempt EventType = "login_attempt"
|
|
EventTypeLoginSuccess EventType = "login_success"
|
|
EventTypeLoginFailure EventType = "login_failure"
|
|
EventTypeLogout EventType = "logout"
|
|
EventTypeTokenRefresh EventType = "token_refresh"
|
|
EventTypeTokenRevoked EventType = "token_revoked"
|
|
|
|
// Account events
|
|
EventTypeAccountCreated EventType = "account_created"
|
|
EventTypeAccountDeleted EventType = "account_deleted"
|
|
EventTypeAccountLocked EventType = "account_locked"
|
|
EventTypeAccountUnlocked EventType = "account_unlocked"
|
|
EventTypeEmailVerified EventType = "email_verified"
|
|
|
|
// Recovery events
|
|
EventTypeRecoveryInitiated EventType = "recovery_initiated"
|
|
EventTypeRecoveryCompleted EventType = "recovery_completed"
|
|
EventTypeRecoveryFailed EventType = "recovery_failed"
|
|
|
|
// Access control events
|
|
EventTypeAccessDenied EventType = "access_denied"
|
|
EventTypePermissionChanged EventType = "permission_changed"
|
|
|
|
// Sharing events
|
|
EventTypeCollectionShared EventType = "collection_shared"
|
|
EventTypeCollectionUnshared EventType = "collection_unshared"
|
|
EventTypeSharingBlocked EventType = "sharing_blocked"
|
|
)
|
|
|
|
// Outcome represents the result of the audited action
|
|
type Outcome string
|
|
|
|
const (
|
|
OutcomeSuccess Outcome = "success"
|
|
OutcomeFailure Outcome = "failure"
|
|
OutcomeBlocked Outcome = "blocked"
|
|
)
|
|
|
|
// AuditEvent represents a security audit event
|
|
type AuditEvent struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
EventType EventType `json:"event_type"`
|
|
Outcome Outcome `json:"outcome"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Email string `json:"email,omitempty"` // Always masked
|
|
ClientIP string `json:"client_ip,omitempty"`
|
|
UserAgent string `json:"user_agent,omitempty"`
|
|
Resource string `json:"resource,omitempty"`
|
|
Action string `json:"action,omitempty"`
|
|
Details map[string]string `json:"details,omitempty"`
|
|
FailReason string `json:"fail_reason,omitempty"`
|
|
}
|
|
|
|
// AuditLogger provides security audit logging functionality
|
|
type AuditLogger interface {
|
|
// Log records a security audit event
|
|
Log(ctx context.Context, event AuditEvent)
|
|
|
|
// LogAuth logs an authentication event with common fields
|
|
LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string)
|
|
|
|
// LogAccess logs an access control event
|
|
LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string)
|
|
}
|
|
|
|
type auditLoggerImpl struct {
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewAuditLogger creates a new audit logger
|
|
func NewAuditLogger(logger *zap.Logger) AuditLogger {
|
|
// Create a named logger specifically for audit events
|
|
// This allows filtering audit logs separately from application logs
|
|
auditLogger := logger.Named("AUDIT")
|
|
|
|
return &auditLoggerImpl{
|
|
logger: auditLogger,
|
|
}
|
|
}
|
|
|
|
// Log records a security audit event
|
|
func (a *auditLoggerImpl) Log(ctx context.Context, event AuditEvent) {
|
|
// Set timestamp if not provided
|
|
if event.Timestamp.IsZero() {
|
|
event.Timestamp = time.Now().UTC()
|
|
}
|
|
|
|
// Build zap fields
|
|
fields := []zap.Field{
|
|
zap.String("audit_event", string(event.EventType)),
|
|
zap.String("outcome", string(event.Outcome)),
|
|
zap.Time("event_time", event.Timestamp),
|
|
}
|
|
|
|
if event.UserID != "" {
|
|
fields = append(fields, zap.String("user_id", event.UserID))
|
|
}
|
|
if event.Email != "" {
|
|
fields = append(fields, zap.String("email", validation.MaskEmail(event.Email))) // Always mask for safety
|
|
}
|
|
if event.ClientIP != "" {
|
|
fields = append(fields, zap.String("client_ip", validation.MaskIP(event.ClientIP))) // Always mask for safety
|
|
}
|
|
if event.UserAgent != "" {
|
|
fields = append(fields, zap.String("user_agent", event.UserAgent))
|
|
}
|
|
if event.Resource != "" {
|
|
fields = append(fields, zap.String("resource", event.Resource))
|
|
}
|
|
if event.Action != "" {
|
|
fields = append(fields, zap.String("action", event.Action))
|
|
}
|
|
if event.FailReason != "" {
|
|
fields = append(fields, zap.String("fail_reason", event.FailReason))
|
|
}
|
|
if len(event.Details) > 0 {
|
|
fields = append(fields, zap.Any("details", event.Details))
|
|
}
|
|
|
|
// Try to get request ID from context
|
|
if requestID, ok := ctx.Value(constants.SessionID).(string); ok && requestID != "" {
|
|
fields = append(fields, zap.String("request_id", requestID))
|
|
}
|
|
|
|
// Log at INFO level - audit events are always important
|
|
a.logger.Info("security_audit", fields...)
|
|
}
|
|
|
|
// LogAuth logs an authentication event with common fields
|
|
func (a *auditLoggerImpl) LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string) {
|
|
event := AuditEvent{
|
|
Timestamp: time.Now().UTC(),
|
|
EventType: eventType,
|
|
Outcome: outcome,
|
|
Email: email, // Should be pre-masked by caller
|
|
ClientIP: clientIP,
|
|
Details: details,
|
|
}
|
|
|
|
// Extract user ID from context if available
|
|
if userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID); ok {
|
|
event.UserID = userID.String()
|
|
}
|
|
|
|
a.Log(ctx, event)
|
|
}
|
|
|
|
// LogAccess logs an access control event
|
|
func (a *auditLoggerImpl) LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string) {
|
|
event := AuditEvent{
|
|
Timestamp: time.Now().UTC(),
|
|
EventType: eventType,
|
|
Outcome: outcome,
|
|
UserID: userID,
|
|
Resource: resource,
|
|
Action: action,
|
|
Details: details,
|
|
}
|
|
|
|
a.Log(ctx, event)
|
|
}
|