diff --git a/cmd/start/start.go b/cmd/start/start.go index 75c2a85..da5c96e 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -95,6 +95,13 @@ var StartCmd = &cobra.Command{ mon.Start() + // ====== 初始化文件检测器 ====== + fileDetector, err := detector.NewFileDetector(cfg) + if err != nil { + logger.Log.Error("初始化文件检测器失败", zap.Error(err)) + os.Exit(1) + } + // ====== 启动进程检测定时任务 ====== procDetector := detector.NewProcessDetector(cfg) procScheduler := timer.NewScheduler(time.Duration(cfg.Scanner.Process.Interval)*time.Second, procDetector) @@ -115,8 +122,8 @@ var StartCmd = &cobra.Command{ ) if event.FileInfo != nil { - // Todo: 处理文件系统事件,例如更新白名单、触发告警等 logger.Log.Debug("文件详情", zap.Int64("size", event.FileInfo.Size())) + fileDetector.HandleEvent(event.Path, event.Op.String()) } case err := <-mon.Errors(): diff --git a/config.yaml b/config.yaml index 20f7035..a043bbb 100644 --- a/config.yaml +++ b/config.yaml @@ -32,4 +32,5 @@ scanner: storage: data_dir: "./data" process_system_file: "process_system.data" - file_system_file: "file_system.data" \ No newline at end of file + file_system_file: "file_system.data" + dubious_file_list_file: "dubious_files.data" \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index eef2a10..4a9fdfa 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,9 +33,10 @@ type ProcessScannerConfig struct { } type StorageConfig struct { - DataDir string `yaml:"data_dir"` - ProcessSystemFile string `yaml:"process_system_file"` - FileSystemFile string `yaml:"file_system_file"` + DataDir string `yaml:"data_dir"` + ProcessSystemFile string `yaml:"process_system_file"` + FileSystemFile string `yaml:"file_system_file"` + DubiousFileListFile string `yaml:"dubious_file_list_file"` } type FileScannerConfig struct { diff --git a/internal/monitor/detector/file_detector.go b/internal/monitor/detector/file_detector.go new file mode 100644 index 0000000..5df7f45 --- /dev/null +++ b/internal/monitor/detector/file_detector.go @@ -0,0 +1,98 @@ +package detector + +import ( + "sync" + "sysmonitord/internal/config" + "sysmonitord/internal/scanner/hash" + "sysmonitord/internal/storage" + "sysmonitord/pkg/logger" + "time" + + "go.uber.org/zap" +) + +type FileDetector struct { + cfg *config.Config + whiteList map[string]string + storageDir string + mu sync.RWMutex +} + +func NewFileDetector(cfg *config.Config) (*FileDetector, error) { + d := &FileDetector{ + cfg: cfg, + storageDir: cfg.Storage.DataDir, + } + + if err := d.loadWhiteList(); err != nil { + return nil, err + } + return d, nil +} + +func (d *FileDetector) loadWhiteList() error { + whiteMap, err := storage.LoadFileSystemWhitelist(d.cfg.Storage.DataDir, d.cfg.Storage.FileSystemFile) + if err != nil { + logger.Log.Warn("[monitor] 加载文件白名单失败, 使用空白名单...", zap.Error((err))) + d.whiteList = make(map[string]string) + return nil + } + + d.whiteList = whiteMap + logger.Log.Info("[monitor] 文件白名单加载成功", zap.Int("count", len(d.whiteList))) + return nil +} + +func (d *FileDetector) HandleEvent(eventPath string, opStr string) { + // Todo: 忽略临时文件等 + + info, err := storage.GetFileInfo(eventPath) + if err != nil { + logger.Log.Warn("[monitor] 获取文件信息失败", zap.String("path", eventPath), zap.Error(err)) + return + } + + if info.IsDir() { + return + } + + hashCfg, err := d.cfg.GetHashConfig() + if err != nil { + logger.Log.Warn("[monitor] 获取哈希配置失败", zap.Error(err)) + return + } + curHash, err := hash.Calculate(eventPath, info.Size(), hashCfg) + if err != nil { + logger.Log.Warn("[monitor] 计算文件哈希失败", zap.String("path", eventPath), zap.Error(err)) + return + } + + d.mu.RLock() + whiteHash, exist := d.whiteList[eventPath] + d.mu.RUnlock() + + isSuspicious := false + reason := "" + + if !exist { + // 新文件 + isSuspicious = true + reason = "新文件" + } else if whiteHash != curHash { + // 文件被修改 + isSuspicious = true + reason = "文件被修改" + } + + if isSuspicious { + logger.Log.Warn("[monitor] 可疑文件事件", zap.String("path", eventPath), zap.String("reason", reason), zap.String("hash", curHash)) + dubiousInfo := storage.DubiousFileInfo{ + Path: eventPath, + Hash: curHash, + DiscoveredAt: time.Now().Format("2006-01-02 15:04:05"), + } + if err := storage.SaveDubiousFiles(dubiousInfo, d.storageDir, d.cfg.Storage.DubiousFileListFile); err != nil { + logger.Log.Error("[monitor] 保存可疑文件信息失败", zap.String("path", eventPath), zap.Error(err)) + } + } +} diff --git a/internal/monitor/detector/detector.go b/internal/monitor/detector/process_detector.go similarity index 100% rename from internal/monitor/detector/detector.go rename to internal/monitor/detector/process_detector.go diff --git a/internal/storage/storage.go b/internal/storage/storage.go index d214584..8bd30f0 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "sysmonitord/internal/scanner/file" "sysmonitord/internal/scanner/process" "sysmonitord/pkg/logger" @@ -96,3 +97,57 @@ func SaveFileSystem(files []file.FileInfo, dataDir string, fileSystemFile string return nil } + +type DubiousFileInfo struct { + Path string + Hash string + DiscoveredAt string +} + +func SaveDubiousFiles(files DubiousFileInfo, dataDir string, dubiousFileName string) error { + filePath := filepath.Join(dataDir, dubiousFileName) + + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("[storage]无法创建或打开可疑文件记录文件%s: %w", filePath, err) + } + defer f.Close() + + writer := bufio.NewWriter(f) + + line := fmt.Sprintf("%s:%s:%s\n", files.Path, files.Hash, files.DiscoveredAt) + if _, err := writer.WriteString(line); err != nil { + return err + } + + return writer.Flush() +} + +func LoadFileSystemWhitelist(dataDir string, fileSystemFile string) (map[string]string, error) { + filePath := filepath.Join(dataDir, fileSystemFile) + f, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("[storage]无法打开文件系统白名单文件%s: %w", filePath, err) + } + defer f.Close() + + whitelist := make(map[string]string) + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "#") || line == "" { + continue + } + + parts := strings.SplitN(line, ":", 2) + if len(parts) >= 2 { + whitelist[parts[0]] = parts[1] + } + } + return whitelist, nil +} + +func GetFileInfo(path string) (os.FileInfo, error) { + return os.Stat(path) +}