monorepo/cloud/maplepress-backend/internal/service/securityevent/logger.go

177 lines
5.6 KiB
Go

// File Path: monorepo/cloud/maplepress-backend/internal/service/securityevent/logger.go
package securityevent
import (
"context"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/securityevent"
)
// Logger handles logging of security events
// CWE-778: Ensures sufficient logging of security events for audit and forensics
type Logger interface {
// LogEvent logs a security event
LogEvent(ctx context.Context, event *securityevent.SecurityEvent) error
// LogAccountLocked logs an account lockout event
LogAccountLocked(ctx context.Context, emailHash, clientIP string, failedAttempts int, lockoutDuration string) error
// LogAccountUnlocked logs an account unlock event
LogAccountUnlocked(ctx context.Context, emailHash, unlockedBy string) error
// LogFailedLogin logs a failed login attempt
LogFailedLogin(ctx context.Context, emailHash, clientIP string, remainingAttempts int) error
// LogExcessiveFailedLogin logs excessive failed login attempts
LogExcessiveFailedLogin(ctx context.Context, emailHash, clientIP string, attemptCount int) error
// LogSuccessfulLogin logs a successful login
LogSuccessfulLogin(ctx context.Context, emailHash, clientIP string) error
// LogIPRateLimitExceeded logs IP rate limit exceeded
LogIPRateLimitExceeded(ctx context.Context, clientIP string) error
}
type securityEventLogger struct {
logger *zap.Logger
}
// NewSecurityEventLogger creates a new security event logger
func NewSecurityEventLogger(logger *zap.Logger) Logger {
return &securityEventLogger{
logger: logger.Named("security-events"),
}
}
// ProvideSecurityEventLogger provides a SecurityEventLogger for dependency injection
func ProvideSecurityEventLogger(logger *zap.Logger) Logger {
return NewSecurityEventLogger(logger)
}
// LogEvent logs a security event
func (s *securityEventLogger) LogEvent(ctx context.Context, event *securityevent.SecurityEvent) error {
// Map severity to log level
logFunc := s.logger.Info
switch event.Severity {
case securityevent.SeverityLow:
logFunc = s.logger.Info
case securityevent.SeverityMedium:
logFunc = s.logger.Warn
case securityevent.SeverityHigh, securityevent.SeverityCritical:
logFunc = s.logger.Error
}
// Build log fields
fields := []zap.Field{
zap.String("event_id", event.ID),
zap.String("event_type", string(event.EventType)),
zap.String("severity", string(event.Severity)),
zap.String("email_hash", event.EmailHash),
zap.String("client_ip", event.ClientIP),
zap.Time("timestamp", event.Timestamp),
}
if event.UserAgent != "" {
fields = append(fields, zap.String("user_agent", event.UserAgent))
}
// Add metadata fields
for key, value := range event.Metadata {
fields = append(fields, zap.Any(key, value))
}
logFunc(event.Message, fields...)
// TODO: In production, also persist to a security event database/SIEM
// This could be implemented as a repository pattern:
// - Store in Cassandra for long-term retention
// - Send to SIEM (Splunk, ELK, etc.) for analysis
// - Send to monitoring/alerting system
return nil
}
// LogAccountLocked logs an account lockout event
func (s *securityEventLogger) LogAccountLocked(ctx context.Context, emailHash, clientIP string, failedAttempts int, lockoutDuration string) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeAccountLocked,
securityevent.SeverityHigh,
emailHash,
clientIP,
"Account locked due to excessive failed login attempts",
)
event.WithMetadata("failed_attempts", failedAttempts)
event.WithMetadata("lockout_duration", lockoutDuration)
return s.LogEvent(ctx, event)
}
// LogAccountUnlocked logs an account unlock event
func (s *securityEventLogger) LogAccountUnlocked(ctx context.Context, emailHash, unlockedBy string) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeAccountUnlocked,
securityevent.SeverityMedium,
emailHash,
"",
"Account manually unlocked by administrator",
)
event.WithMetadata("unlocked_by", unlockedBy)
return s.LogEvent(ctx, event)
}
// LogFailedLogin logs a failed login attempt
func (s *securityEventLogger) LogFailedLogin(ctx context.Context, emailHash, clientIP string, remainingAttempts int) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeFailedLogin,
securityevent.SeverityMedium,
emailHash,
clientIP,
"Failed login attempt - invalid credentials",
)
event.WithMetadata("remaining_attempts", remainingAttempts)
return s.LogEvent(ctx, event)
}
// LogExcessiveFailedLogin logs excessive failed login attempts
func (s *securityEventLogger) LogExcessiveFailedLogin(ctx context.Context, emailHash, clientIP string, attemptCount int) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeExcessiveFailedLogin,
securityevent.SeverityHigh,
emailHash,
clientIP,
"Excessive failed login attempts detected",
)
event.WithMetadata("attempt_count", attemptCount)
return s.LogEvent(ctx, event)
}
// LogSuccessfulLogin logs a successful login
func (s *securityEventLogger) LogSuccessfulLogin(ctx context.Context, emailHash, clientIP string) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeSuccessfulLogin,
securityevent.SeverityLow,
emailHash,
clientIP,
"Successful login",
)
return s.LogEvent(ctx, event)
}
// LogIPRateLimitExceeded logs IP rate limit exceeded
func (s *securityEventLogger) LogIPRateLimitExceeded(ctx context.Context, clientIP string) error {
event := securityevent.NewSecurityEvent(
securityevent.EventTypeIPRateLimitExceeded,
securityevent.SeverityMedium,
"",
clientIP,
"IP rate limit exceeded for login attempts",
)
return s.LogEvent(ctx, event)
}