diff --git a/internal/storage/audit.go b/internal/storage/audit.go deleted file mode 100644 index 65ab576..0000000 --- a/internal/storage/audit.go +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2026 Safronov Grigorii - * - * Licensed under the CDDL, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * https://opensource.org/licenses/CDDL-1.0 - */ - -// Файл: internal/storage/audit.go -// Назначение: Аудит всех операций создания, изменения, удаления данных -// с записью временной метки с точностью до миллисекунды -// Аудит теперь персистентный - пишется в файл с fsync - -package storage - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sync" - "time" -) - -// AuditEntry представляет запись аудита -type AuditEntry struct { - ID string `msgpack:"id"` - Timestamp int64 `msgpack:"timestamp"` // Unix миллисекунды - TimestampStr string `msgpack:"timestamp_str"` // Человекочитаемая строка - Operation string `msgpack:"operation"` // CREATE, UPDATE, DELETE, START, COMMIT, ABORT, CLUSTER, SOFT_DELETE, RESTORE, PERMANENT_DELETE - DataType string `msgpack:"data_type"` // DATABASE, COLLECTION, DOCUMENT, FIELD, TUPLE, SESSION, TRANSACTION, CLUSTER, INDEX - Name string `msgpack:"name"` // Имя объекта - Details map[string]interface{} `msgpack:"details"` // Детали операции -} - -// AuditLogger управляет аудитом -// Добавлена персистентность (файл + fsync) -type AuditLogger struct { - entries []AuditEntry - mu sync.RWMutex - filePath string - fileMu sync.Mutex - file *os.File - writer *bufio.Writer - enabled bool - maxMemory int // Максимум записей в памяти -} - -var globalAuditLogger = &AuditLogger{ - entries: make([]AuditEntry, 0), - maxMemory: 100000, - enabled: true, -} - -// InitAuditLogger инициализирует персистентный аудит логгер -// Добавлена инициализация файла для аудита -func InitAuditLogger(filePath string) error { - globalAuditLogger.mu.Lock() - defer globalAuditLogger.mu.Unlock() - - if filePath == "" { - return nil // Аудит только в памяти - } - - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return fmt.Errorf("failed to create audit log directory: %v", err) - } - - f, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("failed to open audit log: %v", err) - } - - globalAuditLogger.filePath = filePath - globalAuditLogger.file = f - globalAuditLogger.writer = bufio.NewWriterSize(f, 64*1024) - - // Загружаем последние записи из файла - globalAuditLogger.loadFromFileLocked() - - return nil -} - -// loadFromFileLocked загружает последние записи из файла (вызывается под mu) -func (al *AuditLogger) loadFromFileLocked() { - if al.filePath == "" { - return - } - - data, err := os.ReadFile(al.filePath) - if err != nil { - return - } - - // Читаем построчно JSON-lines формат - lines := splitLines(data) - start := 0 - if len(lines) > al.maxMemory { - start = len(lines) - al.maxMemory - } - - al.entries = make([]AuditEntry, 0, len(lines)-start) - for i := start; i < len(lines); i++ { - if len(lines[i]) == 0 { - continue - } - var entry AuditEntry - if err := json.Unmarshal(lines[i], &entry); err != nil { - continue - } - al.entries = append(al.entries, entry) - } -} - -// splitLines разбивает данные на строки -func splitLines(data []byte) [][]byte { - lines := make([][]byte, 0) - start := 0 - for i := 0; i < len(data); i++ { - if data[i] == '\n' { - lines = append(lines, data[start:i]) - start = i + 1 - } - } - if start < len(data) { - lines = append(lines, data[start:]) - } - return lines -} - -// CloseAuditLogger закрывает аудит логгер с fsync -func CloseAuditLogger() error { - globalAuditLogger.mu.Lock() - defer globalAuditLogger.mu.Unlock() - - if globalAuditLogger.writer != nil { - if err := globalAuditLogger.writer.Flush(); err != nil { - return err - } - } - if globalAuditLogger.file != nil { - if err := RealFsync(globalAuditLogger.file); err != nil { - return err - } - return globalAuditLogger.file.Close() - } - return nil -} - -// GetCurrentTimestamp возвращает текущую временную метку с миллисекундами -func GetCurrentTimestamp() (int64, string) { - now := time.Now() - timestampMs := now.UnixMilli() - timestampStr := now.Format("2006-01-02 15:04:05.000") - return timestampMs, timestampStr -} - -// LogAudit записывает событие в аудит -// Добавлена запись в файл с fsync -func LogAudit(operation, dataType, name string, details map[string]interface{}) { - if !globalAuditLogger.enabled { - return - } - - timestampMs, timestampStr := GetCurrentTimestamp() - - // Если details не содержат timestamp, добавляем его - if details == nil { - details = make(map[string]interface{}) - } - if _, ok := details["audit_timestamp"]; !ok { - details["audit_timestamp"] = timestampMs - details["audit_timestamp_str"] = timestampStr - } - - entry := AuditEntry{ - ID: fmt.Sprintf("%d", timestampMs), - Timestamp: timestampMs, - TimestampStr: timestampStr, - Operation: operation, - DataType: dataType, - Name: name, - Details: details, - } - - globalAuditLogger.mu.Lock() - globalAuditLogger.entries = append(globalAuditLogger.entries, entry) - // Ограничиваем память - if len(globalAuditLogger.entries) > globalAuditLogger.maxMemory { - globalAuditLogger.entries = globalAuditLogger.entries[len(globalAuditLogger.entries)-globalAuditLogger.maxMemory:] - } - globalAuditLogger.mu.Unlock() - - // Персистентная запись в файл - globalAuditLogger.persistEntry(entry) -} - -// persistEntry записывает запись в файл с fsync -func (al *AuditLogger) persistEntry(entry AuditEntry) { - if al.file == nil { - return - } - - al.fileMu.Lock() - defer al.fileMu.Unlock() - - data, err := json.Marshal(entry) - if err != nil { - return - } - - data = append(data, '\n') - if _, err := al.writer.Write(data); err != nil { - return - } - - // Flush + fsync для гарантии сохранности - if err := al.writer.Flush(); err != nil { - return - } - RealFsync(al.file) -} - -// GetAuditLog возвращает копию лога аудита -func GetAuditLog() []AuditEntry { - globalAuditLogger.mu.RLock() - defer globalAuditLogger.mu.RUnlock() - - result := make([]AuditEntry, len(globalAuditLogger.entries)) - copy(result, globalAuditLogger.entries) - return result -} - -// GetAuditLogFiltered возвращает отфильтрованный лог аудита -func GetAuditLogFiltered(dataType, operation string, fromTime, toTime int64) []AuditEntry { - globalAuditLogger.mu.RLock() - defer globalAuditLogger.mu.RUnlock() - - result := make([]AuditEntry, 0) - for _, entry := range globalAuditLogger.entries { - if dataType != "" && entry.DataType != dataType { - continue - } - if operation != "" && entry.Operation != operation { - continue - } - if fromTime > 0 && entry.Timestamp < fromTime { - continue - } - if toTime > 0 && entry.Timestamp > toTime { - continue - } - result = append(result, entry) - } - return result -} - -// ClearAuditLog очищает лог аудита (только для отладки) -func ClearAuditLog() { - globalAuditLogger.mu.Lock() - defer globalAuditLogger.mu.Unlock() - globalAuditLogger.entries = make([]AuditEntry, 0) -} - -// GetAuditLogSize возвращает количество записей в логе аудита -func GetAuditLogSize() int { - globalAuditLogger.mu.RLock() - defer globalAuditLogger.mu.RUnlock() - return len(globalAuditLogger.entries) -} - -// AuditDatabaseOperation логирует операцию с базой данных -func AuditDatabaseOperation(operation, dbName string) { - LogAudit(operation, "DATABASE", dbName, map[string]interface{}{ - "database": dbName, - }) -} - -// AuditCollectionOperation логирует операцию с коллекцией -func AuditCollectionOperation(operation, dbName, collName string, settings interface{}) { - LogAudit(operation, "COLLECTION", fmt.Sprintf("%s.%s", dbName, collName), map[string]interface{}{ - "database": dbName, - "collection": collName, - "settings": settings, - }) -} - -// AuditDocumentOperation логирует операцию с документом -func AuditDocumentOperation(operation, dbName, collName, docID string, fields map[string]interface{}) { - LogAudit(operation, "DOCUMENT", fmt.Sprintf("%s.%s.%s", dbName, collName, docID), map[string]interface{}{ - "database": dbName, - "collection": collName, - "document_id": docID, - "fields": fields, - }) -} - -// AuditFieldOperation логирует операцию с полем -func AuditFieldOperation(operation, dbName, collName, docID, fieldName string, value interface{}) { - LogAudit(operation, "FIELD", fmt.Sprintf("%s.%s.%s.%s", dbName, collName, docID, fieldName), map[string]interface{}{ - "database": dbName, - "collection": collName, - "document_id": docID, - "field": fieldName, - "value": value, - }) -} - -// AuditTupleOperation логирует операцию с кортежем -func AuditTupleOperation(operation, dbName, collName, docID, tuplePath string) { - LogAudit(operation, "TUPLE", fmt.Sprintf("%s.%s.%s.%s", dbName, collName, docID, tuplePath), map[string]interface{}{ - "database": dbName, - "collection": collName, - "document_id": docID, - "tuple_path": tuplePath, - }) -} - -// AuditIndexOperation логирует операцию с индексом -func AuditIndexOperation(operation, dbName, collName, indexName string, fields []string, unique bool) { - LogAudit(operation, "INDEX", fmt.Sprintf("%s.%s.%s", dbName, collName, indexName), map[string]interface{}{ - "database": dbName, - "collection": collName, - "index_name": indexName, - "fields": fields, - "unique": unique, - }) -}