package audit import ( "context" "encoding/json" "time" "github.com/google/uuid" "go.uber.org/zap" "github.com/kms/api-key-service/internal/config" ) // EventType represents the type of audit event type EventType string const ( // Authentication events EventTypeLogin EventType = "auth.login" EventTypeLoginFailed EventType = "auth.login_failed" EventTypeLogout EventType = "auth.logout" EventTypeTokenCreated EventType = "auth.token_created" EventTypeTokenRevoked EventType = "auth.token_revoked" EventTypeTokenValidated EventType = "auth.token_validated" // Session events EventTypeSessionCreated EventType = "session.created" EventTypeSessionRevoked EventType = "session.revoked" EventTypeSessionExpired EventType = "session.expired" // Application events EventTypeAppCreated EventType = "app.created" EventTypeAppUpdated EventType = "app.updated" EventTypeAppDeleted EventType = "app.deleted" // Permission events EventTypePermissionGranted EventType = "permission.granted" EventTypePermissionRevoked EventType = "permission.revoked" EventTypePermissionDenied EventType = "permission.denied" // Tenant events EventTypeTenantCreated EventType = "tenant.created" EventTypeTenantUpdated EventType = "tenant.updated" EventTypeTenantSuspended EventType = "tenant.suspended" EventTypeTenantActivated EventType = "tenant.activated" // User events EventTypeUserCreated EventType = "user.created" EventTypeUserUpdated EventType = "user.updated" EventTypeUserSuspended EventType = "user.suspended" EventTypeUserActivated EventType = "user.activated" // Security events EventTypeSecurityViolation EventType = "security.violation" EventTypeBruteForceAttempt EventType = "security.brute_force" EventTypeIPBlocked EventType = "security.ip_blocked" EventTypeRateLimitExceeded EventType = "security.rate_limit_exceeded" // System events EventTypeSystemStartup EventType = "system.startup" EventTypeSystemShutdown EventType = "system.shutdown" EventTypeConfigChanged EventType = "system.config_changed" ) // EventSeverity represents the severity level of an audit event type EventSeverity string const ( SeverityInfo EventSeverity = "info" SeverityWarning EventSeverity = "warning" SeverityError EventSeverity = "error" SeverityCritical EventSeverity = "critical" ) // EventStatus represents the status of an audit event type EventStatus string const ( StatusSuccess EventStatus = "success" StatusFailure EventStatus = "failure" StatusPending EventStatus = "pending" ) // AuditEvent represents a single audit event type AuditEvent struct { ID uuid.UUID `json:"id" db:"id"` Type EventType `json:"type" db:"type"` Severity EventSeverity `json:"severity" db:"severity"` Status EventStatus `json:"status" db:"status"` Timestamp time.Time `json:"timestamp" db:"timestamp"` // Actor information ActorID string `json:"actor_id,omitempty" db:"actor_id"` ActorType string `json:"actor_type,omitempty" db:"actor_type"` // user, system, service ActorIP string `json:"actor_ip,omitempty" db:"actor_ip"` UserAgent string `json:"user_agent,omitempty" db:"user_agent"` // Tenant information TenantID *uuid.UUID `json:"tenant_id,omitempty" db:"tenant_id"` // Resource information ResourceID string `json:"resource_id,omitempty" db:"resource_id"` ResourceType string `json:"resource_type,omitempty" db:"resource_type"` // Event details Action string `json:"action" db:"action"` Description string `json:"description" db:"description"` Details map[string]interface{} `json:"details,omitempty" db:"details"` // Request context RequestID string `json:"request_id,omitempty" db:"request_id"` SessionID string `json:"session_id,omitempty" db:"session_id"` // Additional metadata Tags []string `json:"tags,omitempty" db:"tags"` Metadata map[string]string `json:"metadata,omitempty" db:"metadata"` } // AuditLogger defines the interface for audit logging type AuditLogger interface { // LogEvent logs a single audit event LogEvent(ctx context.Context, event *AuditEvent) error // LogAuthEvent logs an authentication-related event LogAuthEvent(ctx context.Context, eventType EventType, actorID, actorIP string, details map[string]interface{}) error // LogPermissionEvent logs a permission-related event LogPermissionEvent(ctx context.Context, eventType EventType, actorID, resourceID, resourceType string, details map[string]interface{}) error // LogSecurityEvent logs a security-related event LogSecurityEvent(ctx context.Context, eventType EventType, actorIP string, severity EventSeverity, details map[string]interface{}) error // LogSystemEvent logs a system-related event LogSystemEvent(ctx context.Context, eventType EventType, details map[string]interface{}) error // QueryEvents queries audit events with filters QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error) // GetEventStats returns audit event statistics GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) } // AuditFilter represents filters for querying audit events type AuditFilter struct { EventTypes []EventType `json:"event_types,omitempty"` Severities []EventSeverity `json:"severities,omitempty"` Statuses []EventStatus `json:"statuses,omitempty"` ActorID string `json:"actor_id,omitempty"` ActorType string `json:"actor_type,omitempty"` TenantID *uuid.UUID `json:"tenant_id,omitempty"` ResourceID string `json:"resource_id,omitempty"` ResourceType string `json:"resource_type,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` Tags []string `json:"tags,omitempty"` Limit int `json:"limit"` Offset int `json:"offset"` OrderBy string `json:"order_by"` // timestamp, type, severity OrderDesc bool `json:"order_desc"` } // AuditStatsFilter represents filters for audit statistics type AuditStatsFilter struct { EventTypes []EventType `json:"event_types,omitempty"` TenantID *uuid.UUID `json:"tenant_id,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` GroupBy string `json:"group_by"` // type, severity, status, hour, day } // AuditStats represents audit event statistics type AuditStats struct { TotalEvents int `json:"total_events"` ByType map[EventType]int `json:"by_type"` BySeverity map[EventSeverity]int `json:"by_severity"` ByStatus map[EventStatus]int `json:"by_status"` ByTime map[string]int `json:"by_time,omitempty"` } // auditLogger implements the AuditLogger interface type auditLogger struct { config config.ConfigProvider logger *zap.Logger repository AuditRepository } // AuditRepository defines the interface for audit event storage type AuditRepository interface { Create(ctx context.Context, event *AuditEvent) error Query(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error) GetStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) DeleteOldEvents(ctx context.Context, olderThan time.Time) (int, error) } // NewAuditLogger creates a new audit logger func NewAuditLogger(config config.ConfigProvider, logger *zap.Logger, repository AuditRepository) AuditLogger { return &auditLogger{ config: config, logger: logger, repository: repository, } } // LogEvent logs a single audit event func (a *auditLogger) LogEvent(ctx context.Context, event *AuditEvent) error { // Set default values if event.ID == uuid.Nil { event.ID = uuid.New() } if event.Timestamp.IsZero() { event.Timestamp = time.Now().UTC() } if event.Severity == "" { event.Severity = SeverityInfo } if event.Status == "" { event.Status = StatusSuccess } // Extract request context if available if requestID := ctx.Value("request_id"); requestID != nil { if reqID, ok := requestID.(string); ok { event.RequestID = reqID } } // Log to structured logger a.logToStructuredLogger(event) // Store in repository if err := a.repository.Create(ctx, event); err != nil { a.logger.Error("Failed to store audit event", zap.Error(err), zap.String("event_id", event.ID.String()), zap.String("event_type", string(event.Type))) return err } return nil } // LogAuthEvent logs an authentication-related event func (a *auditLogger) LogAuthEvent(ctx context.Context, eventType EventType, actorID, actorIP string, details map[string]interface{}) error { severity := SeverityInfo status := StatusSuccess // Determine severity and status based on event type switch eventType { case EventTypeLoginFailed: severity = SeverityWarning status = StatusFailure case EventTypeTokenRevoked: severity = SeverityWarning } event := &AuditEvent{ Type: eventType, Severity: severity, Status: status, ActorID: actorID, ActorType: "user", ActorIP: actorIP, Action: string(eventType), Description: a.generateDescription(eventType, details), Details: details, Tags: []string{"authentication"}, } return a.LogEvent(ctx, event) } // LogPermissionEvent logs a permission-related event func (a *auditLogger) LogPermissionEvent(ctx context.Context, eventType EventType, actorID, resourceID, resourceType string, details map[string]interface{}) error { severity := SeverityInfo status := StatusSuccess // Determine severity and status based on event type switch eventType { case EventTypePermissionDenied: severity = SeverityWarning status = StatusFailure } event := &AuditEvent{ Type: eventType, Severity: severity, Status: status, ActorID: actorID, ActorType: "user", ResourceID: resourceID, ResourceType: resourceType, Action: string(eventType), Description: a.generateDescription(eventType, details), Details: details, Tags: []string{"permission", "authorization"}, } return a.LogEvent(ctx, event) } // LogSecurityEvent logs a security-related event func (a *auditLogger) LogSecurityEvent(ctx context.Context, eventType EventType, actorIP string, severity EventSeverity, details map[string]interface{}) error { status := StatusSuccess if severity == SeverityError || severity == SeverityCritical { status = StatusFailure } event := &AuditEvent{ Type: eventType, Severity: severity, Status: status, ActorIP: actorIP, ActorType: "system", Action: string(eventType), Description: a.generateDescription(eventType, details), Details: details, Tags: []string{"security"}, } return a.LogEvent(ctx, event) } // LogSystemEvent logs a system-related event func (a *auditLogger) LogSystemEvent(ctx context.Context, eventType EventType, details map[string]interface{}) error { event := &AuditEvent{ Type: eventType, Severity: SeverityInfo, Status: StatusSuccess, ActorType: "system", Action: string(eventType), Description: a.generateDescription(eventType, details), Details: details, Tags: []string{"system"}, } return a.LogEvent(ctx, event) } // QueryEvents queries audit events with filters func (a *auditLogger) QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error) { // Set default pagination if filter.Limit <= 0 { filter.Limit = 100 } if filter.Limit > 1000 { filter.Limit = 1000 } if filter.OrderBy == "" { filter.OrderBy = "timestamp" filter.OrderDesc = true } return a.repository.Query(ctx, filter) } // GetEventStats returns audit event statistics func (a *auditLogger) GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) { return a.repository.GetStats(ctx, filter) } // logToStructuredLogger logs the event to the structured logger func (a *auditLogger) logToStructuredLogger(event *AuditEvent) { fields := []zap.Field{ zap.String("audit_event_id", event.ID.String()), zap.String("event_type", string(event.Type)), zap.String("severity", string(event.Severity)), zap.String("status", string(event.Status)), zap.Time("timestamp", event.Timestamp), zap.String("action", event.Action), zap.String("description", event.Description), } if event.ActorID != "" { fields = append(fields, zap.String("actor_id", event.ActorID)) } if event.ActorType != "" { fields = append(fields, zap.String("actor_type", event.ActorType)) } if event.ActorIP != "" { fields = append(fields, zap.String("actor_ip", event.ActorIP)) } if event.TenantID != nil { fields = append(fields, zap.String("tenant_id", event.TenantID.String())) } if event.ResourceID != "" { fields = append(fields, zap.String("resource_id", event.ResourceID)) } if event.ResourceType != "" { fields = append(fields, zap.String("resource_type", event.ResourceType)) } if event.RequestID != "" { fields = append(fields, zap.String("request_id", event.RequestID)) } if event.SessionID != "" { fields = append(fields, zap.String("session_id", event.SessionID)) } if len(event.Tags) > 0 { fields = append(fields, zap.Strings("tags", event.Tags)) } if event.Details != nil { if detailsJSON, err := json.Marshal(event.Details); err == nil { fields = append(fields, zap.String("details", string(detailsJSON))) } } // Log at appropriate level based on severity switch event.Severity { case SeverityInfo: a.logger.Info("Audit event", fields...) case SeverityWarning: a.logger.Warn("Audit event", fields...) case SeverityError: a.logger.Error("Audit event", fields...) case SeverityCritical: a.logger.Error("Critical audit event", fields...) default: a.logger.Info("Audit event", fields...) } } // generateDescription generates a human-readable description for an event func (a *auditLogger) generateDescription(eventType EventType, details map[string]interface{}) string { switch eventType { case EventTypeLogin: return "User successfully logged in" case EventTypeLoginFailed: return "User login attempt failed" case EventTypeLogout: return "User logged out" case EventTypeTokenCreated: return "API token created" case EventTypeTokenRevoked: return "API token revoked" case EventTypeTokenValidated: return "API token validated" case EventTypeSessionCreated: return "User session created" case EventTypeSessionRevoked: return "User session revoked" case EventTypeSessionExpired: return "User session expired" case EventTypeAppCreated: return "Application created" case EventTypeAppUpdated: return "Application updated" case EventTypeAppDeleted: return "Application deleted" case EventTypePermissionGranted: return "Permission granted" case EventTypePermissionRevoked: return "Permission revoked" case EventTypePermissionDenied: return "Permission denied" case EventTypeTenantCreated: return "Tenant created" case EventTypeTenantUpdated: return "Tenant updated" case EventTypeTenantSuspended: return "Tenant suspended" case EventTypeTenantActivated: return "Tenant activated" case EventTypeUserCreated: return "User created" case EventTypeUserUpdated: return "User updated" case EventTypeUserSuspended: return "User suspended" case EventTypeUserActivated: return "User activated" case EventTypeSecurityViolation: return "Security violation detected" case EventTypeBruteForceAttempt: return "Brute force attempt detected" case EventTypeIPBlocked: return "IP address blocked" case EventTypeRateLimitExceeded: return "Rate limit exceeded" case EventTypeSystemStartup: return "System started" case EventTypeSystemShutdown: return "System shutdown" case EventTypeConfigChanged: return "Configuration changed" default: return string(eventType) } } // AuditEventBuilder provides a fluent interface for building audit events type AuditEventBuilder struct { event *AuditEvent } // NewAuditEventBuilder creates a new audit event builder func NewAuditEventBuilder(eventType EventType) *AuditEventBuilder { return &AuditEventBuilder{ event: &AuditEvent{ ID: uuid.New(), Type: eventType, Timestamp: time.Now().UTC(), Severity: SeverityInfo, Status: StatusSuccess, Details: make(map[string]interface{}), Metadata: make(map[string]string), }, } } // WithSeverity sets the event severity func (b *AuditEventBuilder) WithSeverity(severity EventSeverity) *AuditEventBuilder { b.event.Severity = severity return b } // WithStatus sets the event status func (b *AuditEventBuilder) WithStatus(status EventStatus) *AuditEventBuilder { b.event.Status = status return b } // WithActor sets the actor information func (b *AuditEventBuilder) WithActor(actorID, actorType, actorIP string) *AuditEventBuilder { b.event.ActorID = actorID b.event.ActorType = actorType b.event.ActorIP = actorIP return b } // WithTenant sets the tenant ID func (b *AuditEventBuilder) WithTenant(tenantID uuid.UUID) *AuditEventBuilder { b.event.TenantID = &tenantID return b } // WithResource sets the resource information func (b *AuditEventBuilder) WithResource(resourceID, resourceType string) *AuditEventBuilder { b.event.ResourceID = resourceID b.event.ResourceType = resourceType return b } // WithAction sets the action func (b *AuditEventBuilder) WithAction(action string) *AuditEventBuilder { b.event.Action = action return b } // WithDescription sets the description func (b *AuditEventBuilder) WithDescription(description string) *AuditEventBuilder { b.event.Description = description return b } // WithDetail adds a detail func (b *AuditEventBuilder) WithDetail(key string, value interface{}) *AuditEventBuilder { b.event.Details[key] = value return b } // WithDetails sets multiple details func (b *AuditEventBuilder) WithDetails(details map[string]interface{}) *AuditEventBuilder { for k, v := range details { b.event.Details[k] = v } return b } // WithRequestContext sets request context information func (b *AuditEventBuilder) WithRequestContext(requestID, sessionID string) *AuditEventBuilder { b.event.RequestID = requestID b.event.SessionID = sessionID return b } // WithTags sets the tags func (b *AuditEventBuilder) WithTags(tags ...string) *AuditEventBuilder { b.event.Tags = tags return b } // WithMetadata adds metadata func (b *AuditEventBuilder) WithMetadata(key, value string) *AuditEventBuilder { b.event.Metadata[key] = value return b } // Build returns the built audit event func (b *AuditEventBuilder) Build() *AuditEvent { return b.event }