package core import ( "context" "database/sql" "encoding/json" "fmt" "log" "time" "github.com/google/uuid" ) var berlinLocation *time.Location func init() { var err error berlinLocation, err = time.LoadLocation("Europe/Berlin") if err != nil { // Fallback zu UTC falls Europe/Berlin nicht verfügbar ist log.Printf("Warnung: Europe/Berlin Zeitzone nicht verfügbar, verwende UTC: %v", err) berlinLocation = time.UTC } } // AuditService handles audit logging type AuditService struct { db *sql.DB } // NewAuditService creates a new AuditService instance func NewAuditService(db *sql.DB) *AuditService { return &AuditService{ db: db, } } // Track logs an audit event asynchronously // ctx: context for the operation // action: the action performed (e.g., "CREATE", "UPDATE", "DELETE") // entity: the entity type (e.g., "user", "space", "fqdn", "csr") // entityID: the ID of the entity (optional) // userID: the ID of the user performing the action (optional) // username: the username of the user performing the action (optional) // details: additional details as a map (will be stored as JSON) // ipAddress: IP address of the request (optional) // userAgent: User-Agent header (optional) func (s *AuditService) Track(ctx context.Context, action, entity, entityID, userID, username string, details map[string]interface{}, ipAddress, userAgent string) { // Check if service is nil (should not happen, but safety check) if s == nil { log.Printf("Warnung: AuditService ist nil, kann kein Audit-Log schreiben") return } // Check if database is available if s.db == nil { log.Printf("Warnung: Datenbank ist nil im AuditService, kann kein Audit-Log schreiben") return } // Execute asynchronously in a goroutine to not block the main operation go func() { if err := s.trackSync(ctx, action, entity, entityID, userID, username, details, ipAddress, userAgent); err != nil { // Log errors but don't fail the main operation log.Printf("Fehler beim Schreiben des Audit-Logs: %v", err) } }() } // trackSync performs the actual database write synchronously func (s *AuditService) trackSync(ctx context.Context, action, entity, entityID, userID, username string, details map[string]interface{}, ipAddress, userAgent string) error { // Safety check: ensure service and database are not nil if s == nil { return fmt.Errorf("AuditService ist nil") } if s.db == nil { return fmt.Errorf("Datenbank ist nil im AuditService") } // Generate unique ID for the log entry logID := uuid.New().String() // Format timestamp for SQLite - verwende Europe/Berlin Zeitzone // Speichere als ISO-String mit Zeitzone für bessere Kompatibilität // Format: YYYY-MM-DD HH:MM:SS (SQLite DATETIME Format) // Die Zeit wird in Europe/Berlin Zeitzone gespeichert now := time.Now().In(berlinLocation) timestamp := now.Format("2006-01-02 15:04:05") // Marshal details to JSON var detailsJSON string if details != nil && len(details) > 0 { jsonBytes, err := json.Marshal(details) if err != nil { return fmt.Errorf("Fehler beim Marshalling der Details: %w", err) } detailsJSON = string(jsonBytes) } // Create context with timeout for database operation dbCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() // Insert audit log entry result, err := s.db.ExecContext(dbCtx, "INSERT INTO audit_logs (id, timestamp, user_id, username, action, resource_type, resource_id, details, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", logID, timestamp, userID, username, action, entity, entityID, detailsJSON, ipAddress, userAgent) if err != nil { return fmt.Errorf("Fehler beim INSERT: %w", err) } // Verify that the insert was successful rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("Fehler beim Prüfen der RowsAffected: %w", err) } if rowsAffected == 0 { return fmt.Errorf("Keine Zeile eingefügt") } log.Printf("Audit-Log geschrieben: %s %s %s (ID: %s)", action, entity, entityID, logID) return nil }